diff --git a/.github/workflows/tidy_checks.yml b/.github/workflows/tidy_checks.yml index e81f2052..4c4eb366 100644 --- a/.github/workflows/tidy_checks.yml +++ b/.github/workflows/tidy_checks.yml @@ -33,7 +33,7 @@ jobs: go tool cover -html=cover.out -o=cover.html - name: Check Test Coverage - uses: vladopajic/go-test-coverage@v2 + uses: vladopajic/go-test-coverage@v2.17.1 with: config: ./.github/testcoverage.yml diff --git a/arrow.go b/arrow.go index b6b598d7..9dc3a690 100644 --- a/arrow.go +++ b/arrow.go @@ -93,26 +93,124 @@ func NewArrowFromConn(driverConn driver.Conn) (*Arrow, error) { return &Arrow{conn: conn}, nil } -var _ array.RecordReader = (*arrowStreamReader)(nil) - -// arrowStreamReader implements array.RecordReader for streaming DuckDB results. -type arrowStreamReader struct { - ctx context.Context - res *arrowmapping.Arrow - schema *arrow.Schema - rowCount uint64 - - mu sync.Mutex // protects readCount and currentRec - refCount int64 - readCount uint64 - currentRec arrow.Record - closed bool // tracks if the reader has been closed/released - err error +// QueryContext prepares statements, executes them, and returns an Apache Arrow array.RecordReader as a result of the last +// executed statement. Arguments are bound to the last statement. +func (a *Arrow) QueryContext(ctx context.Context, query string, args ...any) (array.RecordReader, error) { + if a.conn.closed { + return nil, errClosedCon + } + + r, err := a.conn.QueryContext(ctx, query, a.anyArgsToNamedArgs(args)) + if err != nil { + if r != nil { + err = errors.Join(err, r.Close()) + } + return nil, err + } + + return recordReaderFromRows(ctx, r) +} + +var errArrowScan = errors.New("could not register arrow view due to arrow scan API failure") + +// RegisterView registers an Arrow record reader as a view with the given name in DuckDB. +// The returned release function must be called to release the memory once the view is no longer needed. +func (a *Arrow) RegisterView(reader array.RecordReader, name string) (release func(), err error) { + if a.conn.closed { + return nil, errClosedCon + } + + stream := C.calloc(1, C.sizeof_struct_ArrowArrayStream) + release = func() { + cdata.ReleaseCArrowArrayStream((*cdata.CArrowArrayStream)(stream)) + C.free(stream) + } + cdata.ExportRecordReader(reader, (*cdata.CArrowArrayStream)(stream)) + + arrowStream := arrowmapping.ArrowStream{ + Ptr: unsafe.Pointer(stream), + } + if arrowmapping.ArrowScan(a.conn.conn, name, arrowStream) == mapping.StateError { + release() + return nil, errArrowScan + } + + return release, nil +} + +func (a *Arrow) anyArgsToNamedArgs(args []any) []driver.NamedValue { + if len(args) == 0 { + return nil + } + + values := make([]driver.Value, len(args)) + for i, arg := range args { + values[i] = arg + } + + return argsToNamedArgs(values) +} + +var _ array.RecordReader = (*recordReader)(nil) + +type recordReader struct { + ctx context.Context + res mapping.Result + opts arrowmapping.ArrowOptions + schema *arrow.Schema + rows *rows + + mu sync.Mutex // protects err and current + refCount int64 + closed bool // tracks if the reader has been closed/released + current arrow.RecordBatch + err error +} + +func recordReaderFromRows(ctx context.Context, from driver.Rows) (array.RecordReader, error) { + rr, ok := from.(*rows) + if !ok { + return nil, fmt.Errorf("not a duckdb rows") + } + if rr.stmt == nil || rr.stmt.closed || rr.stmt.conn == nil || rr.stmt.conn.closed { + return nil, errClosedCon + } + if rr.rowCount != 0 { + return nil, fmt.Errorf("cannot convert duckdb rows to arrow reader after reading has started") + } + // arrow options + arrowOptions := arrowmapping.ResultGetArrowOptions(&rr.res) + // get arrow schema + cc := mapping.ColumnCount(&rr.res) + names := make([]string, cc) + types := make([]mapping.LogicalType, cc) + for i := range cc { + names[i] = mapping.ColumnName(&rr.res, i) + types[i] = mapping.ColumnLogicalType(&rr.res, i) + } + defer func() { + for i := range cc { + mapping.DestroyLogicalType(&types[i]) + } + }() + + schema, ed := arrowmapping.NewArrowSchema(arrowOptions, types, names) + if err := errorDataError(ed); err != nil { + defer arrowmapping.DestroyArrowOptions(&arrowOptions) + return nil, fmt.Errorf("failed to create arrow schema: %w", err) + } + + return &recordReader{ + ctx: ctx, + res: rr.res, + opts: arrowOptions, + schema: schema, + rows: rr, + refCount: 1, + }, nil } -// Retain increases the reference count by 1. -// Retain may be called simultaneously from multiple goroutines. -func (r *arrowStreamReader) Retain() { +func (r *recordReader) Retain() { r.mu.Lock() defer r.mu.Unlock() if r.err != nil || r.closed { @@ -121,10 +219,7 @@ func (r *arrowStreamReader) Retain() { r.refCount++ } -// Release decreases the reference count by 1. -// When the reference count goes to zero, the memory is freed. -// Release may be called simultaneously from multiple goroutines. -func (r *arrowStreamReader) Release() { +func (r *recordReader) Release() { r.mu.Lock() defer r.mu.Unlock() @@ -138,34 +233,32 @@ func (r *arrowStreamReader) Release() { // If this is the last reference, we need to clean up. r.closed = true - if r.res != nil { - arrowmapping.DestroyArrow(r.res) - r.res = nil - } - if r.currentRec != nil { - r.currentRec.Release() + if r.current != nil { + r.current.Release() + r.current = nil } + arrowmapping.DestroyArrowOptions(&r.opts) + r.err = r.rows.Close() } -func (r *arrowStreamReader) Schema() *arrow.Schema { +func (r *recordReader) Schema() *arrow.Schema { return r.schema } -func (r *arrowStreamReader) Next() bool { +func (r *recordReader) Next() bool { r.mu.Lock() defer r.mu.Unlock() - if r.readCount >= r.rowCount || r.err != nil { - return false + if r.current != nil { + r.current.Release() + r.current = nil } if r.closed { r.err = errors.New("arrow reader has been closed") return false } - - if r.res == nil { - r.err = errors.New("arrow result has already been released") + if r.err != nil { return false } @@ -174,197 +267,37 @@ func (r *arrowStreamReader) Next() bool { r.err = r.ctx.Err() return false default: - if r.currentRec != nil { - r.currentRec.Release() // Release the previous record. + chunk := mapping.FetchChunk(r.res) + if chunk.Ptr == nil { + return false } - rec, err := queryArrowArray(r.res, r.schema) - if err != nil { - r.err = err - r.currentRec = nil + defer mapping.DestroyDataChunk(&chunk) + rec, ed := arrowmapping.DataChunkToArrowArray(r.opts, r.schema, chunk) + if err := errorDataError(ed); err != nil { + r.err = fmt.Errorf("failed to create arrow array: %w", err) return false } - r.currentRec = rec - r.readCount += uint64(rec.NumRows()) + r.current = rec return true } } -// Deprecated: use RecordBatch() instead. -func (r *arrowStreamReader) Record() arrow.Record { +func (r *recordReader) Record() arrow.RecordBatch { return r.RecordBatch() } -func (r *arrowStreamReader) RecordBatch() arrow.RecordBatch { - r.mu.Lock() - defer r.mu.Unlock() - return r.currentRec -} - -func (r *arrowStreamReader) Err() error { +func (r *recordReader) RecordBatch() arrow.RecordBatch { r.mu.Lock() defer r.mu.Unlock() - return r.err -} - -// QueryContext prepares statements, executes them, returns Apache Arrow array.RecordReader as a result of the last -// executed statement. Arguments are bound to the last statement. -func (a *Arrow) QueryContext(ctx context.Context, query string, args ...any) (array.RecordReader, error) { - if a.conn.closed { - return nil, errClosedCon - } - - cleanupCtx := a.conn.setContext(ctx) - defer cleanupCtx() - - stmts, size, errExtract := a.conn.extractStmts(query) - if errExtract != nil { - return nil, errExtract - } - defer mapping.DestroyExtracted(stmts) - - // Execute all statements without args, except the last one. - for i := mapping.IdxT(0); i < size-mapping.IdxT(1); i++ { - extractedStmt, err := a.conn.prepareExtractedStmt(*stmts, i) - if err != nil { - return nil, err - } - - // Send nil args to execute the statement and ignore the result. - _, err = extractedStmt.ExecContext(ctx, nil) - errClose := extractedStmt.Close() - if err != nil || errClose != nil { - return nil, errors.Join(err, errClose) - } - } - - // Prepare and execute the last statement with args. - stmt, err := a.conn.prepareExtractedStmt(*stmts, size-mapping.IdxT(1)) - if err != nil { - return nil, err - } - defer stmt.Close() - - res, err := a.execute(stmt, a.anyArgsToNamedArgs(args)) - if err != nil { - return nil, err - } - - sc, err := a.queryArrowSchema(res) - if err != nil { - arrowmapping.DestroyArrow(res) - return nil, err - } - - return &arrowStreamReader{ - refCount: 1, - ctx: ctx, - res: res, - schema: sc, - rowCount: uint64(arrowmapping.ArrowRowCount(*res)), - }, nil -} - -// queryArrowSchema fetches the internal arrow schema from the arrow result. -func (a *Arrow) queryArrowSchema(res *arrowmapping.Arrow) (*arrow.Schema, error) { - schema := C.calloc(1, C.sizeof_struct_ArrowSchema) - defer func() { - cdata.ReleaseCArrowSchema((*cdata.CArrowSchema)(schema)) - C.free(schema) - }() - - arrowSchema := arrowmapping.ArrowSchema{ - Ptr: unsafe.Pointer(&schema), - } - if arrowmapping.QueryArrowSchema(*res, &arrowSchema) == mapping.StateError { - return nil, errors.New("duckdb_query_arrow_schema") - } - - sc, err := cdata.ImportCArrowSchema((*cdata.CArrowSchema)(schema)) - if err != nil { - return nil, fmt.Errorf("%w: ImportCArrowSchema", err) - } - - return sc, nil -} - -// queryArrowArray fetches an internal arrow array from the arrow result. -// -// This function can be called multiple time to get next chunks, -// which will free the previous out_array. -func queryArrowArray(res *arrowmapping.Arrow, sc *arrow.Schema) (arrow.Record, error) { - arr := C.calloc(1, C.sizeof_struct_ArrowArray) - defer func() { - cdata.ReleaseCArrowArray((*cdata.CArrowArray)(arr)) - C.free(arr) - }() - - arrowArray := arrowmapping.ArrowArray{ - Ptr: unsafe.Pointer(&arr), - } - if arrowmapping.QueryArrowArray(*res, &arrowArray) == mapping.StateError { - return nil, errors.New("duckdb_query_arrow_array") - } - - rec, err := cdata.ImportCRecordBatchWithSchema((*cdata.CArrowArray)(arr), sc) - if err != nil { - return nil, fmt.Errorf("%w: ImportCRecordBatchWithSchema", err) - } - - return rec, nil -} - -func (a *Arrow) execute(s *Stmt, args []driver.NamedValue) (*arrowmapping.Arrow, error) { - if s.closed { - return nil, errClosedCon - } - if err := s.bind(args); err != nil { - return nil, err - } - var res arrowmapping.Arrow - if arrowmapping.ExecutePreparedArrow(*s.preparedStmt, &res) == mapping.StateError { - errMsg := arrowmapping.QueryArrowError(res) - arrowmapping.DestroyArrow(&res) - return nil, fmt.Errorf("failed to execute the prepared arrow: %v", errMsg) - } - - return &res, nil -} - -func (a *Arrow) anyArgsToNamedArgs(args []any) []driver.NamedValue { - if len(args) == 0 { + if r.err != nil { return nil } - - values := make([]driver.Value, len(args)) - for i, arg := range args { - values[i] = arg - } - - return argsToNamedArgs(values) + return r.current } -// RegisterView registers an Arrow record reader as a view with the given name in DuckDB. -// The returned release function must be called to release the memory once the view is no longer needed. -func (a *Arrow) RegisterView(reader array.RecordReader, name string) (release func(), err error) { - if a.conn.closed { - return nil, errClosedCon - } - - stream := C.calloc(1, C.sizeof_struct_ArrowArrayStream) - release = func() { - cdata.ReleaseCArrowArrayStream((*cdata.CArrowArrayStream)(stream)) - C.free(stream) - } - cdata.ExportRecordReader(reader, (*cdata.CArrowArrayStream)(stream)) - - arrowStream := arrowmapping.ArrowStream{ - Ptr: unsafe.Pointer(stream), - } - if arrowmapping.ArrowScan(a.conn.conn, name, arrowStream) == mapping.StateError { - release() - return nil, errors.New("duckdb_arrow_scan") - } - - return release, nil +func (r *recordReader) Err() error { + r.mu.Lock() + defer r.mu.Unlock() + return r.err } diff --git a/arrow_test.go b/arrow_test.go index eee86ee1..6b623bab 100644 --- a/arrow_test.go +++ b/arrow_test.go @@ -38,7 +38,7 @@ func TestArrow(t *testing.T) { defer rdr.Release() for rdr.Next() { - rec := rdr.Record() + rec := rdr.RecordBatch() require.Equal(t, int64(10), rec.NumRows()) require.NoError(t, err) } @@ -61,7 +61,7 @@ func TestArrow(t *testing.T) { var totalRows int64 for rdr.Next() { - rec := rdr.Record() + rec := rdr.RecordBatch() totalRows += rec.NumRows() } require.Equal(t, int64(10000), totalRows) @@ -84,7 +84,7 @@ func TestArrow(t *testing.T) { defer reader.Release() for reader.Next() { - rec := reader.Record() + rec := reader.RecordBatch() require.Equal(t, int64(2), rec.NumRows()) require.Equal(t, "lala", rec.Column(0).ValueStr(0)) require.Equal(t, "dada", rec.Column(0).ValueStr(1)) @@ -136,17 +136,17 @@ func TestArrow(t *testing.T) { b.Field(1).(*array.Float64Builder).AppendValues([]float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, nil) b.Field(2).(*array.StringBuilder).AppendValues([]string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}, nil) - rec1 := b.NewRecord() + rec1 := b.NewRecordBatch() defer rec1.Release() b.Field(0).(*array.Int32Builder).AppendValues([]int32{11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, nil) b.Field(1).(*array.Float64Builder).AppendValues([]float64{11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, nil) b.Field(2).(*array.StringBuilder).AppendValues([]string{"k", "l", "m", "n", "o", "p", "q", "r", "s", "t"}, nil) - rec2 := b.NewRecord() + rec2 := b.NewRecordBatch() defer rec2.Release() - tbl := array.NewTableFromRecords(schema, []arrow.Record{rec1, rec2}) + tbl := array.NewTableFromRecords(schema, []arrow.RecordBatch{rec1, rec2}) defer tbl.Release() tr := array.NewTableReader(tbl, 5) @@ -260,13 +260,13 @@ func TestArrow(t *testing.T) { require.NoError(t, rdr.Err()) rdr.Retain() - require.Equal(t, int64(2), rdr.(*arrowStreamReader).refCount) + require.Equal(t, int64(2), rdr.(*recordReader).refCount) rdr.Release() - require.Equal(t, int64(1), rdr.(*arrowStreamReader).refCount) + require.Equal(t, int64(1), rdr.(*recordReader).refCount) rdr.Release() - require.Equal(t, int64(0), rdr.(*arrowStreamReader).refCount) + require.Equal(t, int64(0), rdr.(*recordReader).refCount) rdr.Release() - require.Equal(t, int64(0), rdr.(*arrowStreamReader).refCount) + require.Equal(t, int64(0), rdr.(*recordReader).refCount) }) } diff --git a/go.mod b/go.mod index 34d75533..7e8bb372 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,11 @@ go 1.24 require ( github.com/apache/arrow-go/v18 v18.4.1 - github.com/duckdb/duckdb-go/arrowmapping v0.0.25 + github.com/duckdb/duckdb-go/arrowmapping v0.0.26 github.com/duckdb/duckdb-go/mapping v0.0.25 github.com/go-viper/mapstructure/v2 v2.4.0 github.com/google/uuid v1.6.0 - github.com/stretchr/testify v1.11.0 + github.com/stretchr/testify v1.11.1 ) require ( @@ -23,7 +23,6 @@ require ( github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect - github.com/kr/pretty v0.3.1 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect @@ -33,6 +32,5 @@ require ( golang.org/x/sys v0.35.0 // indirect golang.org/x/tools v0.36.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 177c8f7a..244d7756 100644 --- a/go.sum +++ b/go.sum @@ -4,7 +4,6 @@ github.com/apache/arrow-go/v18 v18.4.1 h1:q/jVkBWCJOB9reDgaIZIdruLQUb1kbkvOnOFez github.com/apache/arrow-go/v18 v18.4.1/go.mod h1:tLyFubsAl17bvFdUAy24bsSvA/6ww95Iqi67fTpGu3E= 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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/duckdb/duckdb-go-bindings v0.1.23 h1:sJRXraxfC/gdHI2T7oHqrdp1VdKemrgqWGQ8986mH1c= @@ -19,8 +18,8 @@ github.com/duckdb/duckdb-go-bindings/linux-arm64 v0.1.23 h1:f8NHa8DGes7vg55BxeMV github.com/duckdb/duckdb-go-bindings/linux-arm64 v0.1.23/go.mod h1:kpQSpJmDSSZQ3ikbZR1/8UqecqMeUkWFjFX2xZxlCuI= github.com/duckdb/duckdb-go-bindings/windows-amd64 v0.1.23 h1:HJqVo+09gT6LQWW6PlN/c7K8s0eQhv5giE7kJcMGMSU= github.com/duckdb/duckdb-go-bindings/windows-amd64 v0.1.23/go.mod h1:wa+egSGXTPS16NPADFCK1yFyt3VSXxUS6Pt2fLnvRPM= -github.com/duckdb/duckdb-go/arrowmapping v0.0.25 h1:afPKtibOLxcGRz4tpXjP0nQbs9B4vQR57zCOaWSKzss= -github.com/duckdb/duckdb-go/arrowmapping v0.0.25/go.mod h1:R7egXxZcy0hxKY/MsoM2xjkMvRo4H07TffDhYCnhKfQ= +github.com/duckdb/duckdb-go/arrowmapping v0.0.26 h1:XKhWpNkLtIbcBE2vnKm7FaAju3daplxo8MJIXOAY/Zg= +github.com/duckdb/duckdb-go/arrowmapping v0.0.26/go.mod h1:R7egXxZcy0hxKY/MsoM2xjkMvRo4H07TffDhYCnhKfQ= github.com/duckdb/duckdb-go/mapping v0.0.25 h1:z4RhivKCIRv0MWQwtYekqH+ikoA29/n8L+rzgreKvsc= github.com/duckdb/duckdb-go/mapping v0.0.25/go.mod h1:CIo3WbNx3Txl+VO9+P5eNCN9ZifUA/KIp9NY1rTG/uo= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= @@ -41,26 +40,16 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 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/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8= -github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +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.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= @@ -79,8 +68,7 @@ golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhS golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +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/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=