diff --git a/pkg/jobs/errors.go b/pkg/jobs/errors.go new file mode 100644 index 0000000..cff27b0 --- /dev/null +++ b/pkg/jobs/errors.go @@ -0,0 +1,40 @@ +// Copyright 2024 ScyllaDB +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jobs + +import ( + "fmt" + + "github.com/scylladb/gemini/pkg/typedef" +) + +type PrettyCQLError struct { + PrettyCQL error + Err error + Stmt *typedef.Stmt +} + +func (e PrettyCQLError) Error() string { + query, _ := e.Stmt.Query.ToCql() + values := e.Stmt.Values.Copy() + + return fmt.Sprintf( + "Failed to format CQL Statement: prettyCQL {Query(%s) Values(%+v) PrettyCQLError(%+v) Error(%+v)}", + query, + values, + e.PrettyCQL, + e.Err, + ) +} diff --git a/pkg/jobs/jobs.go b/pkg/jobs/jobs.go index 1f71c7b..3730b7d 100644 --- a/pkg/jobs/jobs.go +++ b/pkg/jobs/jobs.go @@ -17,21 +17,20 @@ package jobs import ( "context" "encoding/json" - "errors" "fmt" "time" - "github.com/scylladb/gemini/pkg/generators" - "github.com/scylladb/gemini/pkg/store" - "github.com/scylladb/gemini/pkg/typedef" + "github.com/pkg/errors" + "go.uber.org/zap" + "golang.org/x/exp/rand" + "golang.org/x/sync/errgroup" + "github.com/scylladb/gemini/pkg/generators" "github.com/scylladb/gemini/pkg/joberror" "github.com/scylladb/gemini/pkg/status" "github.com/scylladb/gemini/pkg/stop" - - "go.uber.org/zap" - "golang.org/x/exp/rand" - "golang.org/x/sync/errgroup" + "github.com/scylladb/gemini/pkg/store" + "github.com/scylladb/gemini/pkg/typedef" ) const ( @@ -177,9 +176,15 @@ func mutationJob( } ind := r.Intn(1000000) if ind%100000 == 0 { - _ = ddl(ctx, schema, schemaConfig, table, s, r, p, globalStatus, logger, verbose) + err := ddl(ctx, schema, schemaConfig, table, s, r, p, globalStatus, logger, verbose) + if err != nil { + return err + } } else { - _ = mutation(ctx, schema, schemaConfig, table, s, r, p, g, globalStatus, true, logger) + err := mutation(ctx, schema, schemaConfig, table, s, r, p, g, globalStatus, true, logger) + if err != nil { + return err + } } if failFast && globalStatus.HasErrors() { stopFlag.SetSoft(true) @@ -240,16 +245,20 @@ func validationJob( return nil default: query, prettyErr := stmt.PrettyCQL() + if prettyErr != nil { + return PrettyCQLError{ + PrettyCQL: prettyErr, + Stmt: stmt, + Err: err, + } + } + globalStatus.AddReadError(&joberror.JobError{ Timestamp: time.Now(), - StmtType: stmt.QueryType.ToString(), + StmtType: stmt.QueryType.String(), Message: "Validation failed: " + err.Error(), Query: query, }) - - if prettyErr != nil { - return prettyErr - } } if failFast && globalStatus.HasErrors() { @@ -288,7 +297,11 @@ func warmupJob( return nil } // Do we care about errors during warmup? - _ = mutation(ctx, schema, schemaConfig, table, s, r, p, g, globalStatus, false, logger) + err := mutation(ctx, schema, schemaConfig, table, s, r, p, g, globalStatus, false, logger) + if err != nil { + return err + } + if failFast && globalStatus.HasErrors() { stopFlag.SetSoft(true) return nil @@ -324,20 +337,25 @@ func ddl( globalStatus.WriteErrors.Add(1) return err } + if ddlStmts == nil { if w := logger.Check(zap.DebugLevel, "no statement generated"); w != nil { w.Write(zap.String("job", "ddl")) } return nil } + for _, ddlStmt := range ddlStmts.List { if w := logger.Check(zap.DebugLevel, "ddl statement"); w != nil { prettyCQL, prettyCQLErr := ddlStmt.PrettyCQL() if prettyCQLErr != nil { - logger.Error("Failed! DDL PrettyCQL failed", zap.Error(prettyCQLErr)) - } else { - w.Write(zap.String("pretty_cql", prettyCQL)) + return PrettyCQLError{ + PrettyCQL: prettyCQLErr, + Stmt: ddlStmt, + } } + + w.Write(zap.String("pretty_cql", prettyCQL)) } if err = s.Mutate(ctx, ddlStmt); err != nil { @@ -346,17 +364,21 @@ func ddl( } prettyCQL, prettyCQLErr := ddlStmt.PrettyCQL() + if prettyCQLErr != nil { + return PrettyCQLError{ + PrettyCQL: prettyCQLErr, + Stmt: ddlStmt, + Err: err, + } + } + globalStatus.AddWriteError(&joberror.JobError{ Timestamp: time.Now(), - StmtType: ddlStmts.QueryType.ToString(), + StmtType: ddlStmts.QueryType.String(), Message: "DDL failed: " + err.Error(), Query: prettyCQL, }) - if prettyCQLErr != nil { - logger.Error("Failed! DDL PrettyCQL failed", zap.Error(prettyCQLErr)) - } - return err } globalStatus.WriteOps.Add(1) @@ -398,10 +420,14 @@ func mutation( if w := logger.Check(zap.DebugLevel, "mutation statement"); w != nil { prettyCQL, prettyCQLErr := mutateStmt.PrettyCQL() if prettyCQLErr != nil { - logger.Error("Failed! mutation PrettyCQL failed", zap.Error(prettyCQLErr)) - } else { - w.Write(zap.String("pretty_cql", prettyCQL)) + return PrettyCQLError{ + PrettyCQL: prettyCQLErr, + Stmt: mutateStmt, + Err: err, + } } + + w.Write(zap.String("pretty_cql", prettyCQL)) } if err = s.Mutate(ctx, mutateStmt); err != nil { if errors.Is(err, context.Canceled) { @@ -409,17 +435,21 @@ func mutation( } prettyCQL, prettyCQLErr := mutateStmt.PrettyCQL() + if prettyCQLErr != nil { + return PrettyCQLError{ + PrettyCQL: prettyCQLErr, + Stmt: mutateStmt, + Err: err, + } + } + globalStatus.AddWriteError(&joberror.JobError{ Timestamp: time.Now(), - StmtType: mutateStmt.QueryType.ToString(), + StmtType: mutateStmt.QueryType.String(), Message: "Mutation failed: " + err.Error(), Query: prettyCQL, }) - if prettyCQLErr != nil { - logger.Error("Failed! DDL PrettyCQL failed", zap.Error(prettyCQLErr)) - } - return err } @@ -440,10 +470,13 @@ func validation( if w := logger.Check(zap.DebugLevel, "validation statement"); w != nil { prettyCQL, prettyCQLErr := stmt.PrettyCQL() if prettyCQLErr != nil { - logger.Error("Failed! validation PrettyCQL failed", zap.Error(prettyCQLErr)) - } else { - w.Write(zap.String("pretty_cql", prettyCQL)) + return PrettyCQLError{ + PrettyCQL: prettyCQLErr, + Stmt: stmt, + } } + + w.Write(zap.String("pretty_cql", prettyCQL)) } maxAttempts := 1 diff --git a/pkg/typedef/tuple.go b/pkg/typedef/tuple.go index e98d45a..c1761d4 100644 --- a/pkg/typedef/tuple.go +++ b/pkg/typedef/tuple.go @@ -70,6 +70,10 @@ func (t *TupleType) CQLPretty(builder *bytes.Buffer, value any) error { return nil } + if len(values) != len(t.ValueTypes) { + return errors.Errorf("expected %d values, got %d", len(t.ValueTypes), len(values)) + } + for i, tp := range t.ValueTypes { if err := tp.CQLPretty(builder, values[i]); err != nil { return err diff --git a/pkg/typedef/typedef.go b/pkg/typedef/typedef.go index bf6fd2d..bb2278e 100644 --- a/pkg/typedef/typedef.go +++ b/pkg/typedef/typedef.go @@ -19,6 +19,7 @@ import ( "fmt" "strings" + "github.com/pkg/errors" "github.com/scylladb/gocqlx/v2/qb" "github.com/scylladb/gemini/pkg/replication" @@ -118,7 +119,7 @@ func (s *Stmt) Clone() *Stmt { type StatementType uint8 -func (st StatementType) ToString() string { +func (st StatementType) String() string { switch st { case SelectStatementType: return "SelectStatement" @@ -224,6 +225,10 @@ func prettyCQL(builder *bytes.Buffer, q string, values Values, types []Type) err return nil } + if len(values) < len(types) { + return errors.Errorf("expected at least %d values, got %d", len(types), len(values)) + } + var ( skip int idx int @@ -242,6 +247,10 @@ func prettyCQL(builder *bytes.Buffer, q string, values Values, types []Type) err var value any + if i >= len(types) { + return errors.Errorf("there are more(%d) ? in the query than types(%d), invalid Query: %s", len(types), i, q) + } + switch tt := types[i].(type) { case *TupleType: skip = tt.LenValue() diff --git a/results/.gitignore b/results/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/results/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore