From 2ce0641a6909ca987cedf8ccff8576a94b8b0bd0 Mon Sep 17 00:00:00 2001 From: James Cor Date: Tue, 28 Jan 2025 22:46:45 -0800 Subject: [PATCH] pass at fixing external procedures --- .../queries/external_procedure_queries.go | 410 +++++++++--------- sql/analyzer/stored_procedures.go | 50 +-- sql/planbuilder/proc.go | 20 +- .../resolve_external_stored_procedures.go | 4 +- 4 files changed, 225 insertions(+), 259 deletions(-) rename sql/{analyzer => planbuilder}/resolve_external_stored_procedures.go (97%) diff --git a/enginetest/queries/external_procedure_queries.go b/enginetest/queries/external_procedure_queries.go index 547a3e8034..c4db672e01 100644 --- a/enginetest/queries/external_procedure_queries.go +++ b/enginetest/queries/external_procedure_queries.go @@ -17,15 +17,15 @@ package queries import "github.com/dolthub/go-mysql-server/sql" var ExternalProcedureTests = []ScriptTest{ - //{ - // Name: "Call external stored procedure that does not exist", - // Assertions: []ScriptTestAssertion{ - // { - // Query: "CALL procedure_does_not_exist('foo');", - // ExpectedErr: sql.ErrStoredProcedureDoesNotExist, - // }, - // }, - //}, + { + Name: "Call external stored procedure that does not exist", + Assertions: []ScriptTestAssertion{ + { + Query: "CALL procedure_does_not_exist('foo');", + ExpectedErr: sql.ErrStoredProcedureDoesNotExist, + }, + }, + }, { Name: "INOUT on first param, IN on second param", SetUpScript: []string{ @@ -39,200 +39,200 @@ var ExternalProcedureTests = []ScriptTest{ }, }, }, - //{ - // Name: "Handle setting uninitialized user variables", - // SetUpScript: []string{ - // "CALL memory_inout_set_unitialized(@uservar12, @uservar13, @uservar14, @uservar15);", - // }, - // Assertions: []ScriptTestAssertion{ - // { - // Query: "SELECT @uservar12;", - // Expected: []sql.Row{{5}}, - // }, - // { - // Query: "SELECT @uservar13;", - // Expected: []sql.Row{{uint(5)}}, - // }, - // { - // Query: "SELECT @uservar14;", - // Expected: []sql.Row{{"5"}}, - // }, - // { - // Query: "SELECT @uservar15;", - // Expected: []sql.Row{{0}}, - // }, - // }, - //}, - //{ - // Name: "Called from standard stored procedure", - // SetUpScript: []string{ - // "CREATE PROCEDURE p1(x BIGINT) BEGIN CALL memory_inout_add(x, x); SELECT x; END;", - // }, - // Assertions: []ScriptTestAssertion{ - // { - // Query: "CALL p1(11);", - // Expected: []sql.Row{{22}}, - // }, - // }, - //}, - //{ - // Name: "Overloaded Name", - // Assertions: []ScriptTestAssertion{ - // { - // Query: "CALL memory_overloaded_mult(1);", - // Expected: []sql.Row{{1}}, - // }, - // { - // Query: "CALL memory_overloaded_mult(2, 3);", - // Expected: []sql.Row{{6}}, - // }, - // { - // Query: "CALL memory_overloaded_mult(4, 5, 6);", - // Expected: []sql.Row{{120}}, - // }, - // }, - //}, - //{ - // Name: "Passing in all supported types", - // Assertions: []ScriptTestAssertion{ - // { - // Query: "CALL memory_overloaded_type_test(1, 100, 10000, 1000000, 100000000, 3, 300," + - // "10, 1000, 100000, 10000000, 1000000000, 30, 3000);", - // Expected: []sql.Row{{1111114444}}, - // }, - // { - // Query: "CALL memory_overloaded_type_test(false, 'hi', 'A', '2020-02-20 12:00:00', 123.456," + - // "true, 'bye', 'B', '2022-02-02 12:00:00', 654.32);", - // Expected: []sql.Row{{`aa:false,ba:true,ab:"hi",bb:"bye",ac:[65],bc:[66],ad:2020-02-20,bd:2022-02-02,ae:123.456,be:654.32`}}, - // }, - // { - // Query: "CALL memory_type_test3(1, 100, 10000, 1000000, 100000000, 3, 300," + - // "10, 1000, 100000, 10000000, 1000000000, 30, 3000);", - // Expected: []sql.Row{{uint64(1111114444)}}, - // }, - // }, - //}, - //{ - // Name: "BOOL and []BYTE INOUT conversions", - // SetUpScript: []string{ - // "SET @outparam1 = 1;", - // "SET @outparam2 = 0;", - // "SET @outparam3 = 'A';", - // "SET @outparam4 = 'B';", - // }, - // Assertions: []ScriptTestAssertion{ - // { - // Query: "SELECT @outparam1, @outparam2, @outparam3, @outparam4;", - // Expected: []sql.Row{{1, 0, "A", "B"}}, - // }, - // { - // Query: "CALL memory_inout_bool_byte(@outparam1, @outparam2, @outparam3, @outparam4);", - // Expected: []sql.Row{}, - // }, - // { - // Query: "SELECT @outparam1, @outparam2, @outparam3, @outparam4;", - // Expected: []sql.Row{{1, 1, "A", []byte("C")}}, - // }, - // { - // Query: "CALL memory_inout_bool_byte(@outparam1, @outparam2, @outparam3, @outparam4);", - // Expected: []sql.Row{}, - // }, - // { - // Query: "SELECT @outparam1, @outparam2, @outparam3, @outparam4;", - // Expected: []sql.Row{{1, 0, "A", []byte("D")}}, - // }, - // }, - //}, - //{ - // Name: "Errors returned", - // Assertions: []ScriptTestAssertion{ - // { - // Query: "CALL memory_error_table_not_found();", - // ExpectedErr: sql.ErrTableNotFound, - // }, - // }, - //}, - //{ - // Name: "Variadic parameter", - // Assertions: []ScriptTestAssertion{ - // { - // Query: "CALL memory_variadic_add();", - // Expected: []sql.Row{{0}}, - // }, - // { - // Query: "CALL memory_variadic_add(1);", - // Expected: []sql.Row{{1}}, - // }, - // { - // Query: "CALL memory_variadic_add(1, 2);", - // Expected: []sql.Row{{3}}, - // }, - // { - // Query: "CALL memory_variadic_add(1, 2, 3);", - // Expected: []sql.Row{{6}}, - // }, - // { - // Query: "CALL memory_variadic_add(1, 2, 3, 4);", - // Expected: []sql.Row{{10}}, - // }, - // }, - //}, - //{ - // Name: "Variadic byte slices", - // Assertions: []ScriptTestAssertion{ - // { - // Query: "CALL memory_variadic_byte_slice();", - // Expected: []sql.Row{{""}}, - // }, - // { - // Query: "CALL memory_variadic_byte_slice('A');", - // Expected: []sql.Row{{"A"}}, - // }, - // { - // Query: "CALL memory_variadic_byte_slice('A', 'B');", - // Expected: []sql.Row{{"AB"}}, - // }, - // }, - //}, - //{ - // Name: "Variadic overloading", - // Assertions: []ScriptTestAssertion{ - // { - // Query: "CALL memory_variadic_overload();", - // ExpectedErr: sql.ErrCallIncorrectParameterCount, - // }, - // { - // Query: "CALL memory_variadic_overload('A');", - // ExpectedErr: sql.ErrCallIncorrectParameterCount, - // }, - // { - // Query: "CALL memory_variadic_overload('A', 'B');", - // Expected: []sql.Row{{"A-B"}}, - // }, - // { - // Query: "CALL memory_variadic_overload('A', 'B', 'C');", - // ExpectedErr: sql.ErrInvalidValue, - // }, - // { - // Query: "CALL memory_variadic_overload('A', 'B', 5);", - // Expected: []sql.Row{{"A,B,[5]"}}, - // }, - // }, - //}, - //{ - // Name: "show create procedure for external stored procedures", - // Assertions: []ScriptTestAssertion{ - // { - // Query: "show create procedure memory_variadic_overload;", - // Expected: []sql.Row{{ - // "memory_variadic_overload", - // "", - // "CREATE PROCEDURE memory_variadic_overload() SELECT 'External stored procedure';", - // "utf8mb4", - // "utf8mb4_0900_bin", - // "utf8mb4_0900_bin", - // }}, - // }, - // }, - //}, + { + Name: "Handle setting uninitialized user variables", + SetUpScript: []string{ + "CALL memory_inout_set_unitialized(@uservar12, @uservar13, @uservar14, @uservar15);", + }, + Assertions: []ScriptTestAssertion{ + { + Query: "SELECT @uservar12;", + Expected: []sql.Row{{5}}, + }, + { + Query: "SELECT @uservar13;", + Expected: []sql.Row{{uint(5)}}, + }, + { + Query: "SELECT @uservar14;", + Expected: []sql.Row{{"5"}}, + }, + { + Query: "SELECT @uservar15;", + Expected: []sql.Row{{0}}, + }, + }, + }, + { + Name: "Called from standard stored procedure", + SetUpScript: []string{ + "CREATE PROCEDURE p1(x BIGINT) BEGIN CALL memory_inout_add(x, x); SELECT x; END;", + }, + Assertions: []ScriptTestAssertion{ + { + Query: "CALL p1(11);", + Expected: []sql.Row{{22}}, + }, + }, + }, + { + Name: "Overloaded Name", + Assertions: []ScriptTestAssertion{ + { + Query: "CALL memory_overloaded_mult(1);", + Expected: []sql.Row{{1}}, + }, + { + Query: "CALL memory_overloaded_mult(2, 3);", + Expected: []sql.Row{{6}}, + }, + { + Query: "CALL memory_overloaded_mult(4, 5, 6);", + Expected: []sql.Row{{120}}, + }, + }, + }, + { + Name: "Passing in all supported types", + Assertions: []ScriptTestAssertion{ + { + Query: "CALL memory_overloaded_type_test(1, 100, 10000, 1000000, 100000000, 3, 300," + + "10, 1000, 100000, 10000000, 1000000000, 30, 3000);", + Expected: []sql.Row{{1111114444}}, + }, + { + Query: "CALL memory_overloaded_type_test(false, 'hi', 'A', '2020-02-20 12:00:00', 123.456," + + "true, 'bye', 'B', '2022-02-02 12:00:00', 654.32);", + Expected: []sql.Row{{`aa:false,ba:true,ab:"hi",bb:"bye",ac:[65],bc:[66],ad:2020-02-20,bd:2022-02-02,ae:123.456,be:654.32`}}, + }, + { + Query: "CALL memory_type_test3(1, 100, 10000, 1000000, 100000000, 3, 300," + + "10, 1000, 100000, 10000000, 1000000000, 30, 3000);", + Expected: []sql.Row{{uint64(1111114444)}}, + }, + }, + }, + { + Name: "BOOL and []BYTE INOUT conversions", + SetUpScript: []string{ + "SET @outparam1 = 1;", + "SET @outparam2 = 0;", + "SET @outparam3 = 'A';", + "SET @outparam4 = 'B';", + }, + Assertions: []ScriptTestAssertion{ + { + Query: "SELECT @outparam1, @outparam2, @outparam3, @outparam4;", + Expected: []sql.Row{{1, 0, "A", "B"}}, + }, + { + Query: "CALL memory_inout_bool_byte(@outparam1, @outparam2, @outparam3, @outparam4);", + Expected: []sql.Row{}, + }, + { + Query: "SELECT @outparam1, @outparam2, @outparam3, @outparam4;", + Expected: []sql.Row{{1, 1, "A", []byte("C")}}, + }, + { + Query: "CALL memory_inout_bool_byte(@outparam1, @outparam2, @outparam3, @outparam4);", + Expected: []sql.Row{}, + }, + { + Query: "SELECT @outparam1, @outparam2, @outparam3, @outparam4;", + Expected: []sql.Row{{1, 0, "A", []byte("D")}}, + }, + }, + }, + { + Name: "Errors returned", + Assertions: []ScriptTestAssertion{ + { + Query: "CALL memory_error_table_not_found();", + ExpectedErr: sql.ErrTableNotFound, + }, + }, + }, + { + Name: "Variadic parameter", + Assertions: []ScriptTestAssertion{ + { + Query: "CALL memory_variadic_add();", + Expected: []sql.Row{{0}}, + }, + { + Query: "CALL memory_variadic_add(1);", + Expected: []sql.Row{{1}}, + }, + { + Query: "CALL memory_variadic_add(1, 2);", + Expected: []sql.Row{{3}}, + }, + { + Query: "CALL memory_variadic_add(1, 2, 3);", + Expected: []sql.Row{{6}}, + }, + { + Query: "CALL memory_variadic_add(1, 2, 3, 4);", + Expected: []sql.Row{{10}}, + }, + }, + }, + { + Name: "Variadic byte slices", + Assertions: []ScriptTestAssertion{ + { + Query: "CALL memory_variadic_byte_slice();", + Expected: []sql.Row{{""}}, + }, + { + Query: "CALL memory_variadic_byte_slice('A');", + Expected: []sql.Row{{"A"}}, + }, + { + Query: "CALL memory_variadic_byte_slice('A', 'B');", + Expected: []sql.Row{{"AB"}}, + }, + }, + }, + { + Name: "Variadic overloading", + Assertions: []ScriptTestAssertion{ + { + Query: "CALL memory_variadic_overload();", + ExpectedErr: sql.ErrCallIncorrectParameterCount, + }, + { + Query: "CALL memory_variadic_overload('A');", + ExpectedErr: sql.ErrCallIncorrectParameterCount, + }, + { + Query: "CALL memory_variadic_overload('A', 'B');", + Expected: []sql.Row{{"A-B"}}, + }, + { + Query: "CALL memory_variadic_overload('A', 'B', 'C');", + ExpectedErr: sql.ErrInvalidValue, + }, + { + Query: "CALL memory_variadic_overload('A', 'B', 5);", + Expected: []sql.Row{{"A,B,[5]"}}, + }, + }, + }, + { + Name: "show create procedure for external stored procedures", + Assertions: []ScriptTestAssertion{ + { + Query: "show create procedure memory_variadic_overload;", + Expected: []sql.Row{{ + "memory_variadic_overload", + "", + "CREATE PROCEDURE memory_variadic_overload() SELECT 'External stored procedure';", + "utf8mb4", + "utf8mb4_0900_bin", + "utf8mb4_0900_bin", + }}, + }, + }, + }, } diff --git a/sql/analyzer/stored_procedures.go b/sql/analyzer/stored_procedures.go index 3f625a34de..0aad0e8472 100644 --- a/sql/analyzer/stored_procedures.go +++ b/sql/analyzer/stored_procedures.go @@ -150,18 +150,6 @@ func applyProcedures(ctx *sql.Context, a *Analyzer, n sql.Node, scope *plan.Scop }() } - 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, transform.SameTree, err - } - return call.WithProcedure(externalProcedure), transform.NewTree, nil - } - if _, isStoredProcDb := call.Database().(sql.StoredProcedureDatabase); !isStoredProcDb { return nil, transform.SameTree, sql.ErrStoredProceduresNotSupported.New(call.Database().Name()) } @@ -204,43 +192,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/planbuilder/proc.go b/sql/planbuilder/proc.go index 7a7e7310b5..660d91ec44 100644 --- a/sql/planbuilder/proc.go +++ b/sql/planbuilder/proc.go @@ -16,11 +16,11 @@ package planbuilder import ( "fmt" + "gopkg.in/src-d/go-errors.v1" "strconv" "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" @@ -296,14 +296,28 @@ func (b *Builder) buildCall(inScope *scope, c *ast.Call) (outScope *scope) { b.handleErr(sql.ErrDatabaseNotFound.New(c.ProcName.Qualifier.String())) } - // TODO: external stored procedures? + procName := c.ProcName.Name.String() + + esp, err := b.cat.ExternalStoredProcedure(b.ctx, procName, len(params)) + if err != nil { + b.handleErr(err) + } + if esp != nil { + externalProcedure, err := resolveExternalStoredProcedure(*esp) + if err != nil { + b.handleErr(err) + } + + outScope.node = plan.NewCall(db, procName, params, externalProcedure, asOf, b.cat) + return outScope + } + spdb, ok := db.(sql.StoredProcedureDatabase) if !ok { err := sql.ErrStoredProceduresNotSupported.New(db.Name()) b.handleErr(err) } - procName := c.ProcName.Name.String() proc, ok, err := spdb.GetStoredProcedure(b.ctx, procName) if err != nil { b.handleErr(err) 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 {