예제 #1
0
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
}
예제 #2
0
// 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
}
예제 #3
0
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
}
예제 #4
0
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)))
	}
}
예제 #5
0
// 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
}
예제 #6
0
// 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
}
예제 #7
0
// 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
}
예제 #8
0
// 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
}
예제 #9
0
// 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()
}