Documentation
¶
Overview ¶
Package couac provides a convenient, ergonomic wrapper around ADBC (Arrow Database Connectivity) for DuckDB.
Couac simplifies working with DuckDB through ADBC by providing:
- Database lifecycle management with automatic driver discovery
- Connection pooling with concurrency-safe tracking
- Query execution returning Apache Arrow record batches
- Bulk ingestion from Arrow record batches with automatic table creation
- Schema evolution via UNION BY NAME merge strategy
- Hierarchical catalog/schema/table metadata with typed navigation
- Safe database compaction without disrupting open connections
- Extension and secret management
- Pragma, tuning, and introspection convenience wrappers
DuckDB's ADBC driver supports the full ADBC specification except for ReadPartition and ExecutePartitions (not applicable to a non-distributed database). Notably, DuckDB does not support the CreateAppend ingest mode or statement-level AutoCommit; couac works around both limitations.
Concurrency Model ¶
DuckDB uses MVCC with optimistic concurrency control. Within a single process, multiple connections may read and write concurrently:
- Appends never conflict, even on the same table.
- Concurrent updates/deletes on different rows succeed.
- Concurrent updates/deletes on the same row produce a conflict error.
- Only one process may open a database file for writing at a time.
Couac protects its internal connection list with a sync.RWMutex. Normal operations acquire a read lock (allowing full concurrency). Maintenance operations like DB.Compact acquire a write lock, blocking new operations until maintenance completes.
Driver Discovery ¶
Couac supports three modes for locating the DuckDB shared library:
- WithDriverName: pass a bare name (e.g. "duckdb") and let the ADBC driver manager resolve it via installed TOML manifests (requires [dbc install duckdb]).
- WithDriverPath: explicit path to libduckdb.so / .dylib / .dll.
- WithDriverLookup: programmatic search of ADBC manifest directories using the github.com/columnar-tech/dbc/config package.
If no driver option is supplied, couac defaults to WithDriverName("duckdb").
Index ¶
- Variables
- func Elem[T any](l List, index int) (T, bool)
- func ExtensionsDir() string
- func Field[T any](s Struct, name string) (T, bool)
- func SecretsDir() string
- func Value[T any](m Map, key string) (T, bool)
- type AttachOption
- type CatalogInfo
- type CatalogTree
- func (o *CatalogTree) Catalogs() []string
- func (o *CatalogTree) Columns(catalog, schema, table string) []ColumnSchema
- func (o *CatalogTree) Constraints(catalog, schema, table string) []ConstraintSchema
- func (o *CatalogTree) FindTable(name string) (catalog, schema string, table *TableSchema, found bool)
- func (o *CatalogTree) Map() map[string]map[string][]TableSchema
- func (o *CatalogTree) Raw() []CatalogInfo
- func (o *CatalogTree) Schemas(catalog string) []string
- func (o *CatalogTree) TableExists(catalog, schema, table string) bool
- func (o *CatalogTree) Tables(catalog, schema string) []TableSchema
- type ColumnInfo
- type ColumnSchema
- type Conn
- func (q *Conn) Attach(ctx context.Context, path, alias string, opts ...AttachOption) error
- func (q *Conn) Catalog() string
- func (q *Conn) Close() error
- func (q *Conn) CopyDatabase(ctx context.Context, srcAlias, dstPath string) error
- func (q *Conn) DBSchema() string
- func (q *Conn) DatabaseSize(ctx context.Context) ([]DatabaseSize, error)
- func (q *Conn) Databases(ctx context.Context) ([]string, error)
- func (q *Conn) Describe(ctx context.Context, tableOrQuery string) ([]ColumnInfo, error)
- func (q *Conn) Detach(ctx context.Context, alias string) error
- func (q *Conn) DisableProfiling(ctx context.Context) error
- func (q *Conn) EnableProfiling(ctx context.Context, format string) error
- func (q *Conn) Exec(ctx context.Context, query string) (int64, error)
- func (q *Conn) Explain(ctx context.Context, query string) (string, error)
- func (q *Conn) Extensions(ctx context.Context) ([]Extension, error)
- func (q *Conn) GetObjects(ctx context.Context, opts ...ObjectsOption) (*CatalogTree, error)deprecated
- func (q *Conn) GetObjectsMap(ctx context.Context) (map[string]map[string][]TableSchema, error)deprecated
- func (q *Conn) GetSetting(ctx context.Context, key string) (string, error)deprecated
- func (q *Conn) GetTableSchema(ctx context.Context, catalog, dbSchema *string, tableName string) (*arrow.Schema, error)deprecated
- func (q *Conn) GetTableTypes(ctx context.Context) ([]string, error)deprecated
- func (q *Conn) Ingest(ctx context.Context, destTable string, rec arrow.RecordBatch) (int64, error)
- func (q *Conn) IngestCreateAppend(ctx context.Context, destTable string, rec arrow.RecordBatch) (int64, error)deprecated
- func (q *Conn) IngestCreateAppendMerge(ctx context.Context, destTable string, rec arrow.RecordBatch) (int64, error)deprecated
- func (q *Conn) IngestMerge(ctx context.Context, destTable string, rec arrow.RecordBatch) (int64, error)
- func (q *Conn) IngestReplace(ctx context.Context, destTable string, rec arrow.RecordBatch) (int64, error)
- func (q *Conn) IngestStream(ctx context.Context, destTable string, reader array.RecordReader) (int64, error)
- func (q *Conn) InstallExtension(ctx context.Context, name string) error
- func (q *Conn) LoadExtension(ctx context.Context, name string) error
- func (q *Conn) LockConfiguration(ctx context.Context) error
- func (q *Conn) NewStatement() (Statement, error)
- func (q *Conn) Objects(ctx context.Context, opts ...ObjectsOption) (*CatalogTree, error)
- func (q *Conn) ObjectsMap(ctx context.Context) (map[string]map[string][]TableSchema, error)
- func (q *Conn) Platform(ctx context.Context) (string, error)
- func (q *Conn) Prepare(ctx context.Context, query string) (Statement, error)
- func (q *Conn) Query(ctx context.Context, query string) (*QueryResult, error)
- func (q *Conn) QueryRaw(ctx context.Context, query string) (array.RecordReader, adbc.Statement, int64, error)
- func (q *Conn) Reset(ctx context.Context, key string) error
- func (q *Conn) Secrets(ctx context.Context) ([]Secret, error)
- func (q *Conn) Set(ctx context.Context, key, value string) error
- func (q *Conn) SetMaxTempDirectorySize(ctx context.Context, size string) error
- func (q *Conn) SetMemoryLimit(ctx context.Context, limit string) error
- func (q *Conn) SetPreserveInsertionOrder(ctx context.Context, preserve bool) error
- func (q *Conn) SetProfilingOutput(ctx context.Context, path string) error
- func (q *Conn) SetTempDirectory(ctx context.Context, path string) error
- func (q *Conn) SetThreads(ctx context.Context, n int) error
- func (q *Conn) Setting(ctx context.Context, key string) (string, error)
- func (q *Conn) Settings(ctx context.Context) ([]Setting, error)
- func (q *Conn) ShowAllTables(ctx context.Context) ([]TableInfo, error)
- func (q *Conn) ShowTables(ctx context.Context) ([]string, error)
- func (q *Conn) StorageInfo(ctx context.Context, table string) (*QueryResult, error)
- func (q *Conn) Summarize(ctx context.Context, tableOrQuery string) (*QueryResult, error)
- func (q *Conn) TableSchema(ctx context.Context, tableName string) (*arrow.Schema, error)
- func (q *Conn) TableSchemaIn(ctx context.Context, catalog, dbSchema, tableName string) (*arrow.Schema, error)
- func (q *Conn) TableTypes(ctx context.Context) ([]string, error)
- func (q *Conn) UserAgent(ctx context.Context) (string, error)
- func (q *Conn) Version(ctx context.Context) (string, error)
- type Connection
- type ConstraintSchema
- type DB
- func (q *DB) Checkpoint(ctx context.Context) error
- func (q *DB) Close() error
- func (q *DB) Compact(ctx context.Context) error
- func (q *DB) Connect() (*Conn, error)
- func (q *DB) ConnectAs(catalog, schema string) (*Conn, error)
- func (q *DB) ConnectionCount() int
- func (q *DB) DefaultContext() context.Context
- func (q *DB) DriverPath() string
- func (q *DB) ForceCheckpoint(ctx context.Context) error
- func (q *DB) NewConnection() (*Conn, error)deprecated
- func (q *DB) NewConnectionWithOpts(catalog, schema string) (*Conn, error)deprecated
- func (q *DB) Path() string
- func (q *DB) Ping(ctx context.Context) error
- func (q *DB) StdDB() *sql.DB
- func (q *DB) WithTransaction(ctx context.Context, fn func(*Conn) error) (retErr error)
- type DBObject
- type DBObjects
- type DBSchema
- type DatabaseSize
- type Decimal
- type DuckDatabase
- type Extension
- type GetObjectsOption
- type List
- func (l List) BoolAt(i int) (bool, bool)
- func (l List) Bools() []bool
- func (l List) Float64At(i int) (float64, bool)
- func (l List) Floats() []float64
- func (l List) Int32At(i int) (int32, bool)
- func (l List) Int64At(i int) (int64, bool)
- func (l List) Ints() []int64
- func (l List) JSON() List
- func (l List) ListAt(i int) List
- func (l List) MapAt(i int) Map
- func (l List) MarshalJSON() ([]byte, error)
- func (l *List) Scan(src any) error
- func (l List) String() string
- func (l List) StringAt(i int) (string, bool)
- func (l List) Strings() []string
- func (l List) StructAt(i int) Struct
- func (l List) Value() (driver.Value, error)
- type Map
- func (m Map) Bool(key string) (bool, bool)
- func (m Map) Float64(key string) (float64, bool)
- func (m Map) Get(key string) (any, bool)
- func (m Map) Int32(key string) (int32, bool)
- func (m Map) Int64(key string) (int64, bool)
- func (m Map) JSON() Map
- func (m Map) Keys() []string
- func (m Map) List(key string) List
- func (m Map) Map(key string) Map
- func (m Map) MarshalJSON() ([]byte, error)
- func (m *Map) Scan(src any) error
- func (m Map) Str(key string) (string, bool)
- func (m Map) String() string
- func (m Map) Struct(key string) Struct
- func (m Map) Value() (driver.Value, error)
- type NullDecimal
- type NullList
- type NullMap
- type NullStruct
- type ObjectDepth
- type ObjectsOption
- func WithCatalogFilter(catalog string) ObjectsOption
- func WithColumnFilter(column string) ObjectsOption
- func WithDepth(depth ObjectDepth) ObjectsOption
- func WithSchemaFilter(schema string) ObjectsOption
- func WithTableFilter(table string) ObjectsOption
- func WithTableTypes(types []string) ObjectsOption
- type Option
- type QuackCon
- type Quacker
- type QueryResult
- type SchemaInfo
- type Secret
- type Setting
- type Statement
- type Struct
- func (s Struct) Bool(name string) (bool, bool)
- func (s Struct) Float64(name string) (float64, bool)
- func (s Struct) Get(name string) (any, bool)
- func (s Struct) Int32(name string) (int32, bool)
- func (s Struct) Int64(name string) (int64, bool)
- func (s Struct) JSON() Struct
- func (s Struct) List(name string) List
- func (s Struct) Map(name string) Map
- func (s Struct) MarshalJSON() ([]byte, error)
- func (s *Struct) Scan(src any) error
- func (s Struct) Str(name string) (string, bool)
- func (s Struct) String() string
- func (s Struct) Struct(name string) Struct
- func (s Struct) Value() (driver.Value, error)
- type TableInfo
- type TableSchema
- type UsageSchema
Examples ¶
- CatalogTree.FindTable
- Conn.Databases
- Conn.Describe
- Conn.Exec
- Conn.Explain
- Conn.Extensions
- Conn.Ingest
- Conn.IngestMerge
- Conn.Objects
- Conn.ObjectsMap
- Conn.Prepare
- Conn.Query
- Conn.QueryRaw
- Conn.Set
- Conn.ShowTables
- Conn.TableSchema
- Conn.Version
- DB.Connect
- DB.StdDB
- DB.StdDB (NativeTypes)
- DB.StdDB (ParameterizedQuery)
- DB.StdDB (PreparedStatement)
- DB.StdDB (Transaction)
- DB.WithTransaction
- Decimal
- List
- List (Nested)
- List (NestedStructWithList)
- List (NullElements)
- Map
- Map (NestedStructValues)
- NewDuck
- NewDuck (FileBacked)
- NullDecimal
- NullList
- Struct
- Struct (Nested)
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNilRecord is returned when a nil arrow.RecordBatch is passed to an ingest function. ErrNilRecord = errors.New("couac: nil arrow record batch") // ErrEmptyTable is returned when an empty destination table name is provided. ErrEmptyTable = errors.New("couac: empty destination table name") // ErrDatabaseClosed is returned when an operation is attempted on a closed database. ErrDatabaseClosed = errors.New("couac: database is closed") // ErrConnectionClosed is returned when an operation is attempted on a closed connection. ErrConnectionClosed = errors.New("couac: connection is closed") // ErrDriverNotFound is returned when the DuckDB driver cannot be located. ErrDriverNotFound = errors.New("couac: duckdb driver not found; install with: dbc install duckdb") // ErrPathAlreadyOpen is returned when attempting to open a database file that // is already open in this process. ErrPathAlreadyOpen = errors.New("couac: database file is already open in this process") )
Sentinel errors returned by couac functions.
Functions ¶
func Elem ¶ added in v1.1.0
Elem returns the element at index i from a List as type T. If the index is out of range, the element is nil, or it cannot be converted to T, the zero value and false are returned. The same Scan-fallback logic as Field applies for nested types.
Example:
n, ok := couac.Elem[int32](l, 0) inner, ok := couac.Elem[couac.Struct](l, 0) // auto-Scans
func ExtensionsDir ¶ added in v1.0.0
func ExtensionsDir() string
ExtensionsDir returns the default directory where DuckDB stores installed extension binaries (~/.duckdb/extensions/).
func Field ¶ added in v1.1.0
Field returns the value of a Struct field as type T. If the field does not exist, is nil, or cannot be converted to T, the zero value and false are returned.
For leaf types (int32, string, bool, etc.) a direct type assertion is used. For nested types (List, Struct, Map) the raw value ([]any or map[string]any) is automatically passed through Scan, so the full wrapper API is available on the result.
Example:
name, ok := couac.Field[string](s, "name") age, ok := couac.Field[int32](s, "age") addr, ok := couac.Field[couac.Struct](s, "address") // auto-Scans map[string]any tags, ok := couac.Field[couac.List](s, "tags") // auto-Scans []any
func SecretsDir ¶ added in v1.0.0
func SecretsDir() string
SecretsDir returns the default directory where DuckDB stores persistent secrets (~/.duckdb/stored_secrets/).
func Value ¶ added in v1.1.0
Value returns the value for a Map key as type T. If the key does not exist, is nil, or cannot be converted to T, the zero value and false are returned. The same Scan-fallback logic as Field applies for nested types.
Example:
count, ok := couac.Value[int32](m, "key1") inner, ok := couac.Value[couac.Struct](m, "alice") // auto-Scans
Types ¶
type AttachOption ¶ added in v1.0.0
type AttachOption func(*attachConfig)
AttachOption configures a Conn.Attach call.
func ReadOnly ¶ added in v1.0.0
func ReadOnly() AttachOption
ReadOnly configures an ATTACH operation to open the database in read-only mode.
func WithBlockSize ¶ added in v1.0.0
func WithBlockSize(size int) AttachOption
WithBlockSize sets the block size for an attached database. Must be a power of 2 between 16384 (16 KB) and 262144 (256 KB).
func WithEncryptionKey ¶ added in v1.0.0
func WithEncryptionKey(key string) AttachOption
WithEncryptionKey sets the encryption key for an attached database.
type CatalogInfo ¶ added in v1.0.0
type CatalogInfo struct {
CatalogName string `json:"catalog_name"`
CatalogDBSchemas []SchemaInfo `json:"catalog_db_schemas"`
}
CatalogInfo represents a single catalog in the DuckDB database hierarchy. When multiple databases are ATTACHed, each appears as a separate CatalogInfo.
type CatalogTree ¶ added in v1.0.0
type CatalogTree struct {
// contains filtered or unexported fields
}
CatalogTree wraps a []CatalogInfo and provides typed navigation methods for exploring catalog metadata without additional ADBC round-trips.
Use Conn.Objects to obtain a CatalogTree instance.
func (*CatalogTree) Catalogs ¶ added in v1.0.0
func (o *CatalogTree) Catalogs() []string
Catalogs returns the names of all catalogs in the result set.
func (*CatalogTree) Columns ¶ added in v1.0.0
func (o *CatalogTree) Columns(catalog, schema, table string) []ColumnSchema
Columns returns the columns for the specified table. Returns nil if the catalog, schema, or table is not found.
func (*CatalogTree) Constraints ¶ added in v1.0.0
func (o *CatalogTree) Constraints(catalog, schema, table string) []ConstraintSchema
Constraints returns the constraints for the specified table. Returns nil if the catalog, schema, or table is not found.
func (*CatalogTree) FindTable ¶ added in v1.0.0
func (o *CatalogTree) FindTable(name string) (catalog, schema string, table *TableSchema, found bool)
FindTable searches all catalogs and schemas for a table with the given name. Returns the catalog, schema, table, and whether it was found. If multiple tables share the same name across catalogs/schemas, the first match is returned.
Example ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
conn, err := db.Connect()
if err != nil {
fmt.Println("Error:", err)
return
}
defer conn.Close()
ctx := context.Background()
conn.Exec(ctx, "CREATE TABLE orders (id INTEGER, total DECIMAL(10,2))")
objs, err := conn.Objects(ctx)
if err != nil {
fmt.Println("Error:", err)
return
}
catalog, schema, table, found := objs.FindTable("orders")
if found {
fmt.Printf("found: %s.%s.%s (%d columns)\n",
catalog, schema, table.TableName, len(table.TableColumns))
}
}
Output: found: memory.main.orders (2 columns)
func (*CatalogTree) Map ¶ added in v1.0.0
func (o *CatalogTree) Map() map[string]map[string][]TableSchema
Map returns the catalog hierarchy as a nested map: catalog name → schema name → []TableSchema. This is useful for bulk iteration over all metadata.
func (*CatalogTree) Raw ¶ added in v1.0.0
func (o *CatalogTree) Raw() []CatalogInfo
Raw returns the underlying []CatalogInfo slice for direct access.
func (*CatalogTree) Schemas ¶ added in v1.0.0
func (o *CatalogTree) Schemas(catalog string) []string
Schemas returns the schema names within the given catalog. Returns nil if the catalog is not found.
func (*CatalogTree) TableExists ¶ added in v1.0.0
func (o *CatalogTree) TableExists(catalog, schema, table string) bool
TableExists reports whether a table with the given name exists in the specified catalog and schema.
func (*CatalogTree) Tables ¶ added in v1.0.0
func (o *CatalogTree) Tables(catalog, schema string) []TableSchema
Tables returns the tables within the given catalog and schema. Returns nil if the catalog or schema is not found.
type ColumnInfo ¶ added in v1.0.0
type ColumnInfo struct {
Name string `json:"column_name"`
Type string `json:"column_type"`
Null string `json:"null"`
Key string `json:"key"`
Default string `json:"default"`
Extra string `json:"extra"`
}
ColumnInfo describes a column as returned by DESCRIBE.
type ColumnSchema ¶ added in v0.5.3
type ColumnSchema struct {
ColumnName string `json:"column_name"`
OrdinalPosition int32 `json:"ordinal_position"`
Remarks string `json:"remarks"`
XdbcDataType int16 `json:"xdbc_data_type"`
XdbcTypeName string `json:"xdbc_type_name"`
XdbcColumnSize int32 `json:"xdbc_column_size"`
XdbcDecimalDigits int16 `json:"xdbc_decimal_digits"`
XdbcNumPrecRadix int16 `json:"xdbc_num_prec_radix"`
XdbcNullable int16 `json:"xdbc_nullable"`
XdbcColumnDef string `json:"xdbc_column_def"`
XdbcSqlDataType int16 `json:"xdbc_sql_data_type"`
XdbcDatetimeSub int16 `json:"xdbc_datetime_sub"`
XdbcCharOctetLength int32 `json:"xdbc_char_octet_length"`
XdbcIsNullable string `json:"xdbc_is_nullable"`
XdbcScopeCatalog string `json:"xdbc_scope_catalog"`
XdbcScopeSchema string `json:"xdbc_scope_schema"`
XdbcScopeTable string `json:"xdbc_scope_table"`
XdbcIsAutoincrement bool `json:"xdbc_is_autoincrement"`
XdbcIsGeneratedColumn bool `json:"xdbc_is_generatedcolumn"`
}
ColumnSchema describes a single column within a table.
type Conn ¶ added in v1.0.0
type Conn struct {
// contains filtered or unexported fields
}
Conn represents a single connection to a DuckDB database.
Conn wraps an ADBC connection and provides methods for executing queries, ingesting data, and inspecting metadata. Each Conn is tracked by its parent DB and will be closed automatically when the parent is closed.
Conn methods that perform database operations acquire a read lock on the parent DB's mutex, allowing concurrent use. Maintenance operations on the parent (e.g. DB.Compact) will block until all in-flight operations complete.
func (*Conn) Attach ¶ added in v1.0.0
Attach attaches an additional database file to the current DuckDB instance under the given alias. The attached database appears as a separate catalog and can be queried with fully-qualified names (e.g. alias.schema.table).
Attachment definitions are NOT persisted between DuckDB sessions; you must re-attach on each NewDuck call.
Options:
- ReadOnly: open in read-only mode
- WithBlockSize: set the block size (power of 2, 16384–262144)
- WithEncryptionKey: set an encryption key
Example:
err := conn.Attach(ctx, "other.db", "other_db", couac.ReadOnly()) res, _ := conn.Query(ctx, "SELECT * FROM other_db.main.users")
func (*Conn) Catalog ¶ added in v1.0.0
Catalog returns the connection's catalog name. An empty string indicates the default catalog.
func (*Conn) Close ¶ added in v1.0.0
Close closes this connection and removes it from the parent's connection tracking. Close is idempotent; calling it more than once returns nil.
It is important to close connections to allow DuckDB to properly commit WAL file changes.
func (*Conn) CopyDatabase ¶ added in v1.0.0
CopyDatabase copies all data from the source database (identified by its catalog alias) to a new database file at dstPath. This creates a perfectly compacted copy.
Example:
conn.CopyDatabase(ctx, "memory", "/tmp/backup.db")
func (*Conn) DBSchema ¶ added in v1.0.0
DBSchema returns the connection's database schema. An empty string indicates the default schema ("main").
func (*Conn) DatabaseSize ¶ added in v1.0.0
func (q *Conn) DatabaseSize(ctx context.Context) ([]DatabaseSize, error)
DatabaseSize returns size information for all databases (including attached databases).
func (*Conn) Databases ¶ added in v1.0.0
Databases returns the list of all attached database names (catalogs), including the default database.
Example ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
conn, err := db.Connect()
if err != nil {
fmt.Println("Error:", err)
return
}
defer conn.Close()
dbs, err := conn.Databases(context.Background())
if err != nil {
fmt.Println("Error:", err)
return
}
// In-memory DuckDB always has at least the "memory" catalog.
fmt.Println("has databases:", len(dbs) > 0)
}
Output: has databases: true
func (*Conn) Describe ¶ added in v1.0.0
Describe returns column information for a table or query result. Pass either a table name or a full SELECT statement.
Example:
cols, err := conn.Describe(ctx, "users") cols, err := conn.Describe(ctx, "SELECT * FROM users WHERE age > 21")
Example ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
conn, err := db.Connect()
if err != nil {
fmt.Println("Error:", err)
return
}
defer conn.Close()
ctx := context.Background()
conn.Exec(ctx, "CREATE TABLE metrics (ts TIMESTAMP, value DOUBLE, tag VARCHAR)")
cols, err := conn.Describe(ctx, "metrics")
if err != nil {
fmt.Println("Error:", err)
return
}
for _, c := range cols {
fmt.Printf("%s: %s\n", c.Name, c.Type)
}
}
Output: ts: TIMESTAMP value: DOUBLE tag: VARCHAR
func (*Conn) Detach ¶ added in v1.0.0
Detach detaches a previously attached database. The alias must match the one used in Conn.Attach.
func (*Conn) DisableProfiling ¶ added in v1.0.0
DisableProfiling disables query profiling.
func (*Conn) EnableProfiling ¶ added in v1.0.0
EnableProfiling enables query profiling. Format can be "json", "query_tree", "query_tree_optimizer", or "no_output".
func (*Conn) Exec ¶ added in v1.0.0
Exec executes a statement that does not generate a result set (DDL, DML). It returns the number of rows affected if known, otherwise -1.
Exec acquires a read lock on the parent database, allowing concurrent execution with other operations but blocking during maintenance.
Example ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
conn, err := db.Connect()
if err != nil {
fmt.Println("Error:", err)
return
}
defer conn.Close()
// Create a table and insert data.
_, err = conn.Exec(context.Background(), "CREATE TABLE greetings (msg VARCHAR)")
if err != nil {
fmt.Println("Error:", err)
return
}
n, err := conn.Exec(context.Background(), "INSERT INTO greetings VALUES ('hello'), ('world')")
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("rows inserted:", n)
}
Output: rows inserted: 2
func (*Conn) Explain ¶ added in v1.0.0
Explain returns the query plan for a SQL statement without executing it.
Example ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
conn, err := db.Connect()
if err != nil {
fmt.Println("Error:", err)
return
}
defer conn.Close()
ctx := context.Background()
conn.Exec(ctx, "CREATE TABLE t (x INT)")
plan, err := conn.Explain(ctx, "SELECT * FROM t WHERE x > 5")
if err != nil {
fmt.Println("Error:", err)
return
}
// The plan contains a scan operator
fmt.Println("has plan:", plan != "")
}
Output: has plan: true
func (*Conn) Extensions ¶ added in v1.0.0
Extensions returns the list of DuckDB extensions with their status.
Example ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
conn, err := db.Connect()
if err != nil {
fmt.Println("Error:", err)
return
}
defer conn.Close()
exts, err := conn.Extensions(context.Background())
if err != nil {
fmt.Println("Error:", err)
return
}
// DuckDB always has at least a few built-in extensions
fmt.Println("has extensions:", len(exts) > 0)
}
Output: has extensions: true
func (*Conn) GetObjects
deprecated
added in
v1.0.0
func (q *Conn) GetObjects(ctx context.Context, opts ...ObjectsOption) (*CatalogTree, error)
GetObjects is a backward-compatible alias for Conn.Objects.
Deprecated: Use Conn.Objects instead.
func (*Conn) GetObjectsMap
deprecated
added in
v1.0.0
GetObjectsMap is a backward-compatible alias for Conn.ObjectsMap.
Deprecated: Use Conn.ObjectsMap instead.
func (*Conn) GetSetting
deprecated
added in
v1.0.0
GetSetting is a backward-compatible alias for Conn.Setting.
Deprecated: Use Conn.Setting instead.
func (*Conn) GetTableSchema
deprecated
added in
v1.0.0
func (q *Conn) GetTableSchema(ctx context.Context, catalog, dbSchema *string, tableName string) (*arrow.Schema, error)
GetTableSchema is a backward-compatible alias for Conn.TableSchemaIn. It accepts *string parameters for catalog and dbSchema.
Deprecated: Use Conn.TableSchema or Conn.TableSchemaIn instead.
func (*Conn) GetTableTypes
deprecated
added in
v1.0.0
GetTableTypes is a backward-compatible alias for Conn.TableTypes.
Deprecated: Use Conn.TableTypes instead.
func (*Conn) Ingest ¶ added in v1.0.0
Ingest ingests an Arrow record batch into the DuckDB database, creating the table from the record's schema if it does not exist, or appending if it does.
DuckDB does not support ADBC's CreateAppend ingest mode, so this method probes for the table via GetTableSchema and switches between Create and Append modes accordingly.
It returns the number of rows affected if known, otherwise -1.
The connection's Catalog and DBSchema are used as the target catalog and schema if set.
Example ¶
package main
import (
"context"
"fmt"
"github.com/apache/arrow-go/v18/arrow"
"github.com/apache/arrow-go/v18/arrow/array"
"github.com/apache/arrow-go/v18/arrow/memory"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
conn, err := db.Connect()
if err != nil {
fmt.Println("Error:", err)
return
}
defer conn.Close()
// Build an Arrow record batch.
schema := arrow.NewSchema([]arrow.Field{
{Name: "id", Type: arrow.PrimitiveTypes.Int64},
{Name: "name", Type: arrow.BinaryTypes.String},
}, nil)
bldr := array.NewRecordBuilder(memory.DefaultAllocator, schema)
defer bldr.Release()
bldr.Field(0).(*array.Int64Builder).AppendValues([]int64{1, 2, 3}, nil)
bldr.Field(1).(*array.StringBuilder).AppendValues([]string{"Alice", "Bob", "Charlie"}, nil)
rec := bldr.NewRecordBatch()
defer rec.Release()
// Ingest creates the table on first call, appends on subsequent calls.
ctx := context.Background()
n, err := conn.Ingest(ctx, "users", rec)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("ingested:", n, "rows")
// Verify.
res, err := conn.Query(ctx, "SELECT count(*) FROM users")
if err != nil {
fmt.Println("Error:", err)
return
}
defer res.Close()
if res.Reader.Next() {
fmt.Println("total:", res.Reader.RecordBatch().Column(0).ValueStr(0))
}
}
Output: ingested: 3 rows total: 3
func (*Conn) IngestCreateAppend
deprecated
added in
v1.0.0
func (q *Conn) IngestCreateAppend(ctx context.Context, destTable string, rec arrow.RecordBatch) (int64, error)
IngestCreateAppend is a backward-compatible alias for Conn.Ingest.
Deprecated: Use Conn.Ingest instead.
func (*Conn) IngestCreateAppendMerge
deprecated
added in
v1.0.0
func (q *Conn) IngestCreateAppendMerge(ctx context.Context, destTable string, rec arrow.RecordBatch) (int64, error)
IngestCreateAppendMerge is a backward-compatible alias for Conn.IngestMerge.
Deprecated: Use Conn.IngestMerge instead.
func (*Conn) IngestMerge ¶ added in v1.0.0
func (q *Conn) IngestMerge(ctx context.Context, destTable string, rec arrow.RecordBatch) (int64, error)
IngestMerge ingests an Arrow record batch with automatic schema evolution. If the target table does not exist, it is created. If the table exists and the record's schema matches, data is appended. If the schemas differ, the record is ingested into a temporary merge table and then merged into the target using UNION BY NAME, which adds any new columns.
It returns the number of rows affected if known, otherwise -1.
This is useful when the schema of incoming data may evolve over time (e.g. new fields added to a protobuf message).
Example ¶
package main
import (
"context"
"fmt"
"github.com/apache/arrow-go/v18/arrow"
"github.com/apache/arrow-go/v18/arrow/array"
"github.com/apache/arrow-go/v18/arrow/memory"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
conn, err := db.Connect()
if err != nil {
fmt.Println("Error:", err)
return
}
defer conn.Close()
ctx := context.Background()
// First ingest: creates table with {id, name}.
schema1 := arrow.NewSchema([]arrow.Field{
{Name: "id", Type: arrow.PrimitiveTypes.Int64},
{Name: "name", Type: arrow.BinaryTypes.String},
}, nil)
bldr1 := array.NewRecordBuilder(memory.DefaultAllocator, schema1)
defer bldr1.Release()
bldr1.Field(0).(*array.Int64Builder).Append(1)
bldr1.Field(1).(*array.StringBuilder).Append("Alice")
rec1 := bldr1.NewRecordBatch()
defer rec1.Release()
conn.Ingest(ctx, "evolving", rec1)
// Second ingest: adds a new "email" column via UNION BY NAME.
schema2 := arrow.NewSchema([]arrow.Field{
{Name: "id", Type: arrow.PrimitiveTypes.Int64},
{Name: "name", Type: arrow.BinaryTypes.String},
{Name: "email", Type: arrow.BinaryTypes.String},
}, nil)
bldr2 := array.NewRecordBuilder(memory.DefaultAllocator, schema2)
defer bldr2.Release()
bldr2.Field(0).(*array.Int64Builder).Append(2)
bldr2.Field(1).(*array.StringBuilder).Append("Bob")
bldr2.Field(2).(*array.StringBuilder).Append("bob@example.com")
rec2 := bldr2.NewRecordBatch()
defer rec2.Release()
n, err := conn.IngestMerge(ctx, "evolving", rec2)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("merged:", n, "rows")
// The table now has 3 columns and 2 rows.
res, _ := conn.Query(ctx, "SELECT * FROM evolving ORDER BY id")
defer res.Close()
fmt.Println("schema:", res.Reader.Schema())
}
Output: merged: 1 rows schema: schema: fields: 3 - id: type=int64, nullable - name: type=utf8, nullable - email: type=utf8, nullable
func (*Conn) IngestReplace ¶ added in v1.0.0
func (q *Conn) IngestReplace(ctx context.Context, destTable string, rec arrow.RecordBatch) (int64, error)
IngestReplace ingests an Arrow record batch, replacing the target table entirely (DROP + CREATE). It returns the number of rows affected if known, otherwise -1.
This uses ADBC's Replace ingest mode, which is supported since ADBC 1.1.0 / DuckDB 0.9.0+.
func (*Conn) IngestStream ¶ added in v1.0.0
func (q *Conn) IngestStream(ctx context.Context, destTable string, reader array.RecordReader) (int64, error)
IngestStream ingests data from an Arrow RecordReader, which provides streaming access to record batches. This is more memory-efficient than [Ingest] for large datasets because it doesn't require holding all data in memory at once.
The table is created if it does not exist, or appended to if it does.
func (*Conn) InstallExtension ¶ added in v1.0.0
InstallExtension installs a DuckDB extension by name. The extension is downloaded from the DuckDB extension repository if not already installed. Use [QuackCon.LoadExtension] to load it after installation.
Example:
conn.InstallExtension(ctx, "httpfs") conn.LoadExtension(ctx, "httpfs")
func (*Conn) LoadExtension ¶ added in v1.0.0
LoadExtension loads a previously installed DuckDB extension. Extensions cannot be unloaded or reloaded at runtime.
func (*Conn) LockConfiguration ¶ added in v1.0.0
LockConfiguration prevents further configuration changes for the remainder of this session. This is useful to lock in settings during stable operation.
func (*Conn) NewStatement ¶ added in v1.0.0
NewStatement creates a raw ADBC Statement on this connection. The caller must close the statement when done.
Use this for advanced ADBC operations not covered by the convenience methods. For most use cases, prefer Conn.Exec, Conn.Query, or Conn.Prepare.
func (*Conn) Objects ¶ added in v1.0.0
func (q *Conn) Objects(ctx context.Context, opts ...ObjectsOption) (*CatalogTree, error)
Objects retrieves a hierarchical view of database objects (catalogs, schemas, tables, columns) and returns a CatalogTree wrapper with typed navigation methods.
Options control the depth of recursion and filtering:
objs, err := conn.Objects(ctx,
couac.WithDepth(couac.ObjectDepthTables),
couac.WithCatalogFilter("memory"),
couac.WithTableTypes([]string{"TABLE"}),
)
if err != nil { ... }
for _, t := range objs.Tables("memory", "main") {
fmt.Println(t.TableName)
}
When databases are ATTACHed, each attached database appears as a separate catalog in the returned hierarchy.
The result is an Arrow dataset with the following schema:
Field Name | Field Type ------------------------|---------------------------- catalog_name | utf8 catalog_db_schemas | list<DB_SCHEMA_SCHEMA>
DB_SCHEMA_SCHEMA is a Struct with the fields:
Field Name | Field Type ------------------------|---------------------------- db_schema_name | utf8 db_schema_tables | list<TABLE_SCHEMA>
TABLE_SCHEMA is a Struct with the fields:
Field Name | Field Type ------------------------|---------------------------- table_name | utf8 not null table_type | utf8 not null table_columns | list<COLUMN_SCHEMA> table_constraints | list<CONSTRAINT_SCHEMA>
See CatalogInfo, SchemaInfo, TableSchema, ColumnSchema, ConstraintSchema, and UsageSchema for the full field definitions.
Example ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
conn, err := db.Connect()
if err != nil {
fmt.Println("Error:", err)
return
}
defer conn.Close()
ctx := context.Background()
conn.Exec(ctx, "CREATE TABLE products (id INTEGER, name VARCHAR)")
objs, err := conn.Objects(ctx, couac.WithDepth(couac.ObjectDepthTables))
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("has catalogs:", len(objs.Catalogs()) > 0)
for _, t := range objs.Tables("memory", "main") {
if t.TableName == "products" {
fmt.Println("found table:", t.TableName, "type:", t.TableType)
}
}
}
Output: has catalogs: true found table: products type: BASE TABLE
func (*Conn) ObjectsMap ¶ added in v1.0.0
ObjectsMap is a convenience function that retrieves all database objects and returns them as a nested map: catalog name → schema name → []TableSchema.
This is equivalent to calling Objects followed by CatalogTree.Map.
Example ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
conn, err := db.Connect()
if err != nil {
fmt.Println("Error:", err)
return
}
defer conn.Close()
ctx := context.Background()
conn.Exec(ctx, "CREATE TABLE map_demo (x INT)")
// ObjectsMap returns a nested catalog→schema→tables map.
m, err := conn.ObjectsMap(ctx)
if err != nil {
fmt.Println("Error:", err)
return
}
// Check if the table exists in the map.
if tables, ok := m["memory"]["main"]; ok {
for _, t := range tables {
if t.TableName == "map_demo" {
fmt.Println("found:", t.TableName, "type:", t.TableType)
}
}
}
}
Output: found: map_demo type: BASE TABLE
func (*Conn) Platform ¶ added in v1.0.0
Platform returns the DuckDB platform identifier (e.g. "linux_amd64", "osx_arm64", "windows_amd64").
func (*Conn) Prepare ¶ added in v1.0.0
Prepare creates a prepared statement for the given SQL query. The caller must close the statement when done.
Use prepared statements for repeatedly executing the same query with different parameters. Bind parameters with [Statement.Bind] before executing.
Example:
stmt, err := conn.Prepare(ctx, "INSERT INTO t VALUES (?, ?)")
if err != nil { ... }
defer stmt.Close()
stmt.Bind(ctx, record)
n, err := stmt.ExecuteUpdate(ctx)
Example ¶
package main
import (
"context"
"fmt"
"github.com/apache/arrow-go/v18/arrow"
"github.com/apache/arrow-go/v18/arrow/array"
"github.com/apache/arrow-go/v18/arrow/memory"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
conn, err := db.Connect()
if err != nil {
fmt.Println("Error:", err)
return
}
defer conn.Close()
ctx := context.Background()
conn.Exec(ctx, "CREATE TABLE kv (key VARCHAR, value INT)")
// Prepare returns a raw ADBC Statement for Arrow-native parameter binding.
stmt, err := conn.Prepare(ctx, "INSERT INTO kv VALUES (?, ?)")
if err != nil {
fmt.Println("Error:", err)
return
}
defer stmt.Close()
// Build an Arrow record batch with the parameters.
schema := arrow.NewSchema([]arrow.Field{
{Name: "key", Type: arrow.BinaryTypes.String},
{Name: "value", Type: arrow.PrimitiveTypes.Int64},
}, nil)
bldr := array.NewRecordBuilder(memory.DefaultAllocator, schema)
defer bldr.Release()
bldr.Field(0).(*array.StringBuilder).Append("hello")
bldr.Field(1).(*array.Int64Builder).Append(42)
rec := bldr.NewRecordBatch()
defer rec.Release()
// Bind the Arrow record batch and execute.
if err := stmt.Bind(ctx, rec); err != nil {
fmt.Println("Error:", err)
return
}
n, err := stmt.ExecuteUpdate(ctx)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("inserted:", n)
}
Output: inserted: 1
func (*Conn) Query ¶ added in v1.0.0
Query executes a SQL query and returns a QueryResult containing a streaming Arrow array.RecordReader. The caller must call QueryResult.Close when done reading results.
Query acquires a read lock only for the statement execution; the returned RecordReader can be consumed after the lock is released.
Example:
res, err := conn.Query(ctx, "SELECT * FROM users WHERE age > 21")
if err != nil {
return err
}
defer res.Close()
for res.Reader.Next() {
rec := res.Reader.RecordBatch()
// process rec...
}
Example ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
conn, err := db.Connect()
if err != nil {
fmt.Println("Error:", err)
return
}
defer conn.Close()
ctx := context.Background()
conn.Exec(ctx, "CREATE TABLE colors (name VARCHAR, hex VARCHAR)")
conn.Exec(ctx, "INSERT INTO colors VALUES ('red', '#FF0000'), ('green', '#00FF00')")
// Query returns a QueryResult with a streaming RecordReader.
res, err := conn.Query(ctx, "SELECT name, hex FROM colors ORDER BY name")
if err != nil {
fmt.Println("Error:", err)
return
}
defer res.Close()
for res.Reader.Next() {
rec := res.Reader.RecordBatch()
for i := 0; i < int(rec.NumRows()); i++ {
fmt.Printf("%s = %s\n", rec.Column(0).ValueStr(i), rec.Column(1).ValueStr(i))
}
}
}
Output: green = #00FF00 red = #FF0000
func (*Conn) QueryRaw ¶ added in v1.0.0
func (q *Conn) QueryRaw(ctx context.Context, query string) (array.RecordReader, adbc.Statement, int64, error)
QueryRaw executes a SQL query and returns the raw components: a RecordReader, the underlying ADBC Statement, and the number of rows affected. This is the original 4-return-value form for callers that need direct access to the statement.
The caller is responsible for closing both the RecordReader and the Statement. Prefer Conn.Query for simpler resource management.
Example ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
conn, err := db.Connect()
if err != nil {
fmt.Println("Error:", err)
return
}
defer conn.Close()
ctx := context.Background()
conn.Exec(ctx, "CREATE TABLE raw_demo (x INT)")
conn.Exec(ctx, "INSERT INTO raw_demo VALUES (10), (20), (30)")
// QueryRaw returns the reader, statement, and row count separately.
rr, stmt, n, err := conn.QueryRaw(ctx, "SELECT * FROM raw_demo")
if err != nil {
fmt.Println("Error:", err)
return
}
defer rr.Release()
defer stmt.Close()
fmt.Println("rows affected:", n)
for rr.Next() {
rec := rr.RecordBatch()
fmt.Println("batch rows:", rec.NumRows())
}
}
Output: rows affected: -1 batch rows: 3
func (*Conn) Reset ¶ added in v1.0.0
Reset restores a DuckDB configuration option to its default value.
func (*Conn) Secrets ¶ added in v1.0.0
Secrets returns the list of stored DuckDB secrets. Sensitive fields are redacted by DuckDB.
func (*Conn) Set ¶ added in v1.0.0
Set sets a DuckDB configuration option. Most options are GLOBAL (instance-wide); some are SESSION-scoped (connection-specific). Use Conn.Setting to read the current value, and Conn.Reset to restore the default.
Example:
conn.Set(ctx, "memory_limit", "4GB") conn.Set(ctx, "threads", "8")
Example ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
conn, err := db.Connect()
if err != nil {
fmt.Println("Error:", err)
return
}
defer conn.Close()
ctx := context.Background()
// Set and read back a configuration value.
if err := conn.Set(ctx, "threads", "2"); err != nil {
fmt.Println("Error:", err)
return
}
val, err := conn.Setting(ctx, "threads")
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("threads:", val)
}
Output: threads: 2
func (*Conn) SetMaxTempDirectorySize ¶ added in v1.0.0
SetMaxTempDirectorySize sets the maximum size of the temp directory (e.g. "50GB"). Defaults to 90% of available disk space.
func (*Conn) SetMemoryLimit ¶ added in v1.0.0
SetMemoryLimit sets the buffer manager's memory limit (e.g. "4GB", "512MB"). Note: actual memory consumption may exceed this limit because vectors, query results, and some aggregate states are allocated outside the buffer manager.
func (*Conn) SetPreserveInsertionOrder ¶ added in v1.0.0
SetPreserveInsertionOrder controls whether result ordering matches insertion order. Disabling this (false) can improve performance and reduce memory usage for larger-than-memory workloads.
func (*Conn) SetProfilingOutput ¶ added in v1.0.0
SetProfilingOutput sets the file path for profiling output.
func (*Conn) SetTempDirectory ¶ added in v1.0.0
SetTempDirectory sets the directory used for spilling data to disk when memory is insufficient. Defaults to <database_file>.tmp for file-backed databases or .tmp for in-memory databases.
func (*Conn) SetThreads ¶ added in v1.0.0
SetThreads sets the number of threads used for parallel query execution. Defaults to the number of CPU cores. Consider setting this to the number of physical cores (not hyperthreads) if hyperthreading causes slowdowns.
func (*Conn) Setting ¶ added in v1.0.0
Setting reads the current value of a DuckDB configuration option.
Example:
threads, err := conn.Setting(ctx, "threads") memLimit, err := conn.Setting(ctx, "memory_limit")
func (*Conn) Settings ¶ added in v1.0.0
Settings returns all DuckDB configuration settings with their current values, descriptions, types, and scopes.
func (*Conn) ShowAllTables ¶ added in v1.0.0
ShowAllTables returns information about all tables across all databases and schemas.
func (*Conn) ShowTables ¶ added in v1.0.0
ShowTables returns the names of all tables in the current schema.
Example ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
conn, err := db.Connect()
if err != nil {
fmt.Println("Error:", err)
return
}
defer conn.Close()
ctx := context.Background()
conn.Exec(ctx, "CREATE TABLE alpha (x INT)")
conn.Exec(ctx, "CREATE TABLE beta (y INT)")
tables, err := conn.ShowTables(ctx)
if err != nil {
fmt.Println("Error:", err)
return
}
for _, t := range tables {
fmt.Println(t)
}
}
Output: alpha beta
func (*Conn) StorageInfo ¶ added in v1.0.0
StorageInfo returns detailed storage information for a table, including row group and compression details.
func (*Conn) Summarize ¶ added in v1.0.0
Summarize computes aggregate statistics (min, max, approx_unique, avg, std, q25, q50, q75, count, null_percentage) over all columns of a table or query result. Returns a QueryResult for streaming access to the statistics.
func (*Conn) TableSchema ¶ added in v1.0.0
TableSchema returns the Arrow schema of a DuckDB table using the connection's default catalog and schema.
Example ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
conn, err := db.Connect()
if err != nil {
fmt.Println("Error:", err)
return
}
defer conn.Close()
ctx := context.Background()
conn.Exec(ctx, "CREATE TABLE typed (id BIGINT, name VARCHAR, active BOOLEAN)")
schema, err := conn.TableSchema(ctx, "typed")
if err != nil {
fmt.Println("Error:", err)
return
}
for _, f := range schema.Fields() {
fmt.Printf("%s: %s\n", f.Name, f.Type)
}
}
Output: id: int64 name: utf8 active: bool
func (*Conn) TableSchemaIn ¶ added in v1.0.0
func (q *Conn) TableSchemaIn(ctx context.Context, catalog, dbSchema, tableName string) (*arrow.Schema, error)
TableSchemaIn returns the Arrow schema of a DuckDB table in the specified catalog and schema. Pass empty strings for catalog and dbSchema to use the defaults.
func (*Conn) TableTypes ¶ added in v1.0.0
TableTypes returns the table types available in the database (e.g. "BASE TABLE", "LOCAL TEMPORARY", "VIEW").
func (*Conn) UserAgent ¶ added in v1.0.0
UserAgent returns the DuckDB user agent string (e.g. "duckdb/v1.5.1(windows_amd64)").
func (*Conn) Version ¶ added in v1.0.0
Version returns the DuckDB version string (e.g. "v1.5.1").
Example ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
conn, err := db.Connect()
if err != nil {
fmt.Println("Error:", err)
return
}
defer conn.Close()
v, err := conn.Version(context.Background())
if err != nil {
fmt.Println("Error:", err)
return
}
// Version string starts with "v"
fmt.Println("has version:", v != "")
}
Output: has version: true
type Connection ¶ added in v0.5.2
type Connection = Conn
Connection is a backward-compatible alias for Conn.
type ConstraintSchema ¶ added in v0.5.3
type ConstraintSchema struct {
ConstraintName string `json:"constraint_name"`
ConstraintType string `json:"constraint_type"`
ConstraintColumnNames []string `json:"constraint_column_names"`
ConstraintColumnUsage []UsageSchema `json:"constraint_column_usage"`
}
ConstraintSchema describes a constraint on a table (CHECK, FOREIGN KEY, PRIMARY KEY, or UNIQUE).
type DB ¶ added in v1.0.0
type DB struct {
// contains filtered or unexported fields
}
DB represents an open DuckDB database accessed through ADBC.
A DB holds the ADBC driver, database handle, and tracks all open connections. It is safe for concurrent use; internal state is protected by a sync.RWMutex. Normal operations (queries, ingests) acquire a read lock, while maintenance operations ([Compact], [ForceCheckpoint]) acquire a write lock to safely pause concurrent work.
Call NewDuck to create a DB and DB.Close to release resources.
func NewDuck ¶
NewDuck opens a DuckDB database via ADBC.
Options control the database file path, driver location, and default context. If no driver option is provided, the driver name "duckdb" is passed to the ADBC driver manager, which resolves it via TOML manifests installed by the dbc CLI.
For file-backed databases, only one DB may be open per file path within a process. Attempting to open the same file twice returns ErrPathAlreadyOpen.
Example:
// In-memory database with default driver resolution:
db, err := couac.NewDuck()
// File-backed database with explicit driver path:
db, err := couac.NewDuck(
couac.WithPath("analytics.db"),
couac.WithDriverPath("/usr/local/lib/libduckdb.so"),
)
Example ¶
package main
import (
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
// Open an in-memory DuckDB database with default driver resolution.
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
fmt.Println("database opened")
}
Output: database opened
Example (FileBacked) ¶
package main
import (
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
// Open a file-backed DuckDB database.
db, err := couac.NewDuck(couac.WithPath("example.db"))
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
fmt.Println("file-backed database opened")
}
Output: file-backed database opened
func NewDuckDatabase ¶ added in v1.0.0
NewDuckDatabase is an alias for NewDuck.
func (*DB) Checkpoint ¶ added in v1.0.0
Checkpoint synchronizes the WAL to the database file. This fails if any connections have running transactions. For a version that waits for running transactions, use DB.ForceCheckpoint.
Checkpoint acquires a read lock, allowing it to run concurrently with queries but not with maintenance operations.
func (*DB) Close ¶ added in v1.0.0
Close closes the database and all its open connections, releasing all associated resources. It is important to call Close to allow DuckDB to properly commit all WAL file changes.
Close acquires the write lock, so it waits for any in-flight operations to complete. Close is idempotent; calling it more than once returns nil.
Close returns an aggregated error if any connection or the database itself fails to close.
func (*DB) Compact ¶ added in v1.0.0
Compact reclaims disk space by checkpointing and, for file-backed databases, creating a compacted copy.
Compact acquires the write lock on the database, which blocks until all in-flight read operations complete and prevents new operations from starting. Open connections remain valid after compaction — they are NOT closed or invalidated.
For file-backed databases, Compact:
- Runs FORCE CHECKPOINT to flush the WAL.
- ATTACHes a temporary database file.
- Runs COPY FROM DATABASE to create a compacted copy.
- DETACHes the temporary database.
- Replaces the original file with the compacted copy.
- Runs FORCE CHECKPOINT again to sync.
For in-memory databases, only FORCE CHECKPOINT is run (which reclaims space from deleted rows when the database was created with COMPRESS mode).
DuckDB's in-memory cache is preserved because at least one internal connection remains open during the operation.
func (*DB) Connect ¶ added in v1.0.0
Connect opens a new connection to the database and tracks it in the parent DB. The connection will be closed automatically when the parent's DB.Close is called, but it is good practice to close connections when no longer needed.
Connect acquires a read lock on the parent; it will block if a maintenance operation (e.g. DB.Compact) is in progress.
Example ¶
package main
import (
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
conn, err := db.Connect()
if err != nil {
fmt.Println("Error:", err)
return
}
defer conn.Close()
fmt.Println("connections:", db.ConnectionCount())
}
Output: connections: 1
func (*DB) ConnectAs ¶ added in v1.0.0
ConnectAs opens a new connection with the specified catalog and schema. The catalog and schema are used as defaults for metadata and ingest operations on this connection.
Pass empty strings to use the default catalog ("memory" for in-memory databases, or the filename for file-backed databases) and default schema ("main").
func (*DB) ConnectionCount ¶ added in v1.0.0
ConnectionCount returns the number of currently tracked open connections.
func (*DB) DefaultContext ¶ added in v1.0.0
DefaultContext returns the context used for opening new connections.
func (*DB) DriverPath ¶ added in v1.0.0
DriverPath returns the resolved driver path being used.
func (*DB) ForceCheckpoint ¶ added in v1.0.0
ForceCheckpoint synchronizes the WAL to the database file, waiting for any running transactions to complete first (DuckDB v1.4+).
ForceCheckpoint acquires the write lock, blocking all concurrent operations until the checkpoint completes. Open connections remain valid afterward.
func (*DB) NewConnection
deprecated
added in
v1.0.0
NewConnection is a backward-compatible alias for DB.Connect.
Deprecated: Use DB.Connect instead.
func (*DB) NewConnectionWithOpts
deprecated
added in
v1.0.0
NewConnectionWithOpts is a backward-compatible alias for DB.ConnectAs.
Deprecated: Use DB.ConnectAs instead.
func (*DB) Path ¶ added in v1.0.0
Path returns the path to the database file. An empty string indicates an in-memory database.
func (*DB) Ping ¶ added in v1.0.0
Ping verifies the database is reachable by executing SELECT 1 on a temporary connection. Returns an error if the database is closed or unreachable.
func (*DB) StdDB ¶ added in v1.0.0
StdDB returns a *sql.DB backed by this DuckDB database. The returned sql.DB shares the same underlying ADBC database and connections.
This is useful for integrating with code that expects a standard *sql.DB, such as ORMs, migration tools, or test harnesses.
The returned *sql.DB should be closed when no longer needed, but closing it does NOT close the underlying couac DB.
Column values are returned as native Go types matching the Arrow column type (int8–uint64, float32/float64, bool, string, []byte, time.Time, Decimal). Nested Arrow types are returned as List, Struct, and Map with full recursive conversion to Go-native values. See [arrowToDriverValue] for the complete mapping.
Parameterized queries are supported with both ? and $N placeholder styles (e.g. "SELECT * FROM t WHERE id = ?" or "WHERE id = $1"). Named parameters ($name) are not supported by DuckDB's ADBC driver. Supported Go parameter types: int64, float64, bool, string, []byte, time.Time, Decimal, and nil.
Limitations of the database/sql bridge:
- For full Arrow-native performance, prefer Conn.Query and Conn.Ingest over database/sql.
Example:
db, _ := couac.NewDuck() defer db.Close() stdDB := db.StdDB() defer stdDB.Close() var count int stdDB.QueryRowContext(ctx, "SELECT count(*) FROM users").Scan(&count)
Example ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
// Set up data via the native API.
conn, err := db.Connect()
if err != nil {
fmt.Println("Error:", err)
return
}
ctx := context.Background()
conn.Exec(ctx, "CREATE TABLE stddb_demo (id INT, name VARCHAR)")
conn.Exec(ctx, "INSERT INTO stddb_demo VALUES (1, 'Pierre'), (2, 'Jean'), (3, 'Jacques')")
conn.Close()
// Use database/sql for querying.
stdDB := db.StdDB()
defer stdDB.Close()
var count int
if err := stdDB.QueryRowContext(ctx, "SELECT count(*) FROM stddb_demo").Scan(&count); err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("count:", count)
}
Output: count: 3
Example (NativeTypes) ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
conn, err := db.Connect()
if err != nil {
fmt.Println("Error:", err)
return
}
ctx := context.Background()
conn.Exec(ctx, "CREATE TABLE products (name VARCHAR, price DECIMAL(10,2), in_stock BOOLEAN)")
conn.Exec(ctx, "INSERT INTO products VALUES ('Widget', 19.99, true), ('Gadget', 49.50, false)")
conn.Close()
// Values come back as native Go types, not strings.
stdDB := db.StdDB()
defer stdDB.Close()
rows, err := stdDB.QueryContext(ctx, "SELECT name, price, in_stock FROM products ORDER BY name")
if err != nil {
fmt.Println("Error:", err)
return
}
defer rows.Close()
for rows.Next() {
var name string
var price couac.Decimal
var inStock bool
if err := rows.Scan(&name, &price, &inStock); err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("%s: $%s (in stock: %v)\n", name, price.String(), inStock)
}
}
Output: Gadget: $49.50 (in stock: false) Widget: $19.99 (in stock: true)
Example (ParameterizedQuery) ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
conn, err := db.Connect()
if err != nil {
fmt.Println("Error:", err)
return
}
ctx := context.Background()
conn.Exec(ctx, "CREATE TABLE inventory (id INT, item VARCHAR, qty INT)")
conn.Exec(ctx, "INSERT INTO inventory VALUES (1,'bolt',100),(2,'nut',250),(3,'screw',75)")
conn.Close()
// Use parameterized queries with ? placeholders.
stdDB := db.StdDB()
defer stdDB.Close()
var item string
err = stdDB.QueryRowContext(ctx,
"SELECT item FROM inventory WHERE qty > ? ORDER BY qty LIMIT 1",
int64(80),
).Scan(&item)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("item:", item)
// $N placeholders also work.
var qty int
err = stdDB.QueryRowContext(ctx,
"SELECT qty FROM inventory WHERE item = $1",
"nut",
).Scan(&qty)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("qty:", qty)
}
Output: item: bolt qty: 250
Example (PreparedStatement) ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
stdDB := db.StdDB()
defer stdDB.Close()
ctx := context.Background()
stdDB.ExecContext(ctx, "CREATE TABLE scores (player VARCHAR, score INT)")
// Prepare a statement once, execute it multiple times with different parameters.
stmt, err := stdDB.PrepareContext(ctx, "INSERT INTO scores VALUES (?, ?)")
if err != nil {
fmt.Println("Error:", err)
return
}
defer stmt.Close()
for _, p := range []struct {
name string
score int64
}{
{"Alice", 95},
{"Bob", 87},
{"Carol", 92},
} {
if _, err := stmt.ExecContext(ctx, p.name, p.score); err != nil {
fmt.Println("Error:", err)
return
}
}
var count int
stdDB.QueryRowContext(ctx, "SELECT count(*) FROM scores").Scan(&count)
fmt.Println("players:", count)
var topName string
stdDB.QueryRowContext(ctx, "SELECT player FROM scores ORDER BY score DESC LIMIT 1").Scan(&topName)
fmt.Println("top scorer:", topName)
}
Output: players: 3 top scorer: Alice
Example (Transaction) ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
stdDB := db.StdDB()
defer stdDB.Close()
ctx := context.Background()
stdDB.ExecContext(ctx, "CREATE TABLE ledger (acct VARCHAR, balance INT)")
stdDB.ExecContext(ctx, "INSERT INTO ledger VALUES ('checking', 1000), ('savings', 500)")
// Execute a transfer inside a transaction.
tx, err := stdDB.BeginTx(ctx, nil)
if err != nil {
fmt.Println("Error:", err)
return
}
tx.ExecContext(ctx, "UPDATE ledger SET balance = balance - ? WHERE acct = ?", int64(200), "checking")
tx.ExecContext(ctx, "UPDATE ledger SET balance = balance + ? WHERE acct = ?", int64(200), "savings")
if err := tx.Commit(); err != nil {
fmt.Println("Error:", err)
return
}
// Verify balances.
rows, _ := stdDB.QueryContext(ctx, "SELECT acct, balance FROM ledger ORDER BY acct")
defer rows.Close()
for rows.Next() {
var acct string
var bal int
rows.Scan(&acct, &bal)
fmt.Printf("%s: %d\n", acct, bal)
}
}
Output: checking: 800 savings: 700
func (*DB) WithTransaction ¶ added in v1.0.0
WithTransaction executes fn within a transaction. It opens a dedicated connection, disables auto-commit, executes fn, and then either commits (if fn returns nil) or rolls back (if fn returns an error or panics). The dedicated connection is closed after the transaction completes.
DuckDB uses optimistic concurrency control. If fn modifies rows that are concurrently modified by another transaction, DuckDB may return a "Transaction conflict" error.
Example:
err := db.WithTransaction(ctx, func(tx *couac.Conn) error {
_, err := tx.Exec(ctx, "INSERT INTO t1 VALUES (1)")
if err != nil { return err }
_, err = tx.Exec(ctx, "INSERT INTO t2 VALUES (2)")
return err
})
Example ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
ctx := context.Background()
// Set up tables via a regular connection.
setup, _ := db.Connect()
setup.Exec(ctx, "CREATE TABLE account (id INT, balance INT)")
setup.Exec(ctx, "INSERT INTO account VALUES (1, 1000), (2, 500)")
setup.Close()
// Run a transfer inside a transaction.
err = db.WithTransaction(ctx, func(tx *couac.Conn) error {
_, err := tx.Exec(ctx, "UPDATE account SET balance = balance - 200 WHERE id = 1")
if err != nil {
return err
}
_, err = tx.Exec(ctx, "UPDATE account SET balance = balance + 200 WHERE id = 2")
return err
})
if err != nil {
fmt.Println("Error:", err)
return
}
// Verify the transfer.
verify, _ := db.Connect()
defer verify.Close()
res, _ := verify.Query(ctx, "SELECT id, balance FROM account ORDER BY id")
defer res.Close()
for res.Reader.Next() {
rec := res.Reader.RecordBatch()
for i := 0; i < int(rec.NumRows()); i++ {
fmt.Printf("account %s: balance %s\n",
rec.Column(0).ValueStr(i), rec.Column(1).ValueStr(i))
}
}
}
Output: account 1: balance 800 account 2: balance 700
type DBObject ¶ added in v0.5.3
type DBObject = CatalogInfo
DBObject is a backward-compatible alias for CatalogInfo.
type DBObjects ¶ added in v1.0.0
type DBObjects = CatalogTree
DBObjects is a backward-compatible alias for CatalogTree.
type DBSchema ¶ added in v0.5.3
type DBSchema = SchemaInfo
DBSchema is a backward-compatible alias for SchemaInfo.
type DatabaseSize ¶ added in v1.0.0
type DatabaseSize struct {
DatabaseName string `json:"database_name"`
DatabaseSize string `json:"database_size"`
BlockSize int64 `json:"block_size"`
TotalBlocks int64 `json:"total_blocks"`
UsedBlocks int64 `json:"used_blocks"`
FreeBlocks int64 `json:"free_blocks"`
WALSize string `json:"wal_size"`
MemoryUsage string `json:"memory_usage"`
MemoryLimit string `json:"memory_limit"`
}
DatabaseSize describes the on-disk size information for a database.
type Decimal ¶ added in v1.0.0
Decimal represents an arbitrary-precision decimal number. Width is the total number of digits, Scale is the number of digits after the decimal point, and Unscaled is the integer value before applying the scale (i.e. the actual value is Unscaled / 10^Scale).
This mirrors the Decimal type used by duckdb-go for compatibility.
Usage with database/sql:
var d couac.Decimal row.Scan(&d) precise := d.BigFloat() // lossless *big.Float approx := d.Float64() // lossy float64 text := d.String() // "123.45"
Example ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
conn, err := db.Connect()
if err != nil {
fmt.Println("Error:", err)
return
}
ctx := context.Background()
conn.Exec(ctx, "CREATE TABLE amounts (v DECIMAL(18,4))")
conn.Exec(ctx, "INSERT INTO amounts VALUES (123456.7890)")
conn.Close()
stdDB := db.StdDB()
defer stdDB.Close()
var d couac.Decimal
if err := stdDB.QueryRowContext(ctx, "SELECT v FROM amounts").Scan(&d); err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("String:", d.String())
fmt.Println("Float64:", d.Float64())
fmt.Println("BigFloat:", d.BigFloat().Text('f', 4))
}
Output: String: 123456.7890 Float64: 123456.789 BigFloat: 123456.7890
func (Decimal) BigFloat ¶ added in v1.0.0
BigFloat converts the Decimal to a *big.Float with full precision. This is lossless for all decimal values that DuckDB can produce.
Usage with database/sql:
var d couac.Decimal row.Scan(&d) precise := d.BigFloat()
func (Decimal) Float64 ¶ added in v1.0.0
Float64 converts the Decimal to a float64. This may lose precision for high-precision values; use Decimal.BigFloat when precision matters.
func (*Decimal) Scan ¶ added in v1.0.0
Scan implements sql.Scanner for Decimal. It accepts the following source types:
- Decimal: direct assignment
- *big.Int: assigns Unscaled directly (Scale=0)
- int64, float64: converts to Decimal with Scale=0
- string, []byte: parses as a decimal string (e.g. "123.45")
- nil: zeroes the Decimal
type DuckDatabase ¶ added in v0.5.2
type DuckDatabase = DB
DuckDatabase is a backward-compatible alias for DB.
type Extension ¶ added in v1.0.0
type Extension struct {
Name string `json:"extension_name"`
Loaded bool `json:"loaded"`
Installed bool `json:"installed"`
Version string `json:"extension_version"`
Description string `json:"description"`
InstallMode string `json:"install_mode"`
}
Extension describes an installed DuckDB extension.
type GetObjectsOption ¶ added in v1.0.0
type GetObjectsOption = ObjectsOption
GetObjectsOption is a backward-compatible alias for ObjectsOption.
type List ¶ added in v1.0.0
type List struct {
Values []any
}
List represents a DuckDB LIST column value as a Go slice. Elements are Go-native types produced by the recursive Arrow-to-Go conversion: int8–uint64, float32, float64, bool, string, []byte, Decimal, nested List, Struct, Map, or nil.
List also represents LargeList and FixedSizeList columns.
List implements sql.Scanner and driver.Valuer.
Usage with database/sql:
var l couac.List
row.Scan(&l)
fmt.Println(l.Values) // []any{1, 2, 3}
ints := l.Ints() // []int64{1, 2, 3}
strs := l.Strings() // []string{"1", "2", "3"}
j, _ := l.MarshalJSON() // [1,2,3]
Typed element access:
n, ok := l.Int32At(0) // leaf value s, ok := l.StringAt(2) // leaf value n, ok = couac.Elem[int32](l, 0) // generic (any leaf type)
Chaining into nested types — navigation methods return a zero value on failure, so further calls safely return zero/false:
name, ok := outer.StructAt(0).Str("name") // LIST<STRUCT<…>>
ints := outer.ListAt(0).Ints() // LIST<LIST<INT>>
v, ok := outer.MapAt(0).Int32("key") // LIST<MAP<…>>
Example ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
stdDB := db.StdDB()
defer stdDB.Close()
ctx := context.Background()
// Scan a DuckDB list column into a couac.List.
var l couac.List
err = stdDB.QueryRowContext(ctx, "SELECT [10, 20, 30]::INTEGER[]").Scan(&l)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("values:", l.Values)
fmt.Println("ints:", l.Ints())
fmt.Println("floats:", l.Floats())
fmt.Println("strings:", l.Strings())
fmt.Println("json:", l.String())
}
Output: values: [10 20 30] ints: [10 20 30] floats: [10 20 30] strings: [10 20 30] json: [10,20,30]
Example (Nested) ¶
ExampleList_nested demonstrates scanning a LIST of LIST (nested list) and using Scan to navigate into inner lists with convenience accessors.
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
stdDB := db.StdDB()
defer stdDB.Close()
ctx := context.Background()
var outer couac.List
err = stdDB.QueryRowContext(ctx, "SELECT [[1, 2], [3, 4, 5]]::INTEGER[][]").Scan(&outer)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("outer length:", len(outer.Values))
// Chain ListAt directly into Ints — no intermediate variables
fmt.Println("inner[0] ints:", outer.ListAt(0).Ints())
fmt.Println("inner[1] ints:", outer.ListAt(1).Ints())
}
Output: outer length: 2 inner[0] ints: [1 2] inner[1] ints: [3 4 5]
Example (NestedStructWithList) ¶
ExampleList_nestedStructWithList demonstrates depth-3 chaining: StructAt → Str / List → Ints in a single expression.
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
stdDB := db.StdDB()
defer stdDB.Close()
ctx := context.Background()
var outer couac.List
err = stdDB.QueryRowContext(ctx,
"SELECT [{'name': 'a', 'vals': [1, 2]}, {'name': 'b', 'vals': [3]}]::"+
"STRUCT(name VARCHAR, vals INTEGER[])[]",
).Scan(&outer)
if err != nil {
fmt.Println("Error:", err)
return
}
for i := range outer.Values {
name, _ := outer.StructAt(i).Str("name")
vals := outer.StructAt(i).List("vals").Ints()
fmt.Printf("elem[%d]: name=%s vals=%v\n", i, name, vals)
}
}
Output: elem[0]: name=a vals=[1 2] elem[1]: name=b vals=[3]
Example (NullElements) ¶
ExampleList_nullElements demonstrates that NULL elements inside a LIST are detected by the ok return from Int32At.
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
stdDB := db.StdDB()
defer stdDB.Close()
ctx := context.Background()
var l couac.List
err = stdDB.QueryRowContext(ctx, "SELECT [1, NULL, 3]::INTEGER[]").Scan(&l)
if err != nil {
fmt.Println("Error:", err)
return
}
for i := range l.Values {
v, ok := l.Int32At(i)
if ok {
fmt.Printf("[%d] = %d\n", i, v)
} else {
fmt.Printf("[%d] = NULL\n", i)
}
}
}
Output: [0] = 1 [1] = NULL [2] = 3
func (List) BoolAt ¶ added in v1.1.0
BoolAt returns the element at index i as bool. Returns (false, false) if i is out of range, nil, or not bool.
func (List) Bools ¶ added in v1.0.0
Bools returns the list elements as []bool. Elements that are not bool are skipped.
func (List) Float64At ¶ added in v1.1.0
Float64At returns the element at index i as float64. Returns (0, false) if i is out of range, nil, or not float64.
func (List) Floats ¶ added in v1.0.0
Floats returns the list elements as []float64. Elements that are not numeric types are skipped.
func (List) Int32At ¶ added in v1.1.0
Int32At returns the element at index i as int32. Returns (0, false) if i is out of range, nil, or not int32.
func (List) Int64At ¶ added in v1.1.0
Int64At returns the element at index i as int64. Returns (0, false) if i is out of range, nil, or not int64.
func (List) Ints ¶ added in v1.0.0
Ints returns the list elements as []int64. Elements that are not integer or float types are skipped. This is a convenience accessor for lists known to contain only integers (TINYINT through UBIGINT).
func (List) JSON ¶ added in v1.0.0
JSON returns a copy of the List with every element converted to its JSON string representation. This is useful when you want to process nested structures as JSON text rather than Go maps/slices.
func (List) ListAt ¶ added in v1.1.0
ListAt returns the element at index i as a List, enabling chaining. Returns a zero List if i is out of range, nil, or not a list; further calls on the zero value safely return zero/false.
func (List) MapAt ¶ added in v1.1.0
MapAt returns the element at index i as a Map, enabling chaining. Returns a zero Map if i is out of range, nil, or not a map; further calls on the zero value safely return zero/false.
func (List) MarshalJSON ¶ added in v1.0.0
MarshalJSON implements [json.Marshaler].
func (*List) Scan ¶ added in v1.0.0
Scan implements sql.Scanner for List. It accepts:
- List: direct assignment
- []any: wraps directly
- string, []byte: parses as JSON array
- nil: zeroes the list
func (List) StringAt ¶ added in v1.1.0
StringAt returns the element at index i as string. Returns ("", false) if i is out of range, nil, or not a string.
func (List) Strings ¶ added in v1.0.0
Strings returns the list elements as []string. Each element is formatted with fmt.Sprint. Nil elements become the empty string.
type Map ¶ added in v1.0.0
Map represents a DuckDB MAP column value as a Go map. DuckDB MAP keys are typically VARCHAR; non-string keys are converted with fmt.Sprint. Values are Go-native types produced by the recursive Arrow-to-Go conversion.
Map implements sql.Scanner and driver.Valuer.
Usage with database/sql:
var m couac.Map
row.Scan(&m)
fmt.Println(m.Values["key1"]) // 42
j, _ := m.MarshalJSON() // {"key1":42,"key2":99}
Typed value access:
count, ok := m.Int32("key1") // int32 value
name, ok := m.Str("key2") // string value
count, ok = couac.Value[int32](m, "key1") // generic (any leaf type)
Chaining into nested types — navigation methods return a zero value on failure, so further calls safely return zero/false:
age, ok := m.Struct("alice").Int32("age") // STRUCT value → Struct
nums := m.List("nums").Ints() // LIST value → List
v, ok := m.Map("sub").Str("key") // MAP value → Map
Example ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
stdDB := db.StdDB()
defer stdDB.Close()
ctx := context.Background()
// Scan a DuckDB map column into a couac.Map.
var m couac.Map
err = stdDB.QueryRowContext(ctx, "SELECT MAP {'a': 1, 'b': 2}").Scan(&m)
if err != nil {
fmt.Println("Error:", err)
return
}
a, _ := m.Get("a")
b, _ := m.Get("b")
fmt.Println("a:", a)
fmt.Println("b:", b)
}
Output: a: 1 b: 2
Example (NestedStructValues) ¶
ExampleMap_nestedStructValues demonstrates chaining into MAP values that are STRUCTs: Struct("key") → Int32("field").
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
stdDB := db.StdDB()
defer stdDB.Close()
ctx := context.Background()
var m couac.Map
err = stdDB.QueryRowContext(ctx,
"SELECT MAP {'alice': {'age': 30}, 'bob': {'age': 25}}::"+
"MAP(VARCHAR, STRUCT(age INTEGER))",
).Scan(&m)
if err != nil {
fmt.Println("Error:", err)
return
}
// Chain: Map → Struct → Int32
age, _ := m.Struct("alice").Int32("age")
fmt.Println("alice age:", age)
bobAge, _ := m.Struct("bob").Int32("age")
fmt.Println("bob age:", bobAge)
}
Output: alice age: 30 bob age: 25
func (Map) Bool ¶ added in v1.1.0
Bool returns the value for key as bool. Returns (false, false) if the key is missing, nil, or not bool.
func (Map) Float64 ¶ added in v1.1.0
Float64 returns the value for key as float64. Returns (0, false) if the key is missing, nil, or not float64.
func (Map) Get ¶ added in v1.0.0
Get returns the value for the given key and whether the key exists in the map. This is a convenience shorthand for m.Values[key].
func (Map) Int32 ¶ added in v1.1.0
Int32 returns the value for key as int32. Returns (0, false) if the key is missing, nil, or not int32.
func (Map) Int64 ¶ added in v1.1.0
Int64 returns the value for key as int64. Returns (0, false) if the key is missing, nil, or not int64.
func (Map) JSON ¶ added in v1.0.0
JSON returns a copy of the Map with every value converted to its JSON string representation.
func (Map) List ¶ added in v1.1.0
List returns the value for key as a List, enabling chaining. Returns a zero List if the key is missing, nil, or not a list; further calls on the zero value safely return zero/false.
func (Map) Map ¶ added in v1.1.0
Map returns the value for key as a Map, enabling chaining. Returns a zero Map if the key is missing, nil, or not a map; further calls on the zero value safely return zero/false.
func (Map) MarshalJSON ¶ added in v1.0.0
MarshalJSON implements [json.Marshaler].
func (*Map) Scan ¶ added in v1.0.0
Scan implements sql.Scanner for Map. It accepts:
- Map: direct assignment
- map[string]any: wraps directly
- string, []byte: parses as JSON object
- nil: zeroes the map
func (Map) Str ¶ added in v1.1.0
Str returns the value for key as string. Returns ("", false) if the key is missing, nil, or not a string. (Named Str to avoid collision with the String() string method.)
type NullDecimal ¶ added in v1.0.0
NullDecimal represents a Decimal that may be null. NullDecimal implements sql.Scanner and driver.Valuer, following the same pattern as sql.NullString, sql.NullInt64, and sql.NullFloat64.
Usage with database/sql:
var nd couac.NullDecimal
err := row.Scan(&nd)
if nd.Valid {
fmt.Println(nd.Decimal.BigFloat())
}
Example ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
conn, err := db.Connect()
if err != nil {
fmt.Println("Error:", err)
return
}
ctx := context.Background()
conn.Exec(ctx, "CREATE TABLE nullable_amounts (v DECIMAL(10,2))")
conn.Exec(ctx, "INSERT INTO nullable_amounts VALUES (42.00), (NULL)")
conn.Close()
stdDB := db.StdDB()
defer stdDB.Close()
rows, err := stdDB.QueryContext(ctx, "SELECT v FROM nullable_amounts ORDER BY v NULLS FIRST")
if err != nil {
fmt.Println("Error:", err)
return
}
defer rows.Close()
for rows.Next() {
var nd couac.NullDecimal
if err := rows.Scan(&nd); err != nil {
fmt.Println("Error:", err)
return
}
if nd.Valid {
fmt.Println("value:", nd.Decimal.String())
} else {
fmt.Println("value: NULL")
}
}
}
Output: value: NULL value: 42.00
func (*NullDecimal) Scan ¶ added in v1.0.0
func (nd *NullDecimal) Scan(src any) error
Scan implements sql.Scanner. It sets Valid to false for nil values and delegates to Decimal.Scan otherwise.
func (NullDecimal) Value ¶ added in v1.0.0
func (nd NullDecimal) Value() (driver.Value, error)
Value implements driver.Valuer. It returns nil when !Valid and delegates to Decimal.Value otherwise.
type NullList ¶ added in v1.0.0
NullList represents a List that may be null, following the same pattern as sql.NullString.
Example ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
stdDB := db.StdDB()
defer stdDB.Close()
ctx := context.Background()
stdDB.ExecContext(ctx, "CREATE TABLE nl (tags INTEGER[])")
stdDB.ExecContext(ctx, "INSERT INTO nl VALUES ([1,2]), (NULL)")
rows, _ := stdDB.QueryContext(ctx, "SELECT tags FROM nl ORDER BY tags NULLS LAST")
defer rows.Close()
for rows.Next() {
var nl couac.NullList
rows.Scan(&nl)
fmt.Println("valid:", nl.Valid)
}
}
Output: valid: true valid: false
func (*NullList) Scan ¶ added in v1.0.0
Scan implements sql.Scanner.
type NullMap ¶ added in v1.0.0
NullMap represents a Map that may be null, following the same pattern as sql.NullString.
func (*NullMap) Scan ¶ added in v1.0.0
Scan implements sql.Scanner.
type NullStruct ¶ added in v1.0.0
NullStruct represents a Struct that may be null, following the same pattern as sql.NullString.
func (*NullStruct) Scan ¶ added in v1.0.0
func (ns *NullStruct) Scan(src any) error
Scan implements sql.Scanner.
func (NullStruct) Value ¶ added in v1.0.0
func (ns NullStruct) Value() (driver.Value, error)
Value implements driver.Valuer.
type ObjectDepth ¶ added in v0.2.0
type ObjectDepth int
ObjectDepth controls how deep Conn.Objects recurses into the catalog hierarchy.
const ( // ObjectDepthAll requests catalogs, schemas, tables, and columns. ObjectDepthAll ObjectDepth = iota // ObjectDepthCatalogs requests only catalog names. ObjectDepthCatalogs // ObjectDepthDBSchemas requests catalogs and their schemas. ObjectDepthDBSchemas // ObjectDepthTables requests catalogs, schemas, and tables (no columns). ObjectDepthTables // ObjectDepthColumns is an alias for ObjectDepthAll. ObjectDepthColumns = ObjectDepthAll )
type ObjectsOption ¶ added in v1.0.0
type ObjectsOption func(*objectsConfig)
ObjectsOption configures a Conn.Objects call.
func WithCatalogFilter ¶ added in v1.0.0
func WithCatalogFilter(catalog string) ObjectsOption
WithCatalogFilter filters Objects results to catalogs matching the given name. An empty string matches only objects without a catalog.
func WithColumnFilter ¶ added in v1.0.0
func WithColumnFilter(column string) ObjectsOption
WithColumnFilter filters Objects results to columns matching the given name.
func WithDepth ¶ added in v1.0.0
func WithDepth(depth ObjectDepth) ObjectsOption
WithDepth sets the ObjectDepth for an Objects call, controlling how deep to recurse into the catalog hierarchy.
func WithSchemaFilter ¶ added in v1.0.0
func WithSchemaFilter(schema string) ObjectsOption
WithSchemaFilter filters Objects results to schemas matching the given name.
func WithTableFilter ¶ added in v1.0.0
func WithTableFilter(table string) ObjectsOption
WithTableFilter filters Objects results to tables matching the given name.
func WithTableTypes ¶ added in v1.0.0
func WithTableTypes(types []string) ObjectsOption
WithTableTypes filters Objects results to the given table types (e.g. []string{"TABLE", "VIEW"}).
type Option ¶ added in v0.4.0
type Option func(config)
Option configures a DB during construction via NewDuck.
func WithContext ¶ added in v0.5.0
WithContext sets the default context used for opening new connections. If omitted, context.Background is used.
func WithDriverLookup ¶ added in v1.0.0
func WithDriverLookup() Option
WithDriverLookup programmatically searches ADBC driver manifest directories for an installed "duckdb" driver, using the github.com/columnar-tech/dbc/config package. It searches in order:
- ADBC_DRIVER_PATH environment variable
- Virtual environment / Conda prefix
- User config directory (~/.config/adbc/drivers on Linux, ~/Library/Application Support/ADBC/Drivers on macOS, %LOCALAPPDATA%\ADBC\Drivers on Windows)
- System config directory (/etc/adbc/drivers on Linux, C:\Program Files\ADBC\Drivers on Windows)
If the driver is not found, NewDuck returns ErrDriverNotFound. The driver must first be installed with:
dbc install duckdb
func WithDriverName ¶ added in v1.0.0
WithDriverName specifies the driver by name (e.g. "duckdb"). The ADBC driver manager resolves the name to a shared library using installed TOML driver manifests. This requires that the driver has been installed with the dbc CLI:
dbc install duckdb
This is the default if no driver option is provided.
func WithDriverPath ¶ added in v0.4.0
WithDriverPath specifies the explicit filesystem path to the DuckDB shared library (e.g. "/usr/local/lib/libduckdb.so" or "C:\\path\\to\\duckdb.dll"). Use this as an escape hatch when the driver is not installed via dbc.
type QuackCon ¶ added in v0.3.0
type QuackCon = Conn
QuackCon is a backward-compatible alias for Conn.
type QueryResult ¶ added in v1.0.0
type QueryResult struct {
// Reader provides streaming access to Arrow record batches.
Reader array.RecordReader
// RowsAffected is the number of rows affected, or -1 if unknown.
RowsAffected int64
// contains filtered or unexported fields
}
QueryResult holds the results of a Conn.Query call.
The Reader field provides streaming access to Arrow record batches. RowsAffected contains the number of rows affected if known, otherwise -1. The caller must call Close when done reading to release resources.
Since ADBC 1.1.0, releasing the Reader without fully consuming it is equivalent to calling AdbcStatementCancel.
func (*QueryResult) Close ¶ added in v1.0.0
func (qr *QueryResult) Close() error
Close releases the resources associated with the query result. It closes both the underlying statement and the record reader. Close is idempotent.
func (*QueryResult) Schema ¶ added in v1.0.0
func (qr *QueryResult) Schema() *arrow.Schema
Schema returns the Arrow schema of the result set, or nil if the Reader has been closed.
type SchemaInfo ¶ added in v1.0.0
type SchemaInfo struct {
DBSchemaName string `json:"db_schema_name"`
DBSchemaTables []TableSchema `json:"db_schema_tables"`
}
SchemaInfo represents a schema within a catalog.
type Secret ¶ added in v1.0.0
type Secret struct {
Name string `json:"name"`
Type string `json:"type"`
Provider string `json:"provider"`
Scope string `json:"scope"`
}
Secret describes a stored DuckDB secret. Sensitive fields are redacted by DuckDB.
type Setting ¶ added in v1.0.0
type Setting struct {
Name string `json:"name"`
Value string `json:"value"`
Description string `json:"description"`
InputType string `json:"input_type"`
Scope string `json:"scope"`
}
Setting describes a DuckDB configuration setting.
type Statement ¶ added in v0.2.0
Statement is an alias for adbc.Statement.
type Struct ¶ added in v1.0.0
Struct represents a DuckDB STRUCT column value as a Go map. Field names are the map keys; values are Go-native types produced by the recursive Arrow-to-Go conversion.
Struct implements sql.Scanner and driver.Valuer.
Usage with database/sql:
var s couac.Struct
row.Scan(&s)
fmt.Println(s.Fields["name"]) // "Alice"
j, _ := s.MarshalJSON() // {"name":"Alice","age":30}
Typed field access:
name, ok := s.Str("name") // string field
age, ok := s.Int32("age") // int32 field
name, ok = couac.Field[string](s, "name") // generic (any leaf type)
Chaining into nested types — navigation methods return a zero value on failure, so further calls safely return zero/false:
city, ok := s.Struct("address").Str("city") // STRUCT field → Struct
vals := s.List("tags").Ints() // LIST field → List
v, ok := s.Map("attrs").Int32("k1") // MAP field → Map
Example ¶
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
stdDB := db.StdDB()
defer stdDB.Close()
ctx := context.Background()
// Scan a DuckDB struct column into a couac.Struct.
var s couac.Struct
err = stdDB.QueryRowContext(ctx,
"SELECT {'name': 'Alice', 'age': 30}::STRUCT(name VARCHAR, age INTEGER)",
).Scan(&s)
if err != nil {
fmt.Println("Error:", err)
return
}
name, _ := s.Get("name")
age, _ := s.Get("age")
fmt.Println("name:", name)
fmt.Println("age:", age)
}
Output: name: Alice age: 30
Example (Nested) ¶
ExampleStruct_nested demonstrates chaining into a nested STRUCT: Struct("field") returns a Struct you can immediately call Str/Int32/etc on.
package main
import (
"context"
"fmt"
"github.com/loicalleyne/couac"
)
func main() {
db, err := couac.NewDuck()
if err != nil {
fmt.Println("Error:", err)
return
}
defer db.Close()
stdDB := db.StdDB()
defer stdDB.Close()
ctx := context.Background()
var s couac.Struct
err = stdDB.QueryRowContext(ctx,
"SELECT {'name': 'Alice', 'address': {'city': 'Montreal', 'zip': '12345'}}::"+
"STRUCT(name VARCHAR, address STRUCT(city VARCHAR, zip VARCHAR))",
).Scan(&s)
if err != nil {
fmt.Println("Error:", err)
return
}
name, _ := s.Str("name")
fmt.Println("name:", name)
// Chain: Struct → Struct → Str
city, _ := s.Struct("address").Str("city")
zip, _ := s.Struct("address").Str("zip")
fmt.Println("city:", city)
fmt.Println("zip:", zip)
}
Output: name: Alice city: Montreal zip: 12345
func (Struct) Bool ¶ added in v1.1.0
Bool returns the named field as bool. Returns (false, false) if the field is missing, nil, or not bool.
func (Struct) Float64 ¶ added in v1.1.0
Float64 returns the named field as float64. Returns (0, false) if the field is missing, nil, or not float64.
func (Struct) Get ¶ added in v1.0.0
Get returns the value for the given field name and whether the field exists in the struct. This is a convenience shorthand for s.Fields[name].
func (Struct) Int32 ¶ added in v1.1.0
Int32 returns the named field as int32. Returns (0, false) if the field is missing, nil, or not int32.
func (Struct) Int64 ¶ added in v1.1.0
Int64 returns the named field as int64. Returns (0, false) if the field is missing, nil, or not int64.
func (Struct) JSON ¶ added in v1.0.0
JSON returns a copy of the Struct with every field value converted to its JSON string representation. This is useful when you want to process nested structures as JSON text.
func (Struct) List ¶ added in v1.1.0
List returns the named field as a List, enabling chaining. Returns a zero List if the field is missing, nil, or not a list; further calls on the zero value safely return zero/false.
func (Struct) Map ¶ added in v1.1.0
Map returns the named field as a Map, enabling chaining. Returns a zero Map if the field is missing, nil, or not a map; further calls on the zero value safely return zero/false.
func (Struct) MarshalJSON ¶ added in v1.0.0
MarshalJSON implements [json.Marshaler].
func (*Struct) Scan ¶ added in v1.0.0
Scan implements sql.Scanner for Struct. It accepts:
- Struct: direct assignment
- map[string]any: wraps directly
- string, []byte: parses as JSON object
- nil: zeroes the struct
func (Struct) Str ¶ added in v1.1.0
Str returns the named field as string. Returns ("", false) if the field is missing, nil, or not a string. (Named Str to avoid collision with the String() string method.)
type TableInfo ¶ added in v1.0.0
type TableInfo struct {
Database string `json:"database"`
Schema string `json:"schema"`
TableName string `json:"table_name"`
ColumnNames []string `json:"column_names"`
ColumnTypes []string `json:"column_types"`
Temporary bool `json:"temporary"`
}
TableInfo describes a table as returned by SHOW ALL TABLES.
type TableSchema ¶ added in v0.5.3
type TableSchema struct {
TableName string `json:"table_name"`
TableType string `json:"table_type"`
TableColumns []ColumnSchema `json:"table_columns"`
TableConstraints []ConstraintSchema `json:"table_constraints"`
}
TableSchema describes a table or view within a schema.