Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

planner/preprocessor: disallow into-outfile clause in some place #21956

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions errno/errcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,7 @@ const (
ErrJSONValueOutOfRangeForFuncIndex = 3904
ErrFunctionalIndexDataIsTooLong = 3907
ErrFunctionalIndexNotApplicable = 3909
ErrMisplacedIntoOutfile = 3954
// MariaDB errors.
ErrOnlyOneDefaultPartionAllowed = 4030
ErrWrongPartitionTypeExpectedSystemTime = 4113
Expand Down
1 change: 1 addition & 0 deletions errno/errname.go
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,7 @@ var MySQLErrName = map[uint16]*mysql.ErrMessage{
ErrJSONValueOutOfRangeForFuncIndex: mysql.Message("Out of range JSON value for CAST for expression index '%s'", nil),
ErrFunctionalIndexDataIsTooLong: mysql.Message("Data too long for expression index '%s'", nil),
ErrFunctionalIndexNotApplicable: mysql.Message("Cannot use expression index '%s' due to type or collation conversion", nil),
ErrMisplacedIntoOutfile: mysql.Message("Misplaced INTO clause, INTO is not allowed inside subqueries, and must be placed at end of UNION clauses", nil),
ErrUnsupportedConstraintCheck: mysql.Message("%s is not supported", nil),
// MariaDB errors.
ErrOnlyOneDefaultPartionAllowed: mysql.Message("Only one DEFAULT partition allowed", nil),
Expand Down
5 changes: 5 additions & 0 deletions errors.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1066,6 +1066,11 @@ error = '''
Variable '%s' cannot be set using SET_VAR hint.
'''

["planner:3954"]
error = '''
Misplaced INTO clause, INTO is not allowed inside subqueries, and must be placed at end of UNION clauses
'''

["planner:8108"]
error = '''
Unsupported type %T
Expand Down
5 changes: 3 additions & 2 deletions planner/core/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ var (
ErrNotHintUpdatable = dbterror.ClassOptimizer.NewStd(mysql.ErrNotHintUpdatable)
ErrWarnConflictingHint = dbterror.ClassOptimizer.NewStd(mysql.ErrWarnConflictingHint)
// Since we cannot know if user logged in with a password, use message of ErrAccessDeniedNoPassword instead
ErrAccessDenied = dbterror.ClassOptimizer.NewStdErr(mysql.ErrAccessDenied, mysql.MySQLErrName[mysql.ErrAccessDeniedNoPassword])
ErrBadNull = dbterror.ClassOptimizer.NewStd(mysql.ErrBadNull)
ErrAccessDenied = dbterror.ClassOptimizer.NewStdErr(mysql.ErrAccessDenied, mysql.MySQLErrName[mysql.ErrAccessDeniedNoPassword])
ErrBadNull = dbterror.ClassOptimizer.NewStd(mysql.ErrBadNull)
ErrMisplacedIntoOutfile = dbterror.ClassOptimizer.NewStd(mysql.ErrMisplacedIntoOutfile)
)
55 changes: 55 additions & 0 deletions planner/core/preprocess.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ const (
// inSequenceFunction is set when visiting a sequence function.
// This flag indicates the tableName in these function should be checked as sequence object.
inSequenceFunction
// disallowSelectIntoOption is set when the select statement is not allow to have "INTO OUTFILE" clause.
disallowSelectIntoOption
)

// preprocessor is an ast.Visitor that preprocess
Expand All @@ -127,18 +129,22 @@ func (p *preprocessor) Enter(in ast.Node) (out ast.Node, skipChildren bool) {
p.stmtTp = TypeDelete
case *ast.SelectStmt:
p.stmtTp = TypeSelect
p.checkIntoOutfileOptionForSelect(node)
case *ast.UpdateStmt:
p.stmtTp = TypeUpdate
case *ast.InsertStmt:
p.stmtTp = TypeInsert
p.flag |= disallowSelectIntoOption
case *ast.CreateTableStmt:
p.stmtTp = TypeCreate
p.flag |= inCreateOrDropTable
p.flag |= disallowSelectIntoOption
p.resolveCreateTableStmt(node)
p.checkCreateTableGrammar(node)
case *ast.CreateViewStmt:
p.stmtTp = TypeCreate
p.flag |= inCreateOrDropTable
p.flag |= disallowSelectIntoOption
p.checkCreateViewGrammar(node)
p.checkCreateViewWithSelectGrammar(node)
case *ast.DropTableStmt:
Expand Down Expand Up @@ -218,6 +224,7 @@ func (p *preprocessor) Enter(in ast.Node) (out ast.Node, skipChildren bool) {
p.flag |= inCreateOrDropTable
}
case *ast.TableSource:
p.flag |= disallowSelectIntoOption
isModeOracle := p.ctx.GetSessionVars().SQLMode&mysql.ModeOracle != 0
if _, ok := node.Source.(*ast.SelectStmt); ok && !isModeOracle && len(node.AsName.L) == 0 {
p.err = ddl.ErrDerivedMustHaveAlias.GenWithStackByArgs()
Expand All @@ -234,6 +241,10 @@ func (p *preprocessor) Enter(in ast.Node) (out ast.Node, skipChildren bool) {
p.checkStatisticsOpGrammar(in)
case *ast.GroupByClause:
p.checkGroupBy(node)
case *ast.SubqueryExpr:
p.flag |= disallowSelectIntoOption
case *ast.SetOprStmt:
p.checkIntoOutfileOptionForSetOpr(node)
default:
p.flag &= ^parentIsJoin
}
Expand Down Expand Up @@ -320,10 +331,14 @@ func (p *preprocessor) Leave(in ast.Node) (out ast.Node, ok bool) {
switch x := in.(type) {
case *ast.CreateTableStmt:
p.flag &= ^inCreateOrDropTable
p.flag &= ^disallowSelectIntoOption
p.checkAutoIncrement(x)
p.checkContainDotColumn(x)
case *ast.CreateViewStmt:
p.flag &= ^inCreateOrDropTable
p.flag &= ^disallowSelectIntoOption
case *ast.InsertStmt:
p.flag &= ^disallowSelectIntoOption
case *ast.DropTableStmt, *ast.AlterTableStmt, *ast.RenameTableStmt:
p.flag &= ^inCreateOrDropTable
case *driver.ParamMarkerExpr:
Expand Down Expand Up @@ -390,6 +405,8 @@ func (p *preprocessor) Leave(in ast.Node) (out ast.Node, ok bool) {
if x.Kind == ast.BRIEKindRestore {
p.flag &= ^inCreateOrDropTable
}
case *ast.TableSource, *ast.SubqueryExpr:
p.flag &= ^disallowSelectIntoOption
}

return in, p.err == nil
Expand Down Expand Up @@ -531,6 +548,44 @@ func (p *preprocessor) checkSetOprSelectList(stmt *ast.SetOprSelectList) {
}
}

func (p *preprocessor) checkIntoOutfileOptionForSelect(n *ast.SelectStmt) {
disallowIntoOpt := p.flag&disallowSelectIntoOption != 0
if disallowIntoOpt && n.SelectIntoOpt != nil {
p.err = ErrMisplacedIntoOutfile
}
}

func (p *preprocessor) checkIntoOutfileOptionForSetOpr(n *ast.SetOprStmt) {
disallowIntoOpt := p.flag&disallowSelectIntoOption != 0
hasIntoOpt, err := checkSetOprStmtIntoOption(n.SelectList)
if err != nil {
p.err = err
return
}
if hasIntoOpt && disallowIntoOpt {
p.err = ErrMisplacedIntoOutfile
}
}

func checkSetOprStmtIntoOption(n ast.Node) (hasIntoOpt bool, err error) {
switch v := n.(type) {
case *ast.SetOprSelectList:
for i, s := range v.Selects {
isLastOne := i+1 == len(v.Selects)
hasIntoOpt, err = checkSetOprStmtIntoOption(s)
if err != nil {
return true, err
}
if hasIntoOpt && !isLastOne {
return true, ErrMisplacedIntoOutfile
}
}
case *ast.SelectStmt:
return v.SelectIntoOpt != nil, nil
}
return false, nil
}

func (p *preprocessor) checkCreateDatabaseGrammar(stmt *ast.CreateDatabaseStmt) {
if isIncorrectName(stmt.Name) {
p.err = ddl.ErrWrongDBName.GenWithStackByArgs(stmt.Name)
Expand Down
21 changes: 21 additions & 0 deletions planner/core/preprocess_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,27 @@ func (s *testValidatorSuite) TestValidator(c *C) {
{"select * from t tablesample bernoulli(10 rows);", false, expression.ErrInvalidTableSample},
{"select * from t tablesample bernoulli(23 percent) repeatable (23);", false, expression.ErrInvalidTableSample},
{"select * from t tablesample system() repeatable (10);", false, expression.ErrInvalidTableSample},

// For https://github.com/pingcap/tidb/issues/19097
{"select * from t union select * from t into outfile 'x' union select * from t", false, core.ErrMisplacedIntoOutfile},
{"select * from t union (select * from t into outfile 'x') union select * from t", false, core.ErrMisplacedIntoOutfile},
{"select * from t union ((select * from t into outfile 'x') union select * from t)", false, core.ErrMisplacedIntoOutfile},
{"trace select * from t union ((select * from t into outfile 'x') union select * from t)", false, core.ErrMisplacedIntoOutfile},
{"explain select * from t union ((select * from t into outfile 'x') union select * from t)", false, core.ErrMisplacedIntoOutfile},
{"create view a as select * from t1 into outfile 'x';", false, ddl.ErrViewSelectClause},
{"create view a as (select * from t1 into outfile 'x');", false, ddl.ErrViewSelectClause},
{"create table a as select * from t1 into outfile 'x';", false, errors.New("'CREATE TABLE ... SELECT' is not implemented yet")},
{"insert into t select * from t into outfile 'x';", false, core.ErrMisplacedIntoOutfile},
{"insert into t (a) select a from t into outfile 'x';", false, core.ErrMisplacedIntoOutfile},
{"select * from (select * from t into outfile 'x') as t;", false, core.ErrMisplacedIntoOutfile},
{"select * from t where (select a from t into outfile 'x') > 1;", false, core.ErrMisplacedIntoOutfile},
{"select * from t union (select * from t into outfile '/tmp/result.txt');", false, nil},
{"select * from t union select * from t into outfile '/tmp/result.txt';", false, nil},
{"create binding for select 1 into outfile '1.txt' using select 1 into outfile '1.txt'", false, nil},
{"(((((select 1 into outfile '1.txt')))))", false, nil},
// TODO(tangenta): the following statement should cause an error.
{"create binding for (select * from t union ((select * from t into outfile 'x') union " +
"select * from t)) using (select * from t union ((select * from t into outfile 'x') union select * from t));", false, nil},
}

_, err := s.se.Execute(context.Background(), "use test")
Expand Down