func (tsv *TabletServer) handleExecErrorNoPanic(sql string, bindVariables map[string]interface{}, err interface{}, logStats *LogStats) error { var terr *TabletError defer func() { if logStats != nil { logStats.Error = terr } }() terr, ok := err.(*TabletError) if !ok { log.Errorf("Uncaught panic for %v:\n%v\n%s", querytypes.QueryAsString(sql, bindVariables), err, tb.Stack(4)) tsv.qe.queryServiceStats.InternalErrors.Add("Panic", 1) terr = NewTabletError(vtrpcpb.ErrorCode_UNKNOWN_ERROR, "%v: uncaught panic for %v", err, querytypes.QueryAsString(sql, bindVariables)) return terr } var myError error if tsv.config.TerseErrors && terr.SQLError != 0 && len(bindVariables) != 0 { myError = &TabletError{ SQLError: terr.SQLError, SQLState: terr.SQLState, ErrorCode: terr.ErrorCode, Message: fmt.Sprintf("(errno %d) (sqlstate %s) during query: %s", terr.SQLError, terr.SQLState, sql), } } else { myError = terr } terr.RecordStats(tsv.qe.queryServiceStats) logMethod := log.Warningf // Suppress or demote some errors in logs switch terr.ErrorCode { case vtrpcpb.ErrorCode_QUERY_NOT_SERVED, vtrpcpb.ErrorCode_RESOURCE_EXHAUSTED: return myError case vtrpcpb.ErrorCode_INTERNAL_ERROR: logMethod = log.Errorf case vtrpcpb.ErrorCode_NOT_IN_TX: // keep as warning default: // default is when we think the query itself is // problematic. This doesn't indicate a system or // component wide degradation, so we log to INFO. logMethod = log.Infof } // We want to suppress/demote some MySQL error codes // (regardless of the ErrorType) switch terr.SQLError { case mysql.ErrDupEntry: return myError case mysql.ErrLockWaitTimeout, mysql.ErrLockDeadlock, mysql.ErrDataTooLong, mysql.ErrDataOutOfRange, mysql.ErrBadNullError: logMethod = log.Infof case 0: if strings.Contains(terr.Error(), "Row count exceeded") { logMethod = log.Infof } } logMethod("%v: %v", terr, querytypes.QueryAsString(sql, bindVariables)) return myError }
// validateSplitQueryParameters perform some validations on the SplitQuery parameters // returns an error that can be returned to the user if a validation fails. func validateSplitQueryParameters( target *querypb.Target, sql string, bindVariables map[string]interface{}, splitColumns []string, splitCount int64, numRowsPerQueryPart int64, algorithm querypb.SplitQueryRequest_Algorithm, ) error { // Check that the caller requested a RDONLY tablet. // Since we're called by VTGate this should not normally be violated. if target.TabletType != topodatapb.TabletType_RDONLY { return NewTabletError( vtrpcpb.ErrorCode_BAD_INPUT, "SplitQuery must be called with a RDONLY tablet. TableType passed is: %v", target.TabletType) } if numRowsPerQueryPart < 0 { return NewTabletError( vtrpcpb.ErrorCode_BAD_INPUT, "splitQuery: numRowsPerQueryPart must be non-negative. Got: %v. SQL: %v", numRowsPerQueryPart, querytypes.QueryAsString(sql, bindVariables)) } if splitCount < 0 { return NewTabletError( vtrpcpb.ErrorCode_BAD_INPUT, "splitQuery: splitCount must be non-negative. Got: %v. SQL: %v", splitCount, querytypes.QueryAsString(sql, bindVariables)) } if (splitCount == 0 && numRowsPerQueryPart == 0) || (splitCount != 0 && numRowsPerQueryPart != 0) { return NewTabletError( vtrpcpb.ErrorCode_BAD_INPUT, "splitQuery: exactly one of {numRowsPerQueryPart, splitCount} must be"+ " non zero. Got: numRowsPerQueryPart=%v, splitCount=%v. SQL: %v", splitCount, querytypes.QueryAsString(sql, bindVariables)) } if algorithm != querypb.SplitQueryRequest_EQUAL_SPLITS && algorithm != querypb.SplitQueryRequest_FULL_SCAN { return NewTabletError( vtrpcpb.ErrorCode_BAD_INPUT, "splitquery: unsupported algorithm: %v. SQL: %v", algorithm, querytypes.QueryAsString(sql, bindVariables)) } return nil }
func (tsv *TabletServer) handleExecErrorNoPanic(sql string, bindVariables map[string]interface{}, err interface{}, logStats *LogStats) error { var terr *TabletError defer func() { if logStats != nil { logStats.Error = terr } }() terr, ok := err.(*TabletError) if !ok { log.Errorf("Uncaught panic for %v:\n%v\n%s", querytypes.QueryAsString(sql, bindVariables), err, tb.Stack(4)) tsv.qe.queryServiceStats.InternalErrors.Add("Panic", 1) terr = NewTabletError(ErrFail, vtrpcpb.ErrorCode_UNKNOWN_ERROR, "%v: uncaught panic for %v", err, querytypes.QueryAsString(sql, bindVariables)) return terr } var myError error if tsv.config.TerseErrors && terr.SQLError != 0 && len(bindVariables) != 0 { myError = &TabletError{ ErrorType: terr.ErrorType, SQLError: terr.SQLError, ErrorCode: terr.ErrorCode, Message: fmt.Sprintf("(errno %d) during query: %s", terr.SQLError, sql), } } else { myError = terr } terr.RecordStats(tsv.qe.queryServiceStats) logMethod := log.Warningf // Suppress or demote some errors in logs switch terr.ErrorType { case ErrRetry, ErrTxPoolFull: return myError case ErrFatal: logMethod = log.Errorf } // We want to suppress/demote some MySQL error codes (regardless of the ErrorType) switch terr.SQLError { case mysql.ErrDupEntry: return myError case mysql.ErrLockWaitTimeout, mysql.ErrLockDeadlock, mysql.ErrDataTooLong, mysql.ErrDataOutOfRange, mysql.ErrBadNullError: logMethod = log.Infof case 0: if strings.Contains(terr.Error(), "Row count exceeded") { logMethod = log.Infof } } logMethod("%v: %v", terr, querytypes.QueryAsString(sql, bindVariables)) return myError }
func createSplitParams( sql string, bindVariables map[string]interface{}, splitColumns []sqlparser.ColIdent, splitCount int64, numRowsPerQueryPart int64, schema map[string]*schema.Table, ) (*splitquery.SplitParams, error) { switch { case numRowsPerQueryPart != 0 && splitCount == 0: splitParams, err := splitquery.NewSplitParamsGivenNumRowsPerQueryPart( sql, bindVariables, splitColumns, numRowsPerQueryPart, schema) return splitParams, splitQueryToTabletError(err) case numRowsPerQueryPart == 0 && splitCount != 0: splitParams, err := splitquery.NewSplitParamsGivenSplitCount( sql, bindVariables, splitColumns, splitCount, schema) return splitParams, splitQueryToTabletError(err) default: panic(fmt.Sprintf("Exactly one of {numRowsPerQueryPart, splitCount} must be"+ " non zero. This should have already been caught by 'validateSplitQueryParameters' and "+ " returned as an error. Got: numRowsPerQueryPart=%v, splitCount=%v. SQL: %v", numRowsPerQueryPart, splitCount, querytypes.QueryAsString(sql, bindVariables))) } }
// validateSplitQueryParameters perform some validations on the SplitQuery parameters // returns an error that can be returned to the user if a validation fails. func validateSplitQueryParameters( sql string, bindVariables map[string]interface{}, splitColumns []string, splitCount int64, numRowsPerQueryPart int64, algorithm querypb.SplitQueryRequest_Algorithm, sessionID int64) error { if numRowsPerQueryPart < 0 { return NewTabletError( ErrFail, vtrpcpb.ErrorCode_BAD_INPUT, "splitQuery: numRowsPerQueryPart must be non-negative. Got: %v. SQL: %v", numRowsPerQueryPart, querytypes.QueryAsString(sql, bindVariables)) } if splitCount < 0 { return NewTabletError( ErrFail, vtrpcpb.ErrorCode_BAD_INPUT, "splitQuery: splitCount must be non-negative. Got: %v. SQL: %v", splitCount, querytypes.QueryAsString(sql, bindVariables)) } if (splitCount == 0 && numRowsPerQueryPart == 0) || (splitCount != 0 && numRowsPerQueryPart != 0) { return NewTabletError( ErrFail, vtrpcpb.ErrorCode_BAD_INPUT, "splitQuery: exactly one of {numRowsPerQueryPart, splitCount} must be"+ " non zero. Got: numRowsPerQueryPart=%v, splitCount=%v. SQL: %v", splitCount, querytypes.QueryAsString(sql, bindVariables)) } if algorithm != querypb.SplitQueryRequest_EQUAL_SPLITS && algorithm != querypb.SplitQueryRequest_FULL_SCAN { return NewTabletError( ErrFail, vtrpcpb.ErrorCode_BAD_INPUT, "splitquery: unsupported algorithm: %v. SQL: %v", algorithm, querytypes.QueryAsString(sql, bindVariables)) } return nil }
// SplitQueryV2 is part of the queryservice.QueryService interface // TODO(erez): Rename to SplitQuery after migration to SplitQuery V2 is done. func (f *FakeQueryService) SplitQueryV2( ctx context.Context, target *querypb.Target, sql string, bindVariables map[string]interface{}, splitColumns []string, splitCount int64, numRowsPerQueryPart int64, algorithm querypb.SplitQueryRequest_Algorithm, ) ([]querytypes.QuerySplit, error) { if f.HasError { return nil, f.TabletError } if f.Panics { panic(fmt.Errorf("test-triggered panic")) } f.checkTargetCallerID(ctx, "SplitQueryV2", target) if !reflect.DeepEqual(querytypes.BoundQuery{ Sql: sql, BindVariables: bindVariables, }, SplitQueryV2BoundQuery) { f.t.Errorf("invalid SplitQuery.SplitQueryRequest.Query: got %v expected %v", querytypes.QueryAsString(sql, bindVariables), SplitQueryV2BoundQuery) } if !reflect.DeepEqual(splitColumns, SplitQueryV2SplitColumns) { f.t.Errorf("invalid SplitQuery.SplitColumn: got %v expected %v", splitColumns, SplitQueryV2SplitColumns) } if !reflect.DeepEqual(splitCount, SplitQueryV2SplitCount) { f.t.Errorf("invalid SplitQuery.SplitCount: got %v expected %v", splitCount, SplitQueryV2SplitCount) } if !reflect.DeepEqual(numRowsPerQueryPart, SplitQueryV2NumRowsPerQueryPart) { f.t.Errorf("invalid SplitQuery.numRowsPerQueryPart: got %v expected %v", numRowsPerQueryPart, SplitQueryV2NumRowsPerQueryPart) } if algorithm != SplitQueryV2Algorithm { f.t.Errorf("invalid SplitQuery.algorithm: got %v expected %v", algorithm, SplitQueryV2Algorithm) } return SplitQueryQueryV2SplitList, nil }
// SplitQuery is part of the queryservice.QueryService interface func (f *FakeQueryService) SplitQuery(ctx context.Context, target *querypb.Target, sql string, bindVariables map[string]interface{}, splitColumn string, splitCount int64) ([]querytypes.QuerySplit, error) { if f.HasError { return nil, f.TabletError } if f.Panics { panic(fmt.Errorf("test-triggered panic")) } f.checkTargetCallerID(ctx, "SplitQuery", target) if !reflect.DeepEqual(querytypes.BoundQuery{ Sql: sql, BindVariables: bindVariables, }, SplitQueryBoundQuery) { f.t.Errorf("invalid SplitQuery.SplitQueryRequest.Query: got %v expected %v", querytypes.QueryAsString(sql, bindVariables), SplitQueryBoundQuery) } if splitColumn != SplitQuerySplitColumn { f.t.Errorf("invalid SplitQuery.SplitColumn: got %v expected %v", splitColumn, SplitQuerySplitColumn) } if splitCount != SplitQuerySplitCount { f.t.Errorf("invalid SplitQuery.SplitCount: got %v expected %v", splitCount, SplitQuerySplitCount) } return SplitQueryQuerySplitList, nil }
// SplitQuery splits a query + bind variables into smaller queries that return a // subset of rows from the original query. // TODO(erez): Remove this method and rename SplitQueryV2 to SplitQuery once we migrate to // SplitQuery V2. func (tsv *TabletServer) SplitQuery(ctx context.Context, target *querypb.Target, sql string, bindVariables map[string]interface{}, splitColumn string, splitCount int64) (splits []querytypes.QuerySplit, err error) { logStats := newLogStats("SplitQuery", ctx) logStats.OriginalSQL = sql logStats.BindVariables = bindVariables defer handleError(&err, logStats, tsv.qe.queryServiceStats) if err = tsv.startRequest(target, false, false); err != nil { return nil, err } ctx, cancel := withTimeout(ctx, tsv.QueryTimeout.Get()) defer func() { cancel() tsv.endRequest(false) }() splitter := NewQuerySplitter(sql, bindVariables, splitColumn, splitCount, tsv.qe.schemaInfo) err = splitter.validateQuery() if err != nil { return nil, NewTabletError(vtrpcpb.ErrorCode_BAD_INPUT, "splitQuery: query validation error: %s, request: %v", err, querytypes.QueryAsString(sql, bindVariables)) } defer func(start time.Time) { addUserTableQueryStats(tsv.qe.queryServiceStats, ctx, splitter.tableName, "SplitQuery", int64(time.Now().Sub(start))) }(time.Now()) qre := &QueryExecutor{ ctx: ctx, logStats: logStats, qe: tsv.qe, } columnType, err := getColumnType(qre, splitter.splitColumn, splitter.tableName) if err != nil { return nil, err } var pkMinMax *sqltypes.Result if sqltypes.IsIntegral(columnType) { pkMinMax, err = getColumnMinMax(qre, splitter.splitColumn, splitter.tableName) if err != nil { return nil, err } } splits, err = splitter.split(columnType, pkMinMax) if err != nil { return nil, NewTabletError(vtrpcpb.ErrorCode_BAD_INPUT, "splitQuery: query split error: %s, request: %v", err, querytypes.QueryAsString(sql, bindVariables)) } return splits, nil }
// SplitQueryV2 splits a query + bind variables into smaller queries that return a // subset of rows from the original query. This is the new version that supports multiple // split columns and multiple split algortihms. // See the documentation of SplitQueryRequest in proto/vtgate.proto for more details. func (tsv *TabletServer) SplitQueryV2( ctx context.Context, target *querypb.Target, sql string, bindVariables map[string]interface{}, splitColumns []string, splitCount int64, numRowsPerQueryPart int64, algorithm querypb.SplitQueryRequest_Algorithm, sessionID int64) (splits []querytypes.QuerySplit, err error) { if err := validateSplitQueryParameters( sql, bindVariables, splitColumns, splitCount, numRowsPerQueryPart, algorithm, sessionID); err != nil { return nil, err } // TODO(erez): ASSERT/Check that we are a rdonly tablet. logStats := newLogStats("SplitQuery", ctx) logStats.OriginalSQL = sql logStats.BindVariables = bindVariables defer handleError(&err, logStats, tsv.qe.queryServiceStats) if err = tsv.startRequest(target, sessionID, false, false); err != nil { return nil, err } ctx, cancel := withTimeout(ctx, tsv.QueryTimeout.Get()) defer func() { cancel() tsv.endRequest(false) }() schema := getSchemaForSplitQuery(tsv.qe.schemaInfo) var splitParams *splitquery.SplitParams switch { case numRowsPerQueryPart != 0 && splitCount == 0: splitParams, err = splitquery.NewSplitParamsGivenNumRowsPerQueryPart( sql, bindVariables, splitColumns, numRowsPerQueryPart, schema) case numRowsPerQueryPart == 0 && splitCount != 0: splitParams, err = splitquery.NewSplitParamsGivenSplitCount( sql, bindVariables, splitColumns, splitCount, schema) default: panic(fmt.Sprintf("Exactly one of {numRowsPerQueryPart, splitCount} must be"+ " non zero. This should have already been caught by 'validateSplitQueryParameters' and "+ " returned as an error. Got: numRowsPerQueryPart=%v, splitCount=%v. SQL: %v", numRowsPerQueryPart, splitCount, querytypes.QueryAsString(sql, bindVariables))) } // TODO(erez): Make the splitquery package return tabletserver errors. if err != nil { return nil, err } defer func(start time.Time) { addUserTableQueryStats( tsv.qe.queryServiceStats, ctx, splitParams.GetSplitTableName(), "SplitQuery", int64(time.Now().Sub(start))) }(time.Now()) sqlExecuter := &splitQuerySQLExecuter{ queryExecutor: &QueryExecutor{ ctx: ctx, logStats: logStats, qe: tsv.qe, }, } algorithmObject, err := createSplitQueryAlgorithmObject(algorithm, splitParams, sqlExecuter) if err != nil { return nil, err } // TODO(erez): Make the splitquery package use Vitess error codes. return splitquery.NewSplitter(splitParams, algorithmObject).Split() }