diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index e4eacec..4eadb24 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -18,7 +18,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.25' + go-version: '1.26' - uses: actions/checkout@v4 @@ -87,4 +87,3 @@ jobs: export PATH="$(pwd)/lib:$PATH" cd example go run main.go - diff --git a/arrow.go b/arrow.go new file mode 100644 index 0000000..22c8c36 --- /dev/null +++ b/arrow.go @@ -0,0 +1,197 @@ +package lbug + +// #include "lbug.h" +// #include +import "C" + +import ( + "fmt" + "unsafe" + + "github.com/apache/arrow-go/v18/arrow" + "github.com/apache/arrow-go/v18/arrow/cdata" +) + +func exportArrowBatches(batches []arrow.RecordBatch) (*C.struct_ArrowSchema, []C.struct_ArrowArray, error) { + if len(batches) == 0 { + return nil, nil, fmt.Errorf("at least one Arrow record batch is required") + } + schema := batches[0].Schema() + for i, batch := range batches { + if !batch.Schema().Equal(schema) { + return nil, nil, fmt.Errorf("Arrow record batch %d has a different schema", i) + } + } + + cSchema := new(C.struct_ArrowSchema) + cArrays := make([]C.struct_ArrowArray, len(batches)) + cdata.ExportArrowSchema(schema, cdata.SchemaFromPtr(uintptr(unsafe.Pointer(cSchema)))) + for i, batch := range batches { + cdata.ExportArrowRecordBatch(batch, cdata.ArrayFromPtr(uintptr(unsafe.Pointer(&cArrays[i]))), nil) + } + return cSchema, cArrays, nil +} + +func releaseExportedArrow(schema *C.struct_ArrowSchema, arrays []C.struct_ArrowArray) { + if schema != nil { + cdata.ReleaseCArrowSchema(cdata.SchemaFromPtr(uintptr(unsafe.Pointer(schema)))) + } + for i := range arrays { + cdata.ReleaseCArrowArray(cdata.ArrayFromPtr(uintptr(unsafe.Pointer(&arrays[i])))) + } +} + +func lastCAPIError(fallback string) error { + cErr := C.lbug_get_last_error() + if cErr == nil { + return fmt.Errorf("%s", fallback) + } + defer C.lbug_destroy_string(cErr) + return fmt.Errorf("%s", C.GoString(cErr)) +} + +func queryResultFromArrowCall(conn *Connection, queryResult *QueryResult, status C.lbug_state, fallback string) (*QueryResult, error) { + queryResult.connection = conn + if status != C.LbugSuccess { + queryResult.Close() + return nil, lastCAPIError(fallback) + } + if !C.lbug_query_result_is_success(&queryResult.cQueryResult) { + cErrMsg := C.lbug_query_result_get_error_message(&queryResult.cQueryResult) + defer C.lbug_destroy_string(cErrMsg) + queryResult.Close() + return nil, fmt.Errorf("%s", C.GoString(cErrMsg)) + } + return queryResult, nil +} + +// CreateArrowTable registers Arrow memory as a node table. +// The first column is used as the table primary key. +// The registered table may outlive this call, so batches should be built with +// memory that is safe to hold through the Arrow C Data Interface. +func (conn *Connection) CreateArrowTable(tableName string, batches []arrow.RecordBatch) (*QueryResult, error) { + cSchema, cArrays, err := exportArrowBatches(batches) + if err != nil { + return nil, err + } + cTableName := C.CString(tableName) + defer C.free(unsafe.Pointer(cTableName)) + + queryResult := &QueryResult{} + status := C.lbug_connection_create_arrow_table(&conn.cConnection, cTableName, cSchema, + (*C.struct_ArrowArray)(unsafe.Pointer(&cArrays[0])), C.uint64_t(len(cArrays)), + &queryResult.cQueryResult) + if status == C.LbugSuccess { + cSchema = nil + cArrays = nil + } + defer releaseExportedArrow(cSchema, cArrays) + return queryResultFromArrowCall(conn, queryResult, status, "failed to create Arrow table") +} + +// CreateArrowRelTable registers Arrow memory as a relationship table. +// The Arrow schema must include endpoint columns named "from" and "to". +// The registered table may outlive this call, so batches should be built with +// memory that is safe to hold through the Arrow C Data Interface. +func (conn *Connection) CreateArrowRelTable(tableName string, batches []arrow.RecordBatch, srcTableName string, dstTableName string) (*QueryResult, error) { + cSchema, cArrays, err := exportArrowBatches(batches) + if err != nil { + return nil, err + } + cTableName := C.CString(tableName) + cSrcTableName := C.CString(srcTableName) + cDstTableName := C.CString(dstTableName) + defer C.free(unsafe.Pointer(cTableName)) + defer C.free(unsafe.Pointer(cSrcTableName)) + defer C.free(unsafe.Pointer(cDstTableName)) + + queryResult := &QueryResult{} + status := C.lbug_connection_create_arrow_rel_table(&conn.cConnection, cTableName, + cSrcTableName, cDstTableName, cSchema, (*C.struct_ArrowArray)(unsafe.Pointer(&cArrays[0])), + C.uint64_t(len(cArrays)), &queryResult.cQueryResult) + if status == C.LbugSuccess { + cSchema = nil + cArrays = nil + } + defer releaseExportedArrow(cSchema, cArrays) + return queryResultFromArrowCall(conn, queryResult, status, "failed to create Arrow relationship table") +} + +// CreateArrowRelTableCSR registers Arrow memory in CSR form as a relationship table. +// If dstColName is omitted, the destination offset column defaults to "to". +// The registered table may outlive this call, so batches should be built with +// memory that is safe to hold through the Arrow C Data Interface. +func (conn *Connection) CreateArrowRelTableCSR(tableName string, indicesBatches []arrow.RecordBatch, indptrBatches []arrow.RecordBatch, srcTableName string, dstTableName string, dstColName ...string) (*QueryResult, error) { + cIndicesSchema, cIndicesArrays, err := exportArrowBatches(indicesBatches) + if err != nil { + return nil, err + } + cIndptrSchema, cIndptrArrays, err := exportArrowBatches(indptrBatches) + if err != nil { + releaseExportedArrow(cIndicesSchema, cIndicesArrays) + return nil, err + } + + cTableName := C.CString(tableName) + cSrcTableName := C.CString(srcTableName) + cDstTableName := C.CString(dstTableName) + var cDstColName *C.char + if len(dstColName) > 0 && dstColName[0] != "" { + cDstColName = C.CString(dstColName[0]) + defer C.free(unsafe.Pointer(cDstColName)) + } + defer C.free(unsafe.Pointer(cTableName)) + defer C.free(unsafe.Pointer(cSrcTableName)) + defer C.free(unsafe.Pointer(cDstTableName)) + + queryResult := &QueryResult{} + status := C.lbug_connection_create_arrow_rel_table_csr(&conn.cConnection, cTableName, + cSrcTableName, cDstTableName, cIndicesSchema, + (*C.struct_ArrowArray)(unsafe.Pointer(&cIndicesArrays[0])), + C.uint64_t(len(cIndicesArrays)), cIndptrSchema, + (*C.struct_ArrowArray)(unsafe.Pointer(&cIndptrArrays[0])), + C.uint64_t(len(cIndptrArrays)), cDstColName, &queryResult.cQueryResult) + if status == C.LbugSuccess { + cIndicesSchema = nil + cIndicesArrays = nil + cIndptrSchema = nil + cIndptrArrays = nil + } + defer releaseExportedArrow(cIndicesSchema, cIndicesArrays) + defer releaseExportedArrow(cIndptrSchema, cIndptrArrays) + return queryResultFromArrowCall(conn, queryResult, status, "failed to create Arrow CSR relationship table") +} + +// DropArrowTable drops an Arrow memory-backed table registered on this connection. +func (conn *Connection) DropArrowTable(tableName string) (*QueryResult, error) { + cTableName := C.CString(tableName) + defer C.free(unsafe.Pointer(cTableName)) + queryResult := &QueryResult{} + status := C.lbug_connection_drop_arrow_table(&conn.cConnection, cTableName, &queryResult.cQueryResult) + return queryResultFromArrowCall(conn, queryResult, status, "failed to drop Arrow table") +} + +// GetArrowSchema returns the query result schema as an Arrow schema. +func (queryResult *QueryResult) GetArrowSchema() (*arrow.Schema, error) { + var cSchema C.struct_ArrowSchema + status := C.lbug_query_result_get_arrow_schema(&queryResult.cQueryResult, &cSchema) + if status != C.LbugSuccess { + return nil, lastCAPIError("failed to get Arrow schema") + } + return cdata.ImportCArrowSchema(cdata.SchemaFromPtr(uintptr(unsafe.Pointer(&cSchema)))) +} + +// GetNextArrowChunk returns the next chunk of the query result as an Arrow record batch. +func (queryResult *QueryResult) GetNextArrowChunk(chunkSize int64) (arrow.RecordBatch, error) { + var cArray C.struct_ArrowArray + status := C.lbug_query_result_get_next_arrow_chunk(&queryResult.cQueryResult, C.int64_t(chunkSize), &cArray) + if status != C.LbugSuccess { + return nil, lastCAPIError("failed to get next Arrow chunk") + } + schema, err := queryResult.GetArrowSchema() + if err != nil { + cdata.ReleaseCArrowArray(cdata.ArrayFromPtr(uintptr(unsafe.Pointer(&cArray)))) + return nil, err + } + return cdata.ImportCRecordBatchWithSchema(cdata.ArrayFromPtr(uintptr(unsafe.Pointer(&cArray))), schema) +} diff --git a/arrow_test.go b/arrow_test.go new file mode 100644 index 0000000..62da475 --- /dev/null +++ b/arrow_test.go @@ -0,0 +1,124 @@ +package lbug + +import ( + "path/filepath" + "strings" + "testing" + + "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/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func setupArrowTestConnection(t *testing.T) (*Database, *Connection) { + t.Helper() + dbPath := filepath.Join(t.TempDir(), "testdb") + dbPath = strings.ReplaceAll(dbPath, "\\", "/") + db, err := OpenDatabase(dbPath, DefaultSystemConfig()) + require.NoError(t, err) + conn, err := OpenConnection(db) + require.NoError(t, err) + t.Cleanup(func() { + conn.Close() + db.Close() + }) + return db, conn +} + +func newPersonBatch(t *testing.T, alloc memory.Allocator) arrow.RecordBatch { + t.Helper() + schema := arrow.NewSchema([]arrow.Field{ + {Name: "id", Type: arrow.PrimitiveTypes.Int64}, + {Name: "name", Type: arrow.BinaryTypes.String}, + }, nil) + builder := array.NewRecordBuilder(alloc, schema) + defer builder.Release() + builder.Field(0).(*array.Int64Builder).AppendValues([]int64{1, 2}, nil) + builder.Field(1).(*array.StringBuilder).AppendValues([]string{"Alice", "Bob"}, nil) + batch := builder.NewRecordBatch() + t.Cleanup(batch.Release) + return batch +} + +func TestCreateArrowTableAndDrop(t *testing.T) { + _, conn := setupArrowTestConnection(t) + alloc := memory.DefaultAllocator + batch := newPersonBatch(t, alloc) + + result, err := conn.CreateArrowTable("Person", []arrow.RecordBatch{batch}) + require.NoError(t, err) + result.Close() + + result, err = conn.Query("MATCH (p:Person) RETURN p.name ORDER BY p.id;") + require.NoError(t, err) + assert.Equal(t, "p.name\nAlice\nBob\n", result.ToString()) + result.Close() + + result, err = conn.DropArrowTable("Person") + require.NoError(t, err) + result.Close() + + _, err = conn.Query("MATCH (p:Person) RETURN p.name;") + require.Error(t, err) + assert.Contains(t, err.Error(), "Table Person does not exist") +} + +func TestCreateArrowRelTableCSR(t *testing.T) { + _, conn := setupArrowTestConnection(t) + alloc := memory.DefaultAllocator + nodes := newPersonBatch(t, alloc) + result, err := conn.CreateArrowTable("Person", []arrow.RecordBatch{nodes}) + require.NoError(t, err) + result.Close() + + indicesSchema := arrow.NewSchema([]arrow.Field{ + {Name: "to", Type: arrow.PrimitiveTypes.Uint64}, + {Name: "weight", Type: arrow.PrimitiveTypes.Int64}, + }, nil) + indicesBuilder := array.NewRecordBuilder(alloc, indicesSchema) + defer indicesBuilder.Release() + indicesBuilder.Field(0).(*array.Uint64Builder).AppendValues([]uint64{1, 0}, nil) + indicesBuilder.Field(1).(*array.Int64Builder).AppendValues([]int64{7, 9}, nil) + indices := indicesBuilder.NewRecordBatch() + defer indices.Release() + + indptrSchema := arrow.NewSchema([]arrow.Field{ + {Name: "indptr", Type: arrow.PrimitiveTypes.Uint64}, + }, nil) + indptrBuilder := array.NewRecordBuilder(alloc, indptrSchema) + defer indptrBuilder.Release() + indptrBuilder.Field(0).(*array.Uint64Builder).AppendValues([]uint64{0, 1, 2}, nil) + indptr := indptrBuilder.NewRecordBatch() + defer indptr.Release() + + result, err = conn.CreateArrowRelTableCSR("Knows", []arrow.RecordBatch{indices}, + []arrow.RecordBatch{indptr}, "Person", "Person") + require.NoError(t, err) + result.Close() + + result, err = conn.Query("MATCH (a:Person)-[r:Knows]->(b:Person) RETURN a.id, r.weight, b.id ORDER BY a.id, b.id;") + require.NoError(t, err) + assert.Equal(t, "a.id|r.weight|b.id\n1|7|2\n2|9|1\n", result.ToString()) + result.Close() +} + +func TestQueryResultArrowChunk(t *testing.T) { + _, conn := setupArrowTestConnection(t) + result, err := conn.Query("RETURN CAST(1, \"INT64\") AS one;") + require.NoError(t, err) + defer result.Close() + + schema, err := result.GetArrowSchema() + require.NoError(t, err) + require.Len(t, schema.Fields(), 1) + assert.Equal(t, "one", schema.Field(0).Name) + + batch, err := result.GetNextArrowChunk(8) + require.NoError(t, err) + defer batch.Release() + require.Equal(t, int64(1), batch.NumRows()) + values := batch.Column(0).(*array.Int64) + assert.Equal(t, int64(1), values.Value(0)) +} diff --git a/example/go.mod b/example/go.mod index 4a35d51..e447af5 100644 --- a/example/go.mod +++ b/example/go.mod @@ -1,6 +1,6 @@ module github.com/LadybugDB/go-ladybug/examples -go 1.25 +go 1.26 replace github.com/LadybugDB/go-ladybug => ../ @@ -8,4 +8,15 @@ require github.com/LadybugDB/go-ladybug v0.0.0 require github.com/google/uuid v1.6.0 // indirect -require github.com/shopspring/decimal v1.4.0 // indirect +require ( + github.com/apache/arrow-go/v18 v18.6.0 // indirect + github.com/goccy/go-json v0.10.6 // indirect + github.com/google/flatbuffers v25.12.19+incompatible // indirect + github.com/klauspost/compress v1.18.5 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/pierrec/lz4/v4 v4.1.26 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/zeebo/xxh3 v1.1.0 // indirect + golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect + golang.org/x/sys v0.43.0 // indirect +) diff --git a/example/go.sum b/example/go.sum index 80ffaf1..d8b1622 100644 --- a/example/go.sum +++ b/example/go.sum @@ -1,12 +1,38 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro= +github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/apache/arrow-go/v18 v18.6.0 h1:GX/Jyd3R7mCLiECAwY9FWbbaYblie2WXBSz4Sw8fNpM= +github.com/apache/arrow-go/v18 v18.6.0/go.mod h1:gm3MiPpY82fLYK5VKPB3WoJbsiLVDfT7flD5/vHReKw= +github.com/apache/thrift v0.22.0 h1:r7mTJdj51TMDe6RtcmNdQxgn9XcyfGDOzegMDRg47uc= +github.com/apache/thrift v0.22.0/go.mod h1:1e7J/O1Ae6ZQMTYdy9xa3w9k+XHWPfRvdPyJeynQ+/g= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= +github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs= +github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= +github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY= +github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= +github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go.mod b/go.mod index b1c5162..35fa9b1 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,26 @@ module github.com/LadybugDB/go-ladybug -go 1.25 +go 1.26 require github.com/google/uuid v1.6.0 require github.com/shopspring/decimal v1.4.0 -require github.com/stretchr/testify v1.9.0 +require ( + github.com/apache/arrow-go/v18 v18.6.0 + github.com/stretchr/testify v1.11.1 +) require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/goccy/go-json v0.10.6 // indirect + github.com/google/flatbuffers v25.12.19+incompatible // indirect + github.com/klauspost/compress v1.18.5 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/pierrec/lz4/v4 v4.1.26 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/zeebo/xxh3 v1.1.0 // indirect + golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect + golang.org/x/sys v0.43.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e768311..b5b29e1 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,39 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro= +github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/apache/arrow-go/v18 v18.6.0 h1:GX/Jyd3R7mCLiECAwY9FWbbaYblie2WXBSz4Sw8fNpM= +github.com/apache/arrow-go/v18 v18.6.0/go.mod h1:gm3MiPpY82fLYK5VKPB3WoJbsiLVDfT7flD5/vHReKw= +github.com/apache/thrift v0.22.0 h1:r7mTJdj51TMDe6RtcmNdQxgn9XcyfGDOzegMDRg47uc= +github.com/apache/thrift v0.22.0/go.mod h1:1e7J/O1Ae6ZQMTYdy9xa3w9k+XHWPfRvdPyJeynQ+/g= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= +github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs= +github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= +github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY= +github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= +github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/prepared_statement.go b/prepared_statement.go index e3fda17..97b6125 100644 --- a/prepared_statement.go +++ b/prepared_statement.go @@ -22,3 +22,8 @@ func (stmt *PreparedStatement) Close() { C.lbug_prepared_statement_destroy(&stmt.cPreparedStatement) stmt.isClosed = true } + +// IsReadOnly returns true if the prepared statement only performs read operations. +func (stmt *PreparedStatement) IsReadOnly() bool { + return bool(C.lbug_prepared_statement_is_read_only(&stmt.cPreparedStatement)) +}