Skip to content

Commit c5a60bd

Browse files
committed
support just chunk based on maxplaceholders
Signed-off-by: hfuss <hayden.fuss@kaleido.io>
1 parent 28bffa8 commit c5a60bd

6 files changed

Lines changed: 1 addition & 221 deletions

File tree

pkg/dbsql/crud.go

Lines changed: 1 addition & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
"fmt"
2424
"reflect"
2525
"strings"
26-
"time"
2726

2827
sq "github.com/Masterminds/squirrel"
2928
"github.com/hyperledger/firefly-common/pkg/ffapi"
@@ -497,11 +496,7 @@ func (c *CrudBase[T]) InsertMany(ctx context.Context, instances []T, allowPartia
497496
return err
498497
}
499498
defer c.DB.RollbackTx(ctx, tx, autoCommit)
500-
if c.DB.Features().ArrayInsertBuilder != nil {
501-
if err := c.insertManyArray(ctx, tx, instances); err != nil {
502-
return err
503-
}
504-
} else if c.DB.Features().MultiRowInsert {
499+
if c.DB.Features().MultiRowInsert {
505500
if err := c.insertManyMultiRow(ctx, tx, instances, allowPartialSuccess); err != nil {
506501
return err
507502
}
@@ -568,54 +563,6 @@ func (c *CrudBase[T]) insertManyMultiRow(ctx context.Context, tx *TXWrapper, ins
568563
return nil
569564
}
570565

571-
func (c *CrudBase[T]) insertManyArray(ctx context.Context, tx *TXWrapper, instances []T) error {
572-
l := log.L(ctx)
573-
builder := c.DB.Features().ArrayInsertBuilder
574-
575-
columnValues := make([][]interface{}, len(c.Columns))
576-
for i := range c.Columns {
577-
columnValues[i] = make([]interface{}, len(instances))
578-
}
579-
580-
for rowIdx, inst := range instances {
581-
c.setInsertTimestamps(inst, fftypes.Now())
582-
for colIdx, col := range c.Columns {
583-
columnValues[colIdx][rowIdx] = c.getFieldValue(inst, col)
584-
}
585-
}
586-
587-
sqlQuery, args, err := builder(ctx, c.Table, c.Columns, columnValues, c.DB.SequenceColumn())
588-
if err != nil {
589-
return i18n.WrapError(ctx, err, i18n.MsgDBQueryBuildFailed)
590-
}
591-
592-
before := time.Now()
593-
l.Tracef(`SQL-> array insert: %s (args: %d arrays)`, sqlQuery, len(args))
594-
rows, err := tx.sqlTX.QueryContext(ctx, sqlQuery, args...)
595-
if err != nil {
596-
l.Errorf(`SQL array insert failed: %s sql=[ %s ]: %s`, err, sqlQuery, err)
597-
return i18n.WrapError(ctx, err, i18n.MsgDBInsertFailed)
598-
}
599-
defer rows.Close()
600-
601-
for i := 0; i < len(instances) && rows.Next(); i++ {
602-
var seq int64
603-
if err := rows.Scan(&seq); err != nil {
604-
return i18n.WrapError(ctx, err, i18n.MsgDBReadErr, c.Table)
605-
}
606-
c.attemptSetSequence(instances[i], seq)
607-
}
608-
609-
for _, inst := range instances {
610-
if c.EventHandler != nil {
611-
c.EventHandler(inst.GetID(), Created)
612-
}
613-
}
614-
615-
l.Debugf(`SQL<- array insert %s rows=%d (%.2fms)`, c.Table, len(instances), floatMillisSince(before))
616-
return nil
617-
}
618-
619566
func (c *CrudBase[T]) Insert(ctx context.Context, inst T, hooks ...PostCompletionHook) (err error) {
620567
ctx, tx, autoCommit, err := c.DB.BeginOrUseTx(ctx)
621568
if err != nil {

pkg/dbsql/crud_test.go

Lines changed: 0 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,83 +1087,6 @@ func TestInsertManyMultiRowNoChunkingWhenUnderLimit(t *testing.T) {
10871087
assert.NoError(t, mock.ExpectationsWereMet())
10881088
}
10891089

1090-
func TestInsertManyArrayInsertOK(t *testing.T) {
1091-
mp := NewMockProvider()
1092-
mp.FakePSQLArrayInsert = true
1093-
db, mock := mp.UTInit()
1094-
tc := newCRUDCollection(&db.Database, "ns1")
1095-
mock.ExpectBegin()
1096-
mock.ExpectQuery(`INSERT INTO crudables .* SELECT UNNEST.*RETURNING seq`).WillReturnRows(
1097-
sqlmock.NewRows([]string{db.sequenceColumn}).
1098-
AddRow(301).
1099-
AddRow(302),
1100-
)
1101-
mock.ExpectCommit()
1102-
err := tc.InsertMany(context.Background(), []*TestCRUDable{
1103-
{ResourceBase: ResourceBase{ID: fftypes.NewUUID()}},
1104-
{ResourceBase: ResourceBase{ID: fftypes.NewUUID()}},
1105-
}, false)
1106-
assert.NoError(t, err)
1107-
assert.NoError(t, mock.ExpectationsWereMet())
1108-
}
1109-
1110-
func TestInsertManyArrayInsertFail(t *testing.T) {
1111-
mp := NewMockProvider()
1112-
mp.FakePSQLArrayInsert = true
1113-
db, mock := mp.UTInit()
1114-
tc := newCRUDCollection(&db.Database, "ns1")
1115-
mock.ExpectBegin()
1116-
mock.ExpectQuery(`INSERT INTO crudables .* SELECT UNNEST.*`).WillReturnError(fmt.Errorf("pop"))
1117-
err := tc.InsertMany(context.Background(), []*TestCRUDable{
1118-
{ResourceBase: ResourceBase{ID: fftypes.NewUUID()}},
1119-
}, false)
1120-
assert.Regexp(t, "FF00177", err)
1121-
assert.NoError(t, mock.ExpectationsWereMet())
1122-
}
1123-
1124-
func TestInsertManyArrayInsertPrefersOverMultiRow(t *testing.T) {
1125-
mp := NewMockProvider()
1126-
mp.FakePSQLArrayInsert = true
1127-
mp.MultiRowInsert = true
1128-
db, mock := mp.UTInit()
1129-
tc := newCRUDCollection(&db.Database, "ns1")
1130-
mock.ExpectBegin()
1131-
// Should use UNNEST, not multi-row VALUES
1132-
mock.ExpectQuery(`INSERT INTO crudables .* SELECT UNNEST.*RETURNING seq`).WillReturnRows(
1133-
sqlmock.NewRows([]string{db.sequenceColumn}).
1134-
AddRow(401),
1135-
)
1136-
mock.ExpectCommit()
1137-
err := tc.InsertMany(context.Background(), []*TestCRUDable{
1138-
{ResourceBase: ResourceBase{ID: fftypes.NewUUID()}},
1139-
}, false)
1140-
assert.NoError(t, err)
1141-
assert.NoError(t, mock.ExpectationsWereMet())
1142-
}
1143-
1144-
func TestInsertManyArrayInsertWithEvents(t *testing.T) {
1145-
mp := NewMockProvider()
1146-
mp.FakePSQLArrayInsert = true
1147-
db, mock := mp.UTInit()
1148-
tc := newCRUDCollection(&db.Database, "ns1")
1149-
mock.ExpectBegin()
1150-
mock.ExpectQuery(`INSERT INTO crudables .* SELECT UNNEST.*RETURNING seq`).WillReturnRows(
1151-
sqlmock.NewRows([]string{db.sequenceColumn}).
1152-
AddRow(501).
1153-
AddRow(502),
1154-
)
1155-
mock.ExpectCommit()
1156-
err := tc.InsertMany(context.Background(), []*TestCRUDable{
1157-
{ResourceBase: ResourceBase{ID: fftypes.NewUUID()}},
1158-
{ResourceBase: ResourceBase{ID: fftypes.NewUUID()}},
1159-
}, false)
1160-
assert.NoError(t, err)
1161-
assert.Equal(t, 2, len(tc.events))
1162-
assert.Equal(t, Created, tc.events[0])
1163-
assert.Equal(t, Created, tc.events[1])
1164-
assert.NoError(t, mock.ExpectationsWereMet())
1165-
}
1166-
11671090
func TestInsertManyFallbackSingleRowFail(t *testing.T) {
11681091
db, mock := NewMockProvider().UTInit()
11691092
tc := newCRUDCollection(&db.Database, "ns1")

pkg/dbsql/mock_provider.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ type MockProviderConfig struct {
5252
MultiRowInsert bool
5353
MaxPlaceholders int
5454
FakePSQLUpsertOptimization bool
55-
FakePSQLArrayInsert bool
5655
}
5756

5857
func NewMockProvider() *MockProvider {
@@ -95,9 +94,6 @@ func (mp *MockProvider) Features() SQLFeatures {
9594
}
9695
features.MultiRowInsert = mp.MultiRowInsert
9796
features.MaxPlaceholders = mp.MaxPlaceholders
98-
if mp.FakePSQLArrayInsert {
99-
features.ArrayInsertBuilder = BuildPostgreSQLArrayInsert
100-
}
10197
if mp.FakePSQLUpsertOptimization {
10298
features.DBOptimizedUpsertBuilder = BuildPostgreSQLOptimizedUpsert
10399
}

pkg/dbsql/postgres_helpers.go

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -45,41 +45,3 @@ func BuildPostgreSQLOptimizedUpsert(ctx context.Context, table string, idColumn
4545
return insert.Suffix(fmt.Sprintf("ON CONFLICT (%s) DO UPDATE", idColumn)).SuffixExpr(sq.Expr(updateSQL, updateValues...)).Suffix("RETURNING " + returnCol), nil
4646
}
4747

48-
// PostgreSQLArrayInsertBuilder returns an ArrayInsertBuilder for PostgreSQL using UNNEST.
49-
// wrapArray must convert a []interface{} into a driver-compatible array value
50-
// (e.g. pass pq.Array from lib/pq). For unit tests, pass a no-op wrapper.
51-
func PostgreSQLArrayInsertBuilder(wrapArray func([]interface{}) interface{}) func(ctx context.Context, table string, columns []string, columnValues [][]interface{}, sequenceColumn string) (string, []interface{}, error) {
52-
return func(_ context.Context, table string, columns []string, columnValues [][]interface{}, sequenceColumn string) (string, []interface{}, error) {
53-
unnests := make([]string, len(columns))
54-
args := make([]interface{}, len(columns))
55-
for i := range columns {
56-
unnests[i] = fmt.Sprintf("UNNEST($%d)", i+1)
57-
args[i] = wrapArray(columnValues[i])
58-
}
59-
sql := fmt.Sprintf(
60-
"INSERT INTO %s (%s) SELECT %s RETURNING %s",
61-
table,
62-
strings.Join(columns, ", "),
63-
strings.Join(unnests, ", "),
64-
sequenceColumn,
65-
)
66-
return sql, args, nil
67-
}
68-
}
69-
70-
// mockArrayValue wraps a slice so database/sql treats it as a single driver.Value.
71-
type mockArrayValue struct {
72-
values []interface{}
73-
}
74-
75-
func (m mockArrayValue) Value() (driver.Value, error) {
76-
return fmt.Sprintf("{%d values}", len(m.values)), nil
77-
}
78-
79-
// BuildPostgreSQLArrayInsert is a convenience for tests that don't need real pq.Array wrapping.
80-
// It uses a mock driver.Valuer so sqlmock can accept the args.
81-
func BuildPostgreSQLArrayInsert(ctx context.Context, table string, columns []string, columnValues [][]interface{}, sequenceColumn string) (string, []interface{}, error) {
82-
return PostgreSQLArrayInsertBuilder(func(vals []interface{}) interface{} {
83-
return mockArrayValue{values: vals}
84-
})(ctx, table, columns, columnValues, sequenceColumn)
85-
}

pkg/dbsql/postgres_helpers_test.go

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -61,46 +61,3 @@ func TestBuildPostgreSQLOptimizedUpsertFail(t *testing.T) {
6161

6262
}
6363

64-
func TestBuildPostgreSQLArrayInsert(t *testing.T) {
65-
sqlStr, args, err := BuildPostgreSQLArrayInsert(
66-
context.Background(),
67-
"transfers",
68-
[]string{"id", "sender", "amount"},
69-
[][]interface{}{
70-
{"id1", "id2"},
71-
{"0xaaa", "0xbbb"},
72-
{100, 200},
73-
},
74-
"seq",
75-
)
76-
assert.NoError(t, err)
77-
assert.Equal(t, "INSERT INTO transfers (id, sender, amount) SELECT UNNEST($1), UNNEST($2), UNNEST($3) RETURNING seq", sqlStr)
78-
assert.Len(t, args, 3)
79-
// Args are mockArrayValue wrappers for database/sql compatibility
80-
for _, arg := range args {
81-
_, ok := arg.(mockArrayValue)
82-
assert.True(t, ok, "expected mockArrayValue, got %T", arg)
83-
}
84-
}
85-
86-
func TestPostgreSQLArrayInsertBuilderWithCustomWrapper(t *testing.T) {
87-
wrapCalled := 0
88-
builder := PostgreSQLArrayInsertBuilder(func(vals []interface{}) interface{} {
89-
wrapCalled++
90-
return vals
91-
})
92-
sql, args, err := builder(
93-
context.Background(),
94-
"things",
95-
[]string{"a", "b"},
96-
[][]interface{}{
97-
{1, 2, 3},
98-
{"x", "y", "z"},
99-
},
100-
"seq",
101-
)
102-
assert.NoError(t, err)
103-
assert.Equal(t, "INSERT INTO things (a, b) SELECT UNNEST($1), UNNEST($2) RETURNING seq", sql)
104-
assert.Len(t, args, 2)
105-
assert.Equal(t, 2, wrapCalled)
106-
}

pkg/dbsql/provider.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,6 @@ type SQLFeatures struct {
3838
// When >0 and MultiRowInsert is true, InsertMany will chunk rows so that
3939
// rows*columns never exceeds this limit. PostgreSQL's wire protocol limit is 65535.
4040
MaxPlaceholders int
41-
// ArrayInsertBuilder enables column-wise array-based bulk inserts (e.g. PostgreSQL UNNEST).
42-
// When set, InsertMany prefers this over multi-row VALUES inserts.
43-
// The builder receives column names and per-column value slices, and returns raw SQL + args.
44-
// The returned SQL must include a RETURNING clause for the sequence column if sequences are needed.
45-
ArrayInsertBuilder func(ctx context.Context, table string, columns []string, columnValues [][]interface{}, sequenceColumn string) (sql string, args []interface{}, err error)
4641
}
4742

4843
func DefaultSQLProviderFeatures() SQLFeatures {

0 commit comments

Comments
 (0)