diff --git a/pkg/resources/schema_acceptance_test.go b/pkg/resources/schema_acceptance_test.go index 435561c958..4d21b5d62d 100644 --- a/pkg/resources/schema_acceptance_test.go +++ b/pkg/resources/schema_acceptance_test.go @@ -4,11 +4,12 @@ import ( "context" "database/sql" "fmt" + "strings" + "testing" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/terraform" - "strings" - "testing" acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" "github.com/hashicorp/terraform-plugin-testing/config" diff --git a/pkg/resources/testdata/TestAcc_Schema/test.tf b/pkg/resources/testdata/TestAcc_Schema/test.tf index 5f8ca0abfc..ce85eca50c 100644 --- a/pkg/resources/testdata/TestAcc_Schema/test.tf +++ b/pkg/resources/testdata/TestAcc_Schema/test.tf @@ -1,5 +1,5 @@ resource "snowflake_schema" "test" { - name = var.name + name = var.name database = var.database - comment = var.comment + comment = var.comment } \ No newline at end of file diff --git a/pkg/resources/testdata/TestAcc_Schema_Rename/test.tf b/pkg/resources/testdata/TestAcc_Schema_Rename/test.tf index 5f8ca0abfc..ce85eca50c 100644 --- a/pkg/resources/testdata/TestAcc_Schema_Rename/test.tf +++ b/pkg/resources/testdata/TestAcc_Schema_Rename/test.tf @@ -1,5 +1,5 @@ resource "snowflake_schema" "test" { - name = var.name + name = var.name database = var.database - comment = var.comment + comment = var.comment } \ No newline at end of file diff --git a/pkg/resources/testdata/TestAcc_Schema_TwoSchemasWithTheSameNameOnDifferentDatabases/1/test.tf b/pkg/resources/testdata/TestAcc_Schema_TwoSchemasWithTheSameNameOnDifferentDatabases/1/test.tf index 991e3b38d5..4325ee4234 100644 --- a/pkg/resources/testdata/TestAcc_Schema_TwoSchemasWithTheSameNameOnDifferentDatabases/1/test.tf +++ b/pkg/resources/testdata/TestAcc_Schema_TwoSchemasWithTheSameNameOnDifferentDatabases/1/test.tf @@ -1,4 +1,4 @@ resource "snowflake_schema" "test" { - name = var.name + name = var.name database = var.database } \ No newline at end of file diff --git a/pkg/resources/testdata/TestAcc_Schema_TwoSchemasWithTheSameNameOnDifferentDatabases/2/test.tf b/pkg/resources/testdata/TestAcc_Schema_TwoSchemasWithTheSameNameOnDifferentDatabases/2/test.tf index bc1af37dd8..0b1a598ccf 100644 --- a/pkg/resources/testdata/TestAcc_Schema_TwoSchemasWithTheSameNameOnDifferentDatabases/2/test.tf +++ b/pkg/resources/testdata/TestAcc_Schema_TwoSchemasWithTheSameNameOnDifferentDatabases/2/test.tf @@ -1,5 +1,5 @@ resource "snowflake_schema" "test" { - name = var.name + name = var.name database = var.database } @@ -8,6 +8,6 @@ resource "snowflake_database" "test" { } resource "snowflake_schema" "test_2" { - name = var.name + name = var.name database = snowflake_database.test.name } diff --git a/pkg/sdk/client.go b/pkg/sdk/client.go index d333c965f2..c52ab225db 100644 --- a/pkg/sdk/client.go +++ b/pkg/sdk/client.go @@ -45,6 +45,7 @@ type Client struct { Parameters Parameters PasswordPolicies PasswordPolicies Pipes Pipes + Procedures Procedures ResourceMonitors ResourceMonitors Roles Roles Schemas Schemas @@ -174,6 +175,7 @@ func (c *Client) initialize() { c.Parameters = ¶meters{client: c} c.PasswordPolicies = &passwordPolicies{client: c} c.Pipes = &pipes{client: c} + c.Procedures = &procedures{client: c} c.ReplicationFunctions = &replicationFunctions{client: c} c.ResourceMonitors = &resourceMonitors{client: c} c.Roles = &roles{client: c} diff --git a/pkg/sdk/common_types.go b/pkg/sdk/common_types.go index d7703a4ba1..2c5061767f 100644 --- a/pkg/sdk/common_types.go +++ b/pkg/sdk/common_types.go @@ -148,3 +148,31 @@ func (row *propertyRow) toBoolProperty() *BoolProperty { Description: row.Description, } } + +type ExecuteAs string + +func ExecuteAsPointer(v ExecuteAs) *ExecuteAs { + return &v +} + +const ( + ExecuteAsCaller ExecuteAs = "EXECUTE AS CALLER" + ExecuteAsOwner ExecuteAs = "EXECUTE AS OWNER" +) + +type NullInputBehavior string + +func NullInputBehaviorPointer(v NullInputBehavior) *NullInputBehavior { + return &v +} + +const ( + NullInputBehaviorCalledOnNullInput NullInputBehavior = "CALLED ON NULL INPUT" + NullInputBehaviorReturnNullInput NullInputBehavior = "RETURN NULL ON NULL INPUT" + NullInputBehaviorStrict NullInputBehavior = "STRICT" +) + +type Secret struct { + VariableName string `ddl:"keyword,single_quotes"` + Name string `ddl:"parameter,no_quotes"` +} diff --git a/pkg/sdk/poc/generator/field_transformers.go b/pkg/sdk/poc/generator/field_transformers.go index 52da70e69e..ee57f105b7 100644 --- a/pkg/sdk/poc/generator/field_transformers.go +++ b/pkg/sdk/poc/generator/field_transformers.go @@ -85,6 +85,11 @@ func (v *ParameterTransformer) NoQuotes() *ParameterTransformer { return v } +func (v *ListTransformer) MustParentheses() *ListTransformer { + v.parentheses = "must_parentheses" + return v +} + func (v *ParameterTransformer) NoEquals() *ParameterTransformer { v.equals = "no_equals" return v diff --git a/pkg/sdk/poc/generator/keyword_builders.go b/pkg/sdk/poc/generator/keyword_builders.go index db58de2f25..dadb98d34f 100644 --- a/pkg/sdk/poc/generator/keyword_builders.go +++ b/pkg/sdk/poc/generator/keyword_builders.go @@ -79,7 +79,7 @@ func (v *QueryStruct) SetTags() *QueryStruct { } func (v *QueryStruct) OptionalSetTags() *QueryStruct { - return v.setTags(nil) + return v.setTags(KeywordOptions().SQL("SET TAG")) } func (v *QueryStruct) setTags(transformer *KeywordTransformer) *QueryStruct { @@ -91,7 +91,7 @@ func (v *QueryStruct) UnsetTags() *QueryStruct { } func (v *QueryStruct) OptionalUnsetTags() *QueryStruct { - return v.unsetTags(nil) + return v.unsetTags(KeywordOptions().SQL("UNSET TAG")) } func (v *QueryStruct) unsetTags(transformer *KeywordTransformer) *QueryStruct { diff --git a/pkg/sdk/poc/generator/validation.go b/pkg/sdk/poc/generator/validation.go index 7c4d29b881..fa664c9bd6 100644 --- a/pkg/sdk/poc/generator/validation.go +++ b/pkg/sdk/poc/generator/validation.go @@ -22,6 +22,7 @@ const ( ExactlyOneValueSet AtLeastOneValueSet ValidateValue + ValidateValueSet ) type Validation struct { @@ -64,6 +65,8 @@ func (v *Validation) Condition(field *Field) string { return fmt.Sprintf("!exactlyOneValueSet(%s)", strings.Join(v.fieldsWithPath(field), ",")) case AtLeastOneValueSet: return fmt.Sprintf("!anyValueSet(%s)", strings.Join(v.fieldsWithPath(field), ",")) + case ValidateValueSet: + return fmt.Sprintf("!valueSet(%s)", strings.Join(v.fieldsWithPath(field), ",")) case ValidateValue: return fmt.Sprintf("err := %s.validate(); err != nil", strings.Join(v.fieldsWithPath(field.Parent), ",")) } @@ -82,6 +85,8 @@ func (v *Validation) ReturnedError(field *Field) string { return fmt.Sprintf(`errExactlyOneOf("%s", %s)`, field.PathWithRoot(), strings.Join(v.paramsQuoted(), ",")) case AtLeastOneValueSet: return fmt.Sprintf(`errAtLeastOneOf("%s", %s)`, field.PathWithRoot(), strings.Join(v.paramsQuoted(), ",")) + case ValidateValueSet: + return fmt.Sprintf(`errNotSet("%s", %s)`, field.PathWithRoot(), strings.Join(v.paramsQuoted(), ",")) case ValidateValue: return "err" } @@ -100,6 +105,8 @@ func (v *Validation) TodoComment(field *Field) string { return fmt.Sprintf("validation: exactly one field from %v should be present", v.fieldsWithPath(field)) case AtLeastOneValueSet: return fmt.Sprintf("validation: at least one of the fields %v should be set", v.fieldsWithPath(field)) + case ValidateValueSet: + return fmt.Sprintf("validation: %v should be set", v.fieldsWithPath(field)) case ValidateValue: return fmt.Sprintf("validation: %v should be valid", v.fieldsWithPath(field)[0]) } diff --git a/pkg/sdk/poc/main.go b/pkg/sdk/poc/main.go index 6d0115847e..697714125b 100644 --- a/pkg/sdk/poc/main.go +++ b/pkg/sdk/poc/main.go @@ -24,6 +24,7 @@ var definitionMapping = map[string]*generator.Interface{ "application_roles_def.go": sdk.ApplicationRolesDef, "views_def.go": sdk.ViewsDef, "stages_def.go": sdk.StagesDef, + "procedures_def.go": sdk.ProceduresDef, "event_tables_def.go": sdk.EventTablesDef, } diff --git a/pkg/sdk/procedures_def.go b/pkg/sdk/procedures_def.go new file mode 100644 index 0000000000..f1e2a4e86e --- /dev/null +++ b/pkg/sdk/procedures_def.go @@ -0,0 +1,324 @@ +package sdk + +import g "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/poc/generator" + +//go:generate go run ./poc/main.go + +var procedureArgument = g.NewQueryStruct("ProcedureArgument"). + Text("ArgName", g.KeywordOptions().NoQuotes().Required()). + PredefinedQueryStructField("ArgDataType", "DataType", g.KeywordOptions().NoQuotes().Required()). + PredefinedQueryStructField("DefaultValue", "*string", g.ParameterOptions().NoEquals().SQL("DEFAULT")) + +var procedureColumn = g.NewQueryStruct("ProcedureColumn"). + Text("ColumnName", g.KeywordOptions().NoQuotes().Required()). + PredefinedQueryStructField("ColumnDataType", "DataType", g.KeywordOptions().NoQuotes().Required()) + +var procedureReturns = g.NewQueryStruct("ProcedureReturns"). + OptionalQueryStructField( + "ResultDataType", + g.NewQueryStruct("ProcedureReturnsResultDataType"). + PredefinedQueryStructField("ResultDataType", "DataType", g.KeywordOptions().NoQuotes().Required()). + OptionalSQL("NULL").OptionalSQL("NOT NULL"), + g.KeywordOptions(), + ). + OptionalQueryStructField( + "Table", + g.NewQueryStruct("ProcedureReturnsTable"). + ListQueryStructField( + "Columns", + procedureColumn, + g.ListOptions().MustParentheses(), + ), + g.KeywordOptions().SQL("TABLE"), + ).WithValidation(g.ExactlyOneValueSet, "ResultDataType", "Table") + +var procedureSQLReturns = g.NewQueryStruct("ProcedureSQLReturns"). + OptionalQueryStructField( + "ResultDataType", + g.NewQueryStruct("ProcedureReturnsResultDataType"). + PredefinedQueryStructField("ResultDataType", "DataType", g.KeywordOptions().NoQuotes().Required()), + g.KeywordOptions(), + ). + OptionalQueryStructField( + "Table", + g.NewQueryStruct("ProcedureReturnsTable"). + ListQueryStructField( + "Columns", + procedureColumn, + g.ListOptions().MustParentheses(), + ), + g.KeywordOptions().SQL("TABLE"), + ). + OptionalSQL("NOT NULL"). + WithValidation(g.ExactlyOneValueSet, "ResultDataType", "Table") + +var ( + procedureImport = g.NewQueryStruct("ProcedureImport").Text("Import", g.KeywordOptions().SingleQuotes().Required()) + procedurePackage = g.NewQueryStruct("ProcedurePackage").Text("Package", g.KeywordOptions().SingleQuotes().Required()) +) + +var ProceduresDef = g.NewInterface( + "Procedures", + "Procedure", + g.KindOfT[SchemaObjectIdentifier](), +).CustomOperation( + "CreateForJava", + "https://docs.snowflake.com/en/sql-reference/sql/create-procedure#java-handler", + g.NewQueryStruct("CreateForJava"). + Create(). + OrReplace(). + OptionalSQL("SECURE"). + SQL("PROCEDURE"). + Name(). + ListQueryStructField( + "Arguments", + procedureArgument, + g.ListOptions().MustParentheses(), + ). + OptionalSQL("COPY GRANTS"). + QueryStructField( + "Returns", + procedureReturns, + g.KeywordOptions().SQL("RETURNS").Required(), + ). + SQL("LANGUAGE JAVA"). + TextAssignment("RUNTIME_VERSION", g.ParameterOptions().SingleQuotes().Required()). + ListQueryStructField( + "Packages", + procedurePackage, + g.ParameterOptions().Parentheses().SQL("PACKAGES").Required(), + ). + ListQueryStructField( + "Imports", + procedureImport, + g.ParameterOptions().Parentheses().SQL("IMPORTS"), + ). + TextAssignment("HANDLER", g.ParameterOptions().SingleQuotes().Required()). + ListAssignment("EXTERNAL_ACCESS_INTEGRATIONS", "AccountObjectIdentifier", g.ParameterOptions().Parentheses()). + ListAssignment("SECRETS", "Secret", g.ParameterOptions().Parentheses()). + OptionalTextAssignment("TARGET_PATH", g.ParameterOptions().SingleQuotes()). + PredefinedQueryStructField("NullInputBehavior", "*NullInputBehavior", g.KeywordOptions()). + OptionalTextAssignment("COMMENT", g.ParameterOptions().SingleQuotes()). + PredefinedQueryStructField("ExecuteAs", "*ExecuteAs", g.KeywordOptions()). + PredefinedQueryStructField("ProcedureDefinition", "*string", g.ParameterOptions().NoEquals().SingleQuotes().SQL("AS")). + WithValidation(g.ValidateValueSet, "RuntimeVersion"). + WithValidation(g.ValidateValueSet, "Handler"). + WithValidation(g.ValidateValueSet, "Packages"). + WithValidation(g.ValidIdentifier, "name"), +).CustomOperation( + "CreateForJavaScript", + "https://docs.snowflake.com/en/sql-reference/sql/create-procedure#javascript-handler", + g.NewQueryStruct("CreateForJavaScript"). + Create(). + OrReplace(). + OptionalSQL("SECURE"). + SQL("PROCEDURE"). + Name(). + ListQueryStructField( + "Arguments", + procedureArgument, + g.ListOptions().MustParentheses(), + ). + OptionalSQL("COPY GRANTS"). + PredefinedQueryStructField("ResultDataType", "DataType", g.ParameterOptions().NoEquals().SQL("RETURNS").Required()). + OptionalSQL("NOT NULL"). + SQL("LANGUAGE JAVASCRIPT"). + PredefinedQueryStructField("NullInputBehavior", "*NullInputBehavior", g.KeywordOptions()). + OptionalTextAssignment("COMMENT", g.ParameterOptions().SingleQuotes()). + PredefinedQueryStructField("ExecuteAs", "*ExecuteAs", g.KeywordOptions()). + PredefinedQueryStructField("ProcedureDefinition", "string", g.ParameterOptions().NoEquals().SingleQuotes().SQL("AS").Required()). + WithValidation(g.ValidateValueSet, "ProcedureDefinition"). + WithValidation(g.ValidIdentifier, "name"), +).CustomOperation( + "CreateForPython", + "https://docs.snowflake.com/en/sql-reference/sql/create-procedure#python-handler", + g.NewQueryStruct("CreateForPython"). + Create(). + OrReplace(). + OptionalSQL("SECURE"). + SQL("PROCEDURE"). + Name(). + ListQueryStructField( + "Arguments", + procedureArgument, + g.ListOptions().MustParentheses(), + ). + OptionalSQL("COPY GRANTS"). + QueryStructField( + "Returns", + procedureReturns, + g.KeywordOptions().SQL("RETURNS").Required(), + ). + SQL("LANGUAGE PYTHON"). + TextAssignment("RUNTIME_VERSION", g.ParameterOptions().SingleQuotes().Required()). + ListQueryStructField( + "Packages", + procedurePackage, + g.ParameterOptions().Parentheses().SQL("PACKAGES").Required(), + ). + ListQueryStructField( + "Imports", + procedureImport, + g.ParameterOptions().Parentheses().SQL("IMPORTS"), + ). + TextAssignment("HANDLER", g.ParameterOptions().SingleQuotes().Required()). + ListAssignment("EXTERNAL_ACCESS_INTEGRATIONS", "AccountObjectIdentifier", g.ParameterOptions().Parentheses()). + ListAssignment("SECRETS", "Secret", g.ParameterOptions().Parentheses()). + PredefinedQueryStructField("NullInputBehavior", "*NullInputBehavior", g.KeywordOptions()). + OptionalTextAssignment("COMMENT", g.ParameterOptions().SingleQuotes()). + PredefinedQueryStructField("ExecuteAs", "*ExecuteAs", g.KeywordOptions()). + PredefinedQueryStructField("ProcedureDefinition", "*string", g.ParameterOptions().NoEquals().SingleQuotes().SQL("AS")). + WithValidation(g.ValidateValueSet, "RuntimeVersion"). + WithValidation(g.ValidateValueSet, "Handler"). + WithValidation(g.ValidateValueSet, "Packages"). + WithValidation(g.ValidIdentifier, "name"), +).CustomOperation( + "CreateForScala", + "https://docs.snowflake.com/en/sql-reference/sql/create-procedure#scala-handler", + g.NewQueryStruct("CreateForScala"). + Create(). + OrReplace(). + OptionalSQL("SECURE"). + SQL("PROCEDURE"). + Name(). + ListQueryStructField( + "Arguments", + procedureArgument, + g.ListOptions().MustParentheses(), + ). + OptionalSQL("COPY GRANTS"). + QueryStructField( + "Returns", + procedureReturns, + g.KeywordOptions().SQL("RETURNS").Required(), + ). + SQL("LANGUAGE SCALA"). + TextAssignment("RUNTIME_VERSION", g.ParameterOptions().SingleQuotes().Required()). + ListQueryStructField( + "Packages", + procedurePackage, + g.ParameterOptions().Parentheses().SQL("PACKAGES").Required(), + ). + ListQueryStructField( + "Imports", + procedureImport, + g.ParameterOptions().Parentheses().SQL("IMPORTS"), + ). + TextAssignment("HANDLER", g.ParameterOptions().SingleQuotes().Required()). + OptionalTextAssignment("TARGET_PATH", g.ParameterOptions().SingleQuotes()). + PredefinedQueryStructField("NullInputBehavior", "*NullInputBehavior", g.KeywordOptions()). + OptionalTextAssignment("COMMENT", g.ParameterOptions().SingleQuotes()). + PredefinedQueryStructField("ExecuteAs", "*ExecuteAs", g.KeywordOptions()). + PredefinedQueryStructField("ProcedureDefinition", "*string", g.ParameterOptions().NoEquals().SingleQuotes().SQL("AS")). + WithValidation(g.ValidateValueSet, "RuntimeVersion"). + WithValidation(g.ValidateValueSet, "Handler"). + WithValidation(g.ValidateValueSet, "Packages"). + WithValidation(g.ValidIdentifier, "name"), +).CustomOperation( + "CreateForSQL", + "https://docs.snowflake.com/en/sql-reference/sql/create-procedure#snowflake-scripting-handler", + g.NewQueryStruct("CreateForSQL"). + Create(). + OrReplace(). + OptionalSQL("SECURE"). + SQL("PROCEDURE"). + Name(). + ListQueryStructField( + "Arguments", + procedureArgument, + g.ListOptions().MustParentheses(), + ). + OptionalSQL("COPY GRANTS"). + QueryStructField( + "Returns", + procedureSQLReturns, + g.KeywordOptions().SQL("RETURNS").Required(), + ). + SQL("LANGUAGE SQL"). + PredefinedQueryStructField("NullInputBehavior", "*NullInputBehavior", g.KeywordOptions()). + OptionalTextAssignment("COMMENT", g.ParameterOptions().SingleQuotes()). + PredefinedQueryStructField("ExecuteAs", "*ExecuteAs", g.KeywordOptions()). + PredefinedQueryStructField("ProcedureDefinition", "string", g.ParameterOptions().NoEquals().SingleQuotes().SQL("AS").Required()). + WithValidation(g.ValidateValueSet, "ProcedureDefinition"). + WithValidation(g.ValidIdentifier, "name"), +).AlterOperation( + "https://docs.snowflake.com/en/sql-reference/sql/alter-procedure", + g.NewQueryStruct("AlterProcedure"). + Alter(). + SQL("PROCEDURE"). + IfExists(). + Name(). + PredefinedQueryStructField("ArgumentDataTypes", "[]DataType", g.KeywordOptions().Parentheses().Required()). + OptionalIdentifier("RenameTo", g.KindOfT[SchemaObjectIdentifier](), g.IdentifierOptions().SQL("RENAME TO")). + OptionalTextAssignment("SET COMMENT", g.ParameterOptions().SingleQuotes()). + OptionalTextAssignment("SET LOG_LEVEL", g.ParameterOptions().SingleQuotes()). + OptionalTextAssignment("SET TRACE_LEVEL", g.ParameterOptions().SingleQuotes()). + OptionalSQL("UNSET COMMENT"). + OptionalSetTags(). + OptionalUnsetTags(). + PredefinedQueryStructField("ExecuteAs", "*ExecuteAs", g.KeywordOptions()). + WithValidation(g.ValidIdentifier, "name"). + WithValidation(g.ValidIdentifierIfSet, "RenameTo"). + WithValidation(g.ExactlyOneValueSet, "RenameTo", "SetComment", "SetLogLevel", "SetTraceLevel", "UnsetComment", "SetTags", "UnsetTags", "ExecuteAs"), +).DropOperation( + "https://docs.snowflake.com/en/sql-reference/sql/drop-procedure", + g.NewQueryStruct("DropProcedure"). + Drop(). + SQL("PROCEDURE"). + IfExists(). + Name(). + PredefinedQueryStructField("ArgumentDataTypes", "[]DataType", g.KeywordOptions().Parentheses().Required()). + WithValidation(g.ValidIdentifier, "name"), +).ShowOperation( + "https://docs.snowflake.com/en/sql-reference/sql/show-procedures", + g.DbStruct("procedureRow"). + Field("created_on", "string"). + Field("name", "string"). + Field("schema_name", "string"). + Field("is_builtin", "string"). + Field("is_aggregate", "string"). + Field("is_ansi", "string"). + Field("min_num_arguments", "int"). + Field("max_num_arguments", "int"). + Field("arguments", "string"). + Field("description", "string"). + Field("catalog_name", "string"). + Field("is_table_function", "string"). + Field("valid_for_clustering", "string"). + Field("is_secure", "sql.NullString"), + g.PlainStruct("Procedure"). + Field("CreatedOn", "string"). + Field("Name", "string"). + Field("SchemaName", "string"). + Field("IsBuiltin", "bool"). + Field("IsAggregate", "bool"). + Field("IsAnsi", "bool"). + Field("MinNumArguments", "int"). + Field("MaxNumArguments", "int"). + Field("Arguments", "string"). + Field("Description", "string"). + Field("CatalogName", "string"). + Field("IsTableFunction", "bool"). + Field("ValidForClustering", "bool"). + Field("IsSecure", "bool"), + g.NewQueryStruct("ShowProcedures"). + Show(). + SQL("PROCEDURES"). + OptionalLike(). + OptionalIn(), // TODO: 'In' struct for procedures not support keyword "CLASS" now +).ShowByIdOperation().DescribeOperation( + g.DescriptionMappingKindSlice, + "https://docs.snowflake.com/en/sql-reference/sql/desc-procedure", + g.DbStruct("procedureDetailRow"). + Field("property", "string"). + Field("value", "string"), + g.PlainStruct("ProcedureDetail"). + Field("Property", "string"). + Field("Value", "string"), + g.NewQueryStruct("DescribeProcedure"). + Describe(). + SQL("PROCEDURE"). + Name(). + PredefinedQueryStructField("ArgumentDataTypes", "[]DataType", g.KeywordOptions().Parentheses().Required()). + WithValidation(g.ValidIdentifier, "name"), +) diff --git a/pkg/sdk/procedures_dto_builders_gen.go b/pkg/sdk/procedures_dto_builders_gen.go new file mode 100644 index 0000000000..e1c4328fb5 --- /dev/null +++ b/pkg/sdk/procedures_dto_builders_gen.go @@ -0,0 +1,512 @@ +// Code generated by dto builder generator; DO NOT EDIT. + +package sdk + +import () + +func NewCreateForJavaProcedureRequest( + name SchemaObjectIdentifier, + Returns ProcedureReturnsRequest, + RuntimeVersion string, + Packages []ProcedurePackageRequest, + Handler string, +) *CreateForJavaProcedureRequest { + s := CreateForJavaProcedureRequest{} + s.name = name + s.Returns = Returns + s.RuntimeVersion = RuntimeVersion + s.Packages = Packages + s.Handler = Handler + return &s +} + +func (s *CreateForJavaProcedureRequest) WithOrReplace(OrReplace *bool) *CreateForJavaProcedureRequest { + s.OrReplace = OrReplace + return s +} + +func (s *CreateForJavaProcedureRequest) WithSecure(Secure *bool) *CreateForJavaProcedureRequest { + s.Secure = Secure + return s +} + +func (s *CreateForJavaProcedureRequest) WithArguments(Arguments []ProcedureArgumentRequest) *CreateForJavaProcedureRequest { + s.Arguments = Arguments + return s +} + +func (s *CreateForJavaProcedureRequest) WithCopyGrants(CopyGrants *bool) *CreateForJavaProcedureRequest { + s.CopyGrants = CopyGrants + return s +} + +func (s *CreateForJavaProcedureRequest) WithImports(Imports []ProcedureImportRequest) *CreateForJavaProcedureRequest { + s.Imports = Imports + return s +} + +func (s *CreateForJavaProcedureRequest) WithExternalAccessIntegrations(ExternalAccessIntegrations []AccountObjectIdentifier) *CreateForJavaProcedureRequest { + s.ExternalAccessIntegrations = ExternalAccessIntegrations + return s +} + +func (s *CreateForJavaProcedureRequest) WithSecrets(Secrets []Secret) *CreateForJavaProcedureRequest { + s.Secrets = Secrets + return s +} + +func (s *CreateForJavaProcedureRequest) WithTargetPath(TargetPath *string) *CreateForJavaProcedureRequest { + s.TargetPath = TargetPath + return s +} + +func (s *CreateForJavaProcedureRequest) WithNullInputBehavior(NullInputBehavior *NullInputBehavior) *CreateForJavaProcedureRequest { + s.NullInputBehavior = NullInputBehavior + return s +} + +func (s *CreateForJavaProcedureRequest) WithComment(Comment *string) *CreateForJavaProcedureRequest { + s.Comment = Comment + return s +} + +func (s *CreateForJavaProcedureRequest) WithExecuteAs(ExecuteAs *ExecuteAs) *CreateForJavaProcedureRequest { + s.ExecuteAs = ExecuteAs + return s +} + +func (s *CreateForJavaProcedureRequest) WithProcedureDefinition(ProcedureDefinition *string) *CreateForJavaProcedureRequest { + s.ProcedureDefinition = ProcedureDefinition + return s +} + +func NewProcedureArgumentRequest( + ArgName string, + ArgDataType DataType, +) *ProcedureArgumentRequest { + s := ProcedureArgumentRequest{} + s.ArgName = ArgName + s.ArgDataType = ArgDataType + return &s +} + +func (s *ProcedureArgumentRequest) WithDefaultValue(DefaultValue *string) *ProcedureArgumentRequest { + s.DefaultValue = DefaultValue + return s +} + +func NewProcedureReturnsRequest() *ProcedureReturnsRequest { + return &ProcedureReturnsRequest{} +} + +func (s *ProcedureReturnsRequest) WithResultDataType(ResultDataType *ProcedureReturnsResultDataTypeRequest) *ProcedureReturnsRequest { + s.ResultDataType = ResultDataType + return s +} + +func (s *ProcedureReturnsRequest) WithTable(Table *ProcedureReturnsTableRequest) *ProcedureReturnsRequest { + s.Table = Table + return s +} + +func NewProcedureReturnsResultDataTypeRequest( + ResultDataType DataType, +) *ProcedureReturnsResultDataTypeRequest { + s := ProcedureReturnsResultDataTypeRequest{} + s.ResultDataType = ResultDataType + return &s +} + +func (s *ProcedureReturnsResultDataTypeRequest) WithNull(Null *bool) *ProcedureReturnsResultDataTypeRequest { + s.Null = Null + return s +} + +func (s *ProcedureReturnsResultDataTypeRequest) WithNotNull(NotNull *bool) *ProcedureReturnsResultDataTypeRequest { + s.NotNull = NotNull + return s +} + +func NewProcedureReturnsTableRequest() *ProcedureReturnsTableRequest { + return &ProcedureReturnsTableRequest{} +} + +func (s *ProcedureReturnsTableRequest) WithColumns(Columns []ProcedureColumnRequest) *ProcedureReturnsTableRequest { + s.Columns = Columns + return s +} + +func NewProcedureColumnRequest( + ColumnName string, + ColumnDataType DataType, +) *ProcedureColumnRequest { + s := ProcedureColumnRequest{} + s.ColumnName = ColumnName + s.ColumnDataType = ColumnDataType + return &s +} + +func NewProcedurePackageRequest( + Package string, +) *ProcedurePackageRequest { + s := ProcedurePackageRequest{} + s.Package = Package + return &s +} + +func NewProcedureImportRequest( + Import string, +) *ProcedureImportRequest { + s := ProcedureImportRequest{} + s.Import = Import + return &s +} + +func NewCreateForJavaScriptProcedureRequest( + name SchemaObjectIdentifier, + ResultDataType DataType, + ProcedureDefinition string, +) *CreateForJavaScriptProcedureRequest { + s := CreateForJavaScriptProcedureRequest{} + s.name = name + s.ResultDataType = ResultDataType + s.ProcedureDefinition = ProcedureDefinition + return &s +} + +func (s *CreateForJavaScriptProcedureRequest) WithOrReplace(OrReplace *bool) *CreateForJavaScriptProcedureRequest { + s.OrReplace = OrReplace + return s +} + +func (s *CreateForJavaScriptProcedureRequest) WithSecure(Secure *bool) *CreateForJavaScriptProcedureRequest { + s.Secure = Secure + return s +} + +func (s *CreateForJavaScriptProcedureRequest) WithArguments(Arguments []ProcedureArgumentRequest) *CreateForJavaScriptProcedureRequest { + s.Arguments = Arguments + return s +} + +func (s *CreateForJavaScriptProcedureRequest) WithCopyGrants(CopyGrants *bool) *CreateForJavaScriptProcedureRequest { + s.CopyGrants = CopyGrants + return s +} + +func (s *CreateForJavaScriptProcedureRequest) WithNotNull(NotNull *bool) *CreateForJavaScriptProcedureRequest { + s.NotNull = NotNull + return s +} + +func (s *CreateForJavaScriptProcedureRequest) WithNullInputBehavior(NullInputBehavior *NullInputBehavior) *CreateForJavaScriptProcedureRequest { + s.NullInputBehavior = NullInputBehavior + return s +} + +func (s *CreateForJavaScriptProcedureRequest) WithComment(Comment *string) *CreateForJavaScriptProcedureRequest { + s.Comment = Comment + return s +} + +func (s *CreateForJavaScriptProcedureRequest) WithExecuteAs(ExecuteAs *ExecuteAs) *CreateForJavaScriptProcedureRequest { + s.ExecuteAs = ExecuteAs + return s +} + +func NewCreateForPythonProcedureRequest( + name SchemaObjectIdentifier, + Returns ProcedureReturnsRequest, + RuntimeVersion string, + Packages []ProcedurePackageRequest, + Handler string, +) *CreateForPythonProcedureRequest { + s := CreateForPythonProcedureRequest{} + s.name = name + s.Returns = Returns + s.RuntimeVersion = RuntimeVersion + s.Packages = Packages + s.Handler = Handler + return &s +} + +func (s *CreateForPythonProcedureRequest) WithOrReplace(OrReplace *bool) *CreateForPythonProcedureRequest { + s.OrReplace = OrReplace + return s +} + +func (s *CreateForPythonProcedureRequest) WithSecure(Secure *bool) *CreateForPythonProcedureRequest { + s.Secure = Secure + return s +} + +func (s *CreateForPythonProcedureRequest) WithArguments(Arguments []ProcedureArgumentRequest) *CreateForPythonProcedureRequest { + s.Arguments = Arguments + return s +} + +func (s *CreateForPythonProcedureRequest) WithCopyGrants(CopyGrants *bool) *CreateForPythonProcedureRequest { + s.CopyGrants = CopyGrants + return s +} + +func (s *CreateForPythonProcedureRequest) WithImports(Imports []ProcedureImportRequest) *CreateForPythonProcedureRequest { + s.Imports = Imports + return s +} + +func (s *CreateForPythonProcedureRequest) WithExternalAccessIntegrations(ExternalAccessIntegrations []AccountObjectIdentifier) *CreateForPythonProcedureRequest { + s.ExternalAccessIntegrations = ExternalAccessIntegrations + return s +} + +func (s *CreateForPythonProcedureRequest) WithSecrets(Secrets []Secret) *CreateForPythonProcedureRequest { + s.Secrets = Secrets + return s +} + +func (s *CreateForPythonProcedureRequest) WithNullInputBehavior(NullInputBehavior *NullInputBehavior) *CreateForPythonProcedureRequest { + s.NullInputBehavior = NullInputBehavior + return s +} + +func (s *CreateForPythonProcedureRequest) WithComment(Comment *string) *CreateForPythonProcedureRequest { + s.Comment = Comment + return s +} + +func (s *CreateForPythonProcedureRequest) WithExecuteAs(ExecuteAs *ExecuteAs) *CreateForPythonProcedureRequest { + s.ExecuteAs = ExecuteAs + return s +} + +func (s *CreateForPythonProcedureRequest) WithProcedureDefinition(ProcedureDefinition *string) *CreateForPythonProcedureRequest { + s.ProcedureDefinition = ProcedureDefinition + return s +} + +func NewCreateForScalaProcedureRequest( + name SchemaObjectIdentifier, + Returns ProcedureReturnsRequest, + RuntimeVersion string, + Packages []ProcedurePackageRequest, + Handler string, +) *CreateForScalaProcedureRequest { + s := CreateForScalaProcedureRequest{} + s.name = name + s.Returns = Returns + s.RuntimeVersion = RuntimeVersion + s.Packages = Packages + s.Handler = Handler + return &s +} + +func (s *CreateForScalaProcedureRequest) WithOrReplace(OrReplace *bool) *CreateForScalaProcedureRequest { + s.OrReplace = OrReplace + return s +} + +func (s *CreateForScalaProcedureRequest) WithSecure(Secure *bool) *CreateForScalaProcedureRequest { + s.Secure = Secure + return s +} + +func (s *CreateForScalaProcedureRequest) WithArguments(Arguments []ProcedureArgumentRequest) *CreateForScalaProcedureRequest { + s.Arguments = Arguments + return s +} + +func (s *CreateForScalaProcedureRequest) WithCopyGrants(CopyGrants *bool) *CreateForScalaProcedureRequest { + s.CopyGrants = CopyGrants + return s +} + +func (s *CreateForScalaProcedureRequest) WithImports(Imports []ProcedureImportRequest) *CreateForScalaProcedureRequest { + s.Imports = Imports + return s +} + +func (s *CreateForScalaProcedureRequest) WithTargetPath(TargetPath *string) *CreateForScalaProcedureRequest { + s.TargetPath = TargetPath + return s +} + +func (s *CreateForScalaProcedureRequest) WithNullInputBehavior(NullInputBehavior *NullInputBehavior) *CreateForScalaProcedureRequest { + s.NullInputBehavior = NullInputBehavior + return s +} + +func (s *CreateForScalaProcedureRequest) WithComment(Comment *string) *CreateForScalaProcedureRequest { + s.Comment = Comment + return s +} + +func (s *CreateForScalaProcedureRequest) WithExecuteAs(ExecuteAs *ExecuteAs) *CreateForScalaProcedureRequest { + s.ExecuteAs = ExecuteAs + return s +} + +func (s *CreateForScalaProcedureRequest) WithProcedureDefinition(ProcedureDefinition *string) *CreateForScalaProcedureRequest { + s.ProcedureDefinition = ProcedureDefinition + return s +} + +func NewCreateForSQLProcedureRequest( + name SchemaObjectIdentifier, + Returns ProcedureSQLReturnsRequest, + ProcedureDefinition string, +) *CreateForSQLProcedureRequest { + s := CreateForSQLProcedureRequest{} + s.name = name + s.Returns = Returns + s.ProcedureDefinition = ProcedureDefinition + return &s +} + +func (s *CreateForSQLProcedureRequest) WithOrReplace(OrReplace *bool) *CreateForSQLProcedureRequest { + s.OrReplace = OrReplace + return s +} + +func (s *CreateForSQLProcedureRequest) WithSecure(Secure *bool) *CreateForSQLProcedureRequest { + s.Secure = Secure + return s +} + +func (s *CreateForSQLProcedureRequest) WithArguments(Arguments []ProcedureArgumentRequest) *CreateForSQLProcedureRequest { + s.Arguments = Arguments + return s +} + +func (s *CreateForSQLProcedureRequest) WithCopyGrants(CopyGrants *bool) *CreateForSQLProcedureRequest { + s.CopyGrants = CopyGrants + return s +} + +func (s *CreateForSQLProcedureRequest) WithNullInputBehavior(NullInputBehavior *NullInputBehavior) *CreateForSQLProcedureRequest { + s.NullInputBehavior = NullInputBehavior + return s +} + +func (s *CreateForSQLProcedureRequest) WithComment(Comment *string) *CreateForSQLProcedureRequest { + s.Comment = Comment + return s +} + +func (s *CreateForSQLProcedureRequest) WithExecuteAs(ExecuteAs *ExecuteAs) *CreateForSQLProcedureRequest { + s.ExecuteAs = ExecuteAs + return s +} + +func NewProcedureSQLReturnsRequest() *ProcedureSQLReturnsRequest { + return &ProcedureSQLReturnsRequest{} +} + +func (s *ProcedureSQLReturnsRequest) WithResultDataType(ResultDataType *ProcedureReturnsResultDataTypeRequest) *ProcedureSQLReturnsRequest { + s.ResultDataType = ResultDataType + return s +} + +func (s *ProcedureSQLReturnsRequest) WithTable(Table *ProcedureReturnsTableRequest) *ProcedureSQLReturnsRequest { + s.Table = Table + return s +} + +func (s *ProcedureSQLReturnsRequest) WithNotNull(NotNull *bool) *ProcedureSQLReturnsRequest { + s.NotNull = NotNull + return s +} + +func NewAlterProcedureRequest( + name SchemaObjectIdentifier, + ArgumentDataTypes []DataType, +) *AlterProcedureRequest { + s := AlterProcedureRequest{} + s.name = name + s.ArgumentDataTypes = ArgumentDataTypes + return &s +} + +func (s *AlterProcedureRequest) WithIfExists(IfExists *bool) *AlterProcedureRequest { + s.IfExists = IfExists + return s +} + +func (s *AlterProcedureRequest) WithRenameTo(RenameTo *SchemaObjectIdentifier) *AlterProcedureRequest { + s.RenameTo = RenameTo + return s +} + +func (s *AlterProcedureRequest) WithSetComment(SetComment *string) *AlterProcedureRequest { + s.SetComment = SetComment + return s +} + +func (s *AlterProcedureRequest) WithSetLogLevel(SetLogLevel *string) *AlterProcedureRequest { + s.SetLogLevel = SetLogLevel + return s +} + +func (s *AlterProcedureRequest) WithSetTraceLevel(SetTraceLevel *string) *AlterProcedureRequest { + s.SetTraceLevel = SetTraceLevel + return s +} + +func (s *AlterProcedureRequest) WithUnsetComment(UnsetComment *bool) *AlterProcedureRequest { + s.UnsetComment = UnsetComment + return s +} + +func (s *AlterProcedureRequest) WithSetTags(SetTags []TagAssociation) *AlterProcedureRequest { + s.SetTags = SetTags + return s +} + +func (s *AlterProcedureRequest) WithUnsetTags(UnsetTags []ObjectIdentifier) *AlterProcedureRequest { + s.UnsetTags = UnsetTags + return s +} + +func (s *AlterProcedureRequest) WithExecuteAs(ExecuteAs *ExecuteAs) *AlterProcedureRequest { + s.ExecuteAs = ExecuteAs + return s +} + +func NewDropProcedureRequest( + name SchemaObjectIdentifier, + ArgumentDataTypes []DataType, +) *DropProcedureRequest { + s := DropProcedureRequest{} + s.name = name + s.ArgumentDataTypes = ArgumentDataTypes + return &s +} + +func (s *DropProcedureRequest) WithIfExists(IfExists *bool) *DropProcedureRequest { + s.IfExists = IfExists + return s +} + +func NewShowProcedureRequest() *ShowProcedureRequest { + return &ShowProcedureRequest{} +} + +func (s *ShowProcedureRequest) WithLike(Like *Like) *ShowProcedureRequest { + s.Like = Like + return s +} + +func (s *ShowProcedureRequest) WithIn(In *In) *ShowProcedureRequest { + s.In = In + return s +} + +func NewDescribeProcedureRequest( + name SchemaObjectIdentifier, + ArgumentDataTypes []DataType, +) *DescribeProcedureRequest { + s := DescribeProcedureRequest{} + s.name = name + s.ArgumentDataTypes = ArgumentDataTypes + return &s +} diff --git a/pkg/sdk/procedures_dto_gen.go b/pkg/sdk/procedures_dto_gen.go new file mode 100644 index 0000000000..89980f6e90 --- /dev/null +++ b/pkg/sdk/procedures_dto_gen.go @@ -0,0 +1,169 @@ +package sdk + +//go:generate go run ./dto-builder-generator/main.go + +var ( + _ optionsProvider[CreateForJavaProcedureOptions] = new(CreateForJavaProcedureRequest) + _ optionsProvider[CreateForJavaScriptProcedureOptions] = new(CreateForJavaScriptProcedureRequest) + _ optionsProvider[CreateForPythonProcedureOptions] = new(CreateForPythonProcedureRequest) + _ optionsProvider[CreateForScalaProcedureOptions] = new(CreateForScalaProcedureRequest) + _ optionsProvider[CreateForSQLProcedureOptions] = new(CreateForSQLProcedureRequest) + _ optionsProvider[AlterProcedureOptions] = new(AlterProcedureRequest) + _ optionsProvider[DropProcedureOptions] = new(DropProcedureRequest) + _ optionsProvider[ShowProcedureOptions] = new(ShowProcedureRequest) + _ optionsProvider[DescribeProcedureOptions] = new(DescribeProcedureRequest) +) + +type CreateForJavaProcedureRequest struct { + OrReplace *bool + Secure *bool + name SchemaObjectIdentifier // required + Arguments []ProcedureArgumentRequest + CopyGrants *bool + Returns ProcedureReturnsRequest // required + RuntimeVersion string // required + Packages []ProcedurePackageRequest // required + Imports []ProcedureImportRequest + Handler string // required + ExternalAccessIntegrations []AccountObjectIdentifier + Secrets []Secret + TargetPath *string + NullInputBehavior *NullInputBehavior + Comment *string + ExecuteAs *ExecuteAs + ProcedureDefinition *string +} + +type ProcedureArgumentRequest struct { + ArgName string // required + ArgDataType DataType // required + DefaultValue *string +} + +type ProcedureReturnsRequest struct { + ResultDataType *ProcedureReturnsResultDataTypeRequest + Table *ProcedureReturnsTableRequest +} + +type ProcedureReturnsResultDataTypeRequest struct { + ResultDataType DataType // required + Null *bool + NotNull *bool +} + +type ProcedureReturnsTableRequest struct { + Columns []ProcedureColumnRequest +} + +type ProcedureColumnRequest struct { + ColumnName string // required + ColumnDataType DataType // required +} + +type ProcedurePackageRequest struct { + Package string // required +} + +type ProcedureImportRequest struct { + Import string // required +} + +type CreateForJavaScriptProcedureRequest struct { + OrReplace *bool + Secure *bool + name SchemaObjectIdentifier // required + Arguments []ProcedureArgumentRequest + CopyGrants *bool + ResultDataType DataType // required + NotNull *bool + NullInputBehavior *NullInputBehavior + Comment *string + ExecuteAs *ExecuteAs + ProcedureDefinition string // required +} + +type CreateForPythonProcedureRequest struct { + OrReplace *bool + Secure *bool + name SchemaObjectIdentifier // required + Arguments []ProcedureArgumentRequest + CopyGrants *bool + Returns ProcedureReturnsRequest // required + RuntimeVersion string // required + Packages []ProcedurePackageRequest // required + Imports []ProcedureImportRequest + Handler string // required + ExternalAccessIntegrations []AccountObjectIdentifier + Secrets []Secret + NullInputBehavior *NullInputBehavior + Comment *string + ExecuteAs *ExecuteAs + ProcedureDefinition *string +} + +type CreateForScalaProcedureRequest struct { + OrReplace *bool + Secure *bool + name SchemaObjectIdentifier // required + Arguments []ProcedureArgumentRequest + CopyGrants *bool + Returns ProcedureReturnsRequest // required + RuntimeVersion string // required + Packages []ProcedurePackageRequest // required + Imports []ProcedureImportRequest + Handler string // required + TargetPath *string + NullInputBehavior *NullInputBehavior + Comment *string + ExecuteAs *ExecuteAs + ProcedureDefinition *string +} + +type CreateForSQLProcedureRequest struct { + OrReplace *bool + Secure *bool + name SchemaObjectIdentifier // required + Arguments []ProcedureArgumentRequest + CopyGrants *bool + Returns ProcedureSQLReturnsRequest // required + NullInputBehavior *NullInputBehavior + Comment *string + ExecuteAs *ExecuteAs + ProcedureDefinition string // required +} + +type ProcedureSQLReturnsRequest struct { + ResultDataType *ProcedureReturnsResultDataTypeRequest + Table *ProcedureReturnsTableRequest + NotNull *bool +} + +type AlterProcedureRequest struct { + IfExists *bool + name SchemaObjectIdentifier // required + ArgumentDataTypes []DataType // required + RenameTo *SchemaObjectIdentifier + SetComment *string + SetLogLevel *string + SetTraceLevel *string + UnsetComment *bool + SetTags []TagAssociation + UnsetTags []ObjectIdentifier + ExecuteAs *ExecuteAs +} + +type DropProcedureRequest struct { + IfExists *bool + name SchemaObjectIdentifier // required + ArgumentDataTypes []DataType // required +} + +type ShowProcedureRequest struct { + Like *Like + In *In +} + +type DescribeProcedureRequest struct { + name SchemaObjectIdentifier // required + ArgumentDataTypes []DataType // required +} diff --git a/pkg/sdk/procedures_gen.go b/pkg/sdk/procedures_gen.go new file mode 100644 index 0000000000..e6b8728975 --- /dev/null +++ b/pkg/sdk/procedures_gen.go @@ -0,0 +1,249 @@ +package sdk + +import ( + "context" + "database/sql" +) + +type Procedures interface { + CreateForJava(ctx context.Context, request *CreateForJavaProcedureRequest) error + CreateForJavaScript(ctx context.Context, request *CreateForJavaScriptProcedureRequest) error + CreateForPython(ctx context.Context, request *CreateForPythonProcedureRequest) error + CreateForScala(ctx context.Context, request *CreateForScalaProcedureRequest) error + CreateForSQL(ctx context.Context, request *CreateForSQLProcedureRequest) error + Alter(ctx context.Context, request *AlterProcedureRequest) error + Drop(ctx context.Context, request *DropProcedureRequest) error + Show(ctx context.Context, request *ShowProcedureRequest) ([]Procedure, error) + ShowByID(ctx context.Context, id SchemaObjectIdentifier) (*Procedure, error) + Describe(ctx context.Context, request *DescribeProcedureRequest) ([]ProcedureDetail, error) +} + +// CreateForJavaProcedureOptions is based on https://docs.snowflake.com/en/sql-reference/sql/create-procedure#java-handler. +type CreateForJavaProcedureOptions struct { + create bool `ddl:"static" sql:"CREATE"` + OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` + Secure *bool `ddl:"keyword" sql:"SECURE"` + procedure bool `ddl:"static" sql:"PROCEDURE"` + name SchemaObjectIdentifier `ddl:"identifier"` + Arguments []ProcedureArgument `ddl:"list,must_parentheses"` + CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` + Returns ProcedureReturns `ddl:"keyword" sql:"RETURNS"` + languageJava bool `ddl:"static" sql:"LANGUAGE JAVA"` + RuntimeVersion string `ddl:"parameter,single_quotes" sql:"RUNTIME_VERSION"` + Packages []ProcedurePackage `ddl:"parameter,parentheses" sql:"PACKAGES"` + Imports []ProcedureImport `ddl:"parameter,parentheses" sql:"IMPORTS"` + Handler string `ddl:"parameter,single_quotes" sql:"HANDLER"` + ExternalAccessIntegrations []AccountObjectIdentifier `ddl:"parameter,parentheses" sql:"EXTERNAL_ACCESS_INTEGRATIONS"` + Secrets []Secret `ddl:"parameter,parentheses" sql:"SECRETS"` + TargetPath *string `ddl:"parameter,single_quotes" sql:"TARGET_PATH"` + NullInputBehavior *NullInputBehavior `ddl:"keyword"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + ExecuteAs *ExecuteAs `ddl:"keyword"` + ProcedureDefinition *string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` +} + +type ProcedureArgument struct { + ArgName string `ddl:"keyword,no_quotes"` + ArgDataType DataType `ddl:"keyword,no_quotes"` + DefaultValue *string `ddl:"parameter,no_equals" sql:"DEFAULT"` +} + +type ProcedureReturns struct { + ResultDataType *ProcedureReturnsResultDataType `ddl:"keyword"` + Table *ProcedureReturnsTable `ddl:"keyword" sql:"TABLE"` +} + +type ProcedureReturnsResultDataType struct { + ResultDataType DataType `ddl:"keyword,no_quotes"` + Null *bool `ddl:"keyword" sql:"NULL"` + NotNull *bool `ddl:"keyword" sql:"NOT NULL"` +} + +type ProcedureReturnsTable struct { + Columns []ProcedureColumn `ddl:"list,must_parentheses"` +} + +type ProcedureColumn struct { + ColumnName string `ddl:"keyword,no_quotes"` + ColumnDataType DataType `ddl:"keyword,no_quotes"` +} + +type ProcedurePackage struct { + Package string `ddl:"keyword,single_quotes"` +} + +type ProcedureImport struct { + Import string `ddl:"keyword,single_quotes"` +} + +// CreateForJavaScriptProcedureOptions is based on https://docs.snowflake.com/en/sql-reference/sql/create-procedure#javascript-handler. +type CreateForJavaScriptProcedureOptions struct { + create bool `ddl:"static" sql:"CREATE"` + OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` + Secure *bool `ddl:"keyword" sql:"SECURE"` + procedure bool `ddl:"static" sql:"PROCEDURE"` + name SchemaObjectIdentifier `ddl:"identifier"` + Arguments []ProcedureArgument `ddl:"list,must_parentheses"` + CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` + ResultDataType DataType `ddl:"parameter,no_equals" sql:"RETURNS"` + NotNull *bool `ddl:"keyword" sql:"NOT NULL"` + languageJavascript bool `ddl:"static" sql:"LANGUAGE JAVASCRIPT"` + NullInputBehavior *NullInputBehavior `ddl:"keyword"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + ExecuteAs *ExecuteAs `ddl:"keyword"` + ProcedureDefinition string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` +} + +// CreateForPythonProcedureOptions is based on https://docs.snowflake.com/en/sql-reference/sql/create-procedure#python-handler. +type CreateForPythonProcedureOptions struct { + create bool `ddl:"static" sql:"CREATE"` + OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` + Secure *bool `ddl:"keyword" sql:"SECURE"` + procedure bool `ddl:"static" sql:"PROCEDURE"` + name SchemaObjectIdentifier `ddl:"identifier"` + Arguments []ProcedureArgument `ddl:"list,must_parentheses"` + CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` + Returns ProcedureReturns `ddl:"keyword" sql:"RETURNS"` + languagePython bool `ddl:"static" sql:"LANGUAGE PYTHON"` + RuntimeVersion string `ddl:"parameter,single_quotes" sql:"RUNTIME_VERSION"` + Packages []ProcedurePackage `ddl:"parameter,parentheses" sql:"PACKAGES"` + Imports []ProcedureImport `ddl:"parameter,parentheses" sql:"IMPORTS"` + Handler string `ddl:"parameter,single_quotes" sql:"HANDLER"` + ExternalAccessIntegrations []AccountObjectIdentifier `ddl:"parameter,parentheses" sql:"EXTERNAL_ACCESS_INTEGRATIONS"` + Secrets []Secret `ddl:"parameter,parentheses" sql:"SECRETS"` + NullInputBehavior *NullInputBehavior `ddl:"keyword"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + ExecuteAs *ExecuteAs `ddl:"keyword"` + ProcedureDefinition *string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` +} + +// CreateForScalaProcedureOptions is based on https://docs.snowflake.com/en/sql-reference/sql/create-procedure#scala-handler. +type CreateForScalaProcedureOptions struct { + create bool `ddl:"static" sql:"CREATE"` + OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` + Secure *bool `ddl:"keyword" sql:"SECURE"` + procedure bool `ddl:"static" sql:"PROCEDURE"` + name SchemaObjectIdentifier `ddl:"identifier"` + Arguments []ProcedureArgument `ddl:"list,must_parentheses"` + CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` + Returns ProcedureReturns `ddl:"keyword" sql:"RETURNS"` + languageScala bool `ddl:"static" sql:"LANGUAGE SCALA"` + RuntimeVersion string `ddl:"parameter,single_quotes" sql:"RUNTIME_VERSION"` + Packages []ProcedurePackage `ddl:"parameter,parentheses" sql:"PACKAGES"` + Imports []ProcedureImport `ddl:"parameter,parentheses" sql:"IMPORTS"` + Handler string `ddl:"parameter,single_quotes" sql:"HANDLER"` + TargetPath *string `ddl:"parameter,single_quotes" sql:"TARGET_PATH"` + NullInputBehavior *NullInputBehavior `ddl:"keyword"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + ExecuteAs *ExecuteAs `ddl:"keyword"` + ProcedureDefinition *string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` +} + +// CreateForSQLProcedureOptions is based on https://docs.snowflake.com/en/sql-reference/sql/create-procedure#snowflake-scripting-handler. +type CreateForSQLProcedureOptions struct { + create bool `ddl:"static" sql:"CREATE"` + OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` + Secure *bool `ddl:"keyword" sql:"SECURE"` + procedure bool `ddl:"static" sql:"PROCEDURE"` + name SchemaObjectIdentifier `ddl:"identifier"` + Arguments []ProcedureArgument `ddl:"list,must_parentheses"` + CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` + Returns ProcedureSQLReturns `ddl:"keyword" sql:"RETURNS"` + languageSql bool `ddl:"static" sql:"LANGUAGE SQL"` + NullInputBehavior *NullInputBehavior `ddl:"keyword"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + ExecuteAs *ExecuteAs `ddl:"keyword"` + ProcedureDefinition string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` +} + +type ProcedureSQLReturns struct { + ResultDataType *ProcedureReturnsResultDataType `ddl:"keyword"` + Table *ProcedureReturnsTable `ddl:"keyword" sql:"TABLE"` + NotNull *bool `ddl:"keyword" sql:"NOT NULL"` +} + +// AlterProcedureOptions is based on https://docs.snowflake.com/en/sql-reference/sql/alter-procedure. +type AlterProcedureOptions struct { + alter bool `ddl:"static" sql:"ALTER"` + procedure bool `ddl:"static" sql:"PROCEDURE"` + IfExists *bool `ddl:"keyword" sql:"IF EXISTS"` + name SchemaObjectIdentifier `ddl:"identifier"` + ArgumentDataTypes []DataType `ddl:"keyword,parentheses"` + RenameTo *SchemaObjectIdentifier `ddl:"identifier" sql:"RENAME TO"` + SetComment *string `ddl:"parameter,single_quotes" sql:"SET COMMENT"` + SetLogLevel *string `ddl:"parameter,single_quotes" sql:"SET LOG_LEVEL"` + SetTraceLevel *string `ddl:"parameter,single_quotes" sql:"SET TRACE_LEVEL"` + UnsetComment *bool `ddl:"keyword" sql:"UNSET COMMENT"` + SetTags []TagAssociation `ddl:"keyword" sql:"SET TAG"` + UnsetTags []ObjectIdentifier `ddl:"keyword" sql:"UNSET TAG"` + ExecuteAs *ExecuteAs `ddl:"keyword"` +} + +// DropProcedureOptions is based on https://docs.snowflake.com/en/sql-reference/sql/drop-procedure. +type DropProcedureOptions struct { + drop bool `ddl:"static" sql:"DROP"` + procedure bool `ddl:"static" sql:"PROCEDURE"` + IfExists *bool `ddl:"keyword" sql:"IF EXISTS"` + name SchemaObjectIdentifier `ddl:"identifier"` + ArgumentDataTypes []DataType `ddl:"keyword,must_parentheses"` +} + +// ShowProcedureOptions is based on https://docs.snowflake.com/en/sql-reference/sql/show-procedures. +type ShowProcedureOptions struct { + show bool `ddl:"static" sql:"SHOW"` + procedures bool `ddl:"static" sql:"PROCEDURES"` + Like *Like `ddl:"keyword" sql:"LIKE"` + In *In `ddl:"keyword" sql:"IN"` +} + +type procedureRow struct { + CreatedOn string `db:"created_on"` + Name string `db:"name"` + SchemaName string `db:"schema_name"` + IsBuiltin string `db:"is_builtin"` + IsAggregate string `db:"is_aggregate"` + IsAnsi string `db:"is_ansi"` + MinNumArguments int `db:"min_num_arguments"` + MaxNumArguments int `db:"max_num_arguments"` + Arguments string `db:"arguments"` + Description string `db:"description"` + CatalogName string `db:"catalog_name"` + IsTableFunction string `db:"is_table_function"` + ValidForClustering string `db:"valid_for_clustering"` + IsSecure sql.NullString `db:"is_secure"` +} + +type Procedure struct { + CreatedOn string + Name string + SchemaName string + IsBuiltin bool + IsAggregate bool + IsAnsi bool + MinNumArguments int + MaxNumArguments int + Arguments string + Description string + CatalogName string + IsTableFunction bool + ValidForClustering bool + IsSecure bool +} + +// DescribeProcedureOptions is based on https://docs.snowflake.com/en/sql-reference/sql/desc-procedure. +type DescribeProcedureOptions struct { + describe bool `ddl:"static" sql:"DESCRIBE"` + procedure bool `ddl:"static" sql:"PROCEDURE"` + name SchemaObjectIdentifier `ddl:"identifier"` + ArgumentDataTypes []DataType `ddl:"keyword,parentheses"` +} + +type procedureDetailRow struct { + Property string `db:"property"` + Value string `db:"value"` +} + +type ProcedureDetail struct { + Property string + Value string +} diff --git a/pkg/sdk/procedures_gen_test.go b/pkg/sdk/procedures_gen_test.go new file mode 100644 index 0000000000..02a8983e07 --- /dev/null +++ b/pkg/sdk/procedures_gen_test.go @@ -0,0 +1,587 @@ +package sdk + +import ( + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/random" +) + +func TestProcedures_CreateForJava(t *testing.T) { + id := RandomSchemaObjectIdentifier() + + defaultOpts := func() *CreateForJavaProcedureOptions { + return &CreateForJavaProcedureOptions{ + name: id, + } + } + + t.Run("validation: nil options", func(t *testing.T) { + var opts *CreateForJavaProcedureOptions = nil + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("validation: incorrect identifier", func(t *testing.T) { + opts := defaultOpts() + opts.name = NewSchemaObjectIdentifier("", "", "") + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("validation: returns", func(t *testing.T) { + opts := defaultOpts() + opts.Returns = ProcedureReturns{} + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("CreateForJavaProcedureOptions.Returns", "ResultDataType", "Table")) + }) + + t.Run("validation: function definition", func(t *testing.T) { + opts := defaultOpts() + opts.TargetPath = String("@~/testfunc.jar") + opts.Packages = []ProcedurePackage{ + { + Package: "com.snowflake:snowpark:1.2.0", + }, + } + assertOptsInvalidJoinedErrors(t, opts, NewError("TARGET_PATH must be nil when AS is nil")) + }) + + t.Run("validation: options are missing", func(t *testing.T) { + opts := defaultOpts() + opts.Returns = ProcedureReturns{ + ResultDataType: &ProcedureReturnsResultDataType{ + ResultDataType: DataTypeVARCHAR, + }, + } + assertOptsInvalidJoinedErrors(t, opts, errNotSet("CreateForJavaProcedureOptions", "Handler")) + assertOptsInvalidJoinedErrors(t, opts, errNotSet("CreateForJavaProcedureOptions", "RuntimeVersion")) + assertOptsInvalidJoinedErrors(t, opts, errNotSet("CreateForJavaProcedureOptions", "Packages")) + }) + + t.Run("all options", func(t *testing.T) { + opts := defaultOpts() + opts.OrReplace = Bool(true) + opts.Secure = Bool(true) + opts.Arguments = []ProcedureArgument{ + { + ArgName: "id", + ArgDataType: DataTypeNumber, + }, + { + ArgName: "name", + ArgDataType: DataTypeVARCHAR, + DefaultValue: String("'test'"), + }, + } + opts.CopyGrants = Bool(true) + opts.Returns = ProcedureReturns{ + Table: &ProcedureReturnsTable{ + Columns: []ProcedureColumn{ + { + ColumnName: "country_code", + ColumnDataType: DataTypeVARCHAR, + }, + }, + }, + } + opts.RuntimeVersion = "1.8" + opts.Packages = []ProcedurePackage{ + { + Package: "com.snowflake:snowpark:1.2.0", + }, + } + opts.Imports = []ProcedureImport{ + { + Import: "test_jar.jar", + }, + } + opts.Handler = "TestFunc.echoVarchar" + opts.ExternalAccessIntegrations = []AccountObjectIdentifier{ + NewAccountObjectIdentifier("ext_integration"), + } + opts.Secrets = []Secret{ + { + VariableName: "variable1", + Name: "name1", + }, + { + VariableName: "variable2", + Name: "name2", + }, + } + opts.TargetPath = String("@~/testfunc.jar") + opts.NullInputBehavior = NullInputBehaviorPointer(NullInputBehaviorStrict) + opts.Comment = String("test comment") + opts.ExecuteAs = ExecuteAsPointer(ExecuteAsCaller) + opts.ProcedureDefinition = String("return id + name;") + assertOptsValidAndSQLEquals(t, opts, `CREATE OR REPLACE SECURE PROCEDURE %s (id NUMBER, name VARCHAR DEFAULT 'test') COPY GRANTS RETURNS TABLE (country_code VARCHAR) LANGUAGE JAVA RUNTIME_VERSION = '1.8' PACKAGES = ('com.snowflake:snowpark:1.2.0') IMPORTS = ('test_jar.jar') HANDLER = 'TestFunc.echoVarchar' EXTERNAL_ACCESS_INTEGRATIONS = ("ext_integration") SECRETS = ('variable1' = name1, 'variable2' = name2) TARGET_PATH = '@~/testfunc.jar' STRICT COMMENT = 'test comment' EXECUTE AS CALLER AS 'return id + name;'`, id.FullyQualifiedName()) + }) +} + +func TestProcedures_CreateForJavaScript(t *testing.T) { + id := RandomSchemaObjectIdentifier() + + defaultOpts := func() *CreateForJavaScriptProcedureOptions { + return &CreateForJavaScriptProcedureOptions{ + name: id, + } + } + + t.Run("validation: nil options", func(t *testing.T) { + var opts *CreateForJavaScriptProcedureOptions = nil + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("validation: incorrect identifier", func(t *testing.T) { + opts := defaultOpts() + opts.name = NewSchemaObjectIdentifier("", "", "") + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("validation: options are missing", func(t *testing.T) { + opts := defaultOpts() + assertOptsInvalidJoinedErrors(t, opts, errNotSet("CreateForJavaScriptProcedureOptions", "ProcedureDefinition")) + }) + + t.Run("all options", func(t *testing.T) { + opts := defaultOpts() + opts.OrReplace = Bool(true) + opts.Secure = Bool(true) + opts.Arguments = []ProcedureArgument{ + { + ArgName: "d", + ArgDataType: "DOUBLE", + DefaultValue: String("1.0"), + }, + } + opts.CopyGrants = Bool(true) + opts.ResultDataType = "DOUBLE" + opts.NotNull = Bool(true) + opts.NullInputBehavior = NullInputBehaviorPointer(NullInputBehaviorStrict) + opts.Comment = String("test comment") + opts.ExecuteAs = ExecuteAsPointer(ExecuteAsCaller) + opts.ProcedureDefinition = "return 1;" + assertOptsValidAndSQLEquals(t, opts, `CREATE OR REPLACE SECURE PROCEDURE %s (d DOUBLE DEFAULT 1.0) COPY GRANTS RETURNS DOUBLE NOT NULL LANGUAGE JAVASCRIPT STRICT COMMENT = 'test comment' EXECUTE AS CALLER AS 'return 1;'`, id.FullyQualifiedName()) + }) +} + +func TestProcedures_CreateForPython(t *testing.T) { + id := RandomSchemaObjectIdentifier() + + defaultOpts := func() *CreateForPythonProcedureOptions { + return &CreateForPythonProcedureOptions{ + name: id, + } + } + + t.Run("validation: nil options", func(t *testing.T) { + var opts *CreateForPythonProcedureOptions = nil + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("validation: incorrect identifier", func(t *testing.T) { + opts := defaultOpts() + opts.name = NewSchemaObjectIdentifier("", "", "") + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("validation: returns", func(t *testing.T) { + opts := defaultOpts() + opts.Returns = ProcedureReturns{} + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("CreateForPythonProcedureOptions.Returns", "ResultDataType", "Table")) + }) + + t.Run("validation: options are missing", func(t *testing.T) { + opts := defaultOpts() + opts.Returns = ProcedureReturns{ + ResultDataType: &ProcedureReturnsResultDataType{ + ResultDataType: DataTypeVARCHAR, + }, + } + assertOptsInvalidJoinedErrors(t, opts, errNotSet("CreateForPythonProcedureOptions", "Handler")) + assertOptsInvalidJoinedErrors(t, opts, errNotSet("CreateForPythonProcedureOptions", "RuntimeVersion")) + assertOptsInvalidJoinedErrors(t, opts, errNotSet("CreateForPythonProcedureOptions", "Packages")) + }) + + t.Run("all options", func(t *testing.T) { + opts := defaultOpts() + opts.OrReplace = Bool(true) + opts.Secure = Bool(true) + opts.Arguments = []ProcedureArgument{ + { + ArgName: "i", + ArgDataType: "int", + DefaultValue: String("1"), + }, + } + opts.CopyGrants = Bool(true) + opts.Returns = ProcedureReturns{ + ResultDataType: &ProcedureReturnsResultDataType{ + ResultDataType: "VARIANT", + Null: Bool(true), + }, + } + opts.RuntimeVersion = "3.8" + opts.Packages = []ProcedurePackage{ + { + Package: "numpy", + }, + { + Package: "pandas", + }, + } + opts.Imports = []ProcedureImport{ + { + Import: "numpy", + }, + { + Import: "pandas", + }, + } + opts.Handler = "udf" + opts.ExternalAccessIntegrations = []AccountObjectIdentifier{ + NewAccountObjectIdentifier("ext_integration"), + } + opts.Secrets = []Secret{ + { + VariableName: "variable1", + Name: "name1", + }, + { + VariableName: "variable2", + Name: "name2", + }, + } + opts.NullInputBehavior = NullInputBehaviorPointer(NullInputBehaviorStrict) + opts.Comment = String("test comment") + opts.ExecuteAs = ExecuteAsPointer(ExecuteAsCaller) + opts.ProcedureDefinition = String("import numpy as np") + assertOptsValidAndSQLEquals(t, opts, `CREATE OR REPLACE SECURE PROCEDURE %s (i int DEFAULT 1) COPY GRANTS RETURNS VARIANT NULL LANGUAGE PYTHON RUNTIME_VERSION = '3.8' PACKAGES = ('numpy', 'pandas') IMPORTS = ('numpy', 'pandas') HANDLER = 'udf' EXTERNAL_ACCESS_INTEGRATIONS = ("ext_integration") SECRETS = ('variable1' = name1, 'variable2' = name2) STRICT COMMENT = 'test comment' EXECUTE AS CALLER AS 'import numpy as np'`, id.FullyQualifiedName()) + }) +} + +func TestProcedures_CreateForScala(t *testing.T) { + id := RandomSchemaObjectIdentifier() + + defaultOpts := func() *CreateForScalaProcedureOptions { + return &CreateForScalaProcedureOptions{ + name: id, + } + } + + t.Run("validation: nil options", func(t *testing.T) { + var opts *CreateForScalaProcedureOptions = nil + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("validation: incorrect identifier", func(t *testing.T) { + opts := defaultOpts() + opts.name = NewSchemaObjectIdentifier("", "", "") + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("validation: returns", func(t *testing.T) { + opts := defaultOpts() + opts.Returns = ProcedureReturns{} + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("CreateForScalaProcedureOptions.Returns", "ResultDataType", "Table")) + }) + + t.Run("validation: function definition", func(t *testing.T) { + opts := defaultOpts() + opts.TargetPath = String("@~/testfunc.jar") + opts.Packages = []ProcedurePackage{ + { + Package: "com.snowflake:snowpark:1.2.0", + }, + } + assertOptsInvalidJoinedErrors(t, opts, NewError("TARGET_PATH must be nil when AS is nil")) + }) + + t.Run("validation: options are missing", func(t *testing.T) { + opts := defaultOpts() + opts.Returns = ProcedureReturns{ + ResultDataType: &ProcedureReturnsResultDataType{ + ResultDataType: DataTypeVARCHAR, + }, + } + assertOptsInvalidJoinedErrors(t, opts, errNotSet("CreateForScalaProcedureOptions", "Handler")) + assertOptsInvalidJoinedErrors(t, opts, errNotSet("CreateForScalaProcedureOptions", "RuntimeVersion")) + assertOptsInvalidJoinedErrors(t, opts, errNotSet("CreateForScalaProcedureOptions", "Packages")) + }) + + t.Run("all options", func(t *testing.T) { + opts := defaultOpts() + opts.OrReplace = Bool(true) + opts.Secure = Bool(true) + opts.Arguments = []ProcedureArgument{ + { + ArgName: "x", + ArgDataType: "VARCHAR", + DefaultValue: String("'test'"), + }, + } + opts.CopyGrants = Bool(true) + opts.Returns = ProcedureReturns{ + ResultDataType: &ProcedureReturnsResultDataType{ + ResultDataType: "VARCHAR", + NotNull: Bool(true), + }, + } + opts.RuntimeVersion = "2.0" + opts.Packages = []ProcedurePackage{ + { + Package: "com.snowflake:snowpark:1.2.0", + }, + } + opts.Imports = []ProcedureImport{ + { + Import: "@udf_libs/echohandler.jar", + }, + } + opts.Handler = "Echo.echoVarchar" + opts.TargetPath = String("@~/testfunc.jar") + opts.NullInputBehavior = NullInputBehaviorPointer(NullInputBehaviorStrict) + opts.Comment = String("test comment") + opts.ExecuteAs = ExecuteAsPointer(ExecuteAsCaller) + opts.ProcedureDefinition = String("return x") + assertOptsValidAndSQLEquals(t, opts, `CREATE OR REPLACE SECURE PROCEDURE %s (x VARCHAR DEFAULT 'test') COPY GRANTS RETURNS VARCHAR NOT NULL LANGUAGE SCALA RUNTIME_VERSION = '2.0' PACKAGES = ('com.snowflake:snowpark:1.2.0') IMPORTS = ('@udf_libs/echohandler.jar') HANDLER = 'Echo.echoVarchar' TARGET_PATH = '@~/testfunc.jar' STRICT COMMENT = 'test comment' EXECUTE AS CALLER AS 'return x'`, id.FullyQualifiedName()) + }) +} + +func TestProcedures_CreateForSQL(t *testing.T) { + id := RandomSchemaObjectIdentifier() + + defaultOpts := func() *CreateForSQLProcedureOptions { + return &CreateForSQLProcedureOptions{ + name: id, + } + } + + t.Run("validation: nil options", func(t *testing.T) { + var opts *CreateForSQLProcedureOptions = nil + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("validation: incorrect identifier", func(t *testing.T) { + opts := defaultOpts() + opts.name = NewSchemaObjectIdentifier("", "", "") + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("validation: options are missing", func(t *testing.T) { + opts := defaultOpts() + assertOptsInvalidJoinedErrors(t, opts, errNotSet("CreateForSQLProcedureOptions", "ProcedureDefinition")) + }) + + t.Run("create with no arguments", func(t *testing.T) { + opts := defaultOpts() + opts.Returns = ProcedureSQLReturns{ + ResultDataType: &ProcedureReturnsResultDataType{ + ResultDataType: DataTypeFloat, + }, + } + opts.ProcedureDefinition = "3.141592654::FLOAT" + assertOptsValidAndSQLEquals(t, opts, `CREATE PROCEDURE %s () RETURNS FLOAT LANGUAGE SQL AS '3.141592654::FLOAT'`, id.FullyQualifiedName()) + }) + + t.Run("all options", func(t *testing.T) { + opts := defaultOpts() + opts.OrReplace = Bool(true) + opts.Secure = Bool(true) + opts.Arguments = []ProcedureArgument{ + { + ArgName: "message", + ArgDataType: "VARCHAR", + DefaultValue: String("'test'"), + }, + } + opts.CopyGrants = Bool(true) + opts.Returns = ProcedureSQLReturns{ + ResultDataType: &ProcedureReturnsResultDataType{ + ResultDataType: "VARCHAR", + }, + NotNull: Bool(true), + } + opts.NullInputBehavior = NullInputBehaviorPointer(NullInputBehaviorStrict) + opts.Comment = String("test comment") + opts.ExecuteAs = ExecuteAsPointer(ExecuteAsCaller) + opts.ProcedureDefinition = "3.141592654::FLOAT" + assertOptsValidAndSQLEquals(t, opts, `CREATE OR REPLACE SECURE PROCEDURE %s (message VARCHAR DEFAULT 'test') COPY GRANTS RETURNS VARCHAR NOT NULL LANGUAGE SQL STRICT COMMENT = 'test comment' EXECUTE AS CALLER AS '3.141592654::FLOAT'`, id.FullyQualifiedName()) + }) +} + +func TestProcedures_Drop(t *testing.T) { + id := RandomSchemaObjectIdentifier() + + defaultOpts := func() *DropProcedureOptions { + return &DropProcedureOptions{ + name: id, + } + } + t.Run("validation: nil options", func(t *testing.T) { + var opts *DropProcedureOptions = nil + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("validation: incorrect identifier", func(t *testing.T) { + opts := defaultOpts() + opts.name = NewSchemaObjectIdentifier("", "", "") + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("all options", func(t *testing.T) { + opts := defaultOpts() + opts.IfExists = Bool(true) + opts.ArgumentDataTypes = []DataType{DataTypeVARCHAR, DataTypeNumber} + assertOptsValidAndSQLEquals(t, opts, `DROP PROCEDURE IF EXISTS %s (VARCHAR, NUMBER)`, id.FullyQualifiedName()) + }) +} + +func TestProcedures_Alter(t *testing.T) { + id := RandomSchemaObjectIdentifier() + + defaultOpts := func() *AlterProcedureOptions { + return &AlterProcedureOptions{ + name: id, + IfExists: Bool(true), + ArgumentDataTypes: []DataType{DataTypeVARCHAR, DataTypeNumber}, + } + } + + t.Run("validation: nil options", func(t *testing.T) { + opts := (*AlterProcedureOptions)(nil) + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("validation: incorrect identifier", func(t *testing.T) { + opts := defaultOpts() + opts.name = NewSchemaObjectIdentifier("", "", "") + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("validation: exactly one field should be present", func(t *testing.T) { + opts := defaultOpts() + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("AlterProcedureOptions", "RenameTo", "SetComment", "SetLogLevel", "SetTraceLevel", "UnsetComment", "SetTags", "UnsetTags", "ExecuteAs")) + }) + + t.Run("validation: exactly one field should be present", func(t *testing.T) { + opts := defaultOpts() + opts.SetLogLevel = String("DEBUG") + opts.UnsetComment = Bool(true) + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("AlterProcedureOptions", "RenameTo", "SetComment", "SetLogLevel", "SetTraceLevel", "UnsetComment", "SetTags", "UnsetTags", "ExecuteAs")) + }) + + t.Run("alter: rename to", func(t *testing.T) { + opts := defaultOpts() + target := NewSchemaObjectIdentifier(id.DatabaseName(), id.SchemaName(), random.StringN(12)) + opts.RenameTo = &target + assertOptsValidAndSQLEquals(t, opts, `ALTER PROCEDURE IF EXISTS %s (VARCHAR, NUMBER) RENAME TO %s`, id.FullyQualifiedName(), opts.RenameTo.FullyQualifiedName()) + }) + + t.Run("alter: execute as", func(t *testing.T) { + opts := defaultOpts() + executeAs := ExecuteAsCaller + opts.ExecuteAs = &executeAs + assertOptsValidAndSQLEquals(t, opts, `ALTER PROCEDURE IF EXISTS %s (VARCHAR, NUMBER) EXECUTE AS CALLER`, id.FullyQualifiedName()) + }) + + t.Run("alter: set log level", func(t *testing.T) { + opts := defaultOpts() + opts.SetLogLevel = String("DEBUG") + assertOptsValidAndSQLEquals(t, opts, `ALTER PROCEDURE IF EXISTS %s (VARCHAR, NUMBER) SET LOG_LEVEL = 'DEBUG'`, id.FullyQualifiedName()) + }) + + t.Run("alter: set trace level", func(t *testing.T) { + opts := defaultOpts() + opts.SetTraceLevel = String("DEBUG") + assertOptsValidAndSQLEquals(t, opts, `ALTER PROCEDURE IF EXISTS %s (VARCHAR, NUMBER) SET TRACE_LEVEL = 'DEBUG'`, id.FullyQualifiedName()) + }) + + t.Run("alter: set comment", func(t *testing.T) { + opts := defaultOpts() + opts.SetComment = String("comment") + assertOptsValidAndSQLEquals(t, opts, `ALTER PROCEDURE IF EXISTS %s (VARCHAR, NUMBER) SET COMMENT = 'comment'`, id.FullyQualifiedName()) + }) + + t.Run("alter: unset comment", func(t *testing.T) { + opts := defaultOpts() + opts.UnsetComment = Bool(true) + assertOptsValidAndSQLEquals(t, opts, `ALTER PROCEDURE IF EXISTS %s (VARCHAR, NUMBER) UNSET COMMENT`, id.FullyQualifiedName()) + }) + + t.Run("alter: set tags", func(t *testing.T) { + opts := defaultOpts() + opts.SetTags = []TagAssociation{ + { + Name: NewAccountObjectIdentifier("tag1"), + Value: "value1", + }, + } + assertOptsValidAndSQLEquals(t, opts, `ALTER PROCEDURE IF EXISTS %s (VARCHAR, NUMBER) SET TAG "tag1" = 'value1'`, id.FullyQualifiedName()) + }) + + t.Run("alter: unset tags", func(t *testing.T) { + opts := defaultOpts() + opts.UnsetTags = []ObjectIdentifier{ + NewAccountObjectIdentifier("tag1"), + NewAccountObjectIdentifier("tag2"), + } + assertOptsValidAndSQLEquals(t, opts, `ALTER PROCEDURE IF EXISTS %s (VARCHAR, NUMBER) UNSET TAG "tag1", "tag2"`, id.FullyQualifiedName()) + }) +} + +func TestProcedures_Show(t *testing.T) { + defaultOpts := func() *ShowProcedureOptions { + return &ShowProcedureOptions{} + } + + t.Run("validation: nil options", func(t *testing.T) { + opts := (*ShowProcedureOptions)(nil) + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("show with empty options", func(t *testing.T) { + opts := defaultOpts() + assertOptsValidAndSQLEquals(t, opts, `SHOW PROCEDURES`) + }) + + t.Run("show with like", func(t *testing.T) { + opts := defaultOpts() + opts.Like = &Like{ + Pattern: String("pattern"), + } + assertOptsValidAndSQLEquals(t, opts, `SHOW PROCEDURES LIKE 'pattern'`) + }) + + t.Run("show with in", func(t *testing.T) { + opts := defaultOpts() + opts.In = &In{ + Account: Bool(true), + } + assertOptsValidAndSQLEquals(t, opts, `SHOW PROCEDURES IN ACCOUNT`) + }) +} + +func TestProcedures_Describe(t *testing.T) { + id := RandomSchemaObjectIdentifier() + + defaultOpts := func() *DescribeProcedureOptions { + return &DescribeProcedureOptions{ + name: id, + } + } + + t.Run("validation: nil options", func(t *testing.T) { + opts := (*DescribeProcedureOptions)(nil) + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("validation: incorrect identifier", func(t *testing.T) { + opts := defaultOpts() + opts.name = NewSchemaObjectIdentifier("", "", "") + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("all options", func(t *testing.T) { + opts := defaultOpts() + opts.ArgumentDataTypes = []DataType{DataTypeVARCHAR, DataTypeNumber} + assertOptsValidAndSQLEquals(t, opts, `DESCRIBE PROCEDURE %s (VARCHAR, NUMBER)`, id.FullyQualifiedName()) + }) +} diff --git a/pkg/sdk/procedures_impl_gen.go b/pkg/sdk/procedures_impl_gen.go new file mode 100644 index 0000000000..24cf94c072 --- /dev/null +++ b/pkg/sdk/procedures_impl_gen.go @@ -0,0 +1,436 @@ +package sdk + +import ( + "context" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" +) + +var _ Procedures = (*procedures)(nil) + +type procedures struct { + client *Client +} + +func (v *procedures) CreateForJava(ctx context.Context, request *CreateForJavaProcedureRequest) error { + opts := request.toOpts() + return validateAndExec(v.client, ctx, opts) +} + +func (v *procedures) CreateForJavaScript(ctx context.Context, request *CreateForJavaScriptProcedureRequest) error { + opts := request.toOpts() + return validateAndExec(v.client, ctx, opts) +} + +func (v *procedures) CreateForPython(ctx context.Context, request *CreateForPythonProcedureRequest) error { + opts := request.toOpts() + return validateAndExec(v.client, ctx, opts) +} + +func (v *procedures) CreateForScala(ctx context.Context, request *CreateForScalaProcedureRequest) error { + opts := request.toOpts() + return validateAndExec(v.client, ctx, opts) +} + +func (v *procedures) CreateForSQL(ctx context.Context, request *CreateForSQLProcedureRequest) error { + opts := request.toOpts() + return validateAndExec(v.client, ctx, opts) +} + +func (v *procedures) Alter(ctx context.Context, request *AlterProcedureRequest) error { + opts := request.toOpts() + return validateAndExec(v.client, ctx, opts) +} + +func (v *procedures) Drop(ctx context.Context, request *DropProcedureRequest) error { + opts := request.toOpts() + return validateAndExec(v.client, ctx, opts) +} + +func (v *procedures) Show(ctx context.Context, request *ShowProcedureRequest) ([]Procedure, error) { + opts := request.toOpts() + dbRows, err := validateAndQuery[procedureRow](v.client, ctx, opts) + if err != nil { + return nil, err + } + resultList := convertRows[procedureRow, Procedure](dbRows) + return resultList, nil +} + +func (v *procedures) ShowByID(ctx context.Context, id SchemaObjectIdentifier) (*Procedure, error) { + request := NewShowProcedureRequest().WithIn(&In{Database: NewAccountObjectIdentifier(id.DatabaseName())}).WithLike(&Like{String(id.Name())}) + procedures, err := v.Show(ctx, request) + if err != nil { + return nil, err + } + return collections.FindOne(procedures, func(r Procedure) bool { return r.Name == id.Name() }) +} + +func (v *procedures) Describe(ctx context.Context, request *DescribeProcedureRequest) ([]ProcedureDetail, error) { + opts := request.toOpts() + rows, err := validateAndQuery[procedureDetailRow](v.client, ctx, opts) + if err != nil { + return nil, err + } + return convertRows[procedureDetailRow, ProcedureDetail](rows), nil +} + +func (r *CreateForJavaProcedureRequest) toOpts() *CreateForJavaProcedureOptions { + opts := &CreateForJavaProcedureOptions{ + OrReplace: r.OrReplace, + Secure: r.Secure, + name: r.name, + + CopyGrants: r.CopyGrants, + + RuntimeVersion: r.RuntimeVersion, + + Handler: r.Handler, + ExternalAccessIntegrations: r.ExternalAccessIntegrations, + Secrets: r.Secrets, + TargetPath: r.TargetPath, + NullInputBehavior: r.NullInputBehavior, + Comment: r.Comment, + ExecuteAs: r.ExecuteAs, + ProcedureDefinition: r.ProcedureDefinition, + } + if r.Arguments != nil { + s := make([]ProcedureArgument, len(r.Arguments)) + for i, v := range r.Arguments { + s[i] = ProcedureArgument{ + ArgName: v.ArgName, + ArgDataType: v.ArgDataType, + DefaultValue: v.DefaultValue, + } + } + opts.Arguments = s + } + opts.Returns = ProcedureReturns{} + if r.Returns.ResultDataType != nil { + opts.Returns.ResultDataType = &ProcedureReturnsResultDataType{ + ResultDataType: r.Returns.ResultDataType.ResultDataType, + Null: r.Returns.ResultDataType.Null, + NotNull: r.Returns.ResultDataType.NotNull, + } + } + if r.Returns.Table != nil { + opts.Returns.Table = &ProcedureReturnsTable{} + if r.Returns.Table.Columns != nil { + s := make([]ProcedureColumn, len(r.Returns.Table.Columns)) + for i, v := range r.Returns.Table.Columns { + s[i] = ProcedureColumn{ + ColumnName: v.ColumnName, + ColumnDataType: v.ColumnDataType, + } + } + opts.Returns.Table.Columns = s + } + } + if r.Packages != nil { + s := make([]ProcedurePackage, len(r.Packages)) + for i, v := range r.Packages { + s[i] = ProcedurePackage{ + Package: v.Package, + } + } + opts.Packages = s + } + if r.Imports != nil { + s := make([]ProcedureImport, len(r.Imports)) + for i, v := range r.Imports { + s[i] = ProcedureImport{ + Import: v.Import, + } + } + opts.Imports = s + } + return opts +} + +func (r *CreateForJavaScriptProcedureRequest) toOpts() *CreateForJavaScriptProcedureOptions { + opts := &CreateForJavaScriptProcedureOptions{ + OrReplace: r.OrReplace, + Secure: r.Secure, + name: r.name, + + CopyGrants: r.CopyGrants, + ResultDataType: r.ResultDataType, + NotNull: r.NotNull, + NullInputBehavior: r.NullInputBehavior, + Comment: r.Comment, + ExecuteAs: r.ExecuteAs, + ProcedureDefinition: r.ProcedureDefinition, + } + if r.Arguments != nil { + s := make([]ProcedureArgument, len(r.Arguments)) + for i, v := range r.Arguments { + s[i] = ProcedureArgument{ + ArgName: v.ArgName, + ArgDataType: v.ArgDataType, + DefaultValue: v.DefaultValue, + } + } + opts.Arguments = s + } + return opts +} + +func (r *CreateForPythonProcedureRequest) toOpts() *CreateForPythonProcedureOptions { + opts := &CreateForPythonProcedureOptions{ + OrReplace: r.OrReplace, + Secure: r.Secure, + name: r.name, + + CopyGrants: r.CopyGrants, + + RuntimeVersion: r.RuntimeVersion, + + Handler: r.Handler, + ExternalAccessIntegrations: r.ExternalAccessIntegrations, + Secrets: r.Secrets, + NullInputBehavior: r.NullInputBehavior, + Comment: r.Comment, + ExecuteAs: r.ExecuteAs, + ProcedureDefinition: r.ProcedureDefinition, + } + if r.Arguments != nil { + s := make([]ProcedureArgument, len(r.Arguments)) + for i, v := range r.Arguments { + s[i] = ProcedureArgument{ + ArgName: v.ArgName, + ArgDataType: v.ArgDataType, + DefaultValue: v.DefaultValue, + } + } + opts.Arguments = s + } + opts.Returns = ProcedureReturns{} + if r.Returns.ResultDataType != nil { + opts.Returns.ResultDataType = &ProcedureReturnsResultDataType{ + ResultDataType: r.Returns.ResultDataType.ResultDataType, + Null: r.Returns.ResultDataType.Null, + NotNull: r.Returns.ResultDataType.NotNull, + } + } + if r.Returns.Table != nil { + opts.Returns.Table = &ProcedureReturnsTable{} + if r.Returns.Table.Columns != nil { + s := make([]ProcedureColumn, len(r.Returns.Table.Columns)) + for i, v := range r.Returns.Table.Columns { + s[i] = ProcedureColumn{ + ColumnName: v.ColumnName, + ColumnDataType: v.ColumnDataType, + } + } + opts.Returns.Table.Columns = s + } + } + if r.Packages != nil { + s := make([]ProcedurePackage, len(r.Packages)) + for i, v := range r.Packages { + s[i] = ProcedurePackage{ + Package: v.Package, + } + } + opts.Packages = s + } + if r.Imports != nil { + s := make([]ProcedureImport, len(r.Imports)) + for i, v := range r.Imports { + s[i] = ProcedureImport{ + Import: v.Import, + } + } + opts.Imports = s + } + return opts +} + +func (r *CreateForScalaProcedureRequest) toOpts() *CreateForScalaProcedureOptions { + opts := &CreateForScalaProcedureOptions{ + OrReplace: r.OrReplace, + Secure: r.Secure, + name: r.name, + + CopyGrants: r.CopyGrants, + + RuntimeVersion: r.RuntimeVersion, + + Handler: r.Handler, + TargetPath: r.TargetPath, + NullInputBehavior: r.NullInputBehavior, + Comment: r.Comment, + ExecuteAs: r.ExecuteAs, + ProcedureDefinition: r.ProcedureDefinition, + } + if r.Arguments != nil { + s := make([]ProcedureArgument, len(r.Arguments)) + for i, v := range r.Arguments { + s[i] = ProcedureArgument{ + ArgName: v.ArgName, + ArgDataType: v.ArgDataType, + DefaultValue: v.DefaultValue, + } + } + opts.Arguments = s + } + opts.Returns = ProcedureReturns{} + if r.Returns.ResultDataType != nil { + opts.Returns.ResultDataType = &ProcedureReturnsResultDataType{ + ResultDataType: r.Returns.ResultDataType.ResultDataType, + Null: r.Returns.ResultDataType.Null, + NotNull: r.Returns.ResultDataType.NotNull, + } + } + if r.Returns.Table != nil { + opts.Returns.Table = &ProcedureReturnsTable{} + if r.Returns.Table.Columns != nil { + s := make([]ProcedureColumn, len(r.Returns.Table.Columns)) + for i, v := range r.Returns.Table.Columns { + s[i] = ProcedureColumn{ + ColumnName: v.ColumnName, + ColumnDataType: v.ColumnDataType, + } + } + opts.Returns.Table.Columns = s + } + } + if r.Packages != nil { + s := make([]ProcedurePackage, len(r.Packages)) + for i, v := range r.Packages { + s[i] = ProcedurePackage{ + Package: v.Package, + } + } + opts.Packages = s + } + if r.Imports != nil { + s := make([]ProcedureImport, len(r.Imports)) + for i, v := range r.Imports { + s[i] = ProcedureImport{ + Import: v.Import, + } + } + opts.Imports = s + } + return opts +} + +func (r *CreateForSQLProcedureRequest) toOpts() *CreateForSQLProcedureOptions { + opts := &CreateForSQLProcedureOptions{ + OrReplace: r.OrReplace, + Secure: r.Secure, + name: r.name, + + CopyGrants: r.CopyGrants, + + NullInputBehavior: r.NullInputBehavior, + Comment: r.Comment, + ExecuteAs: r.ExecuteAs, + ProcedureDefinition: r.ProcedureDefinition, + } + if r.Arguments != nil { + s := make([]ProcedureArgument, len(r.Arguments)) + for i, v := range r.Arguments { + s[i] = ProcedureArgument{ + ArgName: v.ArgName, + ArgDataType: v.ArgDataType, + DefaultValue: v.DefaultValue, + } + } + opts.Arguments = s + } + opts.Returns = ProcedureSQLReturns{ + NotNull: r.Returns.NotNull, + } + if r.Returns.ResultDataType != nil { + opts.Returns.ResultDataType = &ProcedureReturnsResultDataType{ + ResultDataType: r.Returns.ResultDataType.ResultDataType, + } + } + if r.Returns.Table != nil { + opts.Returns.Table = &ProcedureReturnsTable{} + if r.Returns.Table.Columns != nil { + s := make([]ProcedureColumn, len(r.Returns.Table.Columns)) + for i, v := range r.Returns.Table.Columns { + s[i] = ProcedureColumn{ + ColumnName: v.ColumnName, + ColumnDataType: v.ColumnDataType, + } + } + opts.Returns.Table.Columns = s + } + } + return opts +} + +func (r *AlterProcedureRequest) toOpts() *AlterProcedureOptions { + opts := &AlterProcedureOptions{ + IfExists: r.IfExists, + name: r.name, + ArgumentDataTypes: r.ArgumentDataTypes, + RenameTo: r.RenameTo, + SetComment: r.SetComment, + SetLogLevel: r.SetLogLevel, + SetTraceLevel: r.SetTraceLevel, + UnsetComment: r.UnsetComment, + SetTags: r.SetTags, + UnsetTags: r.UnsetTags, + ExecuteAs: r.ExecuteAs, + } + return opts +} + +func (r *DropProcedureRequest) toOpts() *DropProcedureOptions { + opts := &DropProcedureOptions{ + IfExists: r.IfExists, + name: r.name, + ArgumentDataTypes: r.ArgumentDataTypes, + } + return opts +} + +func (r *ShowProcedureRequest) toOpts() *ShowProcedureOptions { + opts := &ShowProcedureOptions{ + Like: r.Like, + In: r.In, + } + return opts +} + +func (r procedureRow) convert() *Procedure { + e := &Procedure{ + CreatedOn: r.CreatedOn, + Name: r.Name, + SchemaName: r.SchemaName, + IsBuiltin: r.IsBuiltin == "Y", + IsAggregate: r.IsAggregate == "Y", + IsAnsi: r.IsAnsi == "Y", + MinNumArguments: r.MinNumArguments, + MaxNumArguments: r.MaxNumArguments, + Arguments: r.Arguments, + Description: r.Description, + CatalogName: r.CatalogName, + IsTableFunction: r.IsTableFunction == "Y", + ValidForClustering: r.ValidForClustering == "Y", + } + if r.IsSecure.Valid { + e.IsSecure = r.IsSecure.String == "Y" + } + return e +} + +func (r *DescribeProcedureRequest) toOpts() *DescribeProcedureOptions { + opts := &DescribeProcedureOptions{ + name: r.name, + ArgumentDataTypes: r.ArgumentDataTypes, + } + return opts +} + +func (r procedureDetailRow) convert() *ProcedureDetail { + return &ProcedureDetail{ + Property: r.Property, + Value: r.Value, + } +} diff --git a/pkg/sdk/procedures_validations_gen.go b/pkg/sdk/procedures_validations_gen.go new file mode 100644 index 0000000000..aed3234ffb --- /dev/null +++ b/pkg/sdk/procedures_validations_gen.go @@ -0,0 +1,174 @@ +package sdk + +var ( + _ validatable = new(CreateForJavaProcedureOptions) + _ validatable = new(CreateForJavaScriptProcedureOptions) + _ validatable = new(CreateForPythonProcedureOptions) + _ validatable = new(CreateForScalaProcedureOptions) + _ validatable = new(CreateForSQLProcedureOptions) + _ validatable = new(AlterProcedureOptions) + _ validatable = new(DropProcedureOptions) + _ validatable = new(ShowProcedureOptions) + _ validatable = new(DescribeProcedureOptions) +) + +func (opts *CreateForJavaProcedureOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + if !valueSet(opts.RuntimeVersion) { + errs = append(errs, errNotSet("CreateForJavaProcedureOptions", "RuntimeVersion")) + } + if !valueSet(opts.Handler) { + errs = append(errs, errNotSet("CreateForJavaProcedureOptions", "Handler")) + } + if !valueSet(opts.Packages) { + errs = append(errs, errNotSet("CreateForJavaProcedureOptions", "Packages")) + } + if !ValidObjectIdentifier(opts.name) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + if valueSet(opts.Returns) { + if !exactlyOneValueSet(opts.Returns.ResultDataType, opts.Returns.Table) { + errs = append(errs, errExactlyOneOf("CreateForJavaProcedureOptions.Returns", "ResultDataType", "Table")) + } + } + if opts.ProcedureDefinition == nil && opts.TargetPath != nil { + errs = append(errs, NewError("TARGET_PATH must be nil when AS is nil")) + } + return JoinErrors(errs...) +} + +func (opts *CreateForJavaScriptProcedureOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + if !valueSet(opts.ProcedureDefinition) { + errs = append(errs, errNotSet("CreateForJavaScriptProcedureOptions", "ProcedureDefinition")) + } + if !ValidObjectIdentifier(opts.name) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + return JoinErrors(errs...) +} + +func (opts *CreateForPythonProcedureOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + if !valueSet(opts.RuntimeVersion) { + errs = append(errs, errNotSet("CreateForPythonProcedureOptions", "RuntimeVersion")) + } + if !valueSet(opts.Handler) { + errs = append(errs, errNotSet("CreateForPythonProcedureOptions", "Handler")) + } + if !valueSet(opts.Packages) { + errs = append(errs, errNotSet("CreateForPythonProcedureOptions", "Packages")) + } + if !ValidObjectIdentifier(opts.name) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + if valueSet(opts.Returns) { + if !exactlyOneValueSet(opts.Returns.ResultDataType, opts.Returns.Table) { + errs = append(errs, errExactlyOneOf("CreateForPythonProcedureOptions.Returns", "ResultDataType", "Table")) + } + } + return JoinErrors(errs...) +} + +func (opts *CreateForScalaProcedureOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + if !valueSet(opts.RuntimeVersion) { + errs = append(errs, errNotSet("CreateForScalaProcedureOptions", "RuntimeVersion")) + } + if !valueSet(opts.Handler) { + errs = append(errs, errNotSet("CreateForScalaProcedureOptions", "Handler")) + } + if !valueSet(opts.Packages) { + errs = append(errs, errNotSet("CreateForScalaProcedureOptions", "Packages")) + } + if !ValidObjectIdentifier(opts.name) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + if valueSet(opts.Returns) { + if !exactlyOneValueSet(opts.Returns.ResultDataType, opts.Returns.Table) { + errs = append(errs, errExactlyOneOf("CreateForScalaProcedureOptions.Returns", "ResultDataType", "Table")) + } + } + if opts.ProcedureDefinition == nil && opts.TargetPath != nil { + errs = append(errs, NewError("TARGET_PATH must be nil when AS is nil")) + } + return JoinErrors(errs...) +} + +func (opts *CreateForSQLProcedureOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + if !valueSet(opts.ProcedureDefinition) { + errs = append(errs, errNotSet("CreateForSQLProcedureOptions", "ProcedureDefinition")) + } + if !ValidObjectIdentifier(opts.name) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + if valueSet(opts.Returns) { + if !exactlyOneValueSet(opts.Returns.ResultDataType, opts.Returns.Table) { + errs = append(errs, errExactlyOneOf("CreateForSQLProcedureOptions.Returns", "ResultDataType", "Table")) + } + } + return JoinErrors(errs...) +} + +func (opts *AlterProcedureOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + if !ValidObjectIdentifier(opts.name) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + if opts.RenameTo != nil && !ValidObjectIdentifier(opts.RenameTo) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + if !exactlyOneValueSet(opts.RenameTo, opts.SetComment, opts.SetLogLevel, opts.SetTraceLevel, opts.UnsetComment, opts.SetTags, opts.UnsetTags, opts.ExecuteAs) { + errs = append(errs, errExactlyOneOf("AlterProcedureOptions", "RenameTo", "SetComment", "SetLogLevel", "SetTraceLevel", "UnsetComment", "SetTags", "UnsetTags", "ExecuteAs")) + } + return JoinErrors(errs...) +} + +func (opts *DropProcedureOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + if !ValidObjectIdentifier(opts.name) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + return JoinErrors(errs...) +} + +func (opts *ShowProcedureOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + return JoinErrors(errs...) +} + +func (opts *DescribeProcedureOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + if !ValidObjectIdentifier(opts.name) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + return JoinErrors(errs...) +} diff --git a/pkg/sdk/sql_builder.go b/pkg/sdk/sql_builder.go index dd71e51b8f..bae8ef485c 100644 --- a/pkg/sdk/sql_builder.go +++ b/pkg/sdk/sql_builder.go @@ -99,8 +99,9 @@ func (qm quoteModifier) String() string { type parenModifier string const ( - NoParentheses parenModifier = "no_parentheses" - Parentheses parenModifier = "parentheses" + NoParentheses parenModifier = "no_parentheses" + Parentheses parenModifier = "parentheses" + MustParentheses parenModifier = "must_parentheses" ) func (pm parenModifier) Modify(v any) string { @@ -110,6 +111,8 @@ func (pm parenModifier) Modify(v any) string { return s case Parentheses: return fmt.Sprintf(`(%v)`, s) + case MustParentheses: + return fmt.Sprintf(`(%v)`, s) default: return s } @@ -434,7 +437,14 @@ func (b sqlBuilder) parseFieldSlice(field reflect.StructField, value reflect.Val } } if len(listClauses) < 1 { - return nil, nil + // handle the case where the list is empty and parentheses are a must + modifier := b.getModifier(field.Tag, "ddl", parenModifierType, NoParentheses).(parenModifier) + switch modifier { + case MustParentheses: + listClauses = append(listClauses, sqlStaticClause("")) + default: + return nil, nil + } } clauses = append(clauses, sqlListClause{ clauses: listClauses, diff --git a/pkg/sdk/testint/procedures_integration_test.go b/pkg/sdk/testint/procedures_integration_test.go new file mode 100644 index 0000000000..6b731f5281 --- /dev/null +++ b/pkg/sdk/testint/procedures_integration_test.go @@ -0,0 +1,557 @@ +package testint + +import ( + "errors" + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/random" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// todo: add tests for: +// - creating procedure with different languages from stages + +func TestInt_CreateProcedures(t *testing.T) { + client := testClient(t) + ctx := testContext(t) + + databaseTest, schemaTest := testDb(t), testSchema(t) + + cleanupProcedureHandle := func(id sdk.SchemaObjectIdentifier, ats []sdk.DataType) func() { + return func() { + err := client.Procedures.Drop(ctx, sdk.NewDropProcedureRequest(id, ats)) + if errors.Is(err, sdk.ErrObjectNotExistOrAuthorized) { + return + } + require.NoError(t, err) + } + } + + t.Run("create procedure for Java: returns result data type", func(t *testing.T) { + // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-java#reading-a-dynamically-specified-file-with-inputstream + name := "file_reader_java_proc_snowflakefile" + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, name) + + definition := ` + import java.io.InputStream; + import java.io.IOException; + import java.nio.charset.StandardCharsets; + import com.snowflake.snowpark_java.types.SnowflakeFile; + import com.snowflake.snowpark_java.Session; + class FileReader { + public String execute(Session session, String fileName) throws IOException { + InputStream input = SnowflakeFile.newInstance(fileName).getInputStream(); + return new String(input.readAllBytes(), StandardCharsets.UTF_8); + } + }` + + dt := sdk.NewProcedureReturnsResultDataTypeRequest(sdk.DataTypeVARCHAR) + returns := sdk.NewProcedureReturnsRequest().WithResultDataType(dt) + argument := sdk.NewProcedureArgumentRequest("input", sdk.DataTypeVARCHAR) + packages := []sdk.ProcedurePackageRequest{*sdk.NewProcedurePackageRequest("com.snowflake:snowpark:latest")} + request := sdk.NewCreateForJavaProcedureRequest(id, *returns, "11", packages, "FileReader.execute"). + WithOrReplace(sdk.Bool(true)). + WithArguments([]sdk.ProcedureArgumentRequest{*argument}). + WithProcedureDefinition(sdk.String(definition)) + err := client.Procedures.CreateForJava(ctx, request) + require.NoError(t, err) + t.Cleanup(cleanupProcedureHandle(id, []sdk.DataType{sdk.DataTypeVARCHAR})) + + procedures, err := client.Procedures.Show(ctx, sdk.NewShowProcedureRequest()) + require.NoError(t, err) + require.GreaterOrEqual(t, len(procedures), 1) + }) + + t.Run("create procedure for Java: returns table", func(t *testing.T) { + // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-java#specifying-return-column-names-and-types + name := "filter_by_role" + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, name) + + definition := ` + import com.snowflake.snowpark_java.*; + public class Filter { + public DataFrame filterByRole(Session session, String tableName, String role) { + DataFrame table = session.table(tableName); + DataFrame filteredRows = table.filter(Functions.col("role").equal_to(Functions.lit(role))); + return filteredRows; + } + }` + column1 := sdk.NewProcedureColumnRequest("id", sdk.DataTypeNumber) + column2 := sdk.NewProcedureColumnRequest("name", sdk.DataTypeVARCHAR) + column3 := sdk.NewProcedureColumnRequest("role", sdk.DataTypeVARCHAR) + returnsTable := sdk.NewProcedureReturnsTableRequest().WithColumns([]sdk.ProcedureColumnRequest{*column1, *column2, *column3}) + returns := sdk.NewProcedureReturnsRequest().WithTable(returnsTable) + arg1 := sdk.NewProcedureArgumentRequest("table_name", sdk.DataTypeVARCHAR) + arg2 := sdk.NewProcedureArgumentRequest("role", sdk.DataTypeVARCHAR) + packages := []sdk.ProcedurePackageRequest{*sdk.NewProcedurePackageRequest("com.snowflake:snowpark:latest")} + request := sdk.NewCreateForJavaProcedureRequest(id, *returns, "11", packages, "Filter.filterByRole"). + WithOrReplace(sdk.Bool(true)). + WithArguments([]sdk.ProcedureArgumentRequest{*arg1, *arg2}). + WithProcedureDefinition(sdk.String(definition)) + err := client.Procedures.CreateForJava(ctx, request) + require.NoError(t, err) + t.Cleanup(cleanupProcedureHandle(id, []sdk.DataType{sdk.DataTypeVARCHAR, sdk.DataTypeVARCHAR})) + + procedures, err := client.Procedures.Show(ctx, sdk.NewShowProcedureRequest()) + require.NoError(t, err) + require.GreaterOrEqual(t, len(procedures), 1) + }) + + t.Run("create procedure for Javascript", func(t *testing.T) { + // https://docs.snowflake.com/en/sql-reference/sql/create-procedure#examples + name := "stproc1" + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, name) + + definition := ` + var sql_command = "INSERT INTO stproc_test_table1 (num_col1) VALUES (" + FLOAT_PARAM1 + ")"; + try { + snowflake.execute ( + {sqlText: sql_command} + ); + return "Succeeded."; // Return a success/error indicator. + } + catch (err) { + return "Failed: " + err; // Return a success/error indicator. + }` + argument := sdk.NewProcedureArgumentRequest("FLOAT_PARAM1", sdk.DataTypeFloat) + request := sdk.NewCreateForJavaScriptProcedureRequest(id, sdk.DataTypeString, definition). + WithArguments([]sdk.ProcedureArgumentRequest{*argument}). + WithNullInputBehavior(sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorStrict)). + WithExecuteAs(sdk.ExecuteAsPointer(sdk.ExecuteAsCaller)) + err := client.Procedures.CreateForJavaScript(ctx, request) + require.NoError(t, err) + t.Cleanup(cleanupProcedureHandle(id, []sdk.DataType{sdk.DataTypeFloat})) + + procedures, err := client.Procedures.Show(ctx, sdk.NewShowProcedureRequest()) + require.NoError(t, err) + require.GreaterOrEqual(t, len(procedures), 1) + }) + + t.Run("create procedure for Javascript: no arguments", func(t *testing.T) { + // https://docs.snowflake.com/en/sql-reference/sql/create-procedure#examples + name := "sp_pi" + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, name) + + definition := `return 3.1415926;` + request := sdk.NewCreateForJavaScriptProcedureRequest(id, sdk.DataTypeFloat, definition).WithNotNull(sdk.Bool(true)).WithOrReplace(sdk.Bool(true)) + err := client.Procedures.CreateForJavaScript(ctx, request) + require.NoError(t, err) + t.Cleanup(cleanupProcedureHandle(id, nil)) + + procedures, err := client.Procedures.Show(ctx, sdk.NewShowProcedureRequest()) + require.NoError(t, err) + require.GreaterOrEqual(t, len(procedures), 1) + }) + + t.Run("create procedure for Scala: returns result data type", func(t *testing.T) { + // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-scala#reading-a-dynamically-specified-file-with-snowflakefile + name := "file_reader_scala_proc_snowflakefile" + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, name) + + definition := ` + import java.io.InputStream + import java.nio.charset.StandardCharsets + import com.snowflake.snowpark_java.types.SnowflakeFile + import com.snowflake.snowpark_java.Session + object FileReader { + def execute(session: Session, fileName: String): String = { + var input: InputStream = SnowflakeFile.newInstance(fileName).getInputStream() + return new String(input.readAllBytes(), StandardCharsets.UTF_8) + } + }` + dt := sdk.NewProcedureReturnsResultDataTypeRequest(sdk.DataTypeVARCHAR) + returns := sdk.NewProcedureReturnsRequest().WithResultDataType(dt) + argument := sdk.NewProcedureArgumentRequest("input", sdk.DataTypeVARCHAR) + packages := []sdk.ProcedurePackageRequest{*sdk.NewProcedurePackageRequest("com.snowflake:snowpark:latest")} + request := sdk.NewCreateForScalaProcedureRequest(id, *returns, "2.12", packages, "FileReader.execute"). + WithOrReplace(sdk.Bool(true)). + WithArguments([]sdk.ProcedureArgumentRequest{*argument}). + WithProcedureDefinition(sdk.String(definition)) + err := client.Procedures.CreateForScala(ctx, request) + require.NoError(t, err) + t.Cleanup(cleanupProcedureHandle(id, []sdk.DataType{sdk.DataTypeVARCHAR})) + + procedures, err := client.Procedures.Show(ctx, sdk.NewShowProcedureRequest()) + require.NoError(t, err) + require.GreaterOrEqual(t, len(procedures), 1) + }) + + t.Run("create procedure for Scala: returns table", func(t *testing.T) { + // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-scala#specifying-return-column-names-and-types + name := "filter_by_role" + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, name) + + definition := ` + import com.snowflake.snowpark.functions._ + import com.snowflake.snowpark._ + object Filter { + def filterByRole(session: Session, tableName: String, role: String): DataFrame = { + val table = session.table(tableName) + val filteredRows = table.filter(col("role") === role) + return filteredRows + } + }` + column1 := sdk.NewProcedureColumnRequest("id", sdk.DataTypeNumber) + column2 := sdk.NewProcedureColumnRequest("name", sdk.DataTypeVARCHAR) + column3 := sdk.NewProcedureColumnRequest("role", sdk.DataTypeVARCHAR) + returnsTable := sdk.NewProcedureReturnsTableRequest().WithColumns([]sdk.ProcedureColumnRequest{*column1, *column2, *column3}) + returns := sdk.NewProcedureReturnsRequest().WithTable(returnsTable) + arg1 := sdk.NewProcedureArgumentRequest("table_name", sdk.DataTypeVARCHAR) + arg2 := sdk.NewProcedureArgumentRequest("role", sdk.DataTypeVARCHAR) + packages := []sdk.ProcedurePackageRequest{*sdk.NewProcedurePackageRequest("com.snowflake:snowpark:latest")} + request := sdk.NewCreateForScalaProcedureRequest(id, *returns, "2.12", packages, "Filter.filterByRole"). + WithOrReplace(sdk.Bool(true)). + WithArguments([]sdk.ProcedureArgumentRequest{*arg1, *arg2}). + WithProcedureDefinition(sdk.String(definition)) + err := client.Procedures.CreateForScala(ctx, request) + require.NoError(t, err) + t.Cleanup(cleanupProcedureHandle(id, []sdk.DataType{sdk.DataTypeVARCHAR, sdk.DataTypeVARCHAR})) + + procedures, err := client.Procedures.Show(ctx, sdk.NewShowProcedureRequest()) + require.NoError(t, err) + require.GreaterOrEqual(t, len(procedures), 1) + }) + + t.Run("create procedure for Python: returns result data type", func(t *testing.T) { + // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-python#running-concurrent-tasks-with-worker-processes + name := "joblib_multiprocessing_proc" + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, name) + + definition := ` +import joblib +from math import sqrt +def joblib_multiprocessing(session, i): + result = joblib.Parallel(n_jobs=-1)(joblib.delayed(sqrt)(i ** 2) for i in range(10)) + return str(result)` + + dt := sdk.NewProcedureReturnsResultDataTypeRequest(sdk.DataTypeString) + returns := sdk.NewProcedureReturnsRequest().WithResultDataType(dt) + argument := sdk.NewProcedureArgumentRequest("i", "INT") + packages := []sdk.ProcedurePackageRequest{ + *sdk.NewProcedurePackageRequest("snowflake-snowpark-python"), + *sdk.NewProcedurePackageRequest("joblib"), + } + request := sdk.NewCreateForPythonProcedureRequest(id, *returns, "3.8", packages, "joblib_multiprocessing"). + WithOrReplace(sdk.Bool(true)). + WithArguments([]sdk.ProcedureArgumentRequest{*argument}). + WithProcedureDefinition(sdk.String(definition)) + err := client.Procedures.CreateForPython(ctx, request) + require.NoError(t, err) + t.Cleanup(cleanupProcedureHandle(id, []sdk.DataType{"string"})) + + procedures, err := client.Procedures.Show(ctx, sdk.NewShowProcedureRequest()) + require.NoError(t, err) + require.GreaterOrEqual(t, len(procedures), 1) + }) + + t.Run("create procedure for Python: returns table", func(t *testing.T) { + // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-python#specifying-return-column-names-and-types + name := "filterByRole" + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, name) + + definition := ` +from snowflake.snowpark.functions import col +def filter_by_role(session, table_name, role): + df = session.table(table_name) + return df.filter(col("role") == role)` + column1 := sdk.NewProcedureColumnRequest("id", sdk.DataTypeNumber) + column2 := sdk.NewProcedureColumnRequest("name", sdk.DataTypeVARCHAR) + column3 := sdk.NewProcedureColumnRequest("role", sdk.DataTypeVARCHAR) + eeturnsTable := sdk.NewProcedureReturnsTableRequest().WithColumns([]sdk.ProcedureColumnRequest{*column1, *column2, *column3}) + returns := sdk.NewProcedureReturnsRequest().WithTable(eeturnsTable) + arg1 := sdk.NewProcedureArgumentRequest("table_name", sdk.DataTypeVARCHAR) + arg2 := sdk.NewProcedureArgumentRequest("role", sdk.DataTypeVARCHAR) + packages := []sdk.ProcedurePackageRequest{*sdk.NewProcedurePackageRequest("snowflake-snowpark-python")} + request := sdk.NewCreateForPythonProcedureRequest(id, *returns, "3.8", packages, "filter_by_role"). + WithOrReplace(sdk.Bool(true)). + WithArguments([]sdk.ProcedureArgumentRequest{*arg1, *arg2}). + WithProcedureDefinition(sdk.String(definition)) + err := client.Procedures.CreateForPython(ctx, request) + require.NoError(t, err) + t.Cleanup(cleanupProcedureHandle(id, []sdk.DataType{sdk.DataTypeVARCHAR, sdk.DataTypeVARCHAR})) + + procedures, err := client.Procedures.Show(ctx, sdk.NewShowProcedureRequest()) + require.NoError(t, err) + require.GreaterOrEqual(t, len(procedures), 1) + }) + + t.Run("create procedure for SQL: returns result data type", func(t *testing.T) { + // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-snowflake-scripting + name := "output_message" + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, name) + + definition := ` + BEGIN + RETURN message; + END;` + + dt := sdk.NewProcedureReturnsResultDataTypeRequest(sdk.DataTypeVARCHAR) + returns := sdk.NewProcedureSQLReturnsRequest().WithResultDataType(dt).WithNotNull(sdk.Bool(true)) + argument := sdk.NewProcedureArgumentRequest("message", sdk.DataTypeVARCHAR) + request := sdk.NewCreateForSQLProcedureRequest(id, *returns, definition). + WithOrReplace(sdk.Bool(true)). + WithArguments([]sdk.ProcedureArgumentRequest{*argument}) + err := client.Procedures.CreateForSQL(ctx, request) + require.NoError(t, err) + t.Cleanup(cleanupProcedureHandle(id, []sdk.DataType{sdk.DataTypeVARCHAR})) + + procedures, err := client.Procedures.Show(ctx, sdk.NewShowProcedureRequest()) + require.NoError(t, err) + require.GreaterOrEqual(t, len(procedures), 1) + }) + + t.Run("create procedure for SQL: returns table", func(t *testing.T) { + name := "find_invoice_by_id" + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, name) + + definition := ` + DECLARE + res RESULTSET DEFAULT (SELECT * FROM invoices WHERE id = :id); + BEGIN + RETURN TABLE(res); + END;` + column1 := sdk.NewProcedureColumnRequest("id", "INTEGER") + column2 := sdk.NewProcedureColumnRequest("price", "NUMBER(12,2)") + returnsTable := sdk.NewProcedureReturnsTableRequest().WithColumns([]sdk.ProcedureColumnRequest{*column1, *column2}) + returns := sdk.NewProcedureSQLReturnsRequest().WithTable(returnsTable) + argument := sdk.NewProcedureArgumentRequest("id", sdk.DataTypeVARCHAR) + request := sdk.NewCreateForSQLProcedureRequest(id, *returns, definition). + WithOrReplace(sdk.Bool(true)). + WithArguments([]sdk.ProcedureArgumentRequest{*argument}) + err := client.Procedures.CreateForSQL(ctx, request) + require.NoError(t, err) + t.Cleanup(cleanupProcedureHandle(id, []sdk.DataType{sdk.DataTypeVARCHAR})) + + procedures, err := client.Procedures.Show(ctx, sdk.NewShowProcedureRequest()) + require.NoError(t, err) + require.GreaterOrEqual(t, len(procedures), 1) + }) +} + +func TestInt_OtherProcedureFunctions(t *testing.T) { + client := testClient(t) + ctx := testContext(t) + + databaseTest, schemaTest := testDb(t), testSchema(t) + tagTest, tagCleanup := createTag(t, client, databaseTest, schemaTest) + t.Cleanup(tagCleanup) + + assertProcedure := func(t *testing.T, id sdk.SchemaObjectIdentifier, secure bool) { + t.Helper() + + procedure, err := client.Procedures.ShowByID(ctx, id) + require.NoError(t, err) + + assert.NotEmpty(t, procedure.CreatedOn) + assert.Equal(t, id.Name(), procedure.Name) + assert.Equal(t, false, procedure.IsBuiltin) + assert.Equal(t, false, procedure.IsAggregate) + assert.Equal(t, false, procedure.IsAnsi) + assert.Equal(t, 1, procedure.MinNumArguments) + assert.Equal(t, 1, procedure.MaxNumArguments) + assert.NotEmpty(t, procedure.Arguments) + assert.NotEmpty(t, procedure.Description) + assert.NotEmpty(t, procedure.CatalogName) + assert.Equal(t, false, procedure.IsTableFunction) + assert.Equal(t, false, procedure.ValidForClustering) + assert.Equal(t, secure, procedure.IsSecure) + } + + cleanupProcedureHandle := func(id sdk.SchemaObjectIdentifier, ats []sdk.DataType) func() { + return func() { + err := client.Procedures.Drop(ctx, sdk.NewDropProcedureRequest(id, ats)) + if errors.Is(err, sdk.ErrObjectNotExistOrAuthorized) { + return + } + require.NoError(t, err) + } + } + + createProcedureForSQLHandle := func(t *testing.T, cleanup bool) *sdk.Procedure { + t.Helper() + + definition := ` + BEGIN + RETURN message; + END;` + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, random.String()) + dt := sdk.NewProcedureReturnsResultDataTypeRequest(sdk.DataTypeVARCHAR) + returns := sdk.NewProcedureSQLReturnsRequest().WithResultDataType(dt).WithNotNull(sdk.Bool(true)) + argument := sdk.NewProcedureArgumentRequest("message", sdk.DataTypeVARCHAR) + request := sdk.NewCreateForSQLProcedureRequest(id, *returns, definition). + WithSecure(sdk.Bool(true)). + WithOrReplace(sdk.Bool(true)). + WithArguments([]sdk.ProcedureArgumentRequest{*argument}). + WithExecuteAs(sdk.ExecuteAsPointer(sdk.ExecuteAsCaller)) + err := client.Procedures.CreateForSQL(ctx, request) + require.NoError(t, err) + if cleanup { + t.Cleanup(cleanupProcedureHandle(id, []sdk.DataType{sdk.DataTypeVARCHAR})) + } + procedure, err := client.Procedures.ShowByID(ctx, id) + require.NoError(t, err) + return procedure + } + + defaultAlterRequest := func(id sdk.SchemaObjectIdentifier) *sdk.AlterProcedureRequest { + return sdk.NewAlterProcedureRequest(id, []sdk.DataType{sdk.DataTypeVARCHAR}) + } + + t.Run("alter procedure: rename", func(t *testing.T) { + f := createProcedureForSQLHandle(t, false) + + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, f.Name) + nid := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, random.String()) + err := client.Procedures.Alter(ctx, defaultAlterRequest(id).WithRenameTo(&nid)) + if err != nil { + t.Cleanup(cleanupProcedureHandle(id, []sdk.DataType{sdk.DataTypeVARCHAR})) + } else { + t.Cleanup(cleanupProcedureHandle(nid, []sdk.DataType{sdk.DataTypeVARCHAR})) + } + require.NoError(t, err) + + _, err = client.Procedures.ShowByID(ctx, id) + assert.ErrorIs(t, err, collections.ErrObjectNotFound) + + e, err := client.Procedures.ShowByID(ctx, nid) + require.NoError(t, err) + require.Equal(t, nid.Name(), e.Name) + }) + + t.Run("alter procedure: set log level", func(t *testing.T) { + f := createProcedureForSQLHandle(t, true) + + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, f.Name) + err := client.Procedures.Alter(ctx, defaultAlterRequest(id).WithSetLogLevel(sdk.String("DEBUG"))) + require.NoError(t, err) + assertProcedure(t, id, true) + }) + + t.Run("alter procedure: set trace level", func(t *testing.T) { + f := createProcedureForSQLHandle(t, true) + + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, f.Name) + err := client.Procedures.Alter(ctx, defaultAlterRequest(id).WithSetTraceLevel(sdk.String("ALWAYS"))) + require.NoError(t, err) + assertProcedure(t, id, true) + }) + + t.Run("alter procedure: set comment", func(t *testing.T) { + f := createProcedureForSQLHandle(t, true) + + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, f.Name) + err := client.Procedures.Alter(ctx, defaultAlterRequest(id).WithSetComment(sdk.String("comment"))) + require.NoError(t, err) + assertProcedure(t, id, true) + }) + + t.Run("alter procedure: unset comment", func(t *testing.T) { + f := createProcedureForSQLHandle(t, true) + + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, f.Name) + err := client.Procedures.Alter(ctx, defaultAlterRequest(id).WithUnsetComment(sdk.Bool(true))) + require.NoError(t, err) + assertProcedure(t, id, true) + }) + + t.Run("alter procedure: set execute as", func(t *testing.T) { + f := createProcedureForSQLHandle(t, true) + + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, f.Name) + err := client.Procedures.Alter(ctx, defaultAlterRequest(id).WithExecuteAs(sdk.ExecuteAsPointer(sdk.ExecuteAsOwner))) + require.NoError(t, err) + assertProcedure(t, id, true) + }) + + t.Run("alter procedure: set and unset tags", func(t *testing.T) { + f := createProcedureForSQLHandle(t, true) + + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, f.Name) + setTags := []sdk.TagAssociation{ + { + Name: tagTest.ID(), + Value: "v1", + }, + } + err := client.Procedures.Alter(ctx, defaultAlterRequest(id).WithSetTags(setTags)) + require.NoError(t, err) + assertProcedure(t, id, true) + + unsetTags := []sdk.ObjectIdentifier{ + tagTest.ID(), + } + err = client.Procedures.Alter(ctx, defaultAlterRequest(id).WithUnsetTags(unsetTags)) + require.NoError(t, err) + assertProcedure(t, id, true) + }) + + t.Run("show procedure for SQL: without like", func(t *testing.T) { + f1 := createProcedureForSQLHandle(t, true) + f2 := createProcedureForSQLHandle(t, true) + + procedures, err := client.Procedures.Show(ctx, sdk.NewShowProcedureRequest()) + require.NoError(t, err) + + require.GreaterOrEqual(t, len(procedures), 1) + require.Contains(t, procedures, *f1) + require.Contains(t, procedures, *f2) + }) + + t.Run("show procedure for SQL: with like", func(t *testing.T) { + f1 := createProcedureForSQLHandle(t, true) + f2 := createProcedureForSQLHandle(t, true) + + procedures, err := client.Procedures.Show(ctx, sdk.NewShowProcedureRequest().WithLike(&sdk.Like{Pattern: &f1.Name})) + require.NoError(t, err) + + require.Equal(t, 1, len(procedures)) + require.Contains(t, procedures, *f1) + require.NotContains(t, procedures, *f2) + }) + + t.Run("show procedure for SQL: no matches", func(t *testing.T) { + procedures, err := client.Procedures.Show(ctx, sdk.NewShowProcedureRequest().WithLike(&sdk.Like{Pattern: sdk.String(random.String())})) + require.NoError(t, err) + require.Equal(t, 0, len(procedures)) + }) + + t.Run("describe function for SQL", func(t *testing.T) { + f := createProcedureForSQLHandle(t, true) + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, f.Name) + + request := sdk.NewDescribeProcedureRequest(id, []sdk.DataType{sdk.DataTypeString}) + details, err := client.Procedures.Describe(ctx, request) + require.NoError(t, err) + pairs := make(map[string]string) + for _, detail := range details { + pairs[detail.Property] = detail.Value + } + require.Equal(t, "SQL", pairs["language"]) + require.Equal(t, "CALLER", pairs["execute as"]) + require.Equal(t, "(MESSAGE VARCHAR)", pairs["signature"]) + require.Equal(t, "\n\tBEGIN\n\t\tRETURN message;\n\tEND;", pairs["body"]) + }) + + t.Run("drop procedure for SQL", func(t *testing.T) { + definition := ` + BEGIN + RETURN message; + END;` + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, random.String()) + dt := sdk.NewProcedureReturnsResultDataTypeRequest(sdk.DataTypeVARCHAR) + returns := sdk.NewProcedureSQLReturnsRequest().WithResultDataType(dt).WithNotNull(sdk.Bool(true)) + argument := sdk.NewProcedureArgumentRequest("message", sdk.DataTypeVARCHAR) + request := sdk.NewCreateForSQLProcedureRequest(id, *returns, definition). + WithOrReplace(sdk.Bool(true)). + WithArguments([]sdk.ProcedureArgumentRequest{*argument}). + WithExecuteAs(sdk.ExecuteAsPointer(sdk.ExecuteAsCaller)) + err := client.Procedures.CreateForSQL(ctx, request) + require.NoError(t, err) + + err = client.Procedures.Drop(ctx, sdk.NewDropProcedureRequest(id, []sdk.DataType{sdk.DataTypeVARCHAR})) + require.NoError(t, err) + }) +}