diff --git a/pkg/jobs/jobs.go b/pkg/jobs/jobs.go index ac21c09..1f71c7b 100644 --- a/pkg/jobs/jobs.go +++ b/pkg/jobs/jobs.go @@ -239,12 +239,17 @@ func validationJob( case errors.Is(err, context.Canceled): return nil default: + query, prettyErr := stmt.PrettyCQL() globalStatus.AddReadError(&joberror.JobError{ Timestamp: time.Now(), StmtType: stmt.QueryType.ToString(), Message: "Validation failed: " + err.Error(), - Query: stmt.PrettyCQL(), + Query: query, }) + + if prettyErr != nil { + return prettyErr + } } if failFast && globalStatus.HasErrors() { @@ -327,18 +332,31 @@ func ddl( } for _, ddlStmt := range ddlStmts.List { if w := logger.Check(zap.DebugLevel, "ddl statement"); w != nil { - w.Write(zap.String("pretty_cql", ddlStmt.PrettyCQL())) + prettyCQL, prettyCQLErr := ddlStmt.PrettyCQL() + if prettyCQLErr != nil { + logger.Error("Failed! DDL PrettyCQL failed", zap.Error(prettyCQLErr)) + } else { + w.Write(zap.String("pretty_cql", prettyCQL)) + } } + if err = s.Mutate(ctx, ddlStmt); err != nil { if errors.Is(err, context.Canceled) { return nil } + + prettyCQL, prettyCQLErr := ddlStmt.PrettyCQL() globalStatus.AddWriteError(&joberror.JobError{ Timestamp: time.Now(), StmtType: ddlStmts.QueryType.ToString(), Message: "DDL failed: " + err.Error(), - Query: ddlStmt.PrettyCQL(), + Query: prettyCQL, }) + + if prettyCQLErr != nil { + logger.Error("Failed! DDL PrettyCQL failed", zap.Error(prettyCQLErr)) + } + return err } globalStatus.WriteOps.Add(1) @@ -378,22 +396,36 @@ func mutation( } if w := logger.Check(zap.DebugLevel, "mutation statement"); w != nil { - w.Write(zap.String("pretty_cql", mutateStmt.PrettyCQL())) + prettyCQL, prettyCQLErr := mutateStmt.PrettyCQL() + if prettyCQLErr != nil { + logger.Error("Failed! mutation PrettyCQL failed", zap.Error(prettyCQLErr)) + } else { + w.Write(zap.String("pretty_cql", prettyCQL)) + } } if err = s.Mutate(ctx, mutateStmt); err != nil { if errors.Is(err, context.Canceled) { return nil } + + prettyCQL, prettyCQLErr := mutateStmt.PrettyCQL() globalStatus.AddWriteError(&joberror.JobError{ Timestamp: time.Now(), StmtType: mutateStmt.QueryType.ToString(), Message: "Mutation failed: " + err.Error(), - Query: mutateStmt.PrettyCQL(), + Query: prettyCQL, }) - } else { - globalStatus.WriteOps.Add(1) - g.GiveOlds(mutateStmt.ValuesWithToken) + + if prettyCQLErr != nil { + logger.Error("Failed! DDL PrettyCQL failed", zap.Error(prettyCQLErr)) + } + + return err } + + globalStatus.WriteOps.Add(1) + g.GiveOlds(mutateStmt.ValuesWithToken) + return nil } @@ -406,7 +438,12 @@ func validation( logger *zap.Logger, ) error { if w := logger.Check(zap.DebugLevel, "validation statement"); w != nil { - w.Write(zap.String("pretty_cql", stmt.PrettyCQL())) + prettyCQL, prettyCQLErr := stmt.PrettyCQL() + if prettyCQLErr != nil { + logger.Error("Failed! validation PrettyCQL failed", zap.Error(prettyCQLErr)) + } else { + w.Write(zap.String("pretty_cql", prettyCQL)) + } } maxAttempts := 1 diff --git a/pkg/stmtlogger/filelogger.go b/pkg/stmtlogger/filelogger.go index d221767..5c863f6 100644 --- a/pkg/stmtlogger/filelogger.go +++ b/pkg/stmtlogger/filelogger.go @@ -15,6 +15,7 @@ package stmtlogger import ( + "io" "log" "os" "strconv" @@ -37,11 +38,10 @@ type StmtToFile interface { Close() error } -type fileLogger struct { - fd *os.File +type logger struct { + fd io.Writer activeChannel atomic.Pointer[loggerChan] channel loggerChan - filename string isFileNonOperational bool } @@ -52,7 +52,7 @@ type logRec struct { ts time.Time } -func (fl *fileLogger) LogStmt(stmt *typedef.Stmt) { +func (fl *logger) LogStmt(stmt *typedef.Stmt) { ch := fl.activeChannel.Load() if ch != nil { *ch <- logRec{ @@ -61,7 +61,7 @@ func (fl *fileLogger) LogStmt(stmt *typedef.Stmt) { } } -func (fl *fileLogger) LogStmtWithTimeStamp(stmt *typedef.Stmt, ts time.Time) { +func (fl *logger) LogStmtWithTimeStamp(stmt *typedef.Stmt, ts time.Time) { ch := fl.activeChannel.Load() if ch != nil { *ch <- logRec{ @@ -71,11 +71,15 @@ func (fl *fileLogger) LogStmtWithTimeStamp(stmt *typedef.Stmt, ts time.Time) { } } -func (fl *fileLogger) Close() error { - return fl.fd.Close() +func (fl *logger) Close() error { + if closer, ok := fl.fd.(io.Closer); ok { + return closer.Close() + } + + return nil } -func (fl *fileLogger) committer() { +func (fl *logger) committer() { var err2 error defer func() { @@ -90,7 +94,13 @@ func (fl *fileLogger) committer() { continue } - _, err1 := fl.fd.Write([]byte(rec.stmt.PrettyCQL())) + query, err := rec.stmt.PrettyCQL() + if err != nil { + log.Printf("failed to pretty print query: %s", err) + continue + } + + _, err1 := fl.fd.Write([]byte(query)) opType := rec.stmt.QueryType.OpType() if rec.ts.IsZero() || !(opType == typedef.OpInsert || opType == typedef.OpUpdate || opType == typedef.OpDelete) { _, err2 = fl.fd.Write([]byte(";\n")) @@ -115,7 +125,8 @@ func (fl *fileLogger) committer() { if err2 != nil { err1 = err2 } - log.Printf("failed to write to file %q: %s", fl.filename, err1) + + log.Printf("failed to write to writer %v", err1) return } } @@ -129,10 +140,13 @@ func NewFileLogger(filename string) (StmtToFile, error) { return nil, err } - out := &fileLogger{ - filename: filename, - fd: fd, - channel: make(loggerChan, defaultChanSize), + return NewLogger(fd) +} + +func NewLogger(w io.Writer) (StmtToFile, error) { + out := &logger{ + fd: w, + channel: make(loggerChan, defaultChanSize), } out.activeChannel.Store(&out.channel) diff --git a/pkg/typedef/bag.go b/pkg/typedef/bag.go index 473adab..9f4f97f 100644 --- a/pkg/typedef/bag.go +++ b/pkg/typedef/bag.go @@ -15,11 +15,12 @@ package typedef import ( - "fmt" "math" "reflect" "strings" + "github.com/pkg/errors" + "github.com/gocql/gocql" "golang.org/x/exp/rand" @@ -59,9 +60,11 @@ func (ct *BagType) CQLHolder() string { return "?" } -func (ct *BagType) CQLPretty(builder *strings.Builder, value any) { +type Tuple []any + +func (ct *BagType) CQLPretty(builder *strings.Builder, value any) error { if reflect.TypeOf(value).Kind() != reflect.Slice { - panic(fmt.Sprintf("set cql pretty, unknown type %v", ct)) + return errors.Errorf("expected slice, got [%T]%v", value, value) } if ct.ComplexType == TYPE_SET { @@ -75,11 +78,16 @@ func (ct *BagType) CQLPretty(builder *strings.Builder, value any) { s := reflect.ValueOf(value) for i := 0; i < s.Len(); i++ { - ct.ValueType.CQLPretty(builder, s.Index(i).Interface()) + if err := ct.ValueType.CQLPretty(builder, s.Index(i).Interface()); err != nil { + return err + } + if i < s.Len()-1 { builder.WriteRune(',') } } + + return nil } func (ct *BagType) GenValue(r *rand.Rand, p *PartitionRangeConfig) []any { diff --git a/pkg/typedef/interfaces.go b/pkg/typedef/interfaces.go index 7e6ceef..31e18e6 100644 --- a/pkg/typedef/interfaces.go +++ b/pkg/typedef/interfaces.go @@ -25,7 +25,7 @@ type Type interface { Name() string CQLDef() string CQLHolder() string - CQLPretty(*strings.Builder, any) + CQLPretty(*strings.Builder, any) error GenValue(*rand.Rand, *PartitionRangeConfig) []any GenJSONValue(*rand.Rand, *PartitionRangeConfig) any LenValue() int @@ -37,7 +37,7 @@ type Type interface { type Statement interface { ToCql() (stmt string, names []string) - PrettyCQL() string + PrettyCQL() (string, error) } type Types []Type diff --git a/pkg/typedef/simple_type.go b/pkg/typedef/simple_type.go index 821cd0c..3994afc 100644 --- a/pkg/typedef/simple_type.go +++ b/pkg/typedef/simple_type.go @@ -25,6 +25,7 @@ import ( "time" "github.com/gocql/gocql" + "github.com/pkg/errors" "golang.org/x/exp/rand" "gopkg.in/inf.v0" @@ -68,11 +69,12 @@ func (st SimpleType) LenValue() int { return 1 } -func (st SimpleType) CQLPretty(builder *strings.Builder, value any) { +func (st SimpleType) CQLPretty(builder *strings.Builder, value any) error { switch st { case TYPE_INET: builder.WriteRune('\'') defer builder.WriteRune('\'') + switch v := value.(type) { case net.IP: builder.WriteString(v.String()) @@ -80,11 +82,20 @@ func (st SimpleType) CQLPretty(builder *strings.Builder, value any) { builder.WriteString(v.String()) case string: builder.WriteString(v) + default: + return errors.Errorf("unexpected inet value [%T]%+v", value, value) } + + return nil case TYPE_ASCII, TYPE_TEXT, TYPE_VARCHAR, TYPE_DATE: - builder.WriteRune('\'') - builder.WriteString(value.(string)) - builder.WriteRune('\'') + if v, ok := value.(string); ok { + builder.WriteRune('\'') + builder.WriteString(v) + builder.WriteRune('\'') + return nil + } + + return errors.Errorf("unexpected string value [%T]%+v", value, value) case TYPE_BLOB: if v, ok := value.(string); ok { if len(v) > 100 { @@ -93,10 +104,10 @@ func (st SimpleType) CQLPretty(builder *strings.Builder, value any) { builder.WriteString("textasblob('") builder.WriteString(v) builder.WriteString("')") - return + return nil } - panic(fmt.Sprintf("unexpected blob value [%T]%+v", value, value)) + return errors.Errorf("unexpected blob value [%T]%+v", value, value) case TYPE_BIGINT, TYPE_INT, TYPE_SMALLINT, TYPE_TINYINT: var i int64 switch v := value.(type) { @@ -112,11 +123,15 @@ func (st SimpleType) CQLPretty(builder *strings.Builder, value any) { i = v case *big.Int: builder.WriteString(v.Text(10)) - return + + return nil default: - panic(fmt.Sprintf("unexpected int value [%T]%+v", value, value)) + return errors.Errorf("unexpected int value [%T]%+v", value, value) } + builder.WriteString(strconv.FormatInt(i, 10)) + + return nil case TYPE_DECIMAL, TYPE_DOUBLE, TYPE_FLOAT: var f float64 switch v := value.(type) { @@ -126,18 +141,22 @@ func (st SimpleType) CQLPretty(builder *strings.Builder, value any) { f = v case *inf.Dec: builder.WriteString(v.String()) - return + + return nil default: - panic(fmt.Sprintf("unexpected float value [%T]%+v", value, value)) + return errors.Errorf("unexpected float value [%T]%+v", value, value) } + builder.WriteString(strconv.FormatFloat(f, 'f', 2, 64)) + return nil case TYPE_BOOLEAN: if v, ok := value.(bool); ok { builder.WriteString(strconv.FormatBool(v)) - return + + return nil } - panic(fmt.Sprintf("unexpected boolean value [%T]%+v", value, value)) + return errors.Errorf("unexpected boolean value [%T]%+v", value, value) case TYPE_TIME: if v, ok := value.(int64); ok { builder.WriteRune('\'') @@ -145,43 +164,41 @@ func (st SimpleType) CQLPretty(builder *strings.Builder, value any) { // '10:10:55.83275+0000': marshaling error: Milliseconds length exceeds expected (5)" builder.WriteString(time.Time{}.Add(time.Duration(v)).Format("15:04:05.999")) builder.WriteRune('\'') - return + + return nil } - panic(fmt.Sprintf("unexpected time value [%T]%+v", value, value)) + return errors.Errorf("unexpected time value [%T]%+v", value, value) case TYPE_TIMESTAMP: if v, ok := value.(int64); ok { // CQL supports only 3 digits milliseconds: // '1976-03-25T10:10:55.83275+0000': marshaling error: Milliseconds length exceeds expected (5)" builder.WriteString(time.UnixMilli(v).UTC().Format("'2006-01-02T15:04:05.999-0700'")) - return + return nil } - panic(fmt.Sprintf("unexpected timestamp value [%T]%+v", value, value)) + return errors.Errorf("unexpected timestamp value [%T]%+v", value, value) case TYPE_DURATION, TYPE_TIMEUUID, TYPE_UUID: switch v := value.(type) { case string: builder.WriteString(v) - return case time.Duration: builder.WriteString(v.String()) - return case gocql.UUID: builder.WriteString(v.String()) - return + default: + return errors.Errorf("unexpected (duration|timeuuid|uuid) value [%T]%+v", value, value) } - - panic(fmt.Sprintf("unexpected (duration|timeuuid|uuid) value [%T]%+v", value, value)) + return nil case TYPE_VARINT: if s, ok := value.(*big.Int); ok { builder.WriteString(s.Text(10)) - return + return nil } - panic(fmt.Sprintf("unexpected varint value [%T]%+v", value, value)) + return errors.Errorf("unexpected varint value [%T]%+v", value, value) default: - panic(fmt.Sprintf("cql pretty: not supported type %s [%T]%+v", st, value, value)) - + return errors.Errorf("cql pretty: not supported type %s [%T]%+v", st, value, value) } } diff --git a/pkg/typedef/tuple.go b/pkg/typedef/tuple.go index e534d3d..ae6f6a8 100644 --- a/pkg/typedef/tuple.go +++ b/pkg/typedef/tuple.go @@ -17,6 +17,8 @@ package typedef import ( "strings" + "github.com/pkg/errors" + "github.com/gocql/gocql" "golang.org/x/exp/rand" ) @@ -54,21 +56,30 @@ func (t *TupleType) CQLHolder() string { return "(" + strings.TrimRight(strings.Repeat("?,", len(t.ValueTypes)), ",") + ")" } -func (t *TupleType) CQLPretty(builder *strings.Builder, value any) { +func (t *TupleType) CQLPretty(builder *strings.Builder, value any) error { values, ok := value.([]any) if !ok { - builder.WriteString("()") + values, ok = value.(Values) + if !ok { + return errors.Errorf("expected []any, got [%T]%v", value, value) + } } - builder.WriteRune('(') - defer builder.WriteRune(')') + if len(values) == 0 { + return nil + } for i, tp := range t.ValueTypes { - tp.CQLPretty(builder, values[i]) + if err := tp.CQLPretty(builder, values[i]); err != nil { + return err + } + if i < len(values)-1 { builder.WriteRune(',') } } + + return nil } func (t *TupleType) Indexable() bool { diff --git a/pkg/typedef/typedef.go b/pkg/typedef/typedef.go index 2baac89..baf8b51 100644 --- a/pkg/typedef/typedef.go +++ b/pkg/typedef/typedef.go @@ -88,15 +88,23 @@ func SimpleStmt(query string, queryType StatementType) *Stmt { } } -func (s *Stmt) PrettyCQL() string { +func (s *Stmt) PrettyCQL() (string, error) { query, _ := s.Query.ToCql() values := s.Values.Copy() - if len(values) == 0 { - return query - } return prettyCQL(query, values, s.Types) } +func (s *Stmt) ToCql() (string, []string) { + return s.Query.ToCql() +} + +func (s *Stmt) Clone() *Stmt { + return &Stmt{ + StmtCache: s.StmtCache, + Values: s.Values.Copy(), + } +} + type StatementType uint8 func (st StatementType) ToString() string { @@ -197,26 +205,49 @@ const ( CacheArrayLen ) -func prettyCQL(query string, values Values, types []Type) string { +func prettyCQL(q string, values Values, types []Type) (string, error) { if len(types) == 0 { - return query + return q, nil } var ( - index int + skip int + idx int builder strings.Builder ) - builder.Grow(len(query)) + builder.Grow(len(q)) + + for pos, i := strings.Index(q[idx:], "?"), 0; pos != -1; pos = strings.Index(q[idx:], "?") { + str := q[idx : idx+pos] + // Just to skip the ? in the query, happens only in TUPLE TYPES + if skip > 0 { + skip-- + idx += pos + 1 + continue + } + + builder.WriteString(str) + + var value any + + switch tt := types[i].(type) { + case *TupleType: + skip = tt.LenValue() + value = values[i : i+skip] + values = values[skip:] + default: + value = values[i] + } - for pos, i := strings.Index(query[index:], "?"), 0; pos != -1; pos, i = strings.Index(query[index:], "?"), i+1 { - actualPos := index + pos - builder.WriteString(query[index:actualPos]) - types[i].CQLPretty(&builder, values[i]) + if err := types[i].CQLPretty(&builder, value); err != nil { + return "", err + } - index = actualPos + 1 + i++ + idx += pos + 1 } - builder.WriteString(query[index:]) - return builder.String() + builder.WriteString(q[idx:]) + return builder.String(), nil } diff --git a/pkg/typedef/typedef_test.go b/pkg/typedef/typedef_test.go index 825059a..1f0c126 100644 --- a/pkg/typedef/typedef_test.go +++ b/pkg/typedef/typedef_test.go @@ -104,8 +104,10 @@ var stmt = &Stmt{ func TestPrettyCQL(t *testing.T) { t.Parallel() - query := stmt.PrettyCQL() - + query, err := stmt.PrettyCQL() + if err != nil { + t.Errorf("failed to generate prettyCQL %v", err) + } //nolint:lll expected := fmt.Sprintf( `INSERT INTO tbl(col1, col2, col3, col4, col5, col6,col7,col8,col9,cold10,col11,col12,col13,col14,col15,col16,col17,col18,col19,col20) VALUES ('a',10,textasblob('a'),true,'1999-12-31',1000,10.00,10m0s,10.00,'192.168.0.1',10,2,'a','%s','%s',63176980-bfde-11d3-bc37-1c4d704231dc,63176980-bfde-11d3-bc37-1c4d704231dc,1,'a',1001);`, @@ -133,7 +135,7 @@ func prettyCQLOld(query string, values Values, types Types) string { builder.Reset() tupleType, ok := typ.(*TupleType) if !ok { - typ.CQLPretty(&builder, values[k]) + _ = typ.CQLPretty(&builder, values[k]) out = append(out, builder.String()) out = append(out, queryChunks[qID]) qID++ @@ -142,7 +144,7 @@ func prettyCQLOld(query string, values Values, types Types) string { } for _, t := range tupleType.ValueTypes { builder.Reset() - t.CQLPretty(&builder, values[k]) + _ = t.CQLPretty(&builder, values[k]) out = append(out, builder.String()) out = append(out, queryChunks[qID]) qID++ @@ -161,7 +163,10 @@ func BenchmarkPrettyCQLOLD(b *testing.B) { for i := 0; i < b.N; i++ { query, _ := stmt.Query.ToCql() values := stmt.Values.Copy() - prettyCQL(query, values, stmt.Types) + _, err := prettyCQL(query, values, stmt.Types) + if err != nil { + b.Error(err) + } } }) diff --git a/pkg/typedef/types.go b/pkg/typedef/types.go index 73ce633..9cc7c5f 100644 --- a/pkg/typedef/types.go +++ b/pkg/typedef/types.go @@ -15,13 +15,14 @@ package typedef import ( - "fmt" "math" "reflect" "strconv" "strings" "sync/atomic" + "github.com/pkg/errors" + "github.com/gocql/gocql" "golang.org/x/exp/rand" @@ -141,9 +142,9 @@ func (mt *MapType) CQLHolder() string { return "?" } -func (mt *MapType) CQLPretty(builder *strings.Builder, value any) { +func (mt *MapType) CQLPretty(builder *strings.Builder, value any) error { if reflect.TypeOf(value).Kind() != reflect.Map { - panic(fmt.Sprintf("map cql pretty, unknown type %v", mt)) + return errors.Errorf("expected map, got [%T]%v", value, value) } builder.WriteRune('{') @@ -154,13 +155,21 @@ func (mt *MapType) CQLPretty(builder *strings.Builder, value any) { length := vof.Len() for id := 0; s.Next(); id++ { - mt.KeyType.CQLPretty(builder, s.Key().Interface()) + if err := mt.KeyType.CQLPretty(builder, s.Key().Interface()); err != nil { + return err + } builder.WriteRune(':') - mt.ValueType.CQLPretty(builder, s.Value().Interface()) + + if err := mt.ValueType.CQLPretty(builder, s.Value().Interface()); err != nil { + return err + } + if id < length-1 { builder.WriteRune(',') } } + + return nil } func (mt *MapType) GenJSONValue(r *rand.Rand, p *PartitionRangeConfig) any { @@ -217,7 +226,7 @@ func (ct *CounterType) CQLHolder() string { return "?" } -func (ct *CounterType) CQLPretty(builder *strings.Builder, value interface{}) { +func (ct *CounterType) CQLPretty(builder *strings.Builder, value any) error { switch v := value.(type) { case int64: builder.WriteString(strconv.FormatInt(v, 10)) @@ -231,7 +240,11 @@ func (ct *CounterType) CQLPretty(builder *strings.Builder, value interface{}) { builder.WriteString(strconv.FormatUint(uint64(v), 10)) case uint: builder.WriteString(strconv.FormatUint(uint64(v), 10)) + default: + return errors.Errorf("counter cql pretty, unknown type [%T]%v", value, value) } + + return nil } func (ct *CounterType) GenJSONValue(r *rand.Rand, _ *PartitionRangeConfig) any { diff --git a/pkg/typedef/types_test.go b/pkg/typedef/types_test.go index 330c78e..225925d 100644 --- a/pkg/typedef/types_test.go +++ b/pkg/typedef/types_test.go @@ -196,10 +196,8 @@ var prettytests = []struct { ValueTypes: []SimpleType{TYPE_ASCII}, Frozen: false, }, - query: "SELECT * FROM tbl WHERE pk0=?", - values: []interface{}{ - []any{"a"}, - }, + query: "SELECT * FROM tbl WHERE pk0=(?)", + values: []interface{}{"a"}, expected: "SELECT * FROM tbl WHERE pk0=('a')", }, { @@ -207,21 +205,8 @@ var prettytests = []struct { ValueTypes: []SimpleType{TYPE_ASCII, TYPE_ASCII}, Frozen: false, }, - query: "SELECT * FROM tbl WHERE pk0=?", - values: []interface{}{ - []any{"a", "b"}, - }, - expected: "SELECT * FROM tbl WHERE pk0=('a','b')", - }, - { - typ: &TupleType{ - ValueTypes: []SimpleType{TYPE_ASCII, TYPE_ASCII}, - Frozen: false, - }, - query: "SELECT * FROM tbl WHERE pk0=?", - values: []interface{}{ - []any{"a", "b"}, - }, + query: "SELECT * FROM tbl WHERE pk0=(?,?)", + values: []interface{}{"a", "b"}, expected: "SELECT * FROM tbl WHERE pk0=('a','b')", }, } @@ -232,9 +217,10 @@ func TestCQLPretty(t *testing.T) { for id := range prettytests { test := prettytests[id] t.Run(test.typ.Name(), func(t *testing.T) { - t.Parallel() - - result := prettyCQL(test.query, test.values, []Type{test.typ}) + result, err := prettyCQL(test.query, test.values, []Type{test.typ}) + if err != nil { + t.Errorf("unexpected error: %v", err) + } if result != test.expected { t.Errorf("expected '%s', got '%s' for values %v and type '%v'", test.expected, result, test.values, test.typ) } diff --git a/pkg/typedef/udt.go b/pkg/typedef/udt.go index b757591..9e62f6a 100644 --- a/pkg/typedef/udt.go +++ b/pkg/typedef/udt.go @@ -15,9 +15,10 @@ package typedef import ( - "fmt" "strings" + "github.com/pkg/errors" + "github.com/gocql/gocql" "golang.org/x/exp/rand" ) @@ -48,10 +49,10 @@ func (t *UDTType) CQLHolder() string { return "?" } -func (t *UDTType) CQLPretty(builder *strings.Builder, value any) { +func (t *UDTType) CQLPretty(builder *strings.Builder, value any) error { s, ok := value.(map[string]any) if !ok { - panic(fmt.Sprintf("udt pretty, unknown type %v", t)) + return errors.Errorf("udt pretty, expected map[string]any, got [%T]%v", value, value) } builder.WriteRune('{') @@ -66,20 +67,26 @@ func (t *UDTType) CQLPretty(builder *strings.Builder, value any) { builder.WriteString(k) builder.WriteRune(':') - v.CQLPretty(builder, keyVal) + if err := v.CQLPretty(builder, keyVal); err != nil { + return err + } + if i != len(s)-1 { builder.WriteRune(',') } i++ } + + return nil } func (t *UDTType) Indexable() bool { - for _, t := range t.ValueTypes { - if t == TYPE_DURATION { + for _, ty := range t.ValueTypes { + if ty == TYPE_DURATION { return false } } + return true }