diff --git a/enginetest/queries/procedure_queries.go b/enginetest/queries/procedure_queries.go index 1e41686fe1..8db4281534 100644 --- a/enginetest/queries/procedure_queries.go +++ b/enginetest/queries/procedure_queries.go @@ -2441,6 +2441,27 @@ var ProcedureCallTests = []ScriptTest{ }, }, }, + { + Name: "creating invalid procedure doesn't error until it is called", + Assertions: []ScriptTestAssertion{ + { + Query: `CREATE PROCEDURE proc1 (OUT out_count INT) READS SQL DATA SELECT COUNT(*) FROM mytable WHERE i = 1 AND s = 'first row' AND func1(i);`, + Expected: []sql.Row{{types.NewOkResult(0)}}, + }, + { + Query: "CALL proc1(@out_count);", + ExpectedErr: sql.ErrTableNotFound, + }, + { + Query: "CREATE TABLE mytable (i int, s varchar(128));", + Expected: []sql.Row{{types.NewOkResult(0)}}, + }, + { + Query: "CALL proc1(@out_count);", + ExpectedErr: sql.ErrFunctionNotFound, + }, + }, + }, } var ProcedureDropTests = []ScriptTest{ @@ -2787,25 +2808,12 @@ var ProcedureShowCreate = []ScriptTest{ } var ProcedureCreateInSubroutineTests = []ScriptTest{ - //TODO: Match MySQL behavior (https://github.com/dolthub/dolt/issues/8053) - { - Name: "procedure must not contain CREATE PROCEDURE", - Assertions: []ScriptTestAssertion{ - { - Query: "CREATE PROCEDURE foo() CREATE PROCEDURE bar() SELECT 0;", - // MySQL output: "Can't create a PROCEDURE from within another stored routine", - ExpectedErrStr: "creating procedures in stored procedures is currently unsupported and will be added in a future release", - }, - }, - }, { Name: "event must not contain CREATE PROCEDURE", Assertions: []ScriptTestAssertion{ { - // Skipped because MySQL errors here but we don't. Query: "CREATE EVENT foo ON SCHEDULE EVERY 1 YEAR DO CREATE PROCEDURE bar() SELECT 1;", - ExpectedErrStr: "Can't create a PROCEDURE from within another stored routine", - Skip: true, + ExpectedErrStr: "can't create a PROCEDURE from within another stored routine", }, }, }, @@ -2828,11 +2836,11 @@ var ProcedureCreateInSubroutineTests = []ScriptTest{ Assertions: []ScriptTestAssertion{ { Query: "create procedure p() create table t (pk int);", - ExpectedErrStr: "creating tables in stored procedures is currently unsupported and will be added in a future release", + ExpectedErrStr: "CREATE statements in CREATE PROCEDURE not yet supported", }, { Query: "create procedure p() begin create table t (pk int); end;", - ExpectedErrStr: "creating tables in stored procedures is currently unsupported and will be added in a future release", + ExpectedErrStr: "CREATE statements in CREATE PROCEDURE not yet supported", }, }, }, @@ -2844,11 +2852,11 @@ var ProcedureCreateInSubroutineTests = []ScriptTest{ Assertions: []ScriptTestAssertion{ { Query: "create procedure p() create trigger trig before insert on t for each row begin select 1; end;", - ExpectedErrStr: "creating triggers in stored procedures is currently unsupported and will be added in a future release", + ExpectedErrStr: "can't create a TRIGGER from within another stored routine", }, { Query: "create procedure p() begin create trigger trig before insert on t for each row begin select 1; end; end;", - ExpectedErrStr: "creating triggers in stored procedures is currently unsupported and will be added in a future release", + ExpectedErrStr: "can't create a TRIGGER from within another stored routine", }, }, }, @@ -2858,11 +2866,11 @@ var ProcedureCreateInSubroutineTests = []ScriptTest{ Assertions: []ScriptTestAssertion{ { Query: "create procedure p() create database procdb;", - ExpectedErrStr: "creating databases in stored procedures is currently unsupported and will be added in a future release", + ExpectedErrStr: "DBDDL in CREATE PROCEDURE not yet supported", }, { Query: "create procedure p() begin create database procdb; end;", - ExpectedErrStr: "creating databases in stored procedures is currently unsupported and will be added in a future release", + ExpectedErrStr: "DBDDL in CREATE PROCEDURE not yet supported", }, }, }, @@ -2872,11 +2880,11 @@ var ProcedureCreateInSubroutineTests = []ScriptTest{ Assertions: []ScriptTestAssertion{ { Query: "create procedure p() create view v as select 1;", - ExpectedErrStr: "creating views in stored procedures is currently unsupported and will be added in a future release", + ExpectedErrStr: "CREATE statements in CREATE PROCEDURE not yet supported", }, { Query: "create procedure p() begin create view v as select 1; end;", - ExpectedErrStr: "creating views in stored procedures is currently unsupported and will be added in a future release", + ExpectedErrStr: "CREATE statements in CREATE PROCEDURE not yet supported", }, }, }, diff --git a/enginetest/queries/queries.go b/enginetest/queries/queries.go index 91c937f401..eab345375e 100644 --- a/enginetest/queries/queries.go +++ b/enginetest/queries/queries.go @@ -10971,11 +10971,6 @@ var ErrorQueries = []QueryErrorTest{ Query: `SELECT * FROM datetime_table where datetime_col >= 'not a valid datetime'`, ExpectedErr: types.ErrConvertingToTime, }, - // this query was panicing, but should be allowed and should return error when this query is called - { - Query: `CREATE PROCEDURE proc1 (OUT out_count INT) READS SQL DATA SELECT COUNT(*) FROM mytable WHERE i = 1 AND s = 'first row' AND func1(i);`, - ExpectedErr: sql.ErrFunctionNotFound, - }, { Query: "CREATE TABLE table_test (id int PRIMARY KEY, c float DEFAULT rand())", ExpectedErr: sql.ErrSyntaxError, diff --git a/enginetest/queries/trigger_queries.go b/enginetest/queries/trigger_queries.go index bc13cb6991..677e16a138 100644 --- a/enginetest/queries/trigger_queries.go +++ b/enginetest/queries/trigger_queries.go @@ -3230,39 +3230,23 @@ for each row } var TriggerCreateInSubroutineTests = []ScriptTest{ - //TODO: Match MySQL behavior (https://github.com/dolthub/dolt/issues/8053) { - Name: "procedure must not contain CREATE TRIGGER", - Assertions: []ScriptTestAssertion{ - { - Query: "CREATE PROCEDURE foo() CREATE PROCEDURE bar() SELECT 0;", - // MySQL's error message: "Can't create a PROCEDURE from within another stored routine", - ExpectedErrStr: "creating procedures in stored procedures is currently unsupported and will be added in a future release", - }, - }, - }, - { - Name: "event must not contain CREATE TRIGGER", - Assertions: []ScriptTestAssertion{ - { - // Skipped because MySQL errors here but we don't. - Query: "CREATE EVENT foo ON SCHEDULE EVERY 1 YEAR DO CREATE PROCEDURE bar() SELECT 1;", - ExpectedErrStr: "Can't create a PROCEDURE from within another stored routine", - Skip: true, - }, - }, - }, - { - Name: "trigger must not contain CREATE TRIGGER", + Name: "triggers cannot contain or be contained in other stored routines", SetUpScript: []string{ "CREATE TABLE t (pk INT PRIMARY KEY);", }, Assertions: []ScriptTestAssertion{ { - // Skipped because MySQL errors here but we don't. + Query: "CREATE PROCEDURE bar() CREATE TRIGGER foo AFTER UPDATE ON t FOR EACH ROW BEGIN SELECT 1; END;", + ExpectedErrStr: "can't create a TRIGGER from within another stored routine", + }, + { Query: "CREATE TRIGGER foo AFTER UPDATE ON t FOR EACH ROW BEGIN CREATE PROCEDURE bar() SELECT 1; END", - ExpectedErrStr: "Can't create a PROCEDURE from within another stored routine", - Skip: true, + ExpectedErrStr: "can't create a PROCEDURE from within another stored routine", + }, + { + Query: "CREATE EVENT foo ON SCHEDULE EVERY 1 YEAR DO CREATE TRIGGER foo AFTER UPDATE ON t FOR EACH ROW BEGIN SELECT 1; END;", + ExpectedErrStr: "can't create a TRIGGER from within another stored routine", }, }, }, diff --git a/sql/analyzer/replace_count_star.go b/sql/analyzer/replace_count_star.go index d8fe52c720..cccedb9f01 100644 --- a/sql/analyzer/replace_count_star.go +++ b/sql/analyzer/replace_count_star.go @@ -35,8 +35,7 @@ func replaceCountStar(ctx *sql.Context, a *Analyzer, n sql.Node, _ *plan.Scope, return transform.Node(n, func(n sql.Node) (sql.Node, transform.TreeIdentity, error) { if agg, ok := n.(*plan.GroupBy); ok { - - if len(agg.GroupByExprs) == 0 && !qFlags.JoinIsSet() && !qFlags.SubqueryIsSet() && !qFlags.IsSet(sql.QFlagAnyAgg) { + if len(agg.GroupByExprs) == 0 && !qFlags.JoinIsSet() && !qFlags.SubqueryIsSet() && !qFlags.IsSet(sql.QFlagAnyAgg) && !qFlags.IsSet(sql.QFlagAnalyzeProcedure) { // top-level aggregation with a single group and no "any_value" functions can only return one row qFlags.Set(sql.QFlagMax1Row) } diff --git a/sql/analyzer/stored_procedures.go b/sql/analyzer/stored_procedures.go index c78d9759e1..f95411e8f0 100644 --- a/sql/analyzer/stored_procedures.go +++ b/sql/analyzer/stored_procedures.go @@ -42,58 +42,37 @@ func loadStoredProcedures(ctx *sql.Context, a *Analyzer, n sql.Node, scope *plan allDatabases := a.Catalog.AllDatabases(ctx) for _, database := range allDatabases { - if pdb, ok := database.(sql.StoredProcedureDatabase); ok { - procedures, err := pdb.GetStoredProcedures(ctx) - if err != nil { - return nil, err + pdb, ok := database.(sql.StoredProcedureDatabase) + if !ok { + continue + } + procedures, err := pdb.GetStoredProcedures(ctx) + if err != nil { + return nil, err + } + for _, procedure := range procedures { + if procedure.Name != "" { } - - for _, procedure := range procedures { - var procToRegister *plan.Procedure - var parsedProcedure sql.Node - b := planbuilder.New(ctx, a.Catalog, nil, nil) - b.DisableAuth() - b.SetParserOptions(sql.NewSqlModeFromString(procedure.SqlMode).ParserOptions()) - parsedProcedure, _, _, _, err = b.Parse(procedure.CreateStatement, nil, false) - if err != nil { - procToRegister = &plan.Procedure{ - CreateProcedureString: procedure.CreateStatement, - } - procToRegister.ValidationError = err - } else if cp, ok := parsedProcedure.(*plan.CreateProcedure); !ok { - return nil, sql.ErrProcedureCreateStatementInvalid.New(procedure.CreateStatement) - } else { - procToRegister = cp.Procedure - } - - procToRegister.CreatedAt = procedure.CreatedAt - procToRegister.ModifiedAt = procedure.ModifiedAt - - err = scope.Procedures.Register(database.Name(), procToRegister) - if err != nil { - return nil, err + proc, _, err := planbuilder.BuildProcedureHelper(ctx, a.Catalog, false, nil, database, nil, procedure) + if err != nil { + // TODO: alternatively just have BuildProcedureHelper always return a procedure with validation error + proc = &plan.Procedure{ + Name: procedure.Name, + CreateProcedureString: procedure.CreateStatement, + CreatedAt: procedure.CreatedAt, + ModifiedAt: procedure.ModifiedAt, + ValidationError: err, } } + err = scope.Procedures.Register(database.Name(), proc) + if err != nil { + return nil, err + } } } return scope, nil } -// analyzeCreateProcedure checks the plan.CreateProcedure and returns a valid plan.Procedure or an error -func analyzeCreateProcedure(ctx *sql.Context, a *Analyzer, cp *plan.CreateProcedure, scope *plan.Scope, sel RuleSelector, qFlags *sql.QueryFlags) (*plan.Procedure, error) { - var analyzedNode sql.Node - var err error - analyzedNode, _, err = analyzeProcedureBodies(ctx, a, cp.Procedure, false, scope, sel, qFlags) - if err != nil { - return nil, err - } - analyzedProc, ok := analyzedNode.(*plan.Procedure) - if !ok { - return nil, fmt.Errorf("analyzed node %T and expected *plan.Procedure", analyzedNode) - } - return analyzedProc, nil -} - func hasProcedureCall(n sql.Node) bool { referencesProcedures := false transform.Inspect(n, func(n sql.Node) bool { @@ -143,6 +122,18 @@ func analyzeProcedureBodies(ctx *sql.Context, a *Analyzer, node sql.Node, skipCa } else { newChild, _, err = a.analyzeWithSelector(ctx, child, scope, SelectAllBatches, procSel, qFlags) } + case *plan.InsertInto: + qFlags.Set(sql.QFlagInsert) + newChild, _, err = a.analyzeWithSelector(ctx, child, scope, SelectAllBatches, procSel, qFlags) + qFlags.Unset(sql.QFlagInsert) + case *plan.Update: + qFlags.Set(sql.QFlagUpdate) + newChild, _, err = a.analyzeWithSelector(ctx, child, scope, SelectAllBatches, procSel, qFlags) + qFlags.Unset(sql.QFlagUpdate) + case *plan.DeleteFrom: + qFlags.Set(sql.QFlagDelete) + newChild, _, err = a.analyzeWithSelector(ctx, child, scope, SelectAllBatches, procSel, qFlags) + qFlags.Unset(sql.QFlagDelete) default: newChild, _, err = a.analyzeWithSelector(ctx, child, scope, SelectAllBatches, procSel, qFlags) } @@ -164,9 +155,7 @@ func applyProcedures(ctx *sql.Context, a *Analyzer, n sql.Node, scope *plan.Scop return n, transform.SameTree, nil } - hasProcedureCall := hasProcedureCall(n) - _, isShowCreateProcedure := n.(*plan.ShowCreateProcedure) - if !hasProcedureCall && !isShowCreateProcedure { + if _, isShowCreateProcedure := n.(*plan.ShowCreateProcedure); !hasProcedureCall(n) && !isShowCreateProcedure { return n, transform.SameTree, nil } @@ -175,6 +164,9 @@ func applyProcedures(ctx *sql.Context, a *Analyzer, n sql.Node, scope *plan.Scop if !ok { return n, transform.SameTree, nil } + if call.Analyzed { + return n, transform.SameTree, nil + } if scope.IsEmpty() { scope = scope.WithProcedureCache(plan.NewProcedureCache()) } @@ -190,53 +182,27 @@ func applyProcedures(ctx *sql.Context, a *Analyzer, n sql.Node, scope *plan.Scop return nil, transform.SameTree, err } if esp != nil { - externalProcedure, err := resolveExternalStoredProcedure(ctx, *esp) - if err != nil { - return nil, transform.SameTree, err - } - return call.WithProcedure(externalProcedure), transform.NewTree, nil + return call, transform.SameTree, nil } - if spdb, ok := call.Database().(sql.StoredProcedureDatabase); ok { - procedure, ok, err := spdb.GetStoredProcedure(ctx, call.Name) - if err != nil { - return nil, transform.SameTree, err - } - if !ok { - err := sql.ErrStoredProcedureDoesNotExist.New(call.Name) - if call.Database().Name() == "" { - return nil, transform.SameTree, fmt.Errorf("%w; this might be because no database is selected", err) - } - return nil, transform.SameTree, err - } - var parsedProcedure sql.Node - b := planbuilder.New(ctx, a.Catalog, nil, nil) - b.DisableAuth() - b.SetParserOptions(sql.NewSqlModeFromString(procedure.SqlMode).ParserOptions()) - if call.AsOf() != nil { - asOf, err := call.AsOf().Eval(ctx, nil) - if err != nil { - return n, transform.SameTree, err - } - b.ProcCtx().AsOf = asOf - } - b.ProcCtx().DbName = call.Database().Name() - parsedProcedure, _, _, _, err = b.Parse(procedure.CreateStatement, nil, false) - if err != nil { - return nil, transform.SameTree, err - } - cp, ok := parsedProcedure.(*plan.CreateProcedure) - if !ok { - return nil, transform.SameTree, sql.ErrProcedureCreateStatementInvalid.New(procedure.CreateStatement) - } - analyzedProc, err := analyzeCreateProcedure(ctx, a, cp, scope, sel, nil) - if err != nil { - return nil, transform.SameTree, err - } - return call.WithProcedure(analyzedProc), transform.NewTree, nil - } else { + if _, isStoredProcDb := call.Database().(sql.StoredProcedureDatabase); !isStoredProcDb { return nil, transform.SameTree, sql.ErrStoredProceduresNotSupported.New(call.Database().Name()) } + + qFlags.Set(sql.QFlagAnalyzeProcedure) + analyzedNode, _, err := analyzeProcedureBodies(ctx, a, call.Procedure, false, scope, sel, qFlags) + qFlags.Unset(sql.QFlagAnalyzeProcedure) + if err != nil { + return nil, transform.SameTree, err + } + analyzedProc, ok := analyzedNode.(*plan.Procedure) + if !ok { + return nil, transform.SameTree, fmt.Errorf("analyzed node %T and expected *plan.Procedure", analyzedNode) + } + // stored procedures nested within triggers may attempt to analyze this twice, causing problems like double projections + newCall := call.WithProcedure(analyzedProc) + newCall.Analyzed = true + return newCall, transform.NewTree, nil }) if err != nil { return nil, transform.SameTree, err @@ -266,43 +232,7 @@ func applyProcedures(ctx *sql.Context, a *Analyzer, n sql.Node, scope *plan.Scop // applyProceduresCall applies the relevant stored procedure to the given *plan.Call. func applyProceduresCall(ctx *sql.Context, a *Analyzer, call *plan.Call, scope *plan.Scope, sel RuleSelector, qFlags *sql.QueryFlags) (sql.Node, transform.TreeIdentity, error) { - var procedure *plan.Procedure - if call.Procedure == nil { - dbName := ctx.GetCurrentDatabase() - if call.Database() != nil { - dbName = call.Database().Name() - } - - esp, err := a.Catalog.ExternalStoredProcedure(ctx, call.Name, len(call.Params)) - if err != nil { - return nil, transform.SameTree, err - } - - if esp != nil { - externalProcedure, err := resolveExternalStoredProcedure(ctx, *esp) - if err != nil { - return nil, false, err - } - procedure = externalProcedure - } else { - procedure = scope.Procedures.Get(dbName, call.Name, len(call.Params)) - } - - if procedure == nil { - err := sql.ErrStoredProcedureDoesNotExist.New(call.Name) - if dbName == "" { - return nil, transform.SameTree, fmt.Errorf("%w; this might be because no database is selected", err) - } - return nil, transform.SameTree, err - } - - if procedure.ValidationError != nil { - return nil, transform.SameTree, procedure.ValidationError - } - } else { - procedure = call.Procedure - } - + procedure := call.Procedure if procedure.HasVariadicParameter() { procedure = procedure.ExtendVariadic(ctx, len(call.Params)) } diff --git a/sql/analyzer/stored_procedures_test.go b/sql/analyzer/stored_procedures_test.go deleted file mode 100644 index be77afa1b5..0000000000 --- a/sql/analyzer/stored_procedures_test.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2022 Dolthub, Inc. -// -// 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 analyzer - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/dolthub/go-mysql-server/memory" - "github.com/dolthub/go-mysql-server/sql" - "github.com/dolthub/go-mysql-server/sql/plan" - "github.com/dolthub/go-mysql-server/sql/transform" -) - -func TestStoredProcedureNotFoundWithNoDatabaseSelected(t *testing.T) { - db := memory.NewDatabase("mydb") - a := NewBuilder(sql.NewDatabaseProvider(db)).Build() - ctx := sql.NewContext(context.Background(), sql.WithSession(sql.NewBaseSession())) - - call := plan.NewCall(nil, "non_existent_procedure", []sql.Expression{}, nil, nil) - scope, err := loadStoredProcedures(ctx, a, call, newTestScope(call), DefaultRuleSelector) - require.NoError(t, err) - - node, identity, err := applyProceduresCall(ctx, a, call, scope, DefaultRuleSelector, nil) - assert.Nil(t, node) - assert.Equal(t, transform.SameTree, identity) - assert.Contains(t, err.Error(), "stored procedure \"non_existent_procedure\" does not exist") - assert.Contains(t, err.Error(), "this might be because no database is selected") -} diff --git a/sql/analyzer/triggers.go b/sql/analyzer/triggers.go index 52e762adcd..47286e38b1 100644 --- a/sql/analyzer/triggers.go +++ b/sql/analyzer/triggers.go @@ -215,6 +215,23 @@ func applyTriggers(ctx *sql.Context, a *Analyzer, n sql.Node, scope *plan.Scope, if !ok { return nil, transform.SameTree, sql.ErrTriggerCreateStatementInvalid.New(trigger.CreateStatement) } + transform.Inspect(ct.Body, func(n sql.Node) bool { + call, isCall := n.(*plan.Call) + if !isCall { + return true + } + if call.Procedure == nil { + return true + } + if call.Procedure.ValidationError == nil { + return true + } + err = call.Procedure.ValidationError + return false + }) + if err != nil { + return nil, transform.SameTree, err + } var triggerTable string switch t := ct.Table.(type) { diff --git a/sql/core.go b/sql/core.go index e861cbd18c..a42c00fe55 100644 --- a/sql/core.go +++ b/sql/core.go @@ -394,6 +394,9 @@ func DebugString(nodeOrExpression interface{}) string { if s, ok := nodeOrExpression.(fmt.Stringer); ok { return s.String() } + if nodeOrExpression == nil { + return "" + } panic(fmt.Sprintf("Expected sql.DebugString or fmt.Stringer for %T", nodeOrExpression)) } diff --git a/sql/plan/call.go b/sql/plan/call.go index 60a6f83008..450ae38af3 100644 --- a/sql/plan/call.go +++ b/sql/plan/call.go @@ -31,6 +31,7 @@ type Call struct { Procedure *Procedure Pref *expression.ProcedureReference cat sql.Catalog + Analyzed bool } var _ sql.Node = (*Call)(nil) @@ -39,13 +40,14 @@ var _ sql.Expressioner = (*Call)(nil) var _ Versionable = (*Call)(nil) // NewCall returns a *Call node. -func NewCall(db sql.Database, name string, params []sql.Expression, asOf sql.Expression, catalog sql.Catalog) *Call { +func NewCall(db sql.Database, name string, params []sql.Expression, proc *Procedure, asOf sql.Expression, catalog sql.Catalog) *Call { return &Call{ - db: db, - Name: name, - Params: params, - asOf: asOf, - cat: catalog, + db: db, + Name: name, + Params: params, + Procedure: proc, + asOf: asOf, + cat: catalog, } } diff --git a/sql/plan/ddl_procedure.go b/sql/plan/ddl_procedure.go index ff824959d1..464de73e85 100644 --- a/sql/plan/ddl_procedure.go +++ b/sql/plan/ddl_procedure.go @@ -1,4 +1,4 @@ -// Copyright 2021 Dolthub, Inc. +// Copyright 2021-2025 Dolthub, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,18 +15,16 @@ package plan import ( - "fmt" - "time" - "github.com/dolthub/go-mysql-server/sql/types" "github.com/dolthub/go-mysql-server/sql" ) type CreateProcedure struct { - *Procedure - ddlNode - BodyString string + ddlNode ddlNode + + StoredProcDetails sql.StoredProcedureDetails + BodyString string } var _ sql.Node = (*CreateProcedure)(nil) @@ -37,48 +35,31 @@ var _ sql.CollationCoercible = (*CreateProcedure)(nil) // NewCreateProcedure returns a *CreateProcedure node. func NewCreateProcedure( db sql.Database, - name, - definer string, - params []ProcedureParam, - createdAt, modifiedAt time.Time, - securityContext ProcedureSecurityContext, - characteristics []Characteristic, - body sql.Node, - comment, createString, bodyString string, + storedProcDetails sql.StoredProcedureDetails, + bodyString string, ) *CreateProcedure { - procedure := NewProcedure( - name, - definer, - params, - securityContext, - comment, - characteristics, - createString, - body, - createdAt, - modifiedAt) return &CreateProcedure{ - Procedure: procedure, - BodyString: bodyString, - ddlNode: ddlNode{db}, + ddlNode: ddlNode{db}, + StoredProcDetails: storedProcDetails, + BodyString: bodyString, } } // Database implements the sql.Databaser interface. func (c *CreateProcedure) Database() sql.Database { - return c.Db + return c.ddlNode.Db } // WithDatabase implements the sql.Databaser interface. func (c *CreateProcedure) WithDatabase(database sql.Database) (sql.Node, error) { cp := *c - cp.Db = database + cp.ddlNode.Db = database return &cp, nil } // Resolved implements the sql.Node interface. func (c *CreateProcedure) Resolved() bool { - return c.ddlNode.Resolved() && c.Procedure.Resolved() + return c.ddlNode.Resolved() } func (c *CreateProcedure) IsReadOnly() bool { @@ -92,22 +73,15 @@ func (c *CreateProcedure) Schema() sql.Schema { // Children implements the sql.Node interface. func (c *CreateProcedure) Children() []sql.Node { - return []sql.Node{c.Procedure} + return []sql.Node{} } // WithChildren implements the sql.Node interface. func (c *CreateProcedure) WithChildren(children ...sql.Node) (sql.Node, error) { - if len(children) != 1 { - return nil, sql.ErrInvalidChildrenNumber.New(c, len(children), 1) - } - procedure, ok := children[0].(*Procedure) - if !ok { - return nil, fmt.Errorf("expected `*Procedure` but got `%T`", children[0]) + if len(children) != 0 { + return nil, sql.ErrInvalidChildrenNumber.New(c, len(children), 0) } - - nc := *c - nc.Procedure = procedure - return &nc, nil + return c, nil } // CollationCoercibility implements the interface sql.CollationCoercible. @@ -117,50 +91,10 @@ func (*CreateProcedure) CollationCoercibility(ctx *sql.Context) (collation sql.C // String implements the sql.Node interface. func (c *CreateProcedure) String() string { - definer := "" - if c.Definer != "" { - definer = fmt.Sprintf(" DEFINER = %s", c.Definer) - } - params := "" - for i, param := range c.Params { - if i > 0 { - params += ", " - } - params += param.String() - } - comment := "" - if c.Comment != "" { - comment = fmt.Sprintf(" COMMENT '%s'", c.Comment) - } - characteristics := "" - for _, characteristic := range c.Characteristics { - characteristics += fmt.Sprintf(" %s", characteristic.String()) - } - return fmt.Sprintf("CREATE%s PROCEDURE %s (%s) %s%s%s %s", - definer, c.Name, params, c.SecurityContext.String(), comment, characteristics, c.Procedure.String()) + return c.StoredProcDetails.CreateStatement } // DebugString implements the sql.DebugStringer interface. func (c *CreateProcedure) DebugString() string { - definer := "" - if c.Definer != "" { - definer = fmt.Sprintf(" DEFINER = %s", c.Definer) - } - params := "" - for i, param := range c.Params { - if i > 0 { - params += ", " - } - params += param.String() - } - comment := "" - if c.Comment != "" { - comment = fmt.Sprintf(" COMMENT '%s'", c.Comment) - } - characteristics := "" - for _, characteristic := range c.Characteristics { - characteristics += fmt.Sprintf(" %s", characteristic.String()) - } - return fmt.Sprintf("CREATE%s PROCEDURE %s (%s) %s%s%s %s", - definer, c.Name, params, c.SecurityContext.String(), comment, characteristics, sql.DebugString(c.Procedure)) + return c.StoredProcDetails.CreateStatement } diff --git a/sql/planbuilder/create_ddl.go b/sql/planbuilder/create_ddl.go index 47a1f5ae13..93017cfa25 100644 --- a/sql/planbuilder/create_ddl.go +++ b/sql/planbuilder/create_ddl.go @@ -16,6 +16,7 @@ package planbuilder import ( "fmt" + "strconv" "strings" "time" "unicode" @@ -25,11 +26,18 @@ import ( "github.com/dolthub/go-mysql-server/sql" "github.com/dolthub/go-mysql-server/sql/expression" "github.com/dolthub/go-mysql-server/sql/plan" - "github.com/dolthub/go-mysql-server/sql/transform" "github.com/dolthub/go-mysql-server/sql/types" ) func (b *Builder) buildCreateTrigger(inScope *scope, subQuery string, fullQuery string, c *ast.DDL) (outScope *scope) { + b.qFlags.Set(sql.QFlagCreateTrigger) + defer func() { + b.qFlags.Unset(sql.QFlagCreateTrigger) + }() + if b.qFlags.IsSet(sql.QFlagCreateEvent) || b.qFlags.IsSet(sql.QFlagCreateProcedure) { + b.handleErr(fmt.Errorf("can't create a TRIGGER from within another stored routine")) + } + outScope = inScope.push() var triggerOrder *plan.TriggerOrder if c.TriggerSpec.Order != nil { @@ -132,12 +140,9 @@ func getCurrentUserForDefiner(ctx *sql.Context, definer string) string { return definer } -func (b *Builder) buildCreateProcedure(inScope *scope, subQuery string, fullQuery string, c *ast.DDL) (outScope *scope) { - b.qFlags.Set(sql.QFlagCreateProcedure) - defer func() { b.qFlags.Unset(sql.QFlagCreateProcedure) }() - +func (b *Builder) buildProcedureParams(procParams []ast.ProcedureParam) []plan.ProcedureParam { var params []plan.ProcedureParam - for _, param := range c.ProcedureSpec.Params { + for _, param := range procParams { var direction plan.ProcedureParamDirection switch param.Direction { case ast.ProcedureParamDirection_In: @@ -161,11 +166,14 @@ func (b *Builder) buildCreateProcedure(inScope *scope, subQuery string, fullQuer Variadic: false, }) } + return params +} +func (b *Builder) buildProcedureCharacteristics(procCharacteristics []ast.Characteristic) ([]plan.Characteristic, plan.ProcedureSecurityContext, string) { var characteristics []plan.Characteristic securityType := plan.ProcedureSecurityContext_Definer // Default Security Context comment := "" - for _, characteristic := range c.ProcedureSpec.Characteristics { + for _, characteristic := range procCharacteristics { switch characteristic.Type { case ast.CharacteristicValue_Comment: comment = characteristic.Comment @@ -192,60 +200,229 @@ func (b *Builder) buildCreateProcedure(inScope *scope, subQuery string, fullQuer b.handleErr(err) } } + return characteristics, securityType, comment +} - inScope.initProc() - procName := strings.ToLower(c.ProcedureSpec.ProcName.Name.String()) - for _, p := range params { - // populate inScope with the procedure parameters. this will be - // subject maybe a bug where an inner procedure has access to - // outer procedure parameters. - inScope.proc.AddVar(expression.NewProcedureParam(strings.ToLower(p.Name), p.Type)) +func (b *Builder) buildCreateProcedure(inScope *scope, subQuery string, fullQuery string, c *ast.DDL) (outScope *scope) { + b.qFlags.Set(sql.QFlagCreateProcedure) + defer func() { + b.qFlags.Unset(sql.QFlagCreateProcedure) + }() + if b.qFlags.IsSet(sql.QFlagCreateEvent) || b.qFlags.IsSet(sql.QFlagCreateTrigger) { + b.handleErr(fmt.Errorf("can't create a PROCEDURE from within another stored routine")) } - bodyStr := strings.TrimSpace(fullQuery[c.SubStatementPositionStart:c.SubStatementPositionEnd]) - bodyScope := b.buildSubquery(inScope, c.ProcedureSpec.Body, bodyStr, fullQuery) - b.validateStoredProcedure(bodyScope.node) - - // Check for recursive calls to same procedure - transform.Inspect(bodyScope.node, func(node sql.Node) bool { - switch n := node.(type) { - case *plan.Call: - if strings.EqualFold(procName, n.Name) { - b.handleErr(sql.ErrProcedureRecursiveCall.New(procName)) - } - return false - default: - return true - } - }) + b.validateCreateProcedure(inScope, subQuery) var db sql.Database = nil - dbName := c.ProcedureSpec.ProcName.Qualifier.String() - if dbName != "" { + if dbName := c.ProcedureSpec.ProcName.Qualifier.String(); dbName != "" { db = b.resolveDb(dbName) } else { db = b.currentDb() } + now := time.Now() + spd := sql.StoredProcedureDetails{ + Name: strings.ToLower(c.ProcedureSpec.ProcName.Name.String()), + CreateStatement: subQuery, + CreatedAt: now, + ModifiedAt: now, + SqlMode: sql.LoadSqlMode(b.ctx).String(), + } + + bodyStr := strings.TrimSpace(fullQuery[c.SubStatementPositionStart:c.SubStatementPositionEnd]) + outScope = inScope.push() - outScope.node = plan.NewCreateProcedure( - db, - procName, - c.ProcedureSpec.Definer, - params, - time.Now(), - time.Now(), - securityType, - characteristics, - bodyScope.node, - comment, - subQuery, - bodyStr, - ) + outScope.node = plan.NewCreateProcedure(db, spd, bodyStr) return outScope } +func (b *Builder) validateBlock(inScope *scope, stmts ast.Statements) { + for _, s := range stmts { + switch s.(type) { + case *ast.Declare: + default: + if inScope.procActive() { + inScope.proc.NewState(dsBody) + } + } + b.validateStatement(inScope, s) + } +} + +func (b *Builder) validateStatement(inScope *scope, stmt ast.Statement) { + // TODO: a ton of this code is repeated from their build counterparts, consider refactoring into helper methods + switch s := stmt.(type) { + case *ast.DDL: + switch s.Action { + case ast.TruncateStr: + case ast.CreateStr: + if s.ProcedureSpec != nil { + b.handleErr(fmt.Errorf("can't create a PROCEDURE from within another stored routine")) + } + if s.TriggerSpec != nil { + b.handleErr(fmt.Errorf("can't create a TRIGGER from within another stored routine")) + } + b.handleErr(fmt.Errorf("CREATE statements in CREATE PROCEDURE not yet supported")) + default: + b.handleErr(fmt.Errorf("DDL in CREATE PROCEDURE not yet supported")) + } + case *ast.DBDDL: + b.handleErr(fmt.Errorf("DBDDL in CREATE PROCEDURE not yet supported")) + case *ast.Declare: + if s.Condition != nil { + dc := s.Condition + if dc.SqlStateValue != "" { + if len(dc.SqlStateValue) != 5 { + err := fmt.Errorf("SQLSTATE VALUE must be a string with length 5 consisting of only integers") + b.handleErr(err) + } + if dc.SqlStateValue[0:2] == "00" { + err := fmt.Errorf("invalid SQLSTATE VALUE: '%s'", dc.SqlStateValue) + b.handleErr(err) + } + } else { + number, err := strconv.ParseUint(string(dc.MysqlErrorCode.Val), 10, 64) + if err != nil || number == 0 { + // We use our own error instead + err := fmt.Errorf("invalid value '%s' for MySQL error code", string(dc.MysqlErrorCode.Val)) + b.handleErr(err) + } + //TODO: implement MySQL error code support + err = sql.ErrUnsupportedSyntax.New(ast.String(s)) + b.handleErr(err) + } + inScope.proc.AddCondition(plan.NewDeclareCondition(dc.Name, 0, "")) + } else if s.Variables != nil { + typ, err := types.ColumnTypeToType(&s.Variables.VarType) + if err != nil { + b.handleErr(err) + } + for _, v := range s.Variables.Names { + varName := strings.ToLower(v.String()) + param := expression.NewProcedureParam(varName, typ) + inScope.proc.AddVar(param) + inScope.newColumn(scopeColumn{col: varName, typ: typ, scalar: param}) + } + } else if s.Cursor != nil { + inScope.proc.AddCursor(s.Cursor.Name) + } else if s.Handler != nil { + switch s.Handler.ConditionValues[0].ValueType { + case ast.DeclareHandlerCondition_NotFound: + case ast.DeclareHandlerCondition_SqlException: + default: + err := sql.ErrUnsupportedSyntax.New(ast.String(s)) + b.handleErr(err) + } + inScope.proc.AddHandler(nil) + } + case *ast.BeginEndBlock: + blockScope := inScope.push() + blockScope.initProc() + blockScope.proc.AddLabel(s.Label, false) + b.validateBlock(blockScope, s.Statements) + case *ast.Loop: + blockScope := inScope.push() + blockScope.initProc() + blockScope.proc.AddLabel(s.Label, true) + b.validateBlock(blockScope, s.Statements) + case *ast.Repeat: + blockScope := inScope.push() + blockScope.initProc() + blockScope.proc.AddLabel(s.Label, true) + b.validateBlock(blockScope, s.Statements) + case *ast.While: + blockScope := inScope.push() + blockScope.initProc() + blockScope.proc.AddLabel(s.Label, true) + b.validateBlock(blockScope, s.Statements) + case *ast.IfStatement: + for _, cond := range s.Conditions { + b.validateBlock(inScope, cond.Statements) + } + if s.Else != nil { + b.validateBlock(inScope, s.Else) + } + case *ast.Iterate: + if exists, isLoop := inScope.proc.HasLabel(s.Label); !exists || !isLoop { + err := sql.ErrLoopLabelNotFound.New("ITERATE", s.Label) + b.handleErr(err) + } + case *ast.Signal: + if s.ConditionName != "" { + signalName := strings.ToLower(s.ConditionName) + condition := inScope.proc.GetCondition(signalName) + if condition == nil { + err := sql.ErrDeclareConditionNotFound.New(signalName) + b.handleErr(err) + } + } + case *ast.FetchCursor: + if !inScope.proc.HasCursor(s.Name) { + b.handleErr(sql.ErrCursorNotFound.New(s.Name)) + } + case *ast.OpenCursor: + if !inScope.proc.HasCursor(s.Name) { + b.handleErr(sql.ErrCursorNotFound.New(s.Name)) + } + case *ast.CloseCursor: + if !inScope.proc.HasCursor(s.Name) { + b.handleErr(sql.ErrCursorNotFound.New(s.Name)) + } + + // limit validation + case *ast.Select: + if s.Limit != nil { + if expr, ok := s.Limit.Rowcount.(*ast.ColName); ok && inScope.procActive() { + if col, ok := inScope.proc.GetVar(expr.String()); ok { + // proc param is OK + if pp, ok := col.scalarGf().(*expression.ProcedureParam); ok { + if !pp.Type().Promote().Equals(types.Int64) && !pp.Type().Promote().Equals(types.Uint64) { + err := fmt.Errorf("the variable '%s' has a non-integer based type: %s", pp.Name(), pp.Type().String()) + b.handleErr(err) + } + } + } + } + } + } +} + +func (b *Builder) validateCreateProcedure(inScope *scope, createStmt string) { + stmt, _, _, _ := b.parser.ParseWithOptions(b.ctx, createStmt, ';', false, b.parserOpts) + procStmt := stmt.(*ast.DDL) + + // validate parameters + procParams := b.buildProcedureParams(procStmt.ProcedureSpec.Params) + paramNames := make(map[string]struct{}) + for _, param := range procParams { + paramName := strings.ToLower(param.Name) + if _, ok := paramNames[paramName]; ok { + b.handleErr(sql.ErrDeclareVariableDuplicate.New(paramName)) + } + paramNames[param.Name] = struct{}{} + } + + inScope.initProc() + for _, p := range procParams { + inScope.proc.AddVar(expression.NewProcedureParam(strings.ToLower(p.Name), p.Type)) + } + + bodyStmt := procStmt.ProcedureSpec.Body + b.validateStatement(inScope, bodyStmt) + + // TODO: check for limit clauses that are not integers +} + func (b *Builder) buildCreateEvent(inScope *scope, subQuery string, fullQuery string, c *ast.DDL) (outScope *scope) { + b.qFlags.Set(sql.QFlagCreateEvent) + defer func() { + b.qFlags.Unset(sql.QFlagCreateEvent) + }() + if b.qFlags.IsSet(sql.QFlagCreateTrigger) || b.qFlags.IsSet(sql.QFlagCreateProcedure) { + b.handleErr(fmt.Errorf("can't create an EVENT from within another stored routine")) + } + outScope = inScope.push() eventSpec := c.EventSpec dbName := strings.ToLower(eventSpec.EventName.Qualifier.String()) diff --git a/sql/planbuilder/proc.go b/sql/planbuilder/proc.go index 56ccda45d1..8f8d9045b4 100644 --- a/sql/planbuilder/proc.go +++ b/sql/planbuilder/proc.go @@ -20,12 +20,10 @@ import ( "strings" ast "github.com/dolthub/vitess/go/vt/sqlparser" - "gopkg.in/src-d/go-errors.v1" "github.com/dolthub/go-mysql-server/sql" "github.com/dolthub/go-mysql-server/sql/expression" "github.com/dolthub/go-mysql-server/sql/plan" - "github.com/dolthub/go-mysql-server/sql/transform" "github.com/dolthub/go-mysql-server/sql/types" ) @@ -226,16 +224,73 @@ func (b *Builder) buildIfConditional(inScope *scope, n ast.IfStatementCondition, return outScope } +func BuildProcedureHelper(ctx *sql.Context, cat sql.Catalog, isCreateProc bool, inScope *scope, db sql.Database, asOf sql.Expression, procDetails sql.StoredProcedureDetails) (proc *plan.Procedure, qFlags *sql.QueryFlags, err error) { + // TODO: new builder necessary? + defer func() { + if r := recover(); r != nil { + switch r := r.(type) { + case parseErr: + err = r.err + default: + panic(r) + } + } + }() + b := New(ctx, cat, nil, nil) + b.DisableAuth() + b.SetParserOptions(sql.NewSqlModeFromString(procDetails.SqlMode).ParserOptions()) + if asOf != nil { + asOf, err := asOf.Eval(b.ctx, nil) + if err != nil { + b.handleErr(err) + } + b.ProcCtx().AsOf = asOf + } + b.ProcCtx().DbName = db.Name() + if isCreateProc { + // TODO: we want to skip certain validations for CREATE PROCEDURE + b.qFlags.Set(sql.QFlagCreateProcedure) + } + stmt, _, _, _ := b.parser.ParseWithOptions(b.ctx, procDetails.CreateStatement, ';', false, b.parserOpts) + procStmt := stmt.(*ast.DDL) + + procParams := b.buildProcedureParams(procStmt.ProcedureSpec.Params) + characteristics, securityType, comment := b.buildProcedureCharacteristics(procStmt.ProcedureSpec.Characteristics) + + // populate inScope with the procedure parameters. this will be + // subject maybe a bug where an inner procedure has access to + // outer procedure parameters. + if inScope == nil { + inScope = b.newScope() + } + inScope.initProc() + for _, p := range procParams { + inScope.proc.AddVar(expression.NewProcedureParam(strings.ToLower(p.Name), p.Type)) + } + + bodyStr := strings.TrimSpace(procDetails.CreateStatement[procStmt.SubStatementPositionStart:procStmt.SubStatementPositionEnd]) + bodyScope := b.buildSubquery(inScope, procStmt.ProcedureSpec.Body, bodyStr, procDetails.CreateStatement) + + proc = plan.NewProcedure( + procDetails.Name, + procStmt.ProcedureSpec.Definer, + procParams, + securityType, + comment, + characteristics, + procDetails.CreateStatement, + bodyScope.node, + procDetails.CreatedAt, + procDetails.ModifiedAt, + ) + qFlags = b.qFlags + return +} + func (b *Builder) buildCall(inScope *scope, c *ast.Call) (outScope *scope) { if err := b.cat.AuthorizationHandler().HandleAuth(b.ctx, b.authQueryState, c.Auth); err != nil && b.authEnabled { b.handleErr(err) } - outScope = inScope.push() - params := make([]sql.Expression, len(c.Params)) - for i, param := range c.Params { - expr := b.buildScalar(inScope, param) - params[i] = expr - } var asOf sql.Expression = nil if c.AsOf != nil { @@ -257,12 +312,53 @@ func (b *Builder) buildCall(inScope *scope, c *ast.Call) (outScope *scope) { db = b.currentDb() } - outScope.node = plan.NewCall( - db, - c.ProcName.Name.String(), - params, - asOf, - b.cat) + var proc *plan.Procedure + var innerQFlags *sql.QueryFlags + procName := c.ProcName.Name.String() + esp, err := b.cat.ExternalStoredProcedure(b.ctx, procName, len(c.Params)) + if err != nil { + b.handleErr(err) + } + if esp != nil { + proc, err = resolveExternalStoredProcedure(*esp) + } else if spdb, ok := db.(sql.StoredProcedureDatabase); ok { + var procDetails sql.StoredProcedureDetails + procDetails, ok, err = spdb.GetStoredProcedure(b.ctx, procName) + if err == nil { + if ok { + proc, innerQFlags, err = BuildProcedureHelper(b.ctx, b.cat, false, inScope, db, asOf, procDetails) + // TODO: somewhat hacky way of preserving this flag + // This is necessary so that the resolveSubqueries analyzer rule + // will apply NodeExecBuilder to Subqueries in procedure body + if innerQFlags.IsSet(sql.QFlagScalarSubquery) { + b.qFlags.Set(sql.QFlagScalarSubquery) + } + } else { + err = sql.ErrStoredProcedureDoesNotExist.New(procName) + if b.qFlags.IsSet(sql.QFlagCreateTrigger) { + proc = &plan.Procedure{ + Name: procName, + ValidationError: err, + } + err = nil + } + } + } + } else { + err = sql.ErrStoredProceduresNotSupported.New(db.Name()) + } + if err != nil { + b.handleErr(err) + } + + params := make([]sql.Expression, len(c.Params)) + for i, param := range c.Params { + expr := b.buildScalar(inScope, param) + params[i] = expr + } + + outScope = inScope.push() + outScope.node = plan.NewCall(db, procName, params, proc, asOf, b.cat) return outScope } @@ -406,55 +502,12 @@ func (b *Builder) buildBlock(inScope *scope, parserStatements ast.Statements, fu } } stmtScope := b.buildSubquery(inScope, s, ast.String(s), fullQuery) - if b.qFlags.IsSet(sql.QFlagCreateProcedure) { - b.validateStoredProcedure(stmtScope.node) - } statements = append(statements, stmtScope.node) } return plan.NewBlock(statements) } -func (b *Builder) validateStoredProcedure(node sql.Node) { - // For now, we don't support creating any of the following within stored procedures. - // These will be removed in the future, but cause issues with the current execution plan. - var err error - spUnsupportedErr := errors.NewKind("creating %s in stored procedures is currently unsupported " + - "and will be added in a future release") - transform.Inspect(node, func(n sql.Node) bool { - switch n.(type) { - case *plan.CreateTable: - err = spUnsupportedErr.New("tables") - case *plan.CreateTrigger: - err = spUnsupportedErr.New("triggers") - case *plan.CreateProcedure: - err = spUnsupportedErr.New("procedures") - case *plan.CreateDB: - err = spUnsupportedErr.New("databases") - case *plan.CreateForeignKey: - err = spUnsupportedErr.New("foreign keys") - case *plan.CreateIndex: - err = spUnsupportedErr.New("indexes") - case *plan.CreateView: - err = spUnsupportedErr.New("views") - case *plan.LockTables: // Blocked in vitess, but this is for safety - err = sql.ErrProcedureInvalidBodyStatement.New("LOCK TABLES") - case *plan.UnlockTables: // Blocked in vitess, but this is for safety - err = sql.ErrProcedureInvalidBodyStatement.New("UNLOCK TABLES") - case *plan.Use: // Blocked in vitess, but this is for safety - err = sql.ErrProcedureInvalidBodyStatement.New("USE") - case *plan.LoadData: - err = sql.ErrProcedureInvalidBodyStatement.New("LOAD DATA") - default: - return true - } - return false - }) - if err != nil { - b.handleErr(err) - } -} - func (b *Builder) buildFetchCursor(inScope *scope, fetchCursor *ast.FetchCursor) (outScope *scope) { if !inScope.proc.HasCursor(fetchCursor.Name) { err := sql.ErrCursorNotFound.New(fetchCursor.Name) diff --git a/sql/analyzer/resolve_external_stored_procedures.go b/sql/planbuilder/resolve_external_stored_procedures.go similarity index 97% rename from sql/analyzer/resolve_external_stored_procedures.go rename to sql/planbuilder/resolve_external_stored_procedures.go index 7062cfd4b2..bbeacba7bd 100644 --- a/sql/analyzer/resolve_external_stored_procedures.go +++ b/sql/planbuilder/resolve_external_stored_procedures.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package analyzer +package planbuilder import ( "reflect" @@ -87,7 +87,7 @@ func init() { // resolveExternalStoredProcedure resolves external stored procedures, converting them to the format expected of // normal stored procedures. -func resolveExternalStoredProcedure(_ *sql.Context, externalProcedure sql.ExternalStoredProcedureDetails) (*plan.Procedure, error) { +func resolveExternalStoredProcedure(externalProcedure sql.ExternalStoredProcedureDetails) (*plan.Procedure, error) { funcVal := reflect.ValueOf(externalProcedure.Function) funcType := funcVal.Type() if funcType.Kind() != reflect.Func { diff --git a/sql/query_flags.go b/sql/query_flags.go index cce6156abc..a7478f2bb9 100644 --- a/sql/query_flags.go +++ b/sql/query_flags.go @@ -51,7 +51,11 @@ const ( // QFlagUndeferrableExprs indicates that the query has expressions that cannot be deferred QFlagUndeferrableExprs QFlagTrigger + + QFlagCreateEvent + QFlagCreateTrigger QFlagCreateProcedure + QFlagAnalyzeProcedure ) type QueryFlags struct { diff --git a/sql/rowexec/ddl.go b/sql/rowexec/ddl.go index af9516aa65..76ff4260b0 100644 --- a/sql/rowexec/ddl.go +++ b/sql/rowexec/ddl.go @@ -1128,16 +1128,9 @@ func createIndexesForCreateTable(ctx *sql.Context, db sql.Database, tableNode sq } func (b *BaseBuilder) buildCreateProcedure(ctx *sql.Context, n *plan.CreateProcedure, row sql.Row) (sql.RowIter, error) { - sqlMode := sql.LoadSqlMode(ctx) return &createProcedureIter{ - spd: sql.StoredProcedureDetails{ - Name: n.Name, - CreateStatement: n.CreateProcedureString, - CreatedAt: n.CreatedAt, - ModifiedAt: n.ModifiedAt, - SqlMode: sqlMode.String(), - }, - db: n.Database(), + spd: n.StoredProcDetails, + db: n.Database(), }, nil }