From 222726b5ba1489a25b707348453d2509e8a5a0af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Thu, 25 Jul 2024 14:10:08 +0200 Subject: [PATCH 01/13] save --- pkg/sdk/client.go | 36 ++-- pkg/sdk/external_functions_impl_gen.go | 61 +++--- pkg/sdk/functions_def.go | 2 +- pkg/sdk/functions_dto_builders_gen.go | 280 ++++++++++++------------- pkg/sdk/functions_dto_gen.go | 22 +- pkg/sdk/functions_gen.go | 250 +++++++++++----------- pkg/sdk/functions_impl_gen.go | 10 +- pkg/sdk/identifier_helpers.go | 107 ++++++---- pkg/sdk/poc/main.go | 1 + pkg/sdk/random_test.go | 4 + 10 files changed, 399 insertions(+), 374 deletions(-) diff --git a/pkg/sdk/client.go b/pkg/sdk/client.go index fe50185a18..dd92fafd95 100644 --- a/pkg/sdk/client.go +++ b/pkg/sdk/client.go @@ -39,23 +39,23 @@ type Client struct { ReplicationFunctions ReplicationFunctions // DDL Commands - Accounts Accounts - Alerts Alerts - ApiIntegrations ApiIntegrations - ApplicationPackages ApplicationPackages - ApplicationRoles ApplicationRoles - Applications Applications - Comments Comments - CortexSearchServices CortexSearchServices - DatabaseRoles DatabaseRoles - Databases Databases - DynamicTables DynamicTables - ExternalFunctions ExternalFunctions - ExternalTables ExternalTables - EventTables EventTables - FailoverGroups FailoverGroups - FileFormats FileFormats - Functions Functions + Accounts Accounts + Alerts Alerts + ApiIntegrations ApiIntegrations + ApplicationPackages ApplicationPackages + ApplicationRoles ApplicationRoles + Applications Applications + Comments Comments + CortexSearchServices CortexSearchServices + DatabaseRoles DatabaseRoles + Databases Databases + DynamicTables DynamicTables + ExternalFunctions ExternalFunctions + ExternalTables ExternalTables + EventTables EventTables + FailoverGroups FailoverGroups + FileFormats FileFormats + //Functions Functions Grants Grants ManagedAccounts ManagedAccounts MaskingPolicies MaskingPolicies @@ -211,7 +211,7 @@ func (c *Client) initialize() { c.EventTables = &eventTables{client: c} c.FailoverGroups = &failoverGroups{client: c} c.FileFormats = &fileFormats{client: c} - c.Functions = &functions{client: c} + //c.Functions = &functions{client: c} c.Grants = &grants{client: c} c.ManagedAccounts = &managedAccounts{client: c} c.MaskingPolicies = &maskingPolicies{client: c} diff --git a/pkg/sdk/external_functions_impl_gen.go b/pkg/sdk/external_functions_impl_gen.go index 471d45f844..2316ff90f9 100644 --- a/pkg/sdk/external_functions_impl_gen.go +++ b/pkg/sdk/external_functions_impl_gen.go @@ -2,9 +2,6 @@ package sdk import ( "context" - "strings" - - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" ) var _ ExternalFunctions = (*externalFunctions)(nil) @@ -34,30 +31,32 @@ func (v *externalFunctions) Show(ctx context.Context, request *ShowExternalFunct } func (v *externalFunctions) ShowByID(ctx context.Context, id SchemaObjectIdentifier) (*ExternalFunction, error) { - arguments := id.Arguments() - externalFunctions, err := v.Show(ctx, NewShowExternalFunctionRequest(). - WithIn(&In{Schema: id.SchemaId()}). - WithLike(&Like{Pattern: String(id.Name())})) - if err != nil { - return nil, err - } - return collections.FindOne(externalFunctions, func(r ExternalFunction) bool { - database := strings.Trim(r.CatalogName, `"`) - schema := strings.Trim(r.SchemaName, `"`) - if r.Name != id.Name() || database != id.DatabaseName() || schema != id.SchemaName() { - return false - } - var sb strings.Builder - sb.WriteString("(") - for i, argument := range arguments { - sb.WriteString(string(argument)) - if i < len(arguments)-1 { - sb.WriteString(", ") - } - } - sb.WriteString(")") - return strings.Contains(r.Arguments, sb.String()) - }) + return nil, nil + // TODO + //arguments := id.Arguments() + //externalFunctions, err := v.Show(ctx, NewShowExternalFunctionRequest(). + // WithIn(&In{Schema: id.SchemaId()}). + // WithLike(&Like{Pattern: String(id.Name())})) + //if err != nil { + // return nil, err + //} + //return collections.FindOne(externalFunctions, func(r ExternalFunction) bool { + // database := strings.Trim(r.CatalogName, `"`) + // schema := strings.Trim(r.SchemaName, `"`) + // if r.Name != id.Name() || database != id.DatabaseName() || schema != id.SchemaName() { + // return false + // } + // var sb strings.Builder + // sb.WriteString("(") + // for i, argument := range arguments { + // sb.WriteString(string(argument)) + // if i < len(arguments)-1 { + // sb.WriteString(", ") + // } + // } + // sb.WriteString(")") + // return strings.Contains(r.Arguments, sb.String()) + //}) } func (v *externalFunctions) Describe(ctx context.Context, request *DescribeExternalFunctionRequest) ([]ExternalFunctionProperty, error) { @@ -73,7 +72,8 @@ func (r *CreateExternalFunctionRequest) toOpts() *CreateExternalFunctionOptions opts := &CreateExternalFunctionOptions{ OrReplace: r.OrReplace, Secure: r.Secure, - name: r.name.WithoutArguments(), + // TODO: + //name: r.name.WithoutArguments(), ResultDataType: r.ResultDataType, ReturnNullValues: r.ReturnNullValues, @@ -114,8 +114,9 @@ func (r *CreateExternalFunctionRequest) toOpts() *CreateExternalFunctionOptions func (r *AlterExternalFunctionRequest) toOpts() *AlterExternalFunctionOptions { opts := &AlterExternalFunctionOptions{ - IfExists: r.IfExists, - name: r.name.WithoutArguments(), + IfExists: r.IfExists, + // TODO: + //name: r.name.WithoutArguments(), ArgumentDataTypes: r.ArgumentDataTypes, } if r.Set != nil { diff --git a/pkg/sdk/functions_def.go b/pkg/sdk/functions_def.go index 2cc52bd256..dbf6ea51e2 100644 --- a/pkg/sdk/functions_def.go +++ b/pkg/sdk/functions_def.go @@ -39,7 +39,7 @@ var ( var FunctionsDef = g.NewInterface( "Functions", "Function", - g.KindOfT[SchemaObjectIdentifier](), + g.KindOfT[SchemaObjectIdentifierWithArguments](), ).CustomOperation( "CreateForJava", "https://docs.snowflake.com/en/sql-reference/sql/create-function#java-handler", diff --git a/pkg/sdk/functions_dto_builders_gen.go b/pkg/sdk/functions_dto_builders_gen.go index 4cb8f160fa..ccb0f21602 100644 --- a/pkg/sdk/functions_dto_builders_gen.go +++ b/pkg/sdk/functions_dto_builders_gen.go @@ -5,7 +5,7 @@ package sdk import () func NewCreateForJavaFunctionRequest( - name SchemaObjectIdentifier, + name SchemaObjectIdentifierWithArguments, Returns FunctionReturnsRequest, Handler string, ) *CreateForJavaFunctionRequest { @@ -16,23 +16,23 @@ func NewCreateForJavaFunctionRequest( return &s } -func (s *CreateForJavaFunctionRequest) WithOrReplace(OrReplace *bool) *CreateForJavaFunctionRequest { - s.OrReplace = OrReplace +func (s *CreateForJavaFunctionRequest) WithOrReplace(OrReplace bool) *CreateForJavaFunctionRequest { + s.OrReplace = &OrReplace return s } -func (s *CreateForJavaFunctionRequest) WithTemporary(Temporary *bool) *CreateForJavaFunctionRequest { - s.Temporary = Temporary +func (s *CreateForJavaFunctionRequest) WithTemporary(Temporary bool) *CreateForJavaFunctionRequest { + s.Temporary = &Temporary return s } -func (s *CreateForJavaFunctionRequest) WithSecure(Secure *bool) *CreateForJavaFunctionRequest { - s.Secure = Secure +func (s *CreateForJavaFunctionRequest) WithSecure(Secure bool) *CreateForJavaFunctionRequest { + s.Secure = &Secure return s } -func (s *CreateForJavaFunctionRequest) WithIfNotExists(IfNotExists *bool) *CreateForJavaFunctionRequest { - s.IfNotExists = IfNotExists +func (s *CreateForJavaFunctionRequest) WithIfNotExists(IfNotExists bool) *CreateForJavaFunctionRequest { + s.IfNotExists = &IfNotExists return s } @@ -41,33 +41,33 @@ func (s *CreateForJavaFunctionRequest) WithArguments(Arguments []FunctionArgumen return s } -func (s *CreateForJavaFunctionRequest) WithCopyGrants(CopyGrants *bool) *CreateForJavaFunctionRequest { - s.CopyGrants = CopyGrants +func (s *CreateForJavaFunctionRequest) WithCopyGrants(CopyGrants bool) *CreateForJavaFunctionRequest { + s.CopyGrants = &CopyGrants return s } -func (s *CreateForJavaFunctionRequest) WithReturnNullValues(ReturnNullValues *ReturnNullValues) *CreateForJavaFunctionRequest { - s.ReturnNullValues = ReturnNullValues +func (s *CreateForJavaFunctionRequest) WithReturnNullValues(ReturnNullValues ReturnNullValues) *CreateForJavaFunctionRequest { + s.ReturnNullValues = &ReturnNullValues return s } -func (s *CreateForJavaFunctionRequest) WithNullInputBehavior(NullInputBehavior *NullInputBehavior) *CreateForJavaFunctionRequest { - s.NullInputBehavior = NullInputBehavior +func (s *CreateForJavaFunctionRequest) WithNullInputBehavior(NullInputBehavior NullInputBehavior) *CreateForJavaFunctionRequest { + s.NullInputBehavior = &NullInputBehavior return s } -func (s *CreateForJavaFunctionRequest) WithReturnResultsBehavior(ReturnResultsBehavior *ReturnResultsBehavior) *CreateForJavaFunctionRequest { - s.ReturnResultsBehavior = ReturnResultsBehavior +func (s *CreateForJavaFunctionRequest) WithReturnResultsBehavior(ReturnResultsBehavior ReturnResultsBehavior) *CreateForJavaFunctionRequest { + s.ReturnResultsBehavior = &ReturnResultsBehavior return s } -func (s *CreateForJavaFunctionRequest) WithRuntimeVersion(RuntimeVersion *string) *CreateForJavaFunctionRequest { - s.RuntimeVersion = RuntimeVersion +func (s *CreateForJavaFunctionRequest) WithRuntimeVersion(RuntimeVersion string) *CreateForJavaFunctionRequest { + s.RuntimeVersion = &RuntimeVersion return s } -func (s *CreateForJavaFunctionRequest) WithComment(Comment *string) *CreateForJavaFunctionRequest { - s.Comment = Comment +func (s *CreateForJavaFunctionRequest) WithComment(Comment string) *CreateForJavaFunctionRequest { + s.Comment = &Comment return s } @@ -91,13 +91,13 @@ func (s *CreateForJavaFunctionRequest) WithSecrets(Secrets []Secret) *CreateForJ return s } -func (s *CreateForJavaFunctionRequest) WithTargetPath(TargetPath *string) *CreateForJavaFunctionRequest { - s.TargetPath = TargetPath +func (s *CreateForJavaFunctionRequest) WithTargetPath(TargetPath string) *CreateForJavaFunctionRequest { + s.TargetPath = &TargetPath return s } -func (s *CreateForJavaFunctionRequest) WithFunctionDefinition(FunctionDefinition *string) *CreateForJavaFunctionRequest { - s.FunctionDefinition = FunctionDefinition +func (s *CreateForJavaFunctionRequest) WithFunctionDefinition(FunctionDefinition string) *CreateForJavaFunctionRequest { + s.FunctionDefinition = &FunctionDefinition return s } @@ -111,8 +111,8 @@ func NewFunctionArgumentRequest( return &s } -func (s *FunctionArgumentRequest) WithDefaultValue(DefaultValue *string) *FunctionArgumentRequest { - s.DefaultValue = DefaultValue +func (s *FunctionArgumentRequest) WithDefaultValue(DefaultValue string) *FunctionArgumentRequest { + s.DefaultValue = &DefaultValue return s } @@ -120,13 +120,13 @@ func NewFunctionReturnsRequest() *FunctionReturnsRequest { return &FunctionReturnsRequest{} } -func (s *FunctionReturnsRequest) WithResultDataType(ResultDataType *FunctionReturnsResultDataTypeRequest) *FunctionReturnsRequest { - s.ResultDataType = ResultDataType +func (s *FunctionReturnsRequest) WithResultDataType(ResultDataType FunctionReturnsResultDataTypeRequest) *FunctionReturnsRequest { + s.ResultDataType = &ResultDataType return s } -func (s *FunctionReturnsRequest) WithTable(Table *FunctionReturnsTableRequest) *FunctionReturnsRequest { - s.Table = Table +func (s *FunctionReturnsRequest) WithTable(Table FunctionReturnsTableRequest) *FunctionReturnsRequest { + s.Table = &Table return s } @@ -176,7 +176,7 @@ func (s *FunctionPackageRequest) WithPackage(Package string) *FunctionPackageReq } func NewCreateForJavascriptFunctionRequest( - name SchemaObjectIdentifier, + name SchemaObjectIdentifierWithArguments, Returns FunctionReturnsRequest, FunctionDefinition string, ) *CreateForJavascriptFunctionRequest { @@ -187,18 +187,18 @@ func NewCreateForJavascriptFunctionRequest( return &s } -func (s *CreateForJavascriptFunctionRequest) WithOrReplace(OrReplace *bool) *CreateForJavascriptFunctionRequest { - s.OrReplace = OrReplace +func (s *CreateForJavascriptFunctionRequest) WithOrReplace(OrReplace bool) *CreateForJavascriptFunctionRequest { + s.OrReplace = &OrReplace return s } -func (s *CreateForJavascriptFunctionRequest) WithTemporary(Temporary *bool) *CreateForJavascriptFunctionRequest { - s.Temporary = Temporary +func (s *CreateForJavascriptFunctionRequest) WithTemporary(Temporary bool) *CreateForJavascriptFunctionRequest { + s.Temporary = &Temporary return s } -func (s *CreateForJavascriptFunctionRequest) WithSecure(Secure *bool) *CreateForJavascriptFunctionRequest { - s.Secure = Secure +func (s *CreateForJavascriptFunctionRequest) WithSecure(Secure bool) *CreateForJavascriptFunctionRequest { + s.Secure = &Secure return s } @@ -207,33 +207,33 @@ func (s *CreateForJavascriptFunctionRequest) WithArguments(Arguments []FunctionA return s } -func (s *CreateForJavascriptFunctionRequest) WithCopyGrants(CopyGrants *bool) *CreateForJavascriptFunctionRequest { - s.CopyGrants = CopyGrants +func (s *CreateForJavascriptFunctionRequest) WithCopyGrants(CopyGrants bool) *CreateForJavascriptFunctionRequest { + s.CopyGrants = &CopyGrants return s } -func (s *CreateForJavascriptFunctionRequest) WithReturnNullValues(ReturnNullValues *ReturnNullValues) *CreateForJavascriptFunctionRequest { - s.ReturnNullValues = ReturnNullValues +func (s *CreateForJavascriptFunctionRequest) WithReturnNullValues(ReturnNullValues ReturnNullValues) *CreateForJavascriptFunctionRequest { + s.ReturnNullValues = &ReturnNullValues return s } -func (s *CreateForJavascriptFunctionRequest) WithNullInputBehavior(NullInputBehavior *NullInputBehavior) *CreateForJavascriptFunctionRequest { - s.NullInputBehavior = NullInputBehavior +func (s *CreateForJavascriptFunctionRequest) WithNullInputBehavior(NullInputBehavior NullInputBehavior) *CreateForJavascriptFunctionRequest { + s.NullInputBehavior = &NullInputBehavior return s } -func (s *CreateForJavascriptFunctionRequest) WithReturnResultsBehavior(ReturnResultsBehavior *ReturnResultsBehavior) *CreateForJavascriptFunctionRequest { - s.ReturnResultsBehavior = ReturnResultsBehavior +func (s *CreateForJavascriptFunctionRequest) WithReturnResultsBehavior(ReturnResultsBehavior ReturnResultsBehavior) *CreateForJavascriptFunctionRequest { + s.ReturnResultsBehavior = &ReturnResultsBehavior return s } -func (s *CreateForJavascriptFunctionRequest) WithComment(Comment *string) *CreateForJavascriptFunctionRequest { - s.Comment = Comment +func (s *CreateForJavascriptFunctionRequest) WithComment(Comment string) *CreateForJavascriptFunctionRequest { + s.Comment = &Comment return s } func NewCreateForPythonFunctionRequest( - name SchemaObjectIdentifier, + name SchemaObjectIdentifierWithArguments, Returns FunctionReturnsRequest, RuntimeVersion string, Handler string, @@ -246,23 +246,23 @@ func NewCreateForPythonFunctionRequest( return &s } -func (s *CreateForPythonFunctionRequest) WithOrReplace(OrReplace *bool) *CreateForPythonFunctionRequest { - s.OrReplace = OrReplace +func (s *CreateForPythonFunctionRequest) WithOrReplace(OrReplace bool) *CreateForPythonFunctionRequest { + s.OrReplace = &OrReplace return s } -func (s *CreateForPythonFunctionRequest) WithTemporary(Temporary *bool) *CreateForPythonFunctionRequest { - s.Temporary = Temporary +func (s *CreateForPythonFunctionRequest) WithTemporary(Temporary bool) *CreateForPythonFunctionRequest { + s.Temporary = &Temporary return s } -func (s *CreateForPythonFunctionRequest) WithSecure(Secure *bool) *CreateForPythonFunctionRequest { - s.Secure = Secure +func (s *CreateForPythonFunctionRequest) WithSecure(Secure bool) *CreateForPythonFunctionRequest { + s.Secure = &Secure return s } -func (s *CreateForPythonFunctionRequest) WithIfNotExists(IfNotExists *bool) *CreateForPythonFunctionRequest { - s.IfNotExists = IfNotExists +func (s *CreateForPythonFunctionRequest) WithIfNotExists(IfNotExists bool) *CreateForPythonFunctionRequest { + s.IfNotExists = &IfNotExists return s } @@ -271,28 +271,28 @@ func (s *CreateForPythonFunctionRequest) WithArguments(Arguments []FunctionArgum return s } -func (s *CreateForPythonFunctionRequest) WithCopyGrants(CopyGrants *bool) *CreateForPythonFunctionRequest { - s.CopyGrants = CopyGrants +func (s *CreateForPythonFunctionRequest) WithCopyGrants(CopyGrants bool) *CreateForPythonFunctionRequest { + s.CopyGrants = &CopyGrants return s } -func (s *CreateForPythonFunctionRequest) WithReturnNullValues(ReturnNullValues *ReturnNullValues) *CreateForPythonFunctionRequest { - s.ReturnNullValues = ReturnNullValues +func (s *CreateForPythonFunctionRequest) WithReturnNullValues(ReturnNullValues ReturnNullValues) *CreateForPythonFunctionRequest { + s.ReturnNullValues = &ReturnNullValues return s } -func (s *CreateForPythonFunctionRequest) WithNullInputBehavior(NullInputBehavior *NullInputBehavior) *CreateForPythonFunctionRequest { - s.NullInputBehavior = NullInputBehavior +func (s *CreateForPythonFunctionRequest) WithNullInputBehavior(NullInputBehavior NullInputBehavior) *CreateForPythonFunctionRequest { + s.NullInputBehavior = &NullInputBehavior return s } -func (s *CreateForPythonFunctionRequest) WithReturnResultsBehavior(ReturnResultsBehavior *ReturnResultsBehavior) *CreateForPythonFunctionRequest { - s.ReturnResultsBehavior = ReturnResultsBehavior +func (s *CreateForPythonFunctionRequest) WithReturnResultsBehavior(ReturnResultsBehavior ReturnResultsBehavior) *CreateForPythonFunctionRequest { + s.ReturnResultsBehavior = &ReturnResultsBehavior return s } -func (s *CreateForPythonFunctionRequest) WithComment(Comment *string) *CreateForPythonFunctionRequest { - s.Comment = Comment +func (s *CreateForPythonFunctionRequest) WithComment(Comment string) *CreateForPythonFunctionRequest { + s.Comment = &Comment return s } @@ -316,13 +316,13 @@ func (s *CreateForPythonFunctionRequest) WithSecrets(Secrets []Secret) *CreateFo return s } -func (s *CreateForPythonFunctionRequest) WithFunctionDefinition(FunctionDefinition *string) *CreateForPythonFunctionRequest { - s.FunctionDefinition = FunctionDefinition +func (s *CreateForPythonFunctionRequest) WithFunctionDefinition(FunctionDefinition string) *CreateForPythonFunctionRequest { + s.FunctionDefinition = &FunctionDefinition return s } func NewCreateForScalaFunctionRequest( - name SchemaObjectIdentifier, + name SchemaObjectIdentifierWithArguments, ResultDataType DataType, Handler string, ) *CreateForScalaFunctionRequest { @@ -333,23 +333,23 @@ func NewCreateForScalaFunctionRequest( return &s } -func (s *CreateForScalaFunctionRequest) WithOrReplace(OrReplace *bool) *CreateForScalaFunctionRequest { - s.OrReplace = OrReplace +func (s *CreateForScalaFunctionRequest) WithOrReplace(OrReplace bool) *CreateForScalaFunctionRequest { + s.OrReplace = &OrReplace return s } -func (s *CreateForScalaFunctionRequest) WithTemporary(Temporary *bool) *CreateForScalaFunctionRequest { - s.Temporary = Temporary +func (s *CreateForScalaFunctionRequest) WithTemporary(Temporary bool) *CreateForScalaFunctionRequest { + s.Temporary = &Temporary return s } -func (s *CreateForScalaFunctionRequest) WithSecure(Secure *bool) *CreateForScalaFunctionRequest { - s.Secure = Secure +func (s *CreateForScalaFunctionRequest) WithSecure(Secure bool) *CreateForScalaFunctionRequest { + s.Secure = &Secure return s } -func (s *CreateForScalaFunctionRequest) WithIfNotExists(IfNotExists *bool) *CreateForScalaFunctionRequest { - s.IfNotExists = IfNotExists +func (s *CreateForScalaFunctionRequest) WithIfNotExists(IfNotExists bool) *CreateForScalaFunctionRequest { + s.IfNotExists = &IfNotExists return s } @@ -358,33 +358,33 @@ func (s *CreateForScalaFunctionRequest) WithArguments(Arguments []FunctionArgume return s } -func (s *CreateForScalaFunctionRequest) WithCopyGrants(CopyGrants *bool) *CreateForScalaFunctionRequest { - s.CopyGrants = CopyGrants +func (s *CreateForScalaFunctionRequest) WithCopyGrants(CopyGrants bool) *CreateForScalaFunctionRequest { + s.CopyGrants = &CopyGrants return s } -func (s *CreateForScalaFunctionRequest) WithReturnNullValues(ReturnNullValues *ReturnNullValues) *CreateForScalaFunctionRequest { - s.ReturnNullValues = ReturnNullValues +func (s *CreateForScalaFunctionRequest) WithReturnNullValues(ReturnNullValues ReturnNullValues) *CreateForScalaFunctionRequest { + s.ReturnNullValues = &ReturnNullValues return s } -func (s *CreateForScalaFunctionRequest) WithNullInputBehavior(NullInputBehavior *NullInputBehavior) *CreateForScalaFunctionRequest { - s.NullInputBehavior = NullInputBehavior +func (s *CreateForScalaFunctionRequest) WithNullInputBehavior(NullInputBehavior NullInputBehavior) *CreateForScalaFunctionRequest { + s.NullInputBehavior = &NullInputBehavior return s } -func (s *CreateForScalaFunctionRequest) WithReturnResultsBehavior(ReturnResultsBehavior *ReturnResultsBehavior) *CreateForScalaFunctionRequest { - s.ReturnResultsBehavior = ReturnResultsBehavior +func (s *CreateForScalaFunctionRequest) WithReturnResultsBehavior(ReturnResultsBehavior ReturnResultsBehavior) *CreateForScalaFunctionRequest { + s.ReturnResultsBehavior = &ReturnResultsBehavior return s } -func (s *CreateForScalaFunctionRequest) WithRuntimeVersion(RuntimeVersion *string) *CreateForScalaFunctionRequest { - s.RuntimeVersion = RuntimeVersion +func (s *CreateForScalaFunctionRequest) WithRuntimeVersion(RuntimeVersion string) *CreateForScalaFunctionRequest { + s.RuntimeVersion = &RuntimeVersion return s } -func (s *CreateForScalaFunctionRequest) WithComment(Comment *string) *CreateForScalaFunctionRequest { - s.Comment = Comment +func (s *CreateForScalaFunctionRequest) WithComment(Comment string) *CreateForScalaFunctionRequest { + s.Comment = &Comment return s } @@ -398,18 +398,18 @@ func (s *CreateForScalaFunctionRequest) WithPackages(Packages []FunctionPackageR return s } -func (s *CreateForScalaFunctionRequest) WithTargetPath(TargetPath *string) *CreateForScalaFunctionRequest { - s.TargetPath = TargetPath +func (s *CreateForScalaFunctionRequest) WithTargetPath(TargetPath string) *CreateForScalaFunctionRequest { + s.TargetPath = &TargetPath return s } -func (s *CreateForScalaFunctionRequest) WithFunctionDefinition(FunctionDefinition *string) *CreateForScalaFunctionRequest { - s.FunctionDefinition = FunctionDefinition +func (s *CreateForScalaFunctionRequest) WithFunctionDefinition(FunctionDefinition string) *CreateForScalaFunctionRequest { + s.FunctionDefinition = &FunctionDefinition return s } func NewCreateForSQLFunctionRequest( - name SchemaObjectIdentifier, + name SchemaObjectIdentifierWithArguments, Returns FunctionReturnsRequest, FunctionDefinition string, ) *CreateForSQLFunctionRequest { @@ -420,18 +420,18 @@ func NewCreateForSQLFunctionRequest( return &s } -func (s *CreateForSQLFunctionRequest) WithOrReplace(OrReplace *bool) *CreateForSQLFunctionRequest { - s.OrReplace = OrReplace +func (s *CreateForSQLFunctionRequest) WithOrReplace(OrReplace bool) *CreateForSQLFunctionRequest { + s.OrReplace = &OrReplace return s } -func (s *CreateForSQLFunctionRequest) WithTemporary(Temporary *bool) *CreateForSQLFunctionRequest { - s.Temporary = Temporary +func (s *CreateForSQLFunctionRequest) WithTemporary(Temporary bool) *CreateForSQLFunctionRequest { + s.Temporary = &Temporary return s } -func (s *CreateForSQLFunctionRequest) WithSecure(Secure *bool) *CreateForSQLFunctionRequest { - s.Secure = Secure +func (s *CreateForSQLFunctionRequest) WithSecure(Secure bool) *CreateForSQLFunctionRequest { + s.Secure = &Secure return s } @@ -440,33 +440,33 @@ func (s *CreateForSQLFunctionRequest) WithArguments(Arguments []FunctionArgument return s } -func (s *CreateForSQLFunctionRequest) WithCopyGrants(CopyGrants *bool) *CreateForSQLFunctionRequest { - s.CopyGrants = CopyGrants +func (s *CreateForSQLFunctionRequest) WithCopyGrants(CopyGrants bool) *CreateForSQLFunctionRequest { + s.CopyGrants = &CopyGrants return s } -func (s *CreateForSQLFunctionRequest) WithReturnNullValues(ReturnNullValues *ReturnNullValues) *CreateForSQLFunctionRequest { - s.ReturnNullValues = ReturnNullValues +func (s *CreateForSQLFunctionRequest) WithReturnNullValues(ReturnNullValues ReturnNullValues) *CreateForSQLFunctionRequest { + s.ReturnNullValues = &ReturnNullValues return s } -func (s *CreateForSQLFunctionRequest) WithReturnResultsBehavior(ReturnResultsBehavior *ReturnResultsBehavior) *CreateForSQLFunctionRequest { - s.ReturnResultsBehavior = ReturnResultsBehavior +func (s *CreateForSQLFunctionRequest) WithReturnResultsBehavior(ReturnResultsBehavior ReturnResultsBehavior) *CreateForSQLFunctionRequest { + s.ReturnResultsBehavior = &ReturnResultsBehavior return s } -func (s *CreateForSQLFunctionRequest) WithMemoizable(Memoizable *bool) *CreateForSQLFunctionRequest { - s.Memoizable = Memoizable +func (s *CreateForSQLFunctionRequest) WithMemoizable(Memoizable bool) *CreateForSQLFunctionRequest { + s.Memoizable = &Memoizable return s } -func (s *CreateForSQLFunctionRequest) WithComment(Comment *string) *CreateForSQLFunctionRequest { - s.Comment = Comment +func (s *CreateForSQLFunctionRequest) WithComment(Comment string) *CreateForSQLFunctionRequest { + s.Comment = &Comment return s } func NewAlterFunctionRequest( - name SchemaObjectIdentifier, + name SchemaObjectIdentifierWithArguments, ArgumentDataTypes []DataType, ) *AlterFunctionRequest { s := AlterFunctionRequest{} @@ -475,53 +475,53 @@ func NewAlterFunctionRequest( return &s } -func (s *AlterFunctionRequest) WithIfExists(IfExists *bool) *AlterFunctionRequest { - s.IfExists = IfExists +func (s *AlterFunctionRequest) WithIfExists(IfExists bool) *AlterFunctionRequest { + s.IfExists = &IfExists return s } -func (s *AlterFunctionRequest) WithRenameTo(RenameTo *SchemaObjectIdentifier) *AlterFunctionRequest { - s.RenameTo = RenameTo +func (s *AlterFunctionRequest) WithRenameTo(RenameTo SchemaObjectIdentifier) *AlterFunctionRequest { + s.RenameTo = &RenameTo return s } -func (s *AlterFunctionRequest) WithSetComment(SetComment *string) *AlterFunctionRequest { - s.SetComment = SetComment +func (s *AlterFunctionRequest) WithSetComment(SetComment string) *AlterFunctionRequest { + s.SetComment = &SetComment return s } -func (s *AlterFunctionRequest) WithSetLogLevel(SetLogLevel *string) *AlterFunctionRequest { - s.SetLogLevel = SetLogLevel +func (s *AlterFunctionRequest) WithSetLogLevel(SetLogLevel string) *AlterFunctionRequest { + s.SetLogLevel = &SetLogLevel return s } -func (s *AlterFunctionRequest) WithSetTraceLevel(SetTraceLevel *string) *AlterFunctionRequest { - s.SetTraceLevel = SetTraceLevel +func (s *AlterFunctionRequest) WithSetTraceLevel(SetTraceLevel string) *AlterFunctionRequest { + s.SetTraceLevel = &SetTraceLevel return s } -func (s *AlterFunctionRequest) WithSetSecure(SetSecure *bool) *AlterFunctionRequest { - s.SetSecure = SetSecure +func (s *AlterFunctionRequest) WithSetSecure(SetSecure bool) *AlterFunctionRequest { + s.SetSecure = &SetSecure return s } -func (s *AlterFunctionRequest) WithUnsetSecure(UnsetSecure *bool) *AlterFunctionRequest { - s.UnsetSecure = UnsetSecure +func (s *AlterFunctionRequest) WithUnsetSecure(UnsetSecure bool) *AlterFunctionRequest { + s.UnsetSecure = &UnsetSecure return s } -func (s *AlterFunctionRequest) WithUnsetLogLevel(UnsetLogLevel *bool) *AlterFunctionRequest { - s.UnsetLogLevel = UnsetLogLevel +func (s *AlterFunctionRequest) WithUnsetLogLevel(UnsetLogLevel bool) *AlterFunctionRequest { + s.UnsetLogLevel = &UnsetLogLevel return s } -func (s *AlterFunctionRequest) WithUnsetTraceLevel(UnsetTraceLevel *bool) *AlterFunctionRequest { - s.UnsetTraceLevel = UnsetTraceLevel +func (s *AlterFunctionRequest) WithUnsetTraceLevel(UnsetTraceLevel bool) *AlterFunctionRequest { + s.UnsetTraceLevel = &UnsetTraceLevel return s } -func (s *AlterFunctionRequest) WithUnsetComment(UnsetComment *bool) *AlterFunctionRequest { - s.UnsetComment = UnsetComment +func (s *AlterFunctionRequest) WithUnsetComment(UnsetComment bool) *AlterFunctionRequest { + s.UnsetComment = &UnsetComment return s } @@ -536,7 +536,7 @@ func (s *AlterFunctionRequest) WithUnsetTags(UnsetTags []ObjectIdentifier) *Alte } func NewDropFunctionRequest( - name SchemaObjectIdentifier, + name SchemaObjectIdentifierWithArguments, ArgumentDataTypes []DataType, ) *DropFunctionRequest { s := DropFunctionRequest{} @@ -545,8 +545,8 @@ func NewDropFunctionRequest( return &s } -func (s *DropFunctionRequest) WithIfExists(IfExists *bool) *DropFunctionRequest { - s.IfExists = IfExists +func (s *DropFunctionRequest) WithIfExists(IfExists bool) *DropFunctionRequest { + s.IfExists = &IfExists return s } @@ -554,18 +554,18 @@ func NewShowFunctionRequest() *ShowFunctionRequest { return &ShowFunctionRequest{} } -func (s *ShowFunctionRequest) WithLike(Like *Like) *ShowFunctionRequest { - s.Like = Like +func (s *ShowFunctionRequest) WithLike(Like Like) *ShowFunctionRequest { + s.Like = &Like return s } -func (s *ShowFunctionRequest) WithIn(In *In) *ShowFunctionRequest { - s.In = In +func (s *ShowFunctionRequest) WithIn(In In) *ShowFunctionRequest { + s.In = &In return s } func NewDescribeFunctionRequest( - name SchemaObjectIdentifier, + name SchemaObjectIdentifierWithArguments, ArgumentDataTypes []DataType, ) *DescribeFunctionRequest { s := DescribeFunctionRequest{} diff --git a/pkg/sdk/functions_dto_gen.go b/pkg/sdk/functions_dto_gen.go index 78b8224c1a..02423bf3e2 100644 --- a/pkg/sdk/functions_dto_gen.go +++ b/pkg/sdk/functions_dto_gen.go @@ -19,7 +19,7 @@ type CreateForJavaFunctionRequest struct { Temporary *bool Secure *bool IfNotExists *bool - name SchemaObjectIdentifier // required + name SchemaObjectIdentifierWithArguments // required Arguments []FunctionArgumentRequest CopyGrants *bool Returns FunctionReturnsRequest // required @@ -73,7 +73,7 @@ type CreateForJavascriptFunctionRequest struct { OrReplace *bool Temporary *bool Secure *bool - name SchemaObjectIdentifier // required + name SchemaObjectIdentifierWithArguments // required Arguments []FunctionArgumentRequest CopyGrants *bool Returns FunctionReturnsRequest // required @@ -89,7 +89,7 @@ type CreateForPythonFunctionRequest struct { Temporary *bool Secure *bool IfNotExists *bool - name SchemaObjectIdentifier // required + name SchemaObjectIdentifierWithArguments // required Arguments []FunctionArgumentRequest CopyGrants *bool Returns FunctionReturnsRequest // required @@ -111,7 +111,7 @@ type CreateForScalaFunctionRequest struct { Temporary *bool Secure *bool IfNotExists *bool - name SchemaObjectIdentifier // required + name SchemaObjectIdentifierWithArguments // required Arguments []FunctionArgumentRequest CopyGrants *bool ResultDataType DataType // required @@ -131,7 +131,7 @@ type CreateForSQLFunctionRequest struct { OrReplace *bool Temporary *bool Secure *bool - name SchemaObjectIdentifier // required + name SchemaObjectIdentifierWithArguments // required Arguments []FunctionArgumentRequest CopyGrants *bool Returns FunctionReturnsRequest // required @@ -144,8 +144,8 @@ type CreateForSQLFunctionRequest struct { type AlterFunctionRequest struct { IfExists *bool - name SchemaObjectIdentifier // required - ArgumentDataTypes []DataType // required + name SchemaObjectIdentifierWithArguments // required + ArgumentDataTypes []DataType // required RenameTo *SchemaObjectIdentifier SetComment *string SetLogLevel *string @@ -161,8 +161,8 @@ type AlterFunctionRequest struct { type DropFunctionRequest struct { IfExists *bool - name SchemaObjectIdentifier // required - ArgumentDataTypes []DataType // required + name SchemaObjectIdentifierWithArguments // required + ArgumentDataTypes []DataType // required } type ShowFunctionRequest struct { @@ -171,6 +171,6 @@ type ShowFunctionRequest struct { } type DescribeFunctionRequest struct { - name SchemaObjectIdentifier // required - ArgumentDataTypes []DataType // required + name SchemaObjectIdentifierWithArguments // required + ArgumentDataTypes []DataType // required } diff --git a/pkg/sdk/functions_gen.go b/pkg/sdk/functions_gen.go index 791ee6ef96..d2c6066699 100644 --- a/pkg/sdk/functions_gen.go +++ b/pkg/sdk/functions_gen.go @@ -14,35 +14,35 @@ type Functions interface { Alter(ctx context.Context, request *AlterFunctionRequest) error Drop(ctx context.Context, request *DropFunctionRequest) error Show(ctx context.Context, request *ShowFunctionRequest) ([]Function, error) - ShowByID(ctx context.Context, id SchemaObjectIdentifier) (*Function, error) - Describe(ctx context.Context, request *DescribeFunctionRequest) ([]FunctionDetail, error) + ShowByID(ctx context.Context, id SchemaObjectIdentifierWithArguments) (*Function, error) + Describe(ctx context.Context, id SchemaObjectIdentifierWithArguments) ([]FunctionDetail, error) } // CreateForJavaFunctionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/create-function#java-handler. type CreateForJavaFunctionOptions struct { - create bool `ddl:"static" sql:"CREATE"` - OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` - Temporary *bool `ddl:"keyword" sql:"TEMPORARY"` - Secure *bool `ddl:"keyword" sql:"SECURE"` - function bool `ddl:"static" sql:"FUNCTION"` - IfNotExists *bool `ddl:"keyword" sql:"IF NOT EXISTS"` - name SchemaObjectIdentifier `ddl:"identifier"` - Arguments []FunctionArgument `ddl:"list,must_parentheses"` - CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` - Returns FunctionReturns `ddl:"keyword" sql:"RETURNS"` - ReturnNullValues *ReturnNullValues `ddl:"keyword"` - languageJava bool `ddl:"static" sql:"LANGUAGE JAVA"` - NullInputBehavior *NullInputBehavior `ddl:"keyword"` - ReturnResultsBehavior *ReturnResultsBehavior `ddl:"keyword"` - RuntimeVersion *string `ddl:"parameter,single_quotes" sql:"RUNTIME_VERSION"` - Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` - Imports []FunctionImport `ddl:"parameter,parentheses" sql:"IMPORTS"` - Packages []FunctionPackage `ddl:"parameter,parentheses" sql:"PACKAGES"` - 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"` - FunctionDefinition *string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` + create bool `ddl:"static" sql:"CREATE"` + OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` + Temporary *bool `ddl:"keyword" sql:"TEMPORARY"` + Secure *bool `ddl:"keyword" sql:"SECURE"` + function bool `ddl:"static" sql:"FUNCTION"` + IfNotExists *bool `ddl:"keyword" sql:"IF NOT EXISTS"` + name SchemaObjectIdentifierWithArguments `ddl:"identifier"` + Arguments []FunctionArgument `ddl:"list,must_parentheses"` + CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` + Returns FunctionReturns `ddl:"keyword" sql:"RETURNS"` + ReturnNullValues *ReturnNullValues `ddl:"keyword"` + languageJava bool `ddl:"static" sql:"LANGUAGE JAVA"` + NullInputBehavior *NullInputBehavior `ddl:"keyword"` + ReturnResultsBehavior *ReturnResultsBehavior `ddl:"keyword"` + RuntimeVersion *string `ddl:"parameter,single_quotes" sql:"RUNTIME_VERSION"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + Imports []FunctionImport `ddl:"parameter,parentheses" sql:"IMPORTS"` + Packages []FunctionPackage `ddl:"parameter,parentheses" sql:"PACKAGES"` + 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"` + FunctionDefinition *string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` } type FunctionArgument struct { @@ -79,119 +79,119 @@ type FunctionPackage struct { // CreateForJavascriptFunctionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/create-function#javascript-handler. type CreateForJavascriptFunctionOptions struct { - create bool `ddl:"static" sql:"CREATE"` - OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` - Temporary *bool `ddl:"keyword" sql:"TEMPORARY"` - Secure *bool `ddl:"keyword" sql:"SECURE"` - function bool `ddl:"static" sql:"FUNCTION"` - name SchemaObjectIdentifier `ddl:"identifier"` - Arguments []FunctionArgument `ddl:"list,must_parentheses"` - CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` - Returns FunctionReturns `ddl:"keyword" sql:"RETURNS"` - ReturnNullValues *ReturnNullValues `ddl:"keyword"` - languageJavascript bool `ddl:"static" sql:"LANGUAGE JAVASCRIPT"` - NullInputBehavior *NullInputBehavior `ddl:"keyword"` - ReturnResultsBehavior *ReturnResultsBehavior `ddl:"keyword"` - Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` - FunctionDefinition string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` + create bool `ddl:"static" sql:"CREATE"` + OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` + Temporary *bool `ddl:"keyword" sql:"TEMPORARY"` + Secure *bool `ddl:"keyword" sql:"SECURE"` + function bool `ddl:"static" sql:"FUNCTION"` + name SchemaObjectIdentifierWithArguments `ddl:"identifier"` + Arguments []FunctionArgument `ddl:"list,must_parentheses"` + CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` + Returns FunctionReturns `ddl:"keyword" sql:"RETURNS"` + ReturnNullValues *ReturnNullValues `ddl:"keyword"` + languageJavascript bool `ddl:"static" sql:"LANGUAGE JAVASCRIPT"` + NullInputBehavior *NullInputBehavior `ddl:"keyword"` + ReturnResultsBehavior *ReturnResultsBehavior `ddl:"keyword"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + FunctionDefinition string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` } // CreateForPythonFunctionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/create-function#python-handler. type CreateForPythonFunctionOptions struct { - create bool `ddl:"static" sql:"CREATE"` - OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` - Temporary *bool `ddl:"keyword" sql:"TEMPORARY"` - Secure *bool `ddl:"keyword" sql:"SECURE"` - function bool `ddl:"static" sql:"FUNCTION"` - IfNotExists *bool `ddl:"keyword" sql:"IF NOT EXISTS"` - name SchemaObjectIdentifier `ddl:"identifier"` - Arguments []FunctionArgument `ddl:"list,must_parentheses"` - CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` - Returns FunctionReturns `ddl:"keyword" sql:"RETURNS"` - ReturnNullValues *ReturnNullValues `ddl:"keyword"` - languagePython bool `ddl:"static" sql:"LANGUAGE PYTHON"` - NullInputBehavior *NullInputBehavior `ddl:"keyword"` - ReturnResultsBehavior *ReturnResultsBehavior `ddl:"keyword"` - RuntimeVersion string `ddl:"parameter,single_quotes" sql:"RUNTIME_VERSION"` - Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` - Imports []FunctionImport `ddl:"parameter,parentheses" sql:"IMPORTS"` - Packages []FunctionPackage `ddl:"parameter,parentheses" sql:"PACKAGES"` - Handler string `ddl:"parameter,single_quotes" sql:"HANDLER"` - ExternalAccessIntegrations []AccountObjectIdentifier `ddl:"parameter,parentheses" sql:"EXTERNAL_ACCESS_INTEGRATIONS"` - Secrets []Secret `ddl:"parameter,parentheses" sql:"SECRETS"` - FunctionDefinition *string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` + create bool `ddl:"static" sql:"CREATE"` + OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` + Temporary *bool `ddl:"keyword" sql:"TEMPORARY"` + Secure *bool `ddl:"keyword" sql:"SECURE"` + function bool `ddl:"static" sql:"FUNCTION"` + IfNotExists *bool `ddl:"keyword" sql:"IF NOT EXISTS"` + name SchemaObjectIdentifierWithArguments `ddl:"identifier"` + Arguments []FunctionArgument `ddl:"list,must_parentheses"` + CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` + Returns FunctionReturns `ddl:"keyword" sql:"RETURNS"` + ReturnNullValues *ReturnNullValues `ddl:"keyword"` + languagePython bool `ddl:"static" sql:"LANGUAGE PYTHON"` + NullInputBehavior *NullInputBehavior `ddl:"keyword"` + ReturnResultsBehavior *ReturnResultsBehavior `ddl:"keyword"` + RuntimeVersion string `ddl:"parameter,single_quotes" sql:"RUNTIME_VERSION"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + Imports []FunctionImport `ddl:"parameter,parentheses" sql:"IMPORTS"` + Packages []FunctionPackage `ddl:"parameter,parentheses" sql:"PACKAGES"` + Handler string `ddl:"parameter,single_quotes" sql:"HANDLER"` + ExternalAccessIntegrations []AccountObjectIdentifier `ddl:"parameter,parentheses" sql:"EXTERNAL_ACCESS_INTEGRATIONS"` + Secrets []Secret `ddl:"parameter,parentheses" sql:"SECRETS"` + FunctionDefinition *string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` } // CreateForScalaFunctionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/create-function#scala-handler. type CreateForScalaFunctionOptions struct { - create bool `ddl:"static" sql:"CREATE"` - OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` - Temporary *bool `ddl:"keyword" sql:"TEMPORARY"` - Secure *bool `ddl:"keyword" sql:"SECURE"` - function bool `ddl:"static" sql:"FUNCTION"` - IfNotExists *bool `ddl:"keyword" sql:"IF NOT EXISTS"` - name SchemaObjectIdentifier `ddl:"identifier"` - Arguments []FunctionArgument `ddl:"list,must_parentheses"` - CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` - ResultDataType DataType `ddl:"parameter,no_equals" sql:"RETURNS"` - ReturnNullValues *ReturnNullValues `ddl:"keyword"` - languageScala bool `ddl:"static" sql:"LANGUAGE SCALA"` - NullInputBehavior *NullInputBehavior `ddl:"keyword"` - ReturnResultsBehavior *ReturnResultsBehavior `ddl:"keyword"` - RuntimeVersion *string `ddl:"parameter,single_quotes" sql:"RUNTIME_VERSION"` - Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` - Imports []FunctionImport `ddl:"parameter,parentheses" sql:"IMPORTS"` - Packages []FunctionPackage `ddl:"parameter,parentheses" sql:"PACKAGES"` - Handler string `ddl:"parameter,single_quotes" sql:"HANDLER"` - TargetPath *string `ddl:"parameter,single_quotes" sql:"TARGET_PATH"` - FunctionDefinition *string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` + create bool `ddl:"static" sql:"CREATE"` + OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` + Temporary *bool `ddl:"keyword" sql:"TEMPORARY"` + Secure *bool `ddl:"keyword" sql:"SECURE"` + function bool `ddl:"static" sql:"FUNCTION"` + IfNotExists *bool `ddl:"keyword" sql:"IF NOT EXISTS"` + name SchemaObjectIdentifierWithArguments `ddl:"identifier"` + Arguments []FunctionArgument `ddl:"list,must_parentheses"` + CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` + ResultDataType DataType `ddl:"parameter,no_equals" sql:"RETURNS"` + ReturnNullValues *ReturnNullValues `ddl:"keyword"` + languageScala bool `ddl:"static" sql:"LANGUAGE SCALA"` + NullInputBehavior *NullInputBehavior `ddl:"keyword"` + ReturnResultsBehavior *ReturnResultsBehavior `ddl:"keyword"` + RuntimeVersion *string `ddl:"parameter,single_quotes" sql:"RUNTIME_VERSION"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + Imports []FunctionImport `ddl:"parameter,parentheses" sql:"IMPORTS"` + Packages []FunctionPackage `ddl:"parameter,parentheses" sql:"PACKAGES"` + Handler string `ddl:"parameter,single_quotes" sql:"HANDLER"` + TargetPath *string `ddl:"parameter,single_quotes" sql:"TARGET_PATH"` + FunctionDefinition *string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` } // CreateForSQLFunctionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/create-function#sql-handler. type CreateForSQLFunctionOptions struct { - create bool `ddl:"static" sql:"CREATE"` - OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` - Temporary *bool `ddl:"keyword" sql:"TEMPORARY"` - Secure *bool `ddl:"keyword" sql:"SECURE"` - function bool `ddl:"static" sql:"FUNCTION"` - name SchemaObjectIdentifier `ddl:"identifier"` - Arguments []FunctionArgument `ddl:"list,must_parentheses"` - CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` - Returns FunctionReturns `ddl:"keyword" sql:"RETURNS"` - ReturnNullValues *ReturnNullValues `ddl:"keyword"` - ReturnResultsBehavior *ReturnResultsBehavior `ddl:"keyword"` - Memoizable *bool `ddl:"keyword" sql:"MEMOIZABLE"` - Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` - FunctionDefinition string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` + create bool `ddl:"static" sql:"CREATE"` + OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` + Temporary *bool `ddl:"keyword" sql:"TEMPORARY"` + Secure *bool `ddl:"keyword" sql:"SECURE"` + function bool `ddl:"static" sql:"FUNCTION"` + name SchemaObjectIdentifierWithArguments `ddl:"identifier"` + Arguments []FunctionArgument `ddl:"list,must_parentheses"` + CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` + Returns FunctionReturns `ddl:"keyword" sql:"RETURNS"` + ReturnNullValues *ReturnNullValues `ddl:"keyword"` + ReturnResultsBehavior *ReturnResultsBehavior `ddl:"keyword"` + Memoizable *bool `ddl:"keyword" sql:"MEMOIZABLE"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + FunctionDefinition string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` } // AlterFunctionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/alter-function. type AlterFunctionOptions struct { - alter bool `ddl:"static" sql:"ALTER"` - function bool `ddl:"static" sql:"FUNCTION"` - IfExists *bool `ddl:"keyword" sql:"IF EXISTS"` - name SchemaObjectIdentifier `ddl:"identifier"` - ArgumentDataTypes []DataType `ddl:"keyword,must_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"` - SetSecure *bool `ddl:"keyword" sql:"SET SECURE"` - UnsetSecure *bool `ddl:"keyword" sql:"UNSET SECURE"` - UnsetLogLevel *bool `ddl:"keyword" sql:"UNSET LOG_LEVEL"` - UnsetTraceLevel *bool `ddl:"keyword" sql:"UNSET TRACE_LEVEL"` - UnsetComment *bool `ddl:"keyword" sql:"UNSET COMMENT"` - SetTags []TagAssociation `ddl:"keyword" sql:"SET TAG"` - UnsetTags []ObjectIdentifier `ddl:"keyword" sql:"UNSET TAG"` + alter bool `ddl:"static" sql:"ALTER"` + function bool `ddl:"static" sql:"FUNCTION"` + IfExists *bool `ddl:"keyword" sql:"IF EXISTS"` + name SchemaObjectIdentifierWithArguments `ddl:"identifier"` + ArgumentDataTypes []DataType `ddl:"keyword,must_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"` + SetSecure *bool `ddl:"keyword" sql:"SET SECURE"` + UnsetSecure *bool `ddl:"keyword" sql:"UNSET SECURE"` + UnsetLogLevel *bool `ddl:"keyword" sql:"UNSET LOG_LEVEL"` + UnsetTraceLevel *bool `ddl:"keyword" sql:"UNSET TRACE_LEVEL"` + UnsetComment *bool `ddl:"keyword" sql:"UNSET COMMENT"` + SetTags []TagAssociation `ddl:"keyword" sql:"SET TAG"` + UnsetTags []ObjectIdentifier `ddl:"keyword" sql:"UNSET TAG"` } // DropFunctionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/drop-function. type DropFunctionOptions struct { - drop bool `ddl:"static" sql:"DROP"` - function bool `ddl:"static" sql:"FUNCTION"` - IfExists *bool `ddl:"keyword" sql:"IF EXISTS"` - name SchemaObjectIdentifier `ddl:"identifier"` - ArgumentDataTypes []DataType `ddl:"keyword,must_parentheses"` + drop bool `ddl:"static" sql:"DROP"` + function bool `ddl:"static" sql:"FUNCTION"` + IfExists *bool `ddl:"keyword" sql:"IF EXISTS"` + name SchemaObjectIdentifierWithArguments `ddl:"identifier"` + ArgumentDataTypes []DataType `ddl:"keyword,must_parentheses"` } // ShowFunctionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/show-user-functions. @@ -242,16 +242,18 @@ type Function struct { IsMemoizable bool } -func (v *Function) ID() SchemaObjectIdentifier { - return NewSchemaObjectIdentifier(v.CatalogName, v.SchemaName, v.Name) +func (v *Function) ID() SchemaObjectIdentifierWithArguments { + //return NewSchemaObjectIdentifier(v.CatalogName, v.SchemaName, v.Name) + // TODO: + return SchemaObjectIdentifierWithArguments{} } // DescribeFunctionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/desc-function. type DescribeFunctionOptions struct { - describe bool `ddl:"static" sql:"DESCRIBE"` - function bool `ddl:"static" sql:"FUNCTION"` - name SchemaObjectIdentifier `ddl:"identifier"` - ArgumentDataTypes []DataType `ddl:"keyword,must_parentheses"` + describe bool `ddl:"static" sql:"DESCRIBE"` + function bool `ddl:"static" sql:"FUNCTION"` + name SchemaObjectIdentifierWithArguments `ddl:"identifier"` + ArgumentDataTypes []DataType `ddl:"keyword,must_parentheses"` } type functionDetailRow struct { diff --git a/pkg/sdk/functions_impl_gen.go b/pkg/sdk/functions_impl_gen.go index 7a30c62da6..63a1818f8a 100644 --- a/pkg/sdk/functions_impl_gen.go +++ b/pkg/sdk/functions_impl_gen.go @@ -57,8 +57,8 @@ func (v *functions) Show(ctx context.Context, request *ShowFunctionRequest) ([]F return resultList, nil } -func (v *functions) ShowByID(ctx context.Context, id SchemaObjectIdentifier) (*Function, error) { - request := NewShowFunctionRequest().WithIn(&In{Schema: id.SchemaId()}).WithLike(&Like{String(id.Name())}) +func (v *functions) ShowByID(ctx context.Context, id SchemaObjectIdentifierWithArguments) (*Function, error) { + request := NewShowFunctionRequest().WithIn(In{Schema: id.SchemaId()}).WithLike(Like{String(id.Name())}) functions, err := v.Show(ctx, request) if err != nil { return nil, err @@ -66,8 +66,10 @@ func (v *functions) ShowByID(ctx context.Context, id SchemaObjectIdentifier) (*F return collections.FindOne(functions, func(r Function) bool { return r.Name == id.Name() }) } -func (v *functions) Describe(ctx context.Context, request *DescribeFunctionRequest) ([]FunctionDetail, error) { - opts := request.toOpts() +func (v *functions) Describe(ctx context.Context, id SchemaObjectIdentifierWithArguments) ([]FunctionDetail, error) { + opts := DescribeFunctionOptions{ + name: id, + } rows, err := validateAndQuery[functionDetailRow](v.client, ctx, opts) if err != nil { return nil, err diff --git a/pkg/sdk/identifier_helpers.go b/pkg/sdk/identifier_helpers.go index c7632347da..d757d4f6ea 100644 --- a/pkg/sdk/identifier_helpers.go +++ b/pkg/sdk/identifier_helpers.go @@ -232,7 +232,6 @@ type SchemaObjectIdentifier struct { databaseName string schemaName string name string - arguments []DataType } func NewSchemaObjectIdentifierInSchema(schemaId DatabaseObjectIdentifier, name string) SchemaObjectIdentifier { @@ -247,38 +246,12 @@ func NewSchemaObjectIdentifier(databaseName, schemaName, name string) SchemaObje } } -func NewSchemaObjectIdentifierWithArguments(databaseName, schemaName, name string, arguments []DataType) SchemaObjectIdentifier { - return SchemaObjectIdentifier{ - databaseName: strings.Trim(databaseName, `"`), - schemaName: strings.Trim(schemaName, `"`), - name: strings.Trim(name, `"`), - arguments: arguments, - } -} - func NewSchemaObjectIdentifierFromFullyQualifiedName(fullyQualifiedName string) SchemaObjectIdentifier { parts := strings.Split(fullyQualifiedName, ".") id := SchemaObjectIdentifier{} id.databaseName = strings.Trim(parts[0], `"`) id.schemaName = strings.Trim(parts[1], `"`) - - // this is either a function or procedure - if strings.HasSuffix(parts[2], ")") { - idx := strings.LastIndex(parts[2], "(") - id.name = strings.Trim(parts[2][:idx], `"`) - strArgs := strings.Split(strings.Trim(parts[2][idx+1:], `)`), ",") - id.arguments = make([]DataType, 0) - for _, arg := range strArgs { - trimmedArg := strings.TrimSpace(strings.Trim(arg, `"`)) - if trimmedArg == "" { - continue - } - dt, _ := ToDataType(trimmedArg) - id.arguments = append(id.arguments, dt) - } - } else { // this is every other kind of schema object - id.name = strings.Trim(parts[2], `"`) - } + id.name = strings.Trim(parts[2], `"`) return id } @@ -294,10 +267,6 @@ func (i SchemaObjectIdentifier) Name() string { return i.name } -func (i SchemaObjectIdentifier) Arguments() []DataType { - return i.arguments -} - func (i SchemaObjectIdentifier) SchemaId() DatabaseObjectIdentifier { return NewDatabaseObjectIdentifier(i.databaseName, i.schemaName) } @@ -310,27 +279,73 @@ func (i SchemaObjectIdentifier) FullyQualifiedName() string { if i.schemaName == "" && i.databaseName == "" && i.name == "" { return "" } - if len(i.arguments) == 0 { - return fmt.Sprintf(`"%v"."%v"."%v"`, i.databaseName, i.schemaName, i.name) + return fmt.Sprintf(`"%v"."%v"."%v"`, i.databaseName, i.schemaName, i.name) +} + +// TODO: +// - Add parser +// - Add to IsValidIdentifier +// - Handle in the sql_builder +// - Use in function,procedure,external_function +// - Function (Test, Impl) +// - Fix after arguments removed from SchemaObjectIdentifier +// - Look for todos on SNOW-999049 + +type SchemaObjectIdentifierWithArguments struct { + databaseName string + schemaName string + name string + arguments []string +} + +func NewSchemaObjectIdentifierWithArguments(databaseName, schemaName, name string, arguments ...string) SchemaObjectIdentifierWithArguments { + return SchemaObjectIdentifierWithArguments{ + databaseName: strings.Trim(databaseName, `"`), + schemaName: strings.Trim(schemaName, `"`), + name: strings.Trim(name, `"`), } - // if this is a function or procedure, we need to include the arguments - args := make([]string, len(i.arguments)) - for i, arg := range i.arguments { - args[i] = string(arg) +} + +func NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(fullyQualifiedName string) SchemaObjectIdentifierWithArguments { + parts := strings.Split(fullyQualifiedName, ".") + id := SchemaObjectIdentifierWithArguments{ + databaseName: strings.Trim(parts[0], `"`), + schemaName: strings.Trim(parts[1], `"`), + name: strings.Trim(parts[2], `"`), + // TODO: Arguments } - return fmt.Sprintf(`"%v"."%v"."%v"(%v)`, i.databaseName, i.schemaName, i.name, strings.Join(args, ", ")) + return id } -func (i SchemaObjectIdentifier) WithoutArguments() SchemaObjectIdentifier { - return NewSchemaObjectIdentifier(i.databaseName, i.schemaName, i.name) +func (i SchemaObjectIdentifierWithArguments) DatabaseName() string { + return i.databaseName +} + +func (i SchemaObjectIdentifierWithArguments) SchemaName() string { + return i.schemaName } -func (i SchemaObjectIdentifier) ArgumentsSignature() string { - arguments := make([]string, len(i.arguments)) - for i, item := range i.arguments { - arguments[i] = string(item) +func (i SchemaObjectIdentifierWithArguments) Name() string { + return i.name +} + +func (i SchemaObjectIdentifierWithArguments) Arguments() []string { + return i.arguments +} + +func (i SchemaObjectIdentifierWithArguments) SchemaId() DatabaseObjectIdentifier { + return NewDatabaseObjectIdentifier(i.databaseName, i.schemaName) +} + +func (i SchemaObjectIdentifierWithArguments) DatabaseId() AccountObjectIdentifier { + return NewAccountObjectIdentifier(i.databaseName) +} + +func (i SchemaObjectIdentifierWithArguments) FullyQualifiedName() string { + if i.schemaName == "" && i.databaseName == "" && i.name == "" && len(i.arguments) == 0 { + return "" } - return fmt.Sprintf("%v(%v)", i.Name(), strings.Join(arguments, ",")) + return fmt.Sprintf(`"%v"."%v"."%v"(%v)`, i.databaseName, i.schemaName, i.name, strings.Join(i.arguments, ",")) } type TableColumnIdentifier struct { diff --git a/pkg/sdk/poc/main.go b/pkg/sdk/poc/main.go index ed5b340deb..09db71b74a 100644 --- a/pkg/sdk/poc/main.go +++ b/pkg/sdk/poc/main.go @@ -5,6 +5,7 @@ package main import ( "bytes" "fmt" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/genhelpers" "io" "log" "os" diff --git a/pkg/sdk/random_test.go b/pkg/sdk/random_test.go index 8b7c981744..c0df129fca 100644 --- a/pkg/sdk/random_test.go +++ b/pkg/sdk/random_test.go @@ -14,6 +14,10 @@ var ( emptySchemaObjectIdentifier = NewSchemaObjectIdentifier("", "", "") ) +func randomSchemaObjectIdentifierWithArguments() SchemaObjectIdentifierWithArguments { + return NewSchemaObjectIdentifierWithArguments(random.StringN(12), random.StringN(12), random.StringN(12), random.StringN(12)) +} + func randomSchemaObjectIdentifier() SchemaObjectIdentifier { return NewSchemaObjectIdentifier(random.StringN(12), random.StringN(12), random.StringN(12)) } From 1e20d3bd9c707fe093546aa0c82bcaaf15dbc288 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Thu, 25 Jul 2024 14:22:54 +0200 Subject: [PATCH 02/13] save --- pkg/sdk/external_functions_impl_gen.go | 2 +- pkg/sdk/functions_def.go | 3 -- pkg/sdk/functions_gen_test.go | 32 ++++++++++---------- pkg/sdk/functions_impl_gen.go | 2 +- pkg/sdk/identifier_helpers.go | 24 ++++++++------- pkg/sdk/identifier_helpers_test.go | 41 +++++++++++++------------- pkg/sdk/random_test.go | 7 +++-- 7 files changed, 56 insertions(+), 55 deletions(-) diff --git a/pkg/sdk/external_functions_impl_gen.go b/pkg/sdk/external_functions_impl_gen.go index 2316ff90f9..ad23a480c6 100644 --- a/pkg/sdk/external_functions_impl_gen.go +++ b/pkg/sdk/external_functions_impl_gen.go @@ -116,7 +116,7 @@ func (r *AlterExternalFunctionRequest) toOpts() *AlterExternalFunctionOptions { opts := &AlterExternalFunctionOptions{ IfExists: r.IfExists, // TODO: - //name: r.name.WithoutArguments(), + //name: r.name.WithoutArguments(), ArgumentDataTypes: r.ArgumentDataTypes, } if r.Set != nil { diff --git a/pkg/sdk/functions_def.go b/pkg/sdk/functions_def.go index dbf6ea51e2..d098f89fe3 100644 --- a/pkg/sdk/functions_def.go +++ b/pkg/sdk/functions_def.go @@ -231,7 +231,6 @@ var FunctionsDef = g.NewInterface( SQL("FUNCTION"). IfExists(). Name(). - PredefinedQueryStructField("ArgumentDataTypes", "[]DataType", g.KeywordOptions().MustParentheses().Required()). Identifier("RenameTo", g.KindOfTPointer[SchemaObjectIdentifier](), g.IdentifierOptions().SQL("RENAME TO")). OptionalTextAssignment("SET COMMENT", g.ParameterOptions().SingleQuotes()). OptionalTextAssignment("SET LOG_LEVEL", g.ParameterOptions().SingleQuotes()). @@ -253,7 +252,6 @@ var FunctionsDef = g.NewInterface( SQL("FUNCTION"). IfExists(). Name(). - PredefinedQueryStructField("ArgumentDataTypes", "[]DataType", g.KeywordOptions().MustParentheses().Required()). WithValidation(g.ValidIdentifier, "name"), ).ShowOperation( "https://docs.snowflake.com/en/sql-reference/sql/show-user-functions", @@ -311,6 +309,5 @@ var FunctionsDef = g.NewInterface( Describe(). SQL("FUNCTION"). Name(). - PredefinedQueryStructField("ArgumentDataTypes", "[]DataType", g.KeywordOptions().MustParentheses().Required()). WithValidation(g.ValidIdentifier, "name"), ) diff --git a/pkg/sdk/functions_gen_test.go b/pkg/sdk/functions_gen_test.go index 985d825b94..d32046fc74 100644 --- a/pkg/sdk/functions_gen_test.go +++ b/pkg/sdk/functions_gen_test.go @@ -5,7 +5,7 @@ import ( ) func TestFunctions_CreateForJava(t *testing.T) { - id := randomSchemaObjectIdentifier() + id := randomSchemaObjectIdentifierWithArguments() defaultOpts := func() *CreateForJavaFunctionOptions { return &CreateForJavaFunctionOptions{ @@ -20,7 +20,7 @@ func TestFunctions_CreateForJava(t *testing.T) { t.Run("validation: incorrect identifier", func(t *testing.T) { opts := defaultOpts() - opts.name = emptySchemaObjectIdentifier + opts.name = emptySchemaObjectIdentifierWithArguments assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) }) @@ -120,7 +120,7 @@ func TestFunctions_CreateForJava(t *testing.T) { } func TestFunctions_CreateForJavascript(t *testing.T) { - id := randomSchemaObjectIdentifier() + id := randomSchemaObjectIdentifierWithArguments() defaultOpts := func() *CreateForJavascriptFunctionOptions { return &CreateForJavascriptFunctionOptions{ @@ -135,7 +135,7 @@ func TestFunctions_CreateForJavascript(t *testing.T) { t.Run("validation: incorrect identifier", func(t *testing.T) { opts := defaultOpts() - opts.name = emptySchemaObjectIdentifier + opts.name = emptySchemaObjectIdentifierWithArguments assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) }) @@ -183,7 +183,7 @@ func TestFunctions_CreateForJavascript(t *testing.T) { } func TestFunctions_CreateForPython(t *testing.T) { - id := randomSchemaObjectIdentifier() + id := randomSchemaObjectIdentifierWithArguments() defaultOpts := func() *CreateForPythonFunctionOptions { return &CreateForPythonFunctionOptions{ @@ -198,7 +198,7 @@ func TestFunctions_CreateForPython(t *testing.T) { t.Run("validation: incorrect identifier", func(t *testing.T) { opts := defaultOpts() - opts.name = emptySchemaObjectIdentifier + opts.name = emptySchemaObjectIdentifierWithArguments assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) }) @@ -288,7 +288,7 @@ func TestFunctions_CreateForPython(t *testing.T) { } func TestFunctions_CreateForScala(t *testing.T) { - id := randomSchemaObjectIdentifier() + id := randomSchemaObjectIdentifierWithArguments() defaultOpts := func() *CreateForScalaFunctionOptions { return &CreateForScalaFunctionOptions{ @@ -303,7 +303,7 @@ func TestFunctions_CreateForScala(t *testing.T) { t.Run("validation: incorrect identifier", func(t *testing.T) { opts := defaultOpts() - opts.name = emptySchemaObjectIdentifier + opts.name = emptySchemaObjectIdentifierWithArguments assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) }) @@ -357,7 +357,7 @@ func TestFunctions_CreateForScala(t *testing.T) { } func TestFunctions_CreateForSQL(t *testing.T) { - id := randomSchemaObjectIdentifier() + id := randomSchemaObjectIdentifierWithArguments() defaultOpts := func() *CreateForSQLFunctionOptions { return &CreateForSQLFunctionOptions{ @@ -372,7 +372,7 @@ func TestFunctions_CreateForSQL(t *testing.T) { t.Run("validation: incorrect identifier", func(t *testing.T) { opts := defaultOpts() - opts.name = emptySchemaObjectIdentifier + opts.name = emptySchemaObjectIdentifierWithArguments assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) }) @@ -431,7 +431,7 @@ func TestFunctions_CreateForSQL(t *testing.T) { } func TestFunctions_Drop(t *testing.T) { - id := randomSchemaObjectIdentifier() + id := randomSchemaObjectIdentifierWithArguments() defaultOpts := func() *DropFunctionOptions { return &DropFunctionOptions{ @@ -446,7 +446,7 @@ func TestFunctions_Drop(t *testing.T) { t.Run("validation: incorrect identifier", func(t *testing.T) { opts := defaultOpts() - opts.name = emptySchemaObjectIdentifier + opts.name = emptySchemaObjectIdentifierWithArguments assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) }) @@ -466,7 +466,7 @@ func TestFunctions_Drop(t *testing.T) { } func TestFunctions_Alter(t *testing.T) { - id := randomSchemaObjectIdentifier() + id := randomSchemaObjectIdentifierWithArguments() defaultOpts := func() *AlterFunctionOptions { return &AlterFunctionOptions{ @@ -483,7 +483,7 @@ func TestFunctions_Alter(t *testing.T) { t.Run("validation: incorrect identifier", func(t *testing.T) { opts := defaultOpts() - opts.name = emptySchemaObjectIdentifier + opts.name = emptySchemaObjectIdentifierWithArguments assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) }) @@ -615,7 +615,7 @@ func TestFunctions_Show(t *testing.T) { } func TestFunctions_Describe(t *testing.T) { - id := randomSchemaObjectIdentifier() + id := randomSchemaObjectIdentifierWithArguments() defaultOpts := func() *DescribeFunctionOptions { return &DescribeFunctionOptions{ @@ -630,7 +630,7 @@ func TestFunctions_Describe(t *testing.T) { t.Run("validation: incorrect identifier", func(t *testing.T) { opts := defaultOpts() - opts.name = emptySchemaObjectIdentifier + opts.name = emptySchemaObjectIdentifierWithArguments assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) }) diff --git a/pkg/sdk/functions_impl_gen.go b/pkg/sdk/functions_impl_gen.go index 63a1818f8a..33e268a252 100644 --- a/pkg/sdk/functions_impl_gen.go +++ b/pkg/sdk/functions_impl_gen.go @@ -67,7 +67,7 @@ func (v *functions) ShowByID(ctx context.Context, id SchemaObjectIdentifierWithA } func (v *functions) Describe(ctx context.Context, id SchemaObjectIdentifierWithArguments) ([]FunctionDetail, error) { - opts := DescribeFunctionOptions{ + opts := &DescribeFunctionOptions{ name: id, } rows, err := validateAndQuery[functionDetailRow](v.client, ctx, opts) diff --git a/pkg/sdk/identifier_helpers.go b/pkg/sdk/identifier_helpers.go index d757d4f6ea..920adfda2a 100644 --- a/pkg/sdk/identifier_helpers.go +++ b/pkg/sdk/identifier_helpers.go @@ -303,19 +303,21 @@ func NewSchemaObjectIdentifierWithArguments(databaseName, schemaName, name strin databaseName: strings.Trim(databaseName, `"`), schemaName: strings.Trim(schemaName, `"`), name: strings.Trim(name, `"`), + arguments: arguments, } } -func NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(fullyQualifiedName string) SchemaObjectIdentifierWithArguments { - parts := strings.Split(fullyQualifiedName, ".") - id := SchemaObjectIdentifierWithArguments{ - databaseName: strings.Trim(parts[0], `"`), - schemaName: strings.Trim(parts[1], `"`), - name: strings.Trim(parts[2], `"`), - // TODO: Arguments - } - return id -} +// TODO: +//func NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(fullyQualifiedName string) SchemaObjectIdentifierWithArguments { +// parts := strings.Split(fullyQualifiedName, ".") +// id := SchemaObjectIdentifierWithArguments{ +// databaseName: strings.Trim(parts[0], `"`), +// schemaName: strings.Trim(parts[1], `"`), +// name: strings.Trim(parts[2], `"`), +// // TODO: Arguments +// } +// return id +//} func (i SchemaObjectIdentifierWithArguments) DatabaseName() string { return i.databaseName @@ -345,7 +347,7 @@ func (i SchemaObjectIdentifierWithArguments) FullyQualifiedName() string { if i.schemaName == "" && i.databaseName == "" && i.name == "" && len(i.arguments) == 0 { return "" } - return fmt.Sprintf(`"%v"."%v"."%v"(%v)`, i.databaseName, i.schemaName, i.name, strings.Join(i.arguments, ",")) + return fmt.Sprintf(`"%v"."%v"."%v"(%v)`, i.databaseName, i.schemaName, i.name, strings.Join(i.arguments, ", ")) } type TableColumnIdentifier struct { diff --git a/pkg/sdk/identifier_helpers_test.go b/pkg/sdk/identifier_helpers_test.go index d3b0a98bd3..3b84c372f5 100644 --- a/pkg/sdk/identifier_helpers_test.go +++ b/pkg/sdk/identifier_helpers_test.go @@ -26,26 +26,27 @@ func TestNewAccountIdentifierFromFullyQualifiedName(t *testing.T) { } } -func TestNewSchemaObjectIdentifierFromFullyQualifiedName(t *testing.T) { - type test struct { - input string - want SchemaObjectIdentifier - } - - tests := []test{ - {input: "\"MY_DB\".\"MY_SCHEMA\".\"multiply\"(number, number)", want: SchemaObjectIdentifier{databaseName: "MY_DB", schemaName: "MY_SCHEMA", name: "multiply", arguments: []DataType{DataTypeNumber, DataTypeNumber}}}, - {input: "MY_DB.MY_SCHEMA.add(number, number)", want: SchemaObjectIdentifier{databaseName: "MY_DB", schemaName: "MY_SCHEMA", name: "add", arguments: []DataType{DataTypeNumber, DataTypeNumber}}}, - {input: "\"MY_DB\".\"MY_SCHEMA\".\"MY_UDF\"()", want: SchemaObjectIdentifier{databaseName: "MY_DB", schemaName: "MY_SCHEMA", name: "MY_UDF", arguments: []DataType{}}}, - {input: "\"MY_DB\".\"MY_SCHEMA\".\"MY_PIPE\"", want: SchemaObjectIdentifier{databaseName: "MY_DB", schemaName: "MY_SCHEMA", name: "MY_PIPE", arguments: nil}}, - {input: "MY_DB.MY_SCHEMA.MY_STAGE", want: SchemaObjectIdentifier{databaseName: "MY_DB", schemaName: "MY_SCHEMA", name: "MY_STAGE", arguments: nil}}, - } - for _, tc := range tests { - t.Run(tc.input, func(t *testing.T) { - id := NewSchemaObjectIdentifierFromFullyQualifiedName(tc.input) - require.Equal(t, tc.want, id) - }) - } -} +// TODO: TEST new identifier +//func TestNewSchemaObjectIdentifierFromFullyQualifiedName(t *testing.T) { +// type test struct { +// input string +// want SchemaObjectIdentifier +// } +// +// tests := []test{ +// {input: "\"MY_DB\".\"MY_SCHEMA\".\"multiply\"(number, number)", want: SchemaObjectIdentifier{databaseName: "MY_DB", schemaName: "MY_SCHEMA", name: "multiply", arguments: []DataType{DataTypeNumber, DataTypeNumber}}}, +// {input: "MY_DB.MY_SCHEMA.add(number, number)", want: SchemaObjectIdentifier{databaseName: "MY_DB", schemaName: "MY_SCHEMA", name: "add", arguments: []DataType{DataTypeNumber, DataTypeNumber}}}, +// {input: "\"MY_DB\".\"MY_SCHEMA\".\"MY_UDF\"()", want: SchemaObjectIdentifier{databaseName: "MY_DB", schemaName: "MY_SCHEMA", name: "MY_UDF", arguments: []DataType{}}}, +// {input: "\"MY_DB\".\"MY_SCHEMA\".\"MY_PIPE\"", want: SchemaObjectIdentifier{databaseName: "MY_DB", schemaName: "MY_SCHEMA", name: "MY_PIPE", arguments: nil}}, +// {input: "MY_DB.MY_SCHEMA.MY_STAGE", want: SchemaObjectIdentifier{databaseName: "MY_DB", schemaName: "MY_SCHEMA", name: "MY_STAGE", arguments: nil}}, +// } +// for _, tc := range tests { +// t.Run(tc.input, func(t *testing.T) { +// id := NewSchemaObjectIdentifierFromFullyQualifiedName(tc.input) +// require.Equal(t, tc.want, id) +// }) +// } +//} func TestDatabaseObjectIdentifier(t *testing.T) { t.Run("create from strings", func(t *testing.T) { diff --git a/pkg/sdk/random_test.go b/pkg/sdk/random_test.go index c0df129fca..8877b3625e 100644 --- a/pkg/sdk/random_test.go +++ b/pkg/sdk/random_test.go @@ -9,9 +9,10 @@ var ( longSchemaObjectIdentifier = NewSchemaObjectIdentifier(random.StringN(255), random.StringN(255), random.StringN(255)) // TODO: Add to the generator - emptyAccountObjectIdentifier = NewAccountObjectIdentifier("") - emptyDatabaseObjectIdentifier = NewDatabaseObjectIdentifier("", "") - emptySchemaObjectIdentifier = NewSchemaObjectIdentifier("", "", "") + emptyAccountObjectIdentifier = NewAccountObjectIdentifier("") + emptyDatabaseObjectIdentifier = NewDatabaseObjectIdentifier("", "") + emptySchemaObjectIdentifier = NewSchemaObjectIdentifier("", "", "") + emptySchemaObjectIdentifierWithArguments = NewSchemaObjectIdentifierWithArguments("", "", "") ) func randomSchemaObjectIdentifierWithArguments() SchemaObjectIdentifierWithArguments { From f75481d98f38986a55c86096803b8a24d6cd9720 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Thu, 25 Jul 2024 16:46:55 +0200 Subject: [PATCH 03/13] save (testing on function) --- pkg/acceptance/check_destroy.go | 6 +- pkg/acceptance/helpers/ids_generator.go | 16 +- pkg/datasources/functions.go | 2 +- pkg/provider/provider.go | 112 +- .../external_function_state_upgraders.go | 142 +- pkg/resources/function.go | 1527 ++++++------- pkg/resources/function_state_upgraders.go | 112 +- pkg/resources/procedure.go | 1571 +++++++------ pkg/resources/procedure_state_upgraders.go | 112 +- pkg/sdk/client.go | 36 +- pkg/sdk/external_functions_impl_gen.go | 4 +- pkg/sdk/functions_dto_builders_gen.go | 6 - pkg/sdk/functions_dto_gen.go | 35 +- pkg/sdk/functions_gen.go | 49 +- pkg/sdk/functions_gen_test.go | 52 +- pkg/sdk/functions_impl_gen.go | 35 +- pkg/sdk/identifier_helpers.go | 37 +- pkg/sdk/poc/main.go | 3 +- pkg/sdk/random_test.go | 4 +- .../external_functions_integration_test.go | 525 +++-- pkg/sdk/testint/functions_integration_test.go | 144 +- .../testint/procedures_integration_test.go | 2033 ++++++++--------- 22 files changed, 3241 insertions(+), 3322 deletions(-) diff --git a/pkg/acceptance/check_destroy.go b/pkg/acceptance/check_destroy.go index 0ab27df968..0c4b8f0e65 100644 --- a/pkg/acceptance/check_destroy.go +++ b/pkg/acceptance/check_destroy.go @@ -115,9 +115,9 @@ var showByIdFunctions = map[resources.Resource]showByIdFunc{ resources.FileFormat: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { return runShowById(ctx, id, client.FileFormats.ShowByID) }, - resources.Function: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { - return runShowById(ctx, id, client.Functions.ShowByID) - }, + //resources.Function: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { + // return runShowById(ctx, id, client.Functions.ShowByID) + //}, resources.ManagedAccount: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { return runShowById(ctx, id, client.ManagedAccounts.ShowByID) }, diff --git a/pkg/acceptance/helpers/ids_generator.go b/pkg/acceptance/helpers/ids_generator.go index ec74e0d8aa..47be24644c 100644 --- a/pkg/acceptance/helpers/ids_generator.go +++ b/pkg/acceptance/helpers/ids_generator.go @@ -77,14 +77,22 @@ func (c *IdsGenerator) RandomSchemaObjectIdentifierWithPrefix(prefix string) sdk return sdk.NewSchemaObjectIdentifierInSchema(c.SchemaId(), c.AlphaWithPrefix(prefix)) } -func (c *IdsGenerator) RandomSchemaObjectIdentifierWithArguments(arguments []sdk.DataType) sdk.SchemaObjectIdentifier { - return sdk.NewSchemaObjectIdentifierWithArguments(c.SchemaId().DatabaseName(), c.SchemaId().Name(), c.Alpha(), arguments) -} - func (c *IdsGenerator) RandomSchemaObjectIdentifierInSchema(schemaId sdk.DatabaseObjectIdentifier) sdk.SchemaObjectIdentifier { return sdk.NewSchemaObjectIdentifierInSchema(schemaId, c.Alpha()) } +func (c *IdsGenerator) NewSchemaObjectIdentifierWithArguments(name string, arguments ...sdk.DataType) sdk.SchemaObjectIdentifierWithArguments { + return sdk.NewSchemaObjectIdentifierWithArguments(c.SchemaId().DatabaseName(), c.SchemaId().Name(), name, arguments...) +} + +func (c *IdsGenerator) RandomSchemaObjectIdentifierWithArguments(arguments ...sdk.DataType) sdk.SchemaObjectIdentifierWithArguments { + return sdk.NewSchemaObjectIdentifierWithArguments(c.SchemaId().DatabaseName(), c.SchemaId().Name(), c.Alpha(), arguments...) +} + +func (c *IdsGenerator) NewSchemaObjectIdentifierWithArgumentsInSchema(name string, schemaId sdk.DatabaseObjectIdentifier, argumentDataTypes ...sdk.DataType) sdk.SchemaObjectIdentifierWithArguments { + return sdk.NewSchemaObjectIdentifierWithArgumentsInSchema(schemaId, name, argumentDataTypes...) +} + func (c *IdsGenerator) Alpha() string { return c.AlphaN(6) } diff --git a/pkg/datasources/functions.go b/pkg/datasources/functions.go index 54aa7b2760..44a92b4568 100644 --- a/pkg/datasources/functions.go +++ b/pkg/datasources/functions.go @@ -75,7 +75,7 @@ func ReadContextFunctions(ctx context.Context, d *schema.ResourceData, meta inte schemaName := d.Get("schema").(string) request := sdk.NewShowFunctionRequest() - request.WithIn(&sdk.In{Schema: sdk.NewDatabaseObjectIdentifier(databaseName, schemaName)}) + request.WithIn(sdk.In{Schema: sdk.NewDatabaseObjectIdentifier(databaseName, schemaName)}) functions, err := client.Functions.Show(ctx, request) if err != nil { id := d.Id() diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index edb760e412..371158f5d6 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -436,62 +436,62 @@ func getResources() map[string]*schema.Resource { "snowflake_database_role": resources.DatabaseRole(), "snowflake_dynamic_table": resources.DynamicTable(), "snowflake_email_notification_integration": resources.EmailNotificationIntegration(), - "snowflake_external_function": resources.ExternalFunction(), - "snowflake_external_oauth_integration": resources.ExternalOauthIntegration(), - "snowflake_external_table": resources.ExternalTable(), - "snowflake_failover_group": resources.FailoverGroup(), - "snowflake_file_format": resources.FileFormat(), - "snowflake_function": resources.Function(), - "snowflake_grant_account_role": resources.GrantAccountRole(), - "snowflake_grant_application_role": resources.GrantApplicationRole(), - "snowflake_grant_database_role": resources.GrantDatabaseRole(), - "snowflake_grant_ownership": resources.GrantOwnership(), - "snowflake_grant_privileges_to_account_role": resources.GrantPrivilegesToAccountRole(), - "snowflake_grant_privileges_to_database_role": resources.GrantPrivilegesToDatabaseRole(), - "snowflake_grant_privileges_to_share": resources.GrantPrivilegesToShare(), - "snowflake_managed_account": resources.ManagedAccount(), - "snowflake_masking_policy": resources.MaskingPolicy(), - "snowflake_materialized_view": resources.MaterializedView(), - "snowflake_network_policy": resources.NetworkPolicy(), - "snowflake_network_policy_attachment": resources.NetworkPolicyAttachment(), - "snowflake_network_rule": resources.NetworkRule(), - "snowflake_notification_integration": resources.NotificationIntegration(), - "snowflake_oauth_integration": resources.OAuthIntegration(), - "snowflake_oauth_integration_for_partner_applications": resources.OauthIntegrationForPartnerApplications(), - "snowflake_oauth_integration_for_custom_clients": resources.OauthIntegrationForCustomClients(), - "snowflake_object_parameter": resources.ObjectParameter(), - "snowflake_password_policy": resources.PasswordPolicy(), - "snowflake_pipe": resources.Pipe(), - "snowflake_procedure": resources.Procedure(), - "snowflake_resource_monitor": resources.ResourceMonitor(), - "snowflake_role": resources.Role(), - "snowflake_row_access_policy": resources.RowAccessPolicy(), - "snowflake_saml_integration": resources.SAMLIntegration(), - "snowflake_saml2_integration": resources.SAML2Integration(), - "snowflake_schema": resources.Schema(), - "snowflake_scim_integration": resources.SCIMIntegration(), - "snowflake_secondary_database": resources.SecondaryDatabase(), - "snowflake_sequence": resources.Sequence(), - "snowflake_session_parameter": resources.SessionParameter(), - "snowflake_share": resources.Share(), - "snowflake_shared_database": resources.SharedDatabase(), - "snowflake_stage": resources.Stage(), - "snowflake_storage_integration": resources.StorageIntegration(), - "snowflake_stream": resources.Stream(), - "snowflake_streamlit": resources.Streamlit(), - "snowflake_table": resources.Table(), - "snowflake_table_column_masking_policy_application": resources.TableColumnMaskingPolicyApplication(), - "snowflake_table_constraint": resources.TableConstraint(), - "snowflake_tag": resources.Tag(), - "snowflake_tag_association": resources.TagAssociation(), - "snowflake_tag_masking_policy_association": resources.TagMaskingPolicyAssociation(), - "snowflake_task": resources.Task(), - "snowflake_unsafe_execute": resources.UnsafeExecute(), - "snowflake_user": resources.User(), - "snowflake_user_password_policy_attachment": resources.UserPasswordPolicyAttachment(), - "snowflake_user_public_keys": resources.UserPublicKeys(), - "snowflake_view": resources.View(), - "snowflake_warehouse": resources.Warehouse(), + //"snowflake_external_function": resources.ExternalFunction(), + "snowflake_external_oauth_integration": resources.ExternalOauthIntegration(), + "snowflake_external_table": resources.ExternalTable(), + "snowflake_failover_group": resources.FailoverGroup(), + "snowflake_file_format": resources.FileFormat(), + //"snowflake_function": resources.Function(), + "snowflake_grant_account_role": resources.GrantAccountRole(), + "snowflake_grant_application_role": resources.GrantApplicationRole(), + "snowflake_grant_database_role": resources.GrantDatabaseRole(), + "snowflake_grant_ownership": resources.GrantOwnership(), + "snowflake_grant_privileges_to_account_role": resources.GrantPrivilegesToAccountRole(), + "snowflake_grant_privileges_to_database_role": resources.GrantPrivilegesToDatabaseRole(), + "snowflake_grant_privileges_to_share": resources.GrantPrivilegesToShare(), + "snowflake_managed_account": resources.ManagedAccount(), + "snowflake_masking_policy": resources.MaskingPolicy(), + "snowflake_materialized_view": resources.MaterializedView(), + "snowflake_network_policy": resources.NetworkPolicy(), + "snowflake_network_policy_attachment": resources.NetworkPolicyAttachment(), + "snowflake_network_rule": resources.NetworkRule(), + "snowflake_notification_integration": resources.NotificationIntegration(), + "snowflake_oauth_integration": resources.OAuthIntegration(), + "snowflake_oauth_integration_for_partner_applications": resources.OauthIntegrationForPartnerApplications(), + "snowflake_oauth_integration_for_custom_clients": resources.OauthIntegrationForCustomClients(), + "snowflake_object_parameter": resources.ObjectParameter(), + "snowflake_password_policy": resources.PasswordPolicy(), + "snowflake_pipe": resources.Pipe(), + //"snowflake_procedure": resources.Procedure(), + "snowflake_resource_monitor": resources.ResourceMonitor(), + "snowflake_role": resources.Role(), + "snowflake_row_access_policy": resources.RowAccessPolicy(), + "snowflake_saml_integration": resources.SAMLIntegration(), + "snowflake_saml2_integration": resources.SAML2Integration(), + "snowflake_schema": resources.Schema(), + "snowflake_scim_integration": resources.SCIMIntegration(), + "snowflake_secondary_database": resources.SecondaryDatabase(), + "snowflake_sequence": resources.Sequence(), + "snowflake_session_parameter": resources.SessionParameter(), + "snowflake_share": resources.Share(), + "snowflake_shared_database": resources.SharedDatabase(), + "snowflake_stage": resources.Stage(), + "snowflake_storage_integration": resources.StorageIntegration(), + "snowflake_stream": resources.Stream(), + "snowflake_streamlit": resources.Streamlit(), + "snowflake_table": resources.Table(), + "snowflake_table_column_masking_policy_application": resources.TableColumnMaskingPolicyApplication(), + "snowflake_table_constraint": resources.TableConstraint(), + "snowflake_tag": resources.Tag(), + "snowflake_tag_association": resources.TagAssociation(), + "snowflake_tag_masking_policy_association": resources.TagMaskingPolicyAssociation(), + "snowflake_task": resources.Task(), + "snowflake_unsafe_execute": resources.UnsafeExecute(), + "snowflake_user": resources.User(), + "snowflake_user_password_policy_attachment": resources.UserPasswordPolicyAttachment(), + "snowflake_user_public_keys": resources.UserPublicKeys(), + "snowflake_view": resources.View(), + "snowflake_warehouse": resources.Warehouse(), } } diff --git a/pkg/resources/external_function_state_upgraders.go b/pkg/resources/external_function_state_upgraders.go index 55d2b95868..d27a732700 100644 --- a/pkg/resources/external_function_state_upgraders.go +++ b/pkg/resources/external_function_state_upgraders.go @@ -1,77 +1,69 @@ package resources -import ( - "context" - "encoding/csv" - "strings" - - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" -) - -type v085ExternalFunctionId struct { - DatabaseName string - SchemaName string - ExternalFunctionName string - ExternalFunctionArgTypes string -} - -func parseV085ExternalFunctionId(stringID string) (*v085ExternalFunctionId, error) { - reader := csv.NewReader(strings.NewReader(stringID)) - reader.Comma = '|' - lines, err := reader.ReadAll() - if err != nil { - return nil, sdk.NewError("not CSV compatible") - } - - if len(lines) != 1 { - return nil, sdk.NewError("1 line at a time") - } - if len(lines[0]) != 4 { - return nil, sdk.NewError("4 fields allowed") - } - - return &v085ExternalFunctionId{ - DatabaseName: lines[0][0], - SchemaName: lines[0][1], - ExternalFunctionName: lines[0][2], - ExternalFunctionArgTypes: lines[0][3], - }, nil -} - -func v085ExternalFunctionStateUpgrader(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { - if rawState == nil { - return rawState, nil - } - - oldId := rawState["id"].(string) - parsedV085ExternalFunctionId, err := parseV085ExternalFunctionId(oldId) - if err != nil { - return nil, err - } - - argDataTypes := make([]sdk.DataType, 0) - if parsedV085ExternalFunctionId.ExternalFunctionArgTypes != "" { - for _, argType := range strings.Split(parsedV085ExternalFunctionId.ExternalFunctionArgTypes, "-") { - argDataType, err := sdk.ToDataType(argType) - if err != nil { - return nil, err - } - argDataTypes = append(argDataTypes, argDataType) - } - } - - schemaObjectIdentifierWithArguments := sdk.NewSchemaObjectIdentifierWithArguments(parsedV085ExternalFunctionId.DatabaseName, parsedV085ExternalFunctionId.SchemaName, parsedV085ExternalFunctionId.ExternalFunctionName, argDataTypes) - rawState["id"] = schemaObjectIdentifierWithArguments.FullyQualifiedName() - - oldDatabase := rawState["database"].(string) - oldSchema := rawState["schema"].(string) - - rawState["database"] = strings.Trim(oldDatabase, "\"") - rawState["schema"] = strings.Trim(oldSchema, "\"") - - if old, isPresent := rawState["return_null_allowed"]; !isPresent || old == nil { - rawState["return_null_allowed"] = "true" - } - - return rawState, nil -} +//type v085ExternalFunctionId struct { +// DatabaseName string +// SchemaName string +// ExternalFunctionName string +// ExternalFunctionArgTypes string +//} +// +//func parseV085ExternalFunctionId(stringID string) (*v085ExternalFunctionId, error) { +// reader := csv.NewReader(strings.NewReader(stringID)) +// reader.Comma = '|' +// lines, err := reader.ReadAll() +// if err != nil { +// return nil, sdk.NewError("not CSV compatible") +// } +// +// if len(lines) != 1 { +// return nil, sdk.NewError("1 line at a time") +// } +// if len(lines[0]) != 4 { +// return nil, sdk.NewError("4 fields allowed") +// } +// +// return &v085ExternalFunctionId{ +// DatabaseName: lines[0][0], +// SchemaName: lines[0][1], +// ExternalFunctionName: lines[0][2], +// ExternalFunctionArgTypes: lines[0][3], +// }, nil +//} +// +//func v085ExternalFunctionStateUpgrader(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { +// if rawState == nil { +// return rawState, nil +// } +// +// oldId := rawState["id"].(string) +// parsedV085ExternalFunctionId, err := parseV085ExternalFunctionId(oldId) +// if err != nil { +// return nil, err +// } +// +// argDataTypes := make([]sdk.DataType, 0) +// if parsedV085ExternalFunctionId.ExternalFunctionArgTypes != "" { +// for _, argType := range strings.Split(parsedV085ExternalFunctionId.ExternalFunctionArgTypes, "-") { +// argDataType, err := sdk.ToDataType(argType) +// if err != nil { +// return nil, err +// } +// argDataTypes = append(argDataTypes, argDataType) +// } +// } +// +// schemaObjectIdentifierWithArguments := sdk.NewSchemaObjectIdentifierWithArguments(parsedV085ExternalFunctionId.DatabaseName, parsedV085ExternalFunctionId.SchemaName, parsedV085ExternalFunctionId.ExternalFunctionName, argDataTypes) +// rawState["id"] = schemaObjectIdentifierWithArguments.FullyQualifiedName() +// +// oldDatabase := rawState["database"].(string) +// oldSchema := rawState["schema"].(string) +// +// rawState["database"] = strings.Trim(oldDatabase, "\"") +// rawState["schema"] = strings.Trim(oldSchema, "\"") +// +// if old, isPresent := rawState["return_null_allowed"]; !isPresent || old == nil { +// rawState["return_null_allowed"] = "true" +// } +// +// return rawState, nil +//} diff --git a/pkg/resources/function.go b/pkg/resources/function.go index ea6da70f53..1e9c309472 100644 --- a/pkg/resources/function.go +++ b/pkg/resources/function.go @@ -1,774 +1,757 @@ package resources -import ( - "context" - "fmt" - "log" - "regexp" - "strings" - - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" - - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/snowflake" - "github.com/hashicorp/go-cty/cty" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" -) - -var languages = []string{"javascript", "scala", "java", "sql", "python"} - -var functionSchema = map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - Description: "Specifies the identifier for the function; does not have to be unique for the schema in which the function is created. Don't use the | character.", - }, - "database": { - Type: schema.TypeString, - Required: true, - Description: "The database in which to create the function. Don't use the | character.", - ForceNew: true, - }, - "schema": { - Type: schema.TypeString, - Required: true, - Description: "The schema in which to create the function. Don't use the | character.", - ForceNew: true, - }, - "arguments": { - Type: schema.TypeList, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - // Suppress the diff shown if the values are equal when both compared in lower case. - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - return strings.EqualFold(old, new) - }, - Description: "The argument name", - }, - "type": { - Type: schema.TypeString, - Required: true, - // Suppress the diff shown if the values are equal when both compared in lower case. - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - return strings.EqualFold(old, new) - }, - Description: "The argument type", - }, - }, - }, - Optional: true, - Description: "List of the arguments for the function", - ForceNew: true, - }, - "return_type": { - Type: schema.TypeString, - Description: "The return type of the function", - // Suppress the diff shown if the values are equal when both compared in lower case. - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - return strings.EqualFold(old, new) - }, - Required: true, - ForceNew: true, - }, - "statement": { - Type: schema.TypeString, - Required: true, - Description: "Specifies the javascript / java / scala / sql / python code used to create the function.", - ForceNew: true, - DiffSuppressFunc: DiffSuppressStatement, - }, - "language": { - Type: schema.TypeString, - Optional: true, - Default: "SQL", - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - return strings.EqualFold(old, new) - }, - ValidateFunc: validation.StringInSlice(languages, true), - Description: "Specifies the language of the stored function code.", - }, - "null_input_behavior": { - Type: schema.TypeString, - Optional: true, - Default: "CALLED ON NULL INPUT", - ForceNew: true, - // We do not use STRICT, because Snowflake then in the Read phase returns RETURNS NULL ON NULL INPUT - ValidateFunc: validation.StringInSlice([]string{"CALLED ON NULL INPUT", "RETURNS NULL ON NULL INPUT"}, false), - Description: "Specifies the behavior of the function when called with null inputs.", - }, - "return_behavior": { - Type: schema.TypeString, - Optional: true, - Default: "VOLATILE", - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{"VOLATILE", "IMMUTABLE"}, false), - Description: "Specifies the behavior of the function when returning results", - }, - "is_secure": { - Type: schema.TypeBool, - Optional: true, - Default: false, - Description: "Specifies that the function is secure.", - }, - "comment": { - Type: schema.TypeString, - Optional: true, - Default: "user-defined function", - Description: "Specifies a comment for the function.", - }, - "runtime_version": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Description: "Required for Python functions. Specifies Python runtime version.", - }, - "packages": { - Type: schema.TypeList, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - Optional: true, - ForceNew: true, - Description: "List of package imports to use for Java / Python functions. For Java, package imports should be of the form: package_name:version_number, where package_name is snowflake_domain:package. For Python use it should be: ('numpy','pandas','xgboost==1.5.0').", - }, - "imports": { - Type: schema.TypeList, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - Optional: true, - ForceNew: true, - Description: "Imports for Java / Python functions. For Java this a list of jar files, for Python this is a list of Python files.", - }, - "handler": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Description: "The handler method for Java / Python function.", - }, - "target_path": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Description: "The target path for the Java / Python functions. For Java, it is the path of compiled jar files and for the Python it is the path of the Python files.", - }, -} - -// Function returns a pointer to the resource representing a stored function. -func Function() *schema.Resource { - return &schema.Resource{ - SchemaVersion: 1, - - CreateContext: CreateContextFunction, - ReadContext: ReadContextFunction, - UpdateContext: UpdateContextFunction, - DeleteContext: DeleteContextFunction, - - Schema: functionSchema, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - - StateUpgraders: []schema.StateUpgrader{ - { - Version: 0, - // setting type to cty.EmptyObject is a bit hacky here but following https://developer.hashicorp.com/terraform/plugin/framework/migrating/resources/state-upgrade#sdkv2-1 would require lots of repetitive code; this should work with cty.EmptyObject - Type: cty.EmptyObject, - Upgrade: v085FunctionIdStateUpgrader, - }, - }, - } -} - -func CreateContextFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - lang := strings.ToUpper(d.Get("language").(string)) - switch lang { - case "JAVA": - return createJavaFunction(ctx, d, meta) - case "JAVASCRIPT": - return createJavascriptFunction(ctx, d, meta) - case "PYTHON": - return createPythonFunction(ctx, d, meta) - case "SCALA": - return createScalaFunction(ctx, d, meta) - case "", "SQL": // SQL if language is not set - return createSQLFunction(ctx, d, meta) - default: - return diag.Diagnostics{ - diag.Diagnostic{ - Severity: diag.Error, - Summary: "Invalid language", - Detail: fmt.Sprintf("Language %s is not supported", lang), - }, - } - } -} - -func createJavaFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*provider.Context).Client - name := d.Get("name").(string) - schema := d.Get("schema").(string) - database := d.Get("database").(string) - id := sdk.NewSchemaObjectIdentifier(database, schema, name) - - // Set required - returns, diags := parseFunctionReturnsRequest(d.Get("return_type").(string)) - if diags != nil { - return diags - } - handler := d.Get("handler").(string) - // create request with required - request := sdk.NewCreateForJavaFunctionRequest(id, *returns, handler) - functionDefinition := d.Get("statement").(string) - request.WithFunctionDefinition(sdk.String(functionDefinition)) - - // Set optionals - if v, ok := d.GetOk("is_secure"); ok { - request.WithSecure(sdk.Bool(v.(bool))) - } - arguments, diags := parseFunctionArguments(d) - if diags != nil { - return diags - } - if len(arguments) > 0 { - request.WithArguments(arguments) - } - if v, ok := d.GetOk("null_input_behavior"); ok { - request.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehavior(v.(string)))) - } - if v, ok := d.GetOk("return_behavior"); ok { - request.WithReturnResultsBehavior(sdk.Pointer(sdk.ReturnResultsBehavior(v.(string)))) - } - if v, ok := d.GetOk("runtime_version"); ok { - request.WithRuntimeVersion(sdk.String(v.(string))) - } - if v, ok := d.GetOk("comment"); ok { - request.WithComment(sdk.String(v.(string))) - } - if _, ok := d.GetOk("imports"); ok { - imports := []sdk.FunctionImportRequest{} - for _, item := range d.Get("imports").([]interface{}) { - imports = append(imports, *sdk.NewFunctionImportRequest().WithImport(item.(string))) - } - request.WithImports(imports) - } - if _, ok := d.GetOk("packages"); ok { - packages := []sdk.FunctionPackageRequest{} - for _, item := range d.Get("packages").([]interface{}) { - packages = append(packages, *sdk.NewFunctionPackageRequest().WithPackage(item.(string))) - } - request.WithPackages(packages) - } - if v, ok := d.GetOk("target_path"); ok { - request.WithTargetPath(sdk.String(v.(string))) - } - - if err := client.Functions.CreateForJava(ctx, request); err != nil { - return diag.FromErr(err) - } - argumentTypes := make([]sdk.DataType, 0, len(arguments)) - for _, item := range arguments { - argumentTypes = append(argumentTypes, item.ArgDataType) - } - nid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argumentTypes) - d.SetId(nid.FullyQualifiedName()) - return ReadContextFunction(ctx, d, meta) -} - -func createScalaFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*provider.Context).Client - name := d.Get("name").(string) - schema := d.Get("schema").(string) - database := d.Get("database").(string) - id := sdk.NewSchemaObjectIdentifier(database, schema, name) - - // Set required - returnType := d.Get("return_type").(string) - returnDataType, diags := convertFunctionDataType(returnType) - if diags != nil { - return diags - } - functionDefinition := d.Get("statement").(string) - handler := d.Get("handler").(string) - // create request with required - request := sdk.NewCreateForScalaFunctionRequest(id, returnDataType, handler) - request.WithFunctionDefinition(sdk.String(functionDefinition)) - - // Set optionals - if v, ok := d.GetOk("is_secure"); ok { - request.WithSecure(sdk.Bool(v.(bool))) - } - arguments, diags := parseFunctionArguments(d) - if diags != nil { - return diags - } - if len(arguments) > 0 { - request.WithArguments(arguments) - } - if v, ok := d.GetOk("null_input_behavior"); ok { - request.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehavior(v.(string)))) - } - if v, ok := d.GetOk("return_behavior"); ok { - request.WithReturnResultsBehavior(sdk.Pointer(sdk.ReturnResultsBehavior(v.(string)))) - } - if v, ok := d.GetOk("runtime_version"); ok { - request.WithRuntimeVersion(sdk.String(v.(string))) - } - if v, ok := d.GetOk("comment"); ok { - request.WithComment(sdk.String(v.(string))) - } - if _, ok := d.GetOk("imports"); ok { - imports := []sdk.FunctionImportRequest{} - for _, item := range d.Get("imports").([]interface{}) { - imports = append(imports, *sdk.NewFunctionImportRequest().WithImport(item.(string))) - } - request.WithImports(imports) - } - if _, ok := d.GetOk("packages"); ok { - packages := []sdk.FunctionPackageRequest{} - for _, item := range d.Get("packages").([]interface{}) { - packages = append(packages, *sdk.NewFunctionPackageRequest().WithPackage(item.(string))) - } - request.WithPackages(packages) - } - if v, ok := d.GetOk("target_path"); ok { - request.WithTargetPath(sdk.String(v.(string))) - } - - if err := client.Functions.CreateForScala(ctx, request); err != nil { - return diag.FromErr(err) - } - argumentTypes := make([]sdk.DataType, 0, len(arguments)) - for _, item := range arguments { - argumentTypes = append(argumentTypes, item.ArgDataType) - } - nid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argumentTypes) - d.SetId(nid.FullyQualifiedName()) - return ReadContextFunction(ctx, d, meta) -} - -func createSQLFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*provider.Context).Client - name := d.Get("name").(string) - schema := d.Get("schema").(string) - database := d.Get("database").(string) - id := sdk.NewSchemaObjectIdentifier(database, schema, name) - - // Set required - returns, diags := parseFunctionReturnsRequest(d.Get("return_type").(string)) - if diags != nil { - return diags - } - functionDefinition := d.Get("statement").(string) - // create request with required - request := sdk.NewCreateForSQLFunctionRequest(id, *returns, functionDefinition) - - // Set optionals - if v, ok := d.GetOk("is_secure"); ok { - request.WithSecure(sdk.Bool(v.(bool))) - } - arguments, diags := parseFunctionArguments(d) - if diags != nil { - return diags - } - if len(arguments) > 0 { - request.WithArguments(arguments) - } - if v, ok := d.GetOk("return_behavior"); ok { - request.WithReturnResultsBehavior(sdk.Pointer(sdk.ReturnResultsBehavior(v.(string)))) - } - if v, ok := d.GetOk("comment"); ok { - request.WithComment(sdk.String(v.(string))) - } - - if err := client.Functions.CreateForSQL(ctx, request); err != nil { - return diag.FromErr(err) - } - argumentTypes := make([]sdk.DataType, 0, len(arguments)) - for _, item := range arguments { - argumentTypes = append(argumentTypes, item.ArgDataType) - } - nid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argumentTypes) - d.SetId(nid.FullyQualifiedName()) - return ReadContextFunction(ctx, d, meta) -} - -func createPythonFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*provider.Context).Client - name := d.Get("name").(string) - schema := d.Get("schema").(string) - database := d.Get("database").(string) - id := sdk.NewSchemaObjectIdentifier(database, schema, name) - - // Set required - returns, diags := parseFunctionReturnsRequest(d.Get("return_type").(string)) - if diags != nil { - return diags - } - functionDefinition := d.Get("statement").(string) - version := d.Get("runtime_version").(string) - handler := d.Get("handler").(string) - // create request with required - request := sdk.NewCreateForPythonFunctionRequest(id, *returns, version, handler) - request.WithFunctionDefinition(sdk.String(functionDefinition)) - - // Set optionals - if v, ok := d.GetOk("is_secure"); ok { - request.WithSecure(sdk.Bool(v.(bool))) - } - arguments, diags := parseFunctionArguments(d) - if diags != nil { - return diags - } - if len(arguments) > 0 { - request.WithArguments(arguments) - } - if v, ok := d.GetOk("null_input_behavior"); ok { - request.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehavior(v.(string)))) - } - if v, ok := d.GetOk("return_behavior"); ok { - request.WithReturnResultsBehavior(sdk.Pointer(sdk.ReturnResultsBehavior(v.(string)))) - } - - if v, ok := d.GetOk("comment"); ok { - request.WithComment(sdk.String(v.(string))) - } - if _, ok := d.GetOk("imports"); ok { - imports := []sdk.FunctionImportRequest{} - for _, item := range d.Get("imports").([]interface{}) { - imports = append(imports, *sdk.NewFunctionImportRequest().WithImport(item.(string))) - } - request.WithImports(imports) - } - if _, ok := d.GetOk("packages"); ok { - packages := []sdk.FunctionPackageRequest{} - for _, item := range d.Get("packages").([]interface{}) { - packages = append(packages, *sdk.NewFunctionPackageRequest().WithPackage(item.(string))) - } - request.WithPackages(packages) - } - - if err := client.Functions.CreateForPython(ctx, request); err != nil { - return diag.FromErr(err) - } - argumentTypes := make([]sdk.DataType, 0, len(arguments)) - for _, item := range arguments { - argumentTypes = append(argumentTypes, item.ArgDataType) - } - nid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argumentTypes) - d.SetId(nid.FullyQualifiedName()) - return ReadContextFunction(ctx, d, meta) -} - -func createJavascriptFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*provider.Context).Client - name := d.Get("name").(string) - schema := d.Get("schema").(string) - database := d.Get("database").(string) - id := sdk.NewSchemaObjectIdentifier(database, schema, name) - - // Set required - returns, diags := parseFunctionReturnsRequest(d.Get("return_type").(string)) - if diags != nil { - return diags - } - functionDefinition := d.Get("statement").(string) - // create request with required - request := sdk.NewCreateForJavascriptFunctionRequest(id, *returns, functionDefinition) - - // Set optionals - if v, ok := d.GetOk("is_secure"); ok { - request.WithSecure(sdk.Bool(v.(bool))) - } - arguments, diags := parseFunctionArguments(d) - if diags != nil { - return diags - } - if len(arguments) > 0 { - request.WithArguments(arguments) - } - if v, ok := d.GetOk("null_input_behavior"); ok { - request.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehavior(v.(string)))) - } - if v, ok := d.GetOk("return_behavior"); ok { - request.WithReturnResultsBehavior(sdk.Pointer(sdk.ReturnResultsBehavior(v.(string)))) - } - if v, ok := d.GetOk("comment"); ok { - request.WithComment(sdk.String(v.(string))) - } - - if err := client.Functions.CreateForJavascript(ctx, request); err != nil { - return diag.FromErr(err) - } - argumentTypes := make([]sdk.DataType, 0, len(arguments)) - for _, item := range arguments { - argumentTypes = append(argumentTypes, item.ArgDataType) - } - nid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argumentTypes) - d.SetId(nid.FullyQualifiedName()) - return ReadContextFunction(ctx, d, meta) -} - -func ReadContextFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - diags := diag.Diagnostics{} - client := meta.(*provider.Context).Client - - id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) - if err := d.Set("name", id.Name()); err != nil { - return diag.FromErr(err) - } - if err := d.Set("database", id.DatabaseName()); err != nil { - return diag.FromErr(err) - } - if err := d.Set("schema", id.SchemaName()); err != nil { - return diag.FromErr(err) - } - - arguments := d.Get("arguments").([]interface{}) - argumentTypes := make([]string, len(arguments)) - for i, arg := range arguments { - argumentTypes[i] = arg.(map[string]interface{})["type"].(string) - } - functionDetails, err := client.Functions.Describe(ctx, sdk.NewDescribeFunctionRequest(id.WithoutArguments(), id.Arguments())) - if err != nil { - // if function is not found then mark resource to be removed from state file during apply or refresh - d.SetId("") - return diag.Diagnostics{ - diag.Diagnostic{ - Severity: diag.Warning, - Summary: "Describe function failed.", - Detail: "See our document on design decisions for functions: ", - }, - } - } - for _, desc := range functionDetails { - switch desc.Property { - case "signature": - // Format in Snowflake DB is: (argName argType, argName argType, ...) - value := strings.ReplaceAll(strings.ReplaceAll(desc.Value, "(", ""), ")", "") - if value != "" { // Do nothing for functions without arguments - pairs := strings.Split(value, ", ") - - arguments := []interface{}{} - for _, pair := range pairs { - item := strings.Split(pair, " ") - argument := map[string]interface{}{} - argument["name"] = item[0] - argument["type"] = item[1] - arguments = append(arguments, argument) - } - if err := d.Set("arguments", arguments); err != nil { - diag.FromErr(err) - } - } - case "null handling": - if err := d.Set("null_input_behavior", desc.Value); err != nil { - diag.FromErr(err) - } - case "volatility": - if err := d.Set("return_behavior", desc.Value); err != nil { - diag.FromErr(err) - } - case "body": - if err := d.Set("statement", desc.Value); err != nil { - diag.FromErr(err) - } - case "returns": - // Format in Snowflake DB is returnType() - re := regexp.MustCompile(`^(.*)\([0-9]*\)$`) - match := re.FindStringSubmatch(desc.Value) - rt := desc.Value - if match != nil { - rt = match[1] - } - if err := d.Set("return_type", rt); err != nil { - diag.FromErr(err) - } - case "language": - if snowflake.Contains(languages, strings.ToLower(desc.Value)) { - if err := d.Set("language", desc.Value); err != nil { - diag.FromErr(err) - } - } else { - log.Printf("[INFO] Unexpected language for function %v returned from Snowflake", desc.Value) - } - case "packages": - value := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(desc.Value, "[", ""), "]", ""), "'", "") - if value != "" { // Do nothing for Java / Python functions without packages - packages := strings.Split(value, ",") - if err := d.Set("packages", packages); err != nil { - diag.FromErr(err) - } - } - case "imports": - value := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(desc.Value, "[", ""), "]", ""), "'", "") - if value != "" { // Do nothing for Java functions without imports - imports := strings.Split(value, ",") - if err := d.Set("imports", imports); err != nil { - diag.FromErr(err) - } - } - case "handler": - if err := d.Set("handler", desc.Value); err != nil { - diag.FromErr(err) - } - case "target_path": - if err := d.Set("target_path", desc.Value); err != nil { - diag.FromErr(err) - } - case "runtime_version": - if err := d.Set("runtime_version", desc.Value); err != nil { - diag.FromErr(err) - } - default: - log.Printf("[INFO] Unexpected function property %v returned from Snowflake with value %v", desc.Property, desc.Value) - } - } - - // Show functions to set is_secure and comment - request := sdk.NewShowFunctionRequest().WithIn(&sdk.In{Schema: sdk.NewDatabaseObjectIdentifier(id.DatabaseName(), id.SchemaName())}).WithLike(&sdk.Like{Pattern: sdk.String(id.Name())}) - functions, err := client.Functions.Show(ctx, request) - if err != nil { - return diag.FromErr(err) - } - for _, function := range functions { - signature := strings.Split(function.Arguments, " RETURN ")[0] - signature = strings.ReplaceAll(signature, " ", "") - id.FullyQualifiedName() - if signature == id.ArgumentsSignature() { - if err := d.Set("is_secure", function.IsSecure); err != nil { - return diag.FromErr(err) - } - if err := d.Set("comment", function.Description); err != nil { - return diag.FromErr(err) - } - } - } - return diags -} - -func UpdateContextFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*provider.Context).Client - - id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) - if d.HasChange("name") { - name := d.Get("name").(string) - newId := sdk.NewSchemaObjectIdentifierWithArguments(id.DatabaseName(), id.SchemaName(), name, id.Arguments()) - - if err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id.WithoutArguments(), id.Arguments()).WithRenameTo(sdk.Pointer(newId.WithoutArguments()))); err != nil { - return diag.FromErr(err) - } - - d.SetId(newId.FullyQualifiedName()) - id = newId - } - - if d.HasChange("is_secure") { - secure := d.Get("is_secure") - if secure.(bool) { - if err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id.WithoutArguments(), id.Arguments()).WithSetSecure(sdk.Bool(true))); err != nil { - return diag.FromErr(err) - } - } else { - if err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id.WithoutArguments(), id.Arguments()).WithUnsetSecure(sdk.Bool(true))); err != nil { - return diag.FromErr(err) - } - } - } - - if d.HasChange("comment") { - comment := d.Get("comment") - if comment != "" { - if err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id.WithoutArguments(), id.Arguments()).WithSetComment(sdk.String(comment.(string)))); err != nil { - return diag.FromErr(err) - } - } else { - if err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id.WithoutArguments(), id.Arguments()).WithUnsetComment(sdk.Bool(true))); err != nil { - return diag.FromErr(err) - } - } - } - - return ReadContextFunction(ctx, d, meta) -} - -func DeleteContextFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*provider.Context).Client - - id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) - if err := client.Functions.Drop(ctx, sdk.NewDropFunctionRequest(id.WithoutArguments(), id.Arguments())); err != nil { - return diag.FromErr(err) - } - d.SetId("") - return nil -} - -func parseFunctionArguments(d *schema.ResourceData) ([]sdk.FunctionArgumentRequest, diag.Diagnostics) { - args := make([]sdk.FunctionArgumentRequest, 0) - if v, ok := d.GetOk("arguments"); ok { - for _, arg := range v.([]interface{}) { - argName := arg.(map[string]interface{})["name"].(string) - argType := arg.(map[string]interface{})["type"].(string) - argDataType, diags := convertFunctionDataType(argType) - if diags != nil { - return nil, diags - } - args = append(args, sdk.FunctionArgumentRequest{ArgName: argName, ArgDataType: argDataType}) - } - } - return args, nil -} - -func convertFunctionDataType(s string) (sdk.DataType, diag.Diagnostics) { - dataType, err := sdk.ToDataType(s) - if err != nil { - return dataType, diag.FromErr(err) - } - return dataType, nil -} - -func convertFunctionColumns(s string) ([]sdk.FunctionColumn, diag.Diagnostics) { - pattern := regexp.MustCompile(`(\w+)\s+(\w+)`) - matches := pattern.FindAllStringSubmatch(s, -1) - var columns []sdk.FunctionColumn - for _, match := range matches { - if len(match) == 3 { - dataType, err := sdk.ToDataType(match[2]) - if err != nil { - return nil, diag.FromErr(err) - } - columns = append(columns, sdk.FunctionColumn{ - ColumnName: match[1], - ColumnDataType: dataType, - }) - } - } - return columns, nil -} - -func parseFunctionReturnsRequest(s string) (*sdk.FunctionReturnsRequest, diag.Diagnostics) { - returns := sdk.NewFunctionReturnsRequest() - if strings.HasPrefix(strings.ToLower(s), "table") { - columns, diags := convertFunctionColumns(s) - if diags != nil { - return nil, diags - } - var cr []sdk.FunctionColumnRequest - for _, item := range columns { - cr = append(cr, *sdk.NewFunctionColumnRequest(item.ColumnName, item.ColumnDataType)) - } - returns.WithTable(sdk.NewFunctionReturnsTableRequest().WithColumns(cr)) - } else { - returnDataType, diags := convertFunctionDataType(s) - if diags != nil { - return nil, diags - } - returns.WithResultDataType(sdk.NewFunctionReturnsResultDataTypeRequest(returnDataType)) - } - return returns, nil -} +//var languages = []string{"javascript", "scala", "java", "sql", "python"} +// +//var functionSchema = map[string]*schema.Schema{ +// "name": { +// Type: schema.TypeString, +// Required: true, +// Description: "Specifies the identifier for the function; does not have to be unique for the schema in which the function is created. Don't use the | character.", +// }, +// "database": { +// Type: schema.TypeString, +// Required: true, +// Description: "The database in which to create the function. Don't use the | character.", +// ForceNew: true, +// }, +// "schema": { +// Type: schema.TypeString, +// Required: true, +// Description: "The schema in which to create the function. Don't use the | character.", +// ForceNew: true, +// }, +// "arguments": { +// Type: schema.TypeList, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "name": { +// Type: schema.TypeString, +// Required: true, +// // Suppress the diff shown if the values are equal when both compared in lower case. +// DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { +// return strings.EqualFold(old, new) +// }, +// Description: "The argument name", +// }, +// "type": { +// Type: schema.TypeString, +// Required: true, +// // Suppress the diff shown if the values are equal when both compared in lower case. +// DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { +// return strings.EqualFold(old, new) +// }, +// Description: "The argument type", +// }, +// }, +// }, +// Optional: true, +// Description: "List of the arguments for the function", +// ForceNew: true, +// }, +// "return_type": { +// Type: schema.TypeString, +// Description: "The return type of the function", +// // Suppress the diff shown if the values are equal when both compared in lower case. +// DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { +// return strings.EqualFold(old, new) +// }, +// Required: true, +// ForceNew: true, +// }, +// "statement": { +// Type: schema.TypeString, +// Required: true, +// Description: "Specifies the javascript / java / scala / sql / python code used to create the function.", +// ForceNew: true, +// DiffSuppressFunc: DiffSuppressStatement, +// }, +// "language": { +// Type: schema.TypeString, +// Optional: true, +// Default: "SQL", +// DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { +// return strings.EqualFold(old, new) +// }, +// ValidateFunc: validation.StringInSlice(languages, true), +// Description: "Specifies the language of the stored function code.", +// }, +// "null_input_behavior": { +// Type: schema.TypeString, +// Optional: true, +// Default: "CALLED ON NULL INPUT", +// ForceNew: true, +// // We do not use STRICT, because Snowflake then in the Read phase returns RETURNS NULL ON NULL INPUT +// ValidateFunc: validation.StringInSlice([]string{"CALLED ON NULL INPUT", "RETURNS NULL ON NULL INPUT"}, false), +// Description: "Specifies the behavior of the function when called with null inputs.", +// }, +// "return_behavior": { +// Type: schema.TypeString, +// Optional: true, +// Default: "VOLATILE", +// ForceNew: true, +// ValidateFunc: validation.StringInSlice([]string{"VOLATILE", "IMMUTABLE"}, false), +// Description: "Specifies the behavior of the function when returning results", +// }, +// "is_secure": { +// Type: schema.TypeBool, +// Optional: true, +// Default: false, +// Description: "Specifies that the function is secure.", +// }, +// "comment": { +// Type: schema.TypeString, +// Optional: true, +// Default: "user-defined function", +// Description: "Specifies a comment for the function.", +// }, +// "runtime_version": { +// Type: schema.TypeString, +// Optional: true, +// ForceNew: true, +// Description: "Required for Python functions. Specifies Python runtime version.", +// }, +// "packages": { +// Type: schema.TypeList, +// Elem: &schema.Schema{ +// Type: schema.TypeString, +// }, +// Optional: true, +// ForceNew: true, +// Description: "List of package imports to use for Java / Python functions. For Java, package imports should be of the form: package_name:version_number, where package_name is snowflake_domain:package. For Python use it should be: ('numpy','pandas','xgboost==1.5.0').", +// }, +// "imports": { +// Type: schema.TypeList, +// Elem: &schema.Schema{ +// Type: schema.TypeString, +// }, +// Optional: true, +// ForceNew: true, +// Description: "Imports for Java / Python functions. For Java this a list of jar files, for Python this is a list of Python files.", +// }, +// "handler": { +// Type: schema.TypeString, +// Optional: true, +// ForceNew: true, +// Description: "The handler method for Java / Python function.", +// }, +// "target_path": { +// Type: schema.TypeString, +// Optional: true, +// ForceNew: true, +// Description: "The target path for the Java / Python functions. For Java, it is the path of compiled jar files and for the Python it is the path of the Python files.", +// }, +//} +// +//// Function returns a pointer to the resource representing a stored function. +//func Function() *schema.Resource { +// return &schema.Resource{ +// SchemaVersion: 1, +// +// CreateContext: CreateContextFunction, +// ReadContext: ReadContextFunction, +// UpdateContext: UpdateContextFunction, +// DeleteContext: DeleteContextFunction, +// +// Schema: functionSchema, +// Importer: &schema.ResourceImporter{ +// StateContext: schema.ImportStatePassthroughContext, +// }, +// +// StateUpgraders: []schema.StateUpgrader{ +// { +// Version: 0, +// // setting type to cty.EmptyObject is a bit hacky here but following https://developer.hashicorp.com/terraform/plugin/framework/migrating/resources/state-upgrade#sdkv2-1 would require lots of repetitive code; this should work with cty.EmptyObject +// Type: cty.EmptyObject, +// Upgrade: v085FunctionIdStateUpgrader, +// }, +// }, +// } +//} +// +//func CreateContextFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// lang := strings.ToUpper(d.Get("language").(string)) +// switch lang { +// case "JAVA": +// return createJavaFunction(ctx, d, meta) +// case "JAVASCRIPT": +// return createJavascriptFunction(ctx, d, meta) +// case "PYTHON": +// return createPythonFunction(ctx, d, meta) +// case "SCALA": +// return createScalaFunction(ctx, d, meta) +// case "", "SQL": // SQL if language is not set +// return createSQLFunction(ctx, d, meta) +// default: +// return diag.Diagnostics{ +// diag.Diagnostic{ +// Severity: diag.Error, +// Summary: "Invalid language", +// Detail: fmt.Sprintf("Language %s is not supported", lang), +// }, +// } +// } +//} +// +//func createJavaFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// client := meta.(*provider.Context).Client +// name := d.Get("name").(string) +// schema := d.Get("schema").(string) +// database := d.Get("database").(string) +// id := sdk.NewSchemaObjectIdentifier(database, schema, name) +// +// // Set required +// returns, diags := parseFunctionReturnsRequest(d.Get("return_type").(string)) +// if diags != nil { +// return diags +// } +// handler := d.Get("handler").(string) +// // create request with required +// request := sdk.NewCreateForJavaFunctionRequest(id, *returns, handler) +// functionDefinition := d.Get("statement").(string) +// request.WithFunctionDefinition(sdk.String(functionDefinition)) +// +// // Set optionals +// if v, ok := d.GetOk("is_secure"); ok { +// request.WithSecure(sdk.Bool(v.(bool))) +// } +// arguments, diags := parseFunctionArguments(d) +// if diags != nil { +// return diags +// } +// if len(arguments) > 0 { +// request.WithArguments(arguments) +// } +// if v, ok := d.GetOk("null_input_behavior"); ok { +// request.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehavior(v.(string)))) +// } +// if v, ok := d.GetOk("return_behavior"); ok { +// request.WithReturnResultsBehavior(sdk.Pointer(sdk.ReturnResultsBehavior(v.(string)))) +// } +// if v, ok := d.GetOk("runtime_version"); ok { +// request.WithRuntimeVersion(sdk.String(v.(string))) +// } +// if v, ok := d.GetOk("comment"); ok { +// request.WithComment(sdk.String(v.(string))) +// } +// if _, ok := d.GetOk("imports"); ok { +// imports := []sdk.FunctionImportRequest{} +// for _, item := range d.Get("imports").([]interface{}) { +// imports = append(imports, *sdk.NewFunctionImportRequest().WithImport(item.(string))) +// } +// request.WithImports(imports) +// } +// if _, ok := d.GetOk("packages"); ok { +// packages := []sdk.FunctionPackageRequest{} +// for _, item := range d.Get("packages").([]interface{}) { +// packages = append(packages, *sdk.NewFunctionPackageRequest().WithPackage(item.(string))) +// } +// request.WithPackages(packages) +// } +// if v, ok := d.GetOk("target_path"); ok { +// request.WithTargetPath(sdk.String(v.(string))) +// } +// +// if err := client.Functions.CreateForJava(ctx, request); err != nil { +// return diag.FromErr(err) +// } +// argumentTypes := make([]sdk.DataType, 0, len(arguments)) +// for _, item := range arguments { +// argumentTypes = append(argumentTypes, item.ArgDataType) +// } +// nid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argumentTypes) +// d.SetId(nid.FullyQualifiedName()) +// return ReadContextFunction(ctx, d, meta) +//} +// +//func createScalaFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// client := meta.(*provider.Context).Client +// name := d.Get("name").(string) +// schema := d.Get("schema").(string) +// database := d.Get("database").(string) +// id := sdk.NewSchemaObjectIdentifier(database, schema, name) +// +// // Set required +// returnType := d.Get("return_type").(string) +// returnDataType, diags := convertFunctionDataType(returnType) +// if diags != nil { +// return diags +// } +// functionDefinition := d.Get("statement").(string) +// handler := d.Get("handler").(string) +// // create request with required +// request := sdk.NewCreateForScalaFunctionRequest(id, returnDataType, handler) +// request.WithFunctionDefinition(sdk.String(functionDefinition)) +// +// // Set optionals +// if v, ok := d.GetOk("is_secure"); ok { +// request.WithSecure(sdk.Bool(v.(bool))) +// } +// arguments, diags := parseFunctionArguments(d) +// if diags != nil { +// return diags +// } +// if len(arguments) > 0 { +// request.WithArguments(arguments) +// } +// if v, ok := d.GetOk("null_input_behavior"); ok { +// request.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehavior(v.(string)))) +// } +// if v, ok := d.GetOk("return_behavior"); ok { +// request.WithReturnResultsBehavior(sdk.Pointer(sdk.ReturnResultsBehavior(v.(string)))) +// } +// if v, ok := d.GetOk("runtime_version"); ok { +// request.WithRuntimeVersion(sdk.String(v.(string))) +// } +// if v, ok := d.GetOk("comment"); ok { +// request.WithComment(sdk.String(v.(string))) +// } +// if _, ok := d.GetOk("imports"); ok { +// imports := []sdk.FunctionImportRequest{} +// for _, item := range d.Get("imports").([]interface{}) { +// imports = append(imports, *sdk.NewFunctionImportRequest().WithImport(item.(string))) +// } +// request.WithImports(imports) +// } +// if _, ok := d.GetOk("packages"); ok { +// packages := []sdk.FunctionPackageRequest{} +// for _, item := range d.Get("packages").([]interface{}) { +// packages = append(packages, *sdk.NewFunctionPackageRequest().WithPackage(item.(string))) +// } +// request.WithPackages(packages) +// } +// if v, ok := d.GetOk("target_path"); ok { +// request.WithTargetPath(sdk.String(v.(string))) +// } +// +// if err := client.Functions.CreateForScala(ctx, request); err != nil { +// return diag.FromErr(err) +// } +// argumentTypes := make([]sdk.DataType, 0, len(arguments)) +// for _, item := range arguments { +// argumentTypes = append(argumentTypes, item.ArgDataType) +// } +// nid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argumentTypes) +// d.SetId(nid.FullyQualifiedName()) +// return ReadContextFunction(ctx, d, meta) +//} +// +//func createSQLFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// client := meta.(*provider.Context).Client +// name := d.Get("name").(string) +// schema := d.Get("schema").(string) +// database := d.Get("database").(string) +// id := sdk.NewSchemaObjectIdentifier(database, schema, name) +// +// // Set required +// returns, diags := parseFunctionReturnsRequest(d.Get("return_type").(string)) +// if diags != nil { +// return diags +// } +// functionDefinition := d.Get("statement").(string) +// // create request with required +// request := sdk.NewCreateForSQLFunctionRequest(id, *returns, functionDefinition) +// +// // Set optionals +// if v, ok := d.GetOk("is_secure"); ok { +// request.WithSecure(sdk.Bool(v.(bool))) +// } +// arguments, diags := parseFunctionArguments(d) +// if diags != nil { +// return diags +// } +// if len(arguments) > 0 { +// request.WithArguments(arguments) +// } +// if v, ok := d.GetOk("return_behavior"); ok { +// request.WithReturnResultsBehavior(sdk.Pointer(sdk.ReturnResultsBehavior(v.(string)))) +// } +// if v, ok := d.GetOk("comment"); ok { +// request.WithComment(sdk.String(v.(string))) +// } +// +// if err := client.Functions.CreateForSQL(ctx, request); err != nil { +// return diag.FromErr(err) +// } +// argumentTypes := make([]sdk.DataType, 0, len(arguments)) +// for _, item := range arguments { +// argumentTypes = append(argumentTypes, item.ArgDataType) +// } +// nid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argumentTypes) +// d.SetId(nid.FullyQualifiedName()) +// return ReadContextFunction(ctx, d, meta) +//} +// +//func createPythonFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// client := meta.(*provider.Context).Client +// name := d.Get("name").(string) +// schema := d.Get("schema").(string) +// database := d.Get("database").(string) +// id := sdk.NewSchemaObjectIdentifier(database, schema, name) +// +// // Set required +// returns, diags := parseFunctionReturnsRequest(d.Get("return_type").(string)) +// if diags != nil { +// return diags +// } +// functionDefinition := d.Get("statement").(string) +// version := d.Get("runtime_version").(string) +// handler := d.Get("handler").(string) +// // create request with required +// request := sdk.NewCreateForPythonFunctionRequest(id, *returns, version, handler) +// request.WithFunctionDefinition(sdk.String(functionDefinition)) +// +// // Set optionals +// if v, ok := d.GetOk("is_secure"); ok { +// request.WithSecure(sdk.Bool(v.(bool))) +// } +// arguments, diags := parseFunctionArguments(d) +// if diags != nil { +// return diags +// } +// if len(arguments) > 0 { +// request.WithArguments(arguments) +// } +// if v, ok := d.GetOk("null_input_behavior"); ok { +// request.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehavior(v.(string)))) +// } +// if v, ok := d.GetOk("return_behavior"); ok { +// request.WithReturnResultsBehavior(sdk.Pointer(sdk.ReturnResultsBehavior(v.(string)))) +// } +// +// if v, ok := d.GetOk("comment"); ok { +// request.WithComment(sdk.String(v.(string))) +// } +// if _, ok := d.GetOk("imports"); ok { +// imports := []sdk.FunctionImportRequest{} +// for _, item := range d.Get("imports").([]interface{}) { +// imports = append(imports, *sdk.NewFunctionImportRequest().WithImport(item.(string))) +// } +// request.WithImports(imports) +// } +// if _, ok := d.GetOk("packages"); ok { +// packages := []sdk.FunctionPackageRequest{} +// for _, item := range d.Get("packages").([]interface{}) { +// packages = append(packages, *sdk.NewFunctionPackageRequest().WithPackage(item.(string))) +// } +// request.WithPackages(packages) +// } +// +// if err := client.Functions.CreateForPython(ctx, request); err != nil { +// return diag.FromErr(err) +// } +// argumentTypes := make([]sdk.DataType, 0, len(arguments)) +// for _, item := range arguments { +// argumentTypes = append(argumentTypes, item.ArgDataType) +// } +// nid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argumentTypes) +// d.SetId(nid.FullyQualifiedName()) +// return ReadContextFunction(ctx, d, meta) +//} +// +//func createJavascriptFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// client := meta.(*provider.Context).Client +// name := d.Get("name").(string) +// schema := d.Get("schema").(string) +// database := d.Get("database").(string) +// id := sdk.NewSchemaObjectIdentifier(database, schema, name) +// +// // Set required +// returns, diags := parseFunctionReturnsRequest(d.Get("return_type").(string)) +// if diags != nil { +// return diags +// } +// functionDefinition := d.Get("statement").(string) +// // create request with required +// request := sdk.NewCreateForJavascriptFunctionRequest(id, *returns, functionDefinition) +// +// // Set optionals +// if v, ok := d.GetOk("is_secure"); ok { +// request.WithSecure(sdk.Bool(v.(bool))) +// } +// arguments, diags := parseFunctionArguments(d) +// if diags != nil { +// return diags +// } +// if len(arguments) > 0 { +// request.WithArguments(arguments) +// } +// if v, ok := d.GetOk("null_input_behavior"); ok { +// request.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehavior(v.(string)))) +// } +// if v, ok := d.GetOk("return_behavior"); ok { +// request.WithReturnResultsBehavior(sdk.Pointer(sdk.ReturnResultsBehavior(v.(string)))) +// } +// if v, ok := d.GetOk("comment"); ok { +// request.WithComment(sdk.String(v.(string))) +// } +// +// if err := client.Functions.CreateForJavascript(ctx, request); err != nil { +// return diag.FromErr(err) +// } +// argumentTypes := make([]sdk.DataType, 0, len(arguments)) +// for _, item := range arguments { +// argumentTypes = append(argumentTypes, item.ArgDataType) +// } +// nid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argumentTypes) +// d.SetId(nid.FullyQualifiedName()) +// return ReadContextFunction(ctx, d, meta) +//} +// +//func ReadContextFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// diags := diag.Diagnostics{} +// client := meta.(*provider.Context).Client +// +// id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) +// if err := d.Set("name", id.Name()); err != nil { +// return diag.FromErr(err) +// } +// if err := d.Set("database", id.DatabaseName()); err != nil { +// return diag.FromErr(err) +// } +// if err := d.Set("schema", id.SchemaName()); err != nil { +// return diag.FromErr(err) +// } +// +// arguments := d.Get("arguments").([]interface{}) +// argumentTypes := make([]string, len(arguments)) +// for i, arg := range arguments { +// argumentTypes[i] = arg.(map[string]interface{})["type"].(string) +// } +// functionDetails, err := client.Functions.Describe(ctx, sdk.NewDescribeFunctionRequest(id.WithoutArguments(), id.Arguments())) +// if err != nil { +// // if function is not found then mark resource to be removed from state file during apply or refresh +// d.SetId("") +// return diag.Diagnostics{ +// diag.Diagnostic{ +// Severity: diag.Warning, +// Summary: "Describe function failed.", +// Detail: "See our document on design decisions for functions: ", +// }, +// } +// } +// for _, desc := range functionDetails { +// switch desc.Property { +// case "signature": +// // Format in Snowflake DB is: (argName argType, argName argType, ...) +// value := strings.ReplaceAll(strings.ReplaceAll(desc.Value, "(", ""), ")", "") +// if value != "" { // Do nothing for functions without arguments +// pairs := strings.Split(value, ", ") +// +// arguments := []interface{}{} +// for _, pair := range pairs { +// item := strings.Split(pair, " ") +// argument := map[string]interface{}{} +// argument["name"] = item[0] +// argument["type"] = item[1] +// arguments = append(arguments, argument) +// } +// if err := d.Set("arguments", arguments); err != nil { +// diag.FromErr(err) +// } +// } +// case "null handling": +// if err := d.Set("null_input_behavior", desc.Value); err != nil { +// diag.FromErr(err) +// } +// case "volatility": +// if err := d.Set("return_behavior", desc.Value); err != nil { +// diag.FromErr(err) +// } +// case "body": +// if err := d.Set("statement", desc.Value); err != nil { +// diag.FromErr(err) +// } +// case "returns": +// // Format in Snowflake DB is returnType() +// re := regexp.MustCompile(`^(.*)\([0-9]*\)$`) +// match := re.FindStringSubmatch(desc.Value) +// rt := desc.Value +// if match != nil { +// rt = match[1] +// } +// if err := d.Set("return_type", rt); err != nil { +// diag.FromErr(err) +// } +// case "language": +// if snowflake.Contains(languages, strings.ToLower(desc.Value)) { +// if err := d.Set("language", desc.Value); err != nil { +// diag.FromErr(err) +// } +// } else { +// log.Printf("[INFO] Unexpected language for function %v returned from Snowflake", desc.Value) +// } +// case "packages": +// value := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(desc.Value, "[", ""), "]", ""), "'", "") +// if value != "" { // Do nothing for Java / Python functions without packages +// packages := strings.Split(value, ",") +// if err := d.Set("packages", packages); err != nil { +// diag.FromErr(err) +// } +// } +// case "imports": +// value := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(desc.Value, "[", ""), "]", ""), "'", "") +// if value != "" { // Do nothing for Java functions without imports +// imports := strings.Split(value, ",") +// if err := d.Set("imports", imports); err != nil { +// diag.FromErr(err) +// } +// } +// case "handler": +// if err := d.Set("handler", desc.Value); err != nil { +// diag.FromErr(err) +// } +// case "target_path": +// if err := d.Set("target_path", desc.Value); err != nil { +// diag.FromErr(err) +// } +// case "runtime_version": +// if err := d.Set("runtime_version", desc.Value); err != nil { +// diag.FromErr(err) +// } +// default: +// log.Printf("[INFO] Unexpected function property %v returned from Snowflake with value %v", desc.Property, desc.Value) +// } +// } +// +// // Show functions to set is_secure and comment +// request := sdk.NewShowFunctionRequest().WithIn(&sdk.In{Schema: sdk.NewDatabaseObjectIdentifier(id.DatabaseName(), id.SchemaName())}).WithLike(&sdk.Like{Pattern: sdk.String(id.Name())}) +// functions, err := client.Functions.Show(ctx, request) +// if err != nil { +// return diag.FromErr(err) +// } +// for _, function := range functions { +// signature := strings.Split(function.Arguments, " RETURN ")[0] +// signature = strings.ReplaceAll(signature, " ", "") +// id.FullyQualifiedName() +// if signature == id.ArgumentsSignature() { +// if err := d.Set("is_secure", function.IsSecure); err != nil { +// return diag.FromErr(err) +// } +// if err := d.Set("comment", function.Description); err != nil { +// return diag.FromErr(err) +// } +// } +// } +// return diags +//} +// +//func UpdateContextFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// client := meta.(*provider.Context).Client +// +// id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) +// if d.HasChange("name") { +// name := d.Get("name").(string) +// newId := sdk.NewSchemaObjectIdentifierWithArguments(id.DatabaseName(), id.SchemaName(), name, id.Arguments()) +// +// if err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id.WithoutArguments(), id.Arguments()).WithRenameTo(sdk.Pointer(newId.WithoutArguments()))); err != nil { +// return diag.FromErr(err) +// } +// +// d.SetId(newId.FullyQualifiedName()) +// id = newId +// } +// +// if d.HasChange("is_secure") { +// secure := d.Get("is_secure") +// if secure.(bool) { +// if err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id.WithoutArguments(), id.Arguments()).WithSetSecure(sdk.Bool(true))); err != nil { +// return diag.FromErr(err) +// } +// } else { +// if err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id.WithoutArguments(), id.Arguments()).WithUnsetSecure(sdk.Bool(true))); err != nil { +// return diag.FromErr(err) +// } +// } +// } +// +// if d.HasChange("comment") { +// comment := d.Get("comment") +// if comment != "" { +// if err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id.WithoutArguments(), id.Arguments()).WithSetComment(sdk.String(comment.(string)))); err != nil { +// return diag.FromErr(err) +// } +// } else { +// if err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id.WithoutArguments(), id.Arguments()).WithUnsetComment(sdk.Bool(true))); err != nil { +// return diag.FromErr(err) +// } +// } +// } +// +// return ReadContextFunction(ctx, d, meta) +//} +// +//func DeleteContextFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// client := meta.(*provider.Context).Client +// +// id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) +// if err := client.Functions.Drop(ctx, sdk.NewDropFunctionRequest(id.WithoutArguments(), id.Arguments())); err != nil { +// return diag.FromErr(err) +// } +// d.SetId("") +// return nil +//} +// +//func parseFunctionArguments(d *schema.ResourceData) ([]sdk.FunctionArgumentRequest, diag.Diagnostics) { +// args := make([]sdk.FunctionArgumentRequest, 0) +// if v, ok := d.GetOk("arguments"); ok { +// for _, arg := range v.([]interface{}) { +// argName := arg.(map[string]interface{})["name"].(string) +// argType := arg.(map[string]interface{})["type"].(string) +// argDataType, diags := convertFunctionDataType(argType) +// if diags != nil { +// return nil, diags +// } +// args = append(args, sdk.FunctionArgumentRequest{ArgName: argName, ArgDataType: argDataType}) +// } +// } +// return args, nil +//} +// +//func convertFunctionDataType(s string) (sdk.DataType, diag.Diagnostics) { +// dataType, err := sdk.ToDataType(s) +// if err != nil { +// return dataType, diag.FromErr(err) +// } +// return dataType, nil +//} +// +//func convertFunctionColumns(s string) ([]sdk.FunctionColumn, diag.Diagnostics) { +// pattern := regexp.MustCompile(`(\w+)\s+(\w+)`) +// matches := pattern.FindAllStringSubmatch(s, -1) +// var columns []sdk.FunctionColumn +// for _, match := range matches { +// if len(match) == 3 { +// dataType, err := sdk.ToDataType(match[2]) +// if err != nil { +// return nil, diag.FromErr(err) +// } +// columns = append(columns, sdk.FunctionColumn{ +// ColumnName: match[1], +// ColumnDataType: dataType, +// }) +// } +// } +// return columns, nil +//} +// +//func parseFunctionReturnsRequest(s string) (*sdk.FunctionReturnsRequest, diag.Diagnostics) { +// returns := sdk.NewFunctionReturnsRequest() +// if strings.HasPrefix(strings.ToLower(s), "table") { +// columns, diags := convertFunctionColumns(s) +// if diags != nil { +// return nil, diags +// } +// var cr []sdk.FunctionColumnRequest +// for _, item := range columns { +// cr = append(cr, *sdk.NewFunctionColumnRequest(item.ColumnName, item.ColumnDataType)) +// } +// returns.WithTable(sdk.NewFunctionReturnsTableRequest().WithColumns(cr)) +// } else { +// returnDataType, diags := convertFunctionDataType(s) +// if diags != nil { +// return nil, diags +// } +// returns.WithResultDataType(sdk.NewFunctionReturnsResultDataTypeRequest(returnDataType)) +// } +// return returns, nil +//} diff --git a/pkg/resources/function_state_upgraders.go b/pkg/resources/function_state_upgraders.go index 43f699e5d5..46687f1bf9 100644 --- a/pkg/resources/function_state_upgraders.go +++ b/pkg/resources/function_state_upgraders.go @@ -1,62 +1,54 @@ package resources -import ( - "context" - "fmt" - "strings" - - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" -) - -type v085FunctionId struct { - DatabaseName string - SchemaName string - FunctionName string - ArgTypes []string -} - -func parseV085FunctionId(v string) (*v085FunctionId, error) { - arr := strings.Split(v, "|") - if len(arr) != 4 { - return nil, sdk.NewError(fmt.Sprintf("ID %v is invalid", v)) - } - - // this is a bit different from V085 state, but it was buggy - var args []string - if arr[3] != "" { - args = strings.Split(arr[3], "-") - } - - return &v085FunctionId{ - DatabaseName: arr[0], - SchemaName: arr[1], - FunctionName: arr[2], - ArgTypes: args, - }, nil -} - -func v085FunctionIdStateUpgrader(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { - if rawState == nil { - return rawState, nil - } - - oldId := rawState["id"].(string) - parsedV085FunctionId, err := parseV085FunctionId(oldId) - if err != nil { - return nil, err - } - - argDataTypes := make([]sdk.DataType, len(parsedV085FunctionId.ArgTypes)) - for i, argType := range parsedV085FunctionId.ArgTypes { - argDataType, err := sdk.ToDataType(argType) - if err != nil { - return nil, err - } - argDataTypes[i] = argDataType - } - - schemaObjectIdentifierWithArguments := sdk.NewSchemaObjectIdentifierWithArguments(parsedV085FunctionId.DatabaseName, parsedV085FunctionId.SchemaName, parsedV085FunctionId.FunctionName, argDataTypes) - rawState["id"] = schemaObjectIdentifierWithArguments.FullyQualifiedName() - - return rawState, nil -} +//type v085FunctionId struct { +// DatabaseName string +// SchemaName string +// FunctionName string +// ArgTypes []string +//} +// +//func parseV085FunctionId(v string) (*v085FunctionId, error) { +// arr := strings.Split(v, "|") +// if len(arr) != 4 { +// return nil, sdk.NewError(fmt.Sprintf("ID %v is invalid", v)) +// } +// +// // this is a bit different from V085 state, but it was buggy +// var args []string +// if arr[3] != "" { +// args = strings.Split(arr[3], "-") +// } +// +// return &v085FunctionId{ +// DatabaseName: arr[0], +// SchemaName: arr[1], +// FunctionName: arr[2], +// ArgTypes: args, +// }, nil +//} +// +//func v085FunctionIdStateUpgrader(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { +// if rawState == nil { +// return rawState, nil +// } +// +// oldId := rawState["id"].(string) +// parsedV085FunctionId, err := parseV085FunctionId(oldId) +// if err != nil { +// return nil, err +// } +// +// argDataTypes := make([]sdk.DataType, len(parsedV085FunctionId.ArgTypes)) +// for i, argType := range parsedV085FunctionId.ArgTypes { +// argDataType, err := sdk.ToDataType(argType) +// if err != nil { +// return nil, err +// } +// argDataTypes[i] = argDataType +// } +// +// schemaObjectIdentifierWithArguments := sdk.NewSchemaObjectIdentifierWithArguments(parsedV085FunctionId.DatabaseName, parsedV085FunctionId.SchemaName, parsedV085FunctionId.FunctionName, argDataTypes) +// rawState["id"] = schemaObjectIdentifierWithArguments.FullyQualifiedName() +// +// return rawState, nil +//} diff --git a/pkg/resources/procedure.go b/pkg/resources/procedure.go index 3f52075ee0..506b210021 100644 --- a/pkg/resources/procedure.go +++ b/pkg/resources/procedure.go @@ -1,796 +1,779 @@ package resources -import ( - "context" - "fmt" - "log" - "regexp" - "slices" - "strings" - - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" - - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" - "github.com/hashicorp/go-cty/cty" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" -) - -var procedureSchema = map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - Description: "Specifies the identifier for the procedure; does not have to be unique for the schema in which the procedure is created. Don't use the | character.", - }, - "database": { - Type: schema.TypeString, - Required: true, - Description: "The database in which to create the procedure. Don't use the | character.", - ForceNew: true, - }, - "schema": { - Type: schema.TypeString, - Required: true, - Description: "The schema in which to create the procedure. Don't use the | character.", - ForceNew: true, - }, - "secure": { - Type: schema.TypeBool, - Optional: true, - Description: "Specifies that the procedure is secure. For more information about secure procedures, see Protecting Sensitive Information with Secure UDFs and Stored Procedures.", - Default: false, - }, - "arguments": { - Type: schema.TypeList, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - // Suppress the diff shown if the values are equal when both compared in lower case. - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - return strings.EqualFold(old, new) - }, - Description: "The argument name", - }, - "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: dataTypeValidateFunc, - DiffSuppressFunc: dataTypeDiffSuppressFunc, - Description: "The argument type", - }, - }, - }, - Optional: true, - Description: "List of the arguments for the procedure", - ForceNew: true, - }, - "return_type": { - Type: schema.TypeString, - Description: "The return type of the procedure", - // Suppress the diff shown if the values are equal when both compared in lower case. - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - if strings.EqualFold(old, new) { - return true - } - - varcharType := []string{"VARCHAR(16777216)", "VARCHAR", "text", "string", "NVARCHAR", "NVARCHAR2", "CHAR VARYING", "NCHAR VARYING"} - if slices.Contains(varcharType, strings.ToUpper(old)) && slices.Contains(varcharType, strings.ToUpper(new)) { - return true - } - - // all these types are equivalent https://docs.snowflake.com/en/sql-reference/data-types-numeric.html#int-integer-bigint-smallint-tinyint-byteint - integerTypes := []string{"INT", "INTEGER", "BIGINT", "SMALLINT", "TINYINT", "BYTEINT", "NUMBER(38,0)"} - if slices.Contains(integerTypes, strings.ToUpper(old)) && slices.Contains(integerTypes, strings.ToUpper(new)) { - return true - } - return false - }, - Required: true, - ForceNew: true, - }, - "statement": { - Type: schema.TypeString, - Required: true, - Description: "Specifies the code used to create the procedure.", - ForceNew: true, - DiffSuppressFunc: DiffSuppressStatement, - }, - "language": { - Type: schema.TypeString, - Optional: true, - Default: "SQL", - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - return strings.EqualFold(old, new) - }, - ValidateFunc: validation.StringInSlice([]string{"javascript", "java", "scala", "SQL", "python"}, true), - Description: "Specifies the language of the stored procedure code.", - }, - "execute_as": { - Type: schema.TypeString, - Optional: true, - Default: "OWNER", - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - return strings.EqualFold(old, new) - }, - ValidateFunc: validation.StringInSlice([]string{"CALLER", "OWNER"}, true), - Description: "Sets execution context. Allowed values are CALLER and OWNER (consult a proper section in the [docs](https://docs.snowflake.com/en/sql-reference/sql/create-procedure#id1)). For more information see [caller's rights and owner's rights](https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-rights).", - }, - "null_input_behavior": { - Type: schema.TypeString, - Optional: true, - Default: "CALLED ON NULL INPUT", - ForceNew: true, - // We do not use STRICT, because Snowflake then in the Read phase returns RETURNS NULL ON NULL INPUT - ValidateFunc: validation.StringInSlice([]string{"CALLED ON NULL INPUT", "RETURNS NULL ON NULL INPUT"}, false), - Description: "Specifies the behavior of the procedure when called with null inputs.", - }, - "return_behavior": { - Type: schema.TypeString, - Optional: true, - Default: "VOLATILE", - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{"VOLATILE", "IMMUTABLE"}, false), - Description: "Specifies the behavior of the function when returning results", - Deprecated: "These keywords are deprecated for stored procedures. These keywords are not intended to apply to stored procedures. In a future release, these keywords will be removed from the documentation.", - }, - "comment": { - Type: schema.TypeString, - Optional: true, - Default: "user-defined procedure", - Description: "Specifies a comment for the procedure.", - }, - "runtime_version": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Description: "Required for Python procedures. Specifies Python runtime version.", - }, - "packages": { - Type: schema.TypeList, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - Optional: true, - ForceNew: true, - Description: "List of package imports to use for Java / Python procedures. For Java, package imports should be of the form: package_name:version_number, where package_name is snowflake_domain:package. For Python use it should be: ('numpy','pandas','xgboost==1.5.0').", - }, - "imports": { - Type: schema.TypeList, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - Optional: true, - ForceNew: true, - Description: "Imports for Java / Python procedures. For Java this a list of jar files, for Python this is a list of Python files.", - }, - "handler": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Description: "The handler method for Java / Python procedures.", - }, -} - -// Procedure returns a pointer to the resource representing a stored procedure. -func Procedure() *schema.Resource { - return &schema.Resource{ - SchemaVersion: 1, - - CreateContext: CreateContextProcedure, - ReadContext: ReadContextProcedure, - UpdateContext: UpdateContextProcedure, - DeleteContext: DeleteContextProcedure, - - Schema: procedureSchema, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - - StateUpgraders: []schema.StateUpgrader{ - { - Version: 0, - // setting type to cty.EmptyObject is a bit hacky here but following https://developer.hashicorp.com/terraform/plugin/framework/migrating/resources/state-upgrade#sdkv2-1 would require lots of repetitive code; this should work with cty.EmptyObject - Type: cty.EmptyObject, - Upgrade: v085ProcedureStateUpgrader, - }, - }, - } -} - -func CreateContextProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - lang := strings.ToUpper(d.Get("language").(string)) - switch lang { - case "JAVA": - return createJavaProcedure(ctx, d, meta) - case "JAVASCRIPT": - return createJavaScriptProcedure(ctx, d, meta) - case "PYTHON": - return createPythonProcedure(ctx, d, meta) - case "SCALA": - return createScalaProcedure(ctx, d, meta) - case "SQL": - return createSQLProcedure(ctx, d, meta) - default: - return diag.Diagnostics{ - diag.Diagnostic{ - Severity: diag.Error, - Summary: "Invalid language", - Detail: fmt.Sprintf("Language %s is not supported", lang), - }, - } - } -} - -func createJavaProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*provider.Context).Client - name := d.Get("name").(string) - schema := d.Get("schema").(string) - database := d.Get("database").(string) - id := sdk.NewSchemaObjectIdentifier(database, schema, name) - - returns, diags := parseProcedureReturnsRequest(d.Get("return_type").(string)) - if diags != nil { - return diags - } - procedureDefinition := d.Get("statement").(string) - runtimeVersion := d.Get("runtime_version").(string) - packages := []sdk.ProcedurePackageRequest{} - for _, item := range d.Get("packages").([]interface{}) { - packages = append(packages, *sdk.NewProcedurePackageRequest(item.(string))) - } - handler := d.Get("handler").(string) - req := sdk.NewCreateForJavaProcedureRequest(id, *returns, runtimeVersion, packages, handler) - req.WithProcedureDefinition(sdk.String(procedureDefinition)) - args, diags := getProcedureArguments(d) - if diags != nil { - return diags - } - if len(args) > 0 { - req.WithArguments(args) - } - - // read optional params - if v, ok := d.GetOk("execute_as"); ok { - if strings.ToUpper(v.(string)) == "OWNER" { - req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsOwner)) - } else if strings.ToUpper(v.(string)) == "CALLER" { - req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsCaller)) - } - } - if v, ok := d.GetOk("comment"); ok { - req.WithComment(sdk.String(v.(string))) - } - if v, ok := d.GetOk("secure"); ok { - req.WithSecure(sdk.Bool(v.(bool))) - } - if _, ok := d.GetOk("imports"); ok { - imports := []sdk.ProcedureImportRequest{} - for _, item := range d.Get("imports").([]interface{}) { - imports = append(imports, *sdk.NewProcedureImportRequest(item.(string))) - } - req.WithImports(imports) - } - - if err := client.Procedures.CreateForJava(ctx, req); err != nil { - return diag.FromErr(err) - } - argTypes := make([]sdk.DataType, 0, len(args)) - for _, item := range args { - argTypes = append(argTypes, item.ArgDataType) - } - sid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argTypes) - d.SetId(sid.FullyQualifiedName()) - return ReadContextProcedure(ctx, d, meta) -} - -func createJavaScriptProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*provider.Context).Client - name := d.Get("name").(string) - schema := d.Get("schema").(string) - database := d.Get("database").(string) - id := sdk.NewSchemaObjectIdentifier(database, schema, name) - - returnType := d.Get("return_type").(string) - returnDataType, diags := convertProcedureDataType(returnType) - if diags != nil { - return diags - } - procedureDefinition := d.Get("statement").(string) - req := sdk.NewCreateForJavaScriptProcedureRequest(id, returnDataType, procedureDefinition) - args, diags := getProcedureArguments(d) - if diags != nil { - return diags - } - if len(args) > 0 { - req.WithArguments(args) - } - - // read optional params - if v, ok := d.GetOk("execute_as"); ok { - if strings.ToUpper(v.(string)) == "OWNER" { - req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsOwner)) - } else if strings.ToUpper(v.(string)) == "CALLER" { - req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsCaller)) - } - } - if v, ok := d.GetOk("null_input_behavior"); ok { - req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehavior(v.(string)))) - } - if v, ok := d.GetOk("comment"); ok { - req.WithComment(sdk.String(v.(string))) - } - if v, ok := d.GetOk("secure"); ok { - req.WithSecure(sdk.Bool(v.(bool))) - } - - if err := client.Procedures.CreateForJavaScript(ctx, req); err != nil { - return diag.FromErr(err) - } - argTypes := make([]sdk.DataType, 0, len(args)) - for _, item := range args { - argTypes = append(argTypes, item.ArgDataType) - } - sid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argTypes) - d.SetId(sid.FullyQualifiedName()) - return ReadContextProcedure(ctx, d, meta) -} - -func createScalaProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*provider.Context).Client - name := d.Get("name").(string) - schema := d.Get("schema").(string) - database := d.Get("database").(string) - id := sdk.NewSchemaObjectIdentifier(database, schema, name) - - returns, diags := parseProcedureReturnsRequest(d.Get("return_type").(string)) - if diags != nil { - return diags - } - procedureDefinition := d.Get("statement").(string) - runtimeVersion := d.Get("runtime_version").(string) - packages := []sdk.ProcedurePackageRequest{} - for _, item := range d.Get("packages").([]interface{}) { - packages = append(packages, *sdk.NewProcedurePackageRequest(item.(string))) - } - handler := d.Get("handler").(string) - req := sdk.NewCreateForScalaProcedureRequest(id, *returns, runtimeVersion, packages, handler) - req.WithProcedureDefinition(sdk.String(procedureDefinition)) - args, diags := getProcedureArguments(d) - if diags != nil { - return diags - } - if len(args) > 0 { - req.WithArguments(args) - } - - // read optional params - if v, ok := d.GetOk("execute_as"); ok { - if strings.ToUpper(v.(string)) == "OWNER" { - req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsOwner)) - } else if strings.ToUpper(v.(string)) == "CALLER" { - req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsCaller)) - } - } - if v, ok := d.GetOk("comment"); ok { - req.WithComment(sdk.String(v.(string))) - } - if v, ok := d.GetOk("secure"); ok { - req.WithSecure(sdk.Bool(v.(bool))) - } - if _, ok := d.GetOk("imports"); ok { - imports := []sdk.ProcedureImportRequest{} - for _, item := range d.Get("imports").([]interface{}) { - imports = append(imports, *sdk.NewProcedureImportRequest(item.(string))) - } - req.WithImports(imports) - } - - if err := client.Procedures.CreateForScala(ctx, req); err != nil { - return diag.FromErr(err) - } - argTypes := make([]sdk.DataType, 0, len(args)) - for _, item := range args { - argTypes = append(argTypes, item.ArgDataType) - } - sid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argTypes) - d.SetId(sid.FullyQualifiedName()) - return ReadContextProcedure(ctx, d, meta) -} - -func createSQLProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*provider.Context).Client - name := d.Get("name").(string) - schema := d.Get("schema").(string) - database := d.Get("database").(string) - id := sdk.NewSchemaObjectIdentifier(database, schema, name) - - returns, diags := parseProcedureSQLReturnsRequest(d.Get("return_type").(string)) - if diags != nil { - return diags - } - procedureDefinition := d.Get("statement").(string) - req := sdk.NewCreateForSQLProcedureRequest(id, *returns, procedureDefinition) - args, diags := getProcedureArguments(d) - if diags != nil { - return diags - } - if len(args) > 0 { - req.WithArguments(args) - } - - // read optional params - if v, ok := d.GetOk("execute_as"); ok { - if strings.ToUpper(v.(string)) == "OWNER" { - req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsOwner)) - } else if strings.ToUpper(v.(string)) == "CALLER" { - req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsCaller)) - } - } - if v, ok := d.GetOk("null_input_behavior"); ok { - req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehavior(v.(string)))) - } - if v, ok := d.GetOk("comment"); ok { - req.WithComment(sdk.String(v.(string))) - } - if v, ok := d.GetOk("secure"); ok { - req.WithSecure(sdk.Bool(v.(bool))) - } - - if err := client.Procedures.CreateForSQL(ctx, req); err != nil { - return diag.FromErr(err) - } - argTypes := make([]sdk.DataType, 0, len(args)) - for _, item := range args { - argTypes = append(argTypes, item.ArgDataType) - } - sid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argTypes) - d.SetId(sid.FullyQualifiedName()) - return ReadContextProcedure(ctx, d, meta) -} - -func createPythonProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*provider.Context).Client - name := d.Get("name").(string) - schema := d.Get("schema").(string) - database := d.Get("database").(string) - id := sdk.NewSchemaObjectIdentifier(database, schema, name) - - returns, diags := parseProcedureReturnsRequest(d.Get("return_type").(string)) - if diags != nil { - return diags - } - procedureDefinition := d.Get("statement").(string) - runtimeVersion := d.Get("runtime_version").(string) - packages := []sdk.ProcedurePackageRequest{} - for _, item := range d.Get("packages").([]interface{}) { - packages = append(packages, *sdk.NewProcedurePackageRequest(item.(string))) - } - handler := d.Get("handler").(string) - req := sdk.NewCreateForPythonProcedureRequest(id, *returns, runtimeVersion, packages, handler) - req.WithProcedureDefinition(sdk.String(procedureDefinition)) - args, diags := getProcedureArguments(d) - if diags != nil { - return diags - } - if len(args) > 0 { - req.WithArguments(args) - } - - // read optional params - if v, ok := d.GetOk("execute_as"); ok { - if strings.ToUpper(v.(string)) == "OWNER" { - req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsOwner)) - } else if strings.ToUpper(v.(string)) == "CALLER" { - req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsCaller)) - } - } - - // [ { CALLED ON NULL INPUT | { RETURNS NULL ON NULL INPUT | STRICT } } ] does not work for java, scala or python - // posted in docs-discuss channel, either docs need to be updated to reflect reality or this feature needs to be added - // https://snowflake.slack.com/archives/C6380540P/p1707511734666249 - // if v, ok := d.GetOk("null_input_behavior"); ok { - // req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehavior(v.(string)))) - // } - - if v, ok := d.GetOk("comment"); ok { - req.WithComment(sdk.String(v.(string))) - } - if v, ok := d.GetOk("secure"); ok { - req.WithSecure(sdk.Bool(v.(bool))) - } - if _, ok := d.GetOk("imports"); ok { - imports := []sdk.ProcedureImportRequest{} - for _, item := range d.Get("imports").([]interface{}) { - imports = append(imports, *sdk.NewProcedureImportRequest(item.(string))) - } - req.WithImports(imports) - } - - if err := client.Procedures.CreateForPython(ctx, req); err != nil { - return diag.FromErr(err) - } - argTypes := make([]sdk.DataType, 0, len(args)) - for _, item := range args { - argTypes = append(argTypes, item.ArgDataType) - } - sid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argTypes) - d.SetId(sid.FullyQualifiedName()) - return ReadContextProcedure(ctx, d, meta) -} - -func ReadContextProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - diags := diag.Diagnostics{} - client := meta.(*provider.Context).Client - - id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) - if err := d.Set("name", id.Name()); err != nil { - return diag.FromErr(err) - } - if err := d.Set("database", id.DatabaseName()); err != nil { - return diag.FromErr(err) - } - if err := d.Set("schema", id.SchemaName()); err != nil { - return diag.FromErr(err) - } - args := d.Get("arguments").([]interface{}) - argTypes := make([]string, len(args)) - for i, arg := range args { - argTypes[i] = arg.(map[string]interface{})["type"].(string) - } - procedureDetails, err := client.Procedures.Describe(ctx, sdk.NewDescribeProcedureRequest(id.WithoutArguments(), id.Arguments())) - if err != nil { - // if procedure is not found then mark resource to be removed from state file during apply or refresh - d.SetId("") - return diag.Diagnostics{ - diag.Diagnostic{ - Severity: diag.Warning, - Summary: "Describe procedure failed.", - Detail: fmt.Sprintf("Describe procedure failed: %v", err), - }, - } - } - for _, desc := range procedureDetails { - switch desc.Property { - case "signature": - // Format in Snowflake DB is: (argName argType, argName argType, ...) - args := strings.ReplaceAll(strings.ReplaceAll(desc.Value, "(", ""), ")", "") - - if args != "" { // Do nothing for functions without arguments - argPairs := strings.Split(args, ", ") - args := []interface{}{} - - for _, argPair := range argPairs { - argItem := strings.Split(argPair, " ") - - arg := map[string]interface{}{} - arg["name"] = argItem[0] - arg["type"] = argItem[1] - args = append(args, arg) - } - - if err := d.Set("arguments", args); err != nil { - return diag.FromErr(err) - } - } - case "null handling": - if err := d.Set("null_input_behavior", desc.Value); err != nil { - return diag.FromErr(err) - } - case "body": - if err := d.Set("statement", desc.Value); err != nil { - return diag.FromErr(err) - } - case "execute as": - if err := d.Set("execute_as", desc.Value); err != nil { - return diag.FromErr(err) - } - case "returns": - if err := d.Set("return_type", desc.Value); err != nil { - return diag.FromErr(err) - } - case "language": - if err := d.Set("language", desc.Value); err != nil { - return diag.FromErr(err) - } - case "runtime_version": - if err := d.Set("runtime_version", desc.Value); err != nil { - return diag.FromErr(err) - } - case "packages": - packagesString := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(desc.Value, "[", ""), "]", ""), "'", "") - if packagesString != "" { // Do nothing for Java / Python functions without packages - packages := strings.Split(packagesString, ",") - if err := d.Set("packages", packages); err != nil { - return diag.FromErr(err) - } - } - case "imports": - importsString := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(desc.Value, "[", ""), "]", ""), "'", ""), " ", "") - if importsString != "" { // Do nothing for Java functions without imports - imports := strings.Split(importsString, ",") - if err := d.Set("imports", imports); err != nil { - return diag.FromErr(err) - } - } - case "handler": - if err := d.Set("handler", desc.Value); err != nil { - return diag.FromErr(err) - } - case "volatility": - if err := d.Set("return_behavior", desc.Value); err != nil { - return diag.FromErr(err) - } - default: - log.Printf("[INFO] Unexpected procedure property %v returned from Snowflake with value %v", desc.Property, desc.Value) - } - } - - request := sdk.NewShowProcedureRequest().WithIn(&sdk.In{Schema: sdk.NewDatabaseObjectIdentifier(id.DatabaseName(), id.SchemaName())}).WithLike(&sdk.Like{Pattern: sdk.String(id.Name())}) - - procedures, err := client.Procedures.Show(ctx, request) - if err != nil { - return diag.FromErr(err) - } - // procedure names can be overloaded with different argument types so we iterate over and find the correct one - // the ShowByID function should probably be updated to also require the list of arg types, like describe procedure - for _, procedure := range procedures { - argumentSignature := strings.Split(procedure.Arguments, " RETURN ")[0] - argumentSignature = strings.ReplaceAll(argumentSignature, " ", "") - if argumentSignature == id.ArgumentsSignature() { - if err := d.Set("secure", procedure.IsSecure); err != nil { - return diag.FromErr(err) - } - if err := d.Set("comment", procedure.Description); err != nil { - return diag.FromErr(err) - } - } - } - - return diags -} - -func UpdateContextProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*provider.Context).Client - - id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) - if d.HasChange("name") { - newId := sdk.NewSchemaObjectIdentifierWithArguments(id.DatabaseName(), id.SchemaName(), d.Get("name").(string), id.Arguments()) - - err := client.Procedures.Alter(ctx, sdk.NewAlterProcedureRequest(id.WithoutArguments(), id.Arguments()).WithRenameTo(sdk.Pointer(newId.WithoutArguments()))) - if err != nil { - return diag.FromErr(err) - } - - d.SetId(newId.FullyQualifiedName()) - id = newId - } - - if d.HasChange("comment") { - comment := d.Get("comment") - if comment != "" { - if err := client.Procedures.Alter(ctx, sdk.NewAlterProcedureRequest(id.WithoutArguments(), id.Arguments()).WithSetComment(sdk.String(comment.(string)))); err != nil { - return diag.FromErr(err) - } - } else { - if err := client.Procedures.Alter(ctx, sdk.NewAlterProcedureRequest(id.WithoutArguments(), id.Arguments()).WithUnsetComment(sdk.Bool(true))); err != nil { - return diag.FromErr(err) - } - } - } - - if d.HasChange("execute_as") { - req := sdk.NewAlterProcedureRequest(id.WithoutArguments(), id.Arguments()) - executeAs := d.Get("execute_as").(string) - if strings.ToUpper(executeAs) == "OWNER" { - req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsOwner)) - } else if strings.ToUpper(executeAs) == "CALLER" { - req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsCaller)) - } - if err := client.Procedures.Alter(ctx, req); err != nil { - return diag.FromErr(err) - } - } - - return ReadContextProcedure(ctx, d, meta) -} - -func DeleteContextProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*provider.Context).Client - - id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) - if err := client.Procedures.Drop(ctx, sdk.NewDropProcedureRequest(id.WithoutArguments(), id.Arguments())); err != nil { - return diag.FromErr(err) - } - d.SetId("") - return nil -} - -func getProcedureArguments(d *schema.ResourceData) ([]sdk.ProcedureArgumentRequest, diag.Diagnostics) { - args := make([]sdk.ProcedureArgumentRequest, 0) - if v, ok := d.GetOk("arguments"); ok { - for _, arg := range v.([]interface{}) { - argName := arg.(map[string]interface{})["name"].(string) - argType := arg.(map[string]interface{})["type"].(string) - argDataType, diags := convertProcedureDataType(argType) - if diags != nil { - return nil, diags - } - args = append(args, sdk.ProcedureArgumentRequest{ArgName: argName, ArgDataType: argDataType}) - } - } - return args, nil -} - -func convertProcedureDataType(s string) (sdk.DataType, diag.Diagnostics) { - dataType, err := sdk.ToDataType(s) - if err != nil { - return dataType, diag.FromErr(err) - } - return dataType, nil -} - -func convertProcedureColumns(s string) ([]sdk.ProcedureColumn, diag.Diagnostics) { - pattern := regexp.MustCompile(`(\w+)\s+(\w+)`) - matches := pattern.FindAllStringSubmatch(s, -1) - var columns []sdk.ProcedureColumn - for _, match := range matches { - if len(match) == 3 { - dataType, err := sdk.ToDataType(match[2]) - if err != nil { - return nil, diag.FromErr(err) - } - columns = append(columns, sdk.ProcedureColumn{ - ColumnName: match[1], - ColumnDataType: dataType, - }) - } - } - return columns, nil -} - -func parseProcedureReturnsRequest(s string) (*sdk.ProcedureReturnsRequest, diag.Diagnostics) { - returns := sdk.NewProcedureReturnsRequest() - if strings.HasPrefix(strings.ToLower(s), "table") { - columns, diags := convertProcedureColumns(s) - if diags != nil { - return nil, diags - } - var cr []sdk.ProcedureColumnRequest - for _, item := range columns { - cr = append(cr, *sdk.NewProcedureColumnRequest(item.ColumnName, item.ColumnDataType)) - } - returns.WithTable(sdk.NewProcedureReturnsTableRequest().WithColumns(cr)) - } else { - returnDataType, diags := convertProcedureDataType(s) - if diags != nil { - return nil, diags - } - returns.WithResultDataType(sdk.NewProcedureReturnsResultDataTypeRequest(returnDataType)) - } - return returns, nil -} - -func parseProcedureSQLReturnsRequest(s string) (*sdk.ProcedureSQLReturnsRequest, diag.Diagnostics) { - returns := sdk.NewProcedureSQLReturnsRequest() - if strings.HasPrefix(strings.ToLower(s), "table") { - columns, diags := convertProcedureColumns(s) - if diags != nil { - return nil, diags - } - var cr []sdk.ProcedureColumnRequest - for _, item := range columns { - cr = append(cr, *sdk.NewProcedureColumnRequest(item.ColumnName, item.ColumnDataType)) - } - returns.WithTable(sdk.NewProcedureReturnsTableRequest().WithColumns(cr)) - } else { - returnDataType, diags := convertProcedureDataType(s) - if diags != nil { - return nil, diags - } - returns.WithResultDataType(sdk.NewProcedureReturnsResultDataTypeRequest(returnDataType)) - } - return returns, nil -} +//var procedureSchema = map[string]*schema.Schema{ +// "name": { +// Type: schema.TypeString, +// Required: true, +// Description: "Specifies the identifier for the procedure; does not have to be unique for the schema in which the procedure is created. Don't use the | character.", +// }, +// "database": { +// Type: schema.TypeString, +// Required: true, +// Description: "The database in which to create the procedure. Don't use the | character.", +// ForceNew: true, +// }, +// "schema": { +// Type: schema.TypeString, +// Required: true, +// Description: "The schema in which to create the procedure. Don't use the | character.", +// ForceNew: true, +// }, +// "secure": { +// Type: schema.TypeBool, +// Optional: true, +// Description: "Specifies that the procedure is secure. For more information about secure procedures, see Protecting Sensitive Information with Secure UDFs and Stored Procedures.", +// Default: false, +// }, +// "arguments": { +// Type: schema.TypeList, +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "name": { +// Type: schema.TypeString, +// Required: true, +// // Suppress the diff shown if the values are equal when both compared in lower case. +// DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { +// return strings.EqualFold(old, new) +// }, +// Description: "The argument name", +// }, +// "type": { +// Type: schema.TypeString, +// Required: true, +// ValidateFunc: dataTypeValidateFunc, +// DiffSuppressFunc: dataTypeDiffSuppressFunc, +// Description: "The argument type", +// }, +// }, +// }, +// Optional: true, +// Description: "List of the arguments for the procedure", +// ForceNew: true, +// }, +// "return_type": { +// Type: schema.TypeString, +// Description: "The return type of the procedure", +// // Suppress the diff shown if the values are equal when both compared in lower case. +// DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { +// if strings.EqualFold(old, new) { +// return true +// } +// +// varcharType := []string{"VARCHAR(16777216)", "VARCHAR", "text", "string", "NVARCHAR", "NVARCHAR2", "CHAR VARYING", "NCHAR VARYING"} +// if slices.Contains(varcharType, strings.ToUpper(old)) && slices.Contains(varcharType, strings.ToUpper(new)) { +// return true +// } +// +// // all these types are equivalent https://docs.snowflake.com/en/sql-reference/data-types-numeric.html#int-integer-bigint-smallint-tinyint-byteint +// integerTypes := []string{"INT", "INTEGER", "BIGINT", "SMALLINT", "TINYINT", "BYTEINT", "NUMBER(38,0)"} +// if slices.Contains(integerTypes, strings.ToUpper(old)) && slices.Contains(integerTypes, strings.ToUpper(new)) { +// return true +// } +// return false +// }, +// Required: true, +// ForceNew: true, +// }, +// "statement": { +// Type: schema.TypeString, +// Required: true, +// Description: "Specifies the code used to create the procedure.", +// ForceNew: true, +// DiffSuppressFunc: DiffSuppressStatement, +// }, +// "language": { +// Type: schema.TypeString, +// Optional: true, +// Default: "SQL", +// DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { +// return strings.EqualFold(old, new) +// }, +// ValidateFunc: validation.StringInSlice([]string{"javascript", "java", "scala", "SQL", "python"}, true), +// Description: "Specifies the language of the stored procedure code.", +// }, +// "execute_as": { +// Type: schema.TypeString, +// Optional: true, +// Default: "OWNER", +// DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { +// return strings.EqualFold(old, new) +// }, +// ValidateFunc: validation.StringInSlice([]string{"CALLER", "OWNER"}, true), +// Description: "Sets execution context. Allowed values are CALLER and OWNER (consult a proper section in the [docs](https://docs.snowflake.com/en/sql-reference/sql/create-procedure#id1)). For more information see [caller's rights and owner's rights](https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-rights).", +// }, +// "null_input_behavior": { +// Type: schema.TypeString, +// Optional: true, +// Default: "CALLED ON NULL INPUT", +// ForceNew: true, +// // We do not use STRICT, because Snowflake then in the Read phase returns RETURNS NULL ON NULL INPUT +// ValidateFunc: validation.StringInSlice([]string{"CALLED ON NULL INPUT", "RETURNS NULL ON NULL INPUT"}, false), +// Description: "Specifies the behavior of the procedure when called with null inputs.", +// }, +// "return_behavior": { +// Type: schema.TypeString, +// Optional: true, +// Default: "VOLATILE", +// ForceNew: true, +// ValidateFunc: validation.StringInSlice([]string{"VOLATILE", "IMMUTABLE"}, false), +// Description: "Specifies the behavior of the function when returning results", +// Deprecated: "These keywords are deprecated for stored procedures. These keywords are not intended to apply to stored procedures. In a future release, these keywords will be removed from the documentation.", +// }, +// "comment": { +// Type: schema.TypeString, +// Optional: true, +// Default: "user-defined procedure", +// Description: "Specifies a comment for the procedure.", +// }, +// "runtime_version": { +// Type: schema.TypeString, +// Optional: true, +// ForceNew: true, +// Description: "Required for Python procedures. Specifies Python runtime version.", +// }, +// "packages": { +// Type: schema.TypeList, +// Elem: &schema.Schema{ +// Type: schema.TypeString, +// }, +// Optional: true, +// ForceNew: true, +// Description: "List of package imports to use for Java / Python procedures. For Java, package imports should be of the form: package_name:version_number, where package_name is snowflake_domain:package. For Python use it should be: ('numpy','pandas','xgboost==1.5.0').", +// }, +// "imports": { +// Type: schema.TypeList, +// Elem: &schema.Schema{ +// Type: schema.TypeString, +// }, +// Optional: true, +// ForceNew: true, +// Description: "Imports for Java / Python procedures. For Java this a list of jar files, for Python this is a list of Python files.", +// }, +// "handler": { +// Type: schema.TypeString, +// Optional: true, +// ForceNew: true, +// Description: "The handler method for Java / Python procedures.", +// }, +//} +// +//// Procedure returns a pointer to the resource representing a stored procedure. +//func Procedure() *schema.Resource { +// return &schema.Resource{ +// SchemaVersion: 1, +// +// CreateContext: CreateContextProcedure, +// ReadContext: ReadContextProcedure, +// UpdateContext: UpdateContextProcedure, +// DeleteContext: DeleteContextProcedure, +// +// Schema: procedureSchema, +// Importer: &schema.ResourceImporter{ +// StateContext: schema.ImportStatePassthroughContext, +// }, +// +// StateUpgraders: []schema.StateUpgrader{ +// { +// Version: 0, +// // setting type to cty.EmptyObject is a bit hacky here but following https://developer.hashicorp.com/terraform/plugin/framework/migrating/resources/state-upgrade#sdkv2-1 would require lots of repetitive code; this should work with cty.EmptyObject +// Type: cty.EmptyObject, +// Upgrade: v085ProcedureStateUpgrader, +// }, +// }, +// } +//} +// +//func CreateContextProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// lang := strings.ToUpper(d.Get("language").(string)) +// switch lang { +// case "JAVA": +// return createJavaProcedure(ctx, d, meta) +// case "JAVASCRIPT": +// return createJavaScriptProcedure(ctx, d, meta) +// case "PYTHON": +// return createPythonProcedure(ctx, d, meta) +// case "SCALA": +// return createScalaProcedure(ctx, d, meta) +// case "SQL": +// return createSQLProcedure(ctx, d, meta) +// default: +// return diag.Diagnostics{ +// diag.Diagnostic{ +// Severity: diag.Error, +// Summary: "Invalid language", +// Detail: fmt.Sprintf("Language %s is not supported", lang), +// }, +// } +// } +//} +// +//func createJavaProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// client := meta.(*provider.Context).Client +// name := d.Get("name").(string) +// schema := d.Get("schema").(string) +// database := d.Get("database").(string) +// id := sdk.NewSchemaObjectIdentifier(database, schema, name) +// +// returns, diags := parseProcedureReturnsRequest(d.Get("return_type").(string)) +// if diags != nil { +// return diags +// } +// procedureDefinition := d.Get("statement").(string) +// runtimeVersion := d.Get("runtime_version").(string) +// packages := []sdk.ProcedurePackageRequest{} +// for _, item := range d.Get("packages").([]interface{}) { +// packages = append(packages, *sdk.NewProcedurePackageRequest(item.(string))) +// } +// handler := d.Get("handler").(string) +// req := sdk.NewCreateForJavaProcedureRequest(id, *returns, runtimeVersion, packages, handler) +// req.WithProcedureDefinition(sdk.String(procedureDefinition)) +// args, diags := getProcedureArguments(d) +// if diags != nil { +// return diags +// } +// if len(args) > 0 { +// req.WithArguments(args) +// } +// +// // read optional params +// if v, ok := d.GetOk("execute_as"); ok { +// if strings.ToUpper(v.(string)) == "OWNER" { +// req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsOwner)) +// } else if strings.ToUpper(v.(string)) == "CALLER" { +// req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsCaller)) +// } +// } +// if v, ok := d.GetOk("comment"); ok { +// req.WithComment(sdk.String(v.(string))) +// } +// if v, ok := d.GetOk("secure"); ok { +// req.WithSecure(sdk.Bool(v.(bool))) +// } +// if _, ok := d.GetOk("imports"); ok { +// imports := []sdk.ProcedureImportRequest{} +// for _, item := range d.Get("imports").([]interface{}) { +// imports = append(imports, *sdk.NewProcedureImportRequest(item.(string))) +// } +// req.WithImports(imports) +// } +// +// if err := client.Procedures.CreateForJava(ctx, req); err != nil { +// return diag.FromErr(err) +// } +// argTypes := make([]sdk.DataType, 0, len(args)) +// for _, item := range args { +// argTypes = append(argTypes, item.ArgDataType) +// } +// sid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argTypes) +// d.SetId(sid.FullyQualifiedName()) +// return ReadContextProcedure(ctx, d, meta) +//} +// +//func createJavaScriptProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// client := meta.(*provider.Context).Client +// name := d.Get("name").(string) +// schema := d.Get("schema").(string) +// database := d.Get("database").(string) +// id := sdk.NewSchemaObjectIdentifier(database, schema, name) +// +// returnType := d.Get("return_type").(string) +// returnDataType, diags := convertProcedureDataType(returnType) +// if diags != nil { +// return diags +// } +// procedureDefinition := d.Get("statement").(string) +// req := sdk.NewCreateForJavaScriptProcedureRequest(id, returnDataType, procedureDefinition) +// args, diags := getProcedureArguments(d) +// if diags != nil { +// return diags +// } +// if len(args) > 0 { +// req.WithArguments(args) +// } +// +// // read optional params +// if v, ok := d.GetOk("execute_as"); ok { +// if strings.ToUpper(v.(string)) == "OWNER" { +// req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsOwner)) +// } else if strings.ToUpper(v.(string)) == "CALLER" { +// req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsCaller)) +// } +// } +// if v, ok := d.GetOk("null_input_behavior"); ok { +// req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehavior(v.(string)))) +// } +// if v, ok := d.GetOk("comment"); ok { +// req.WithComment(sdk.String(v.(string))) +// } +// if v, ok := d.GetOk("secure"); ok { +// req.WithSecure(sdk.Bool(v.(bool))) +// } +// +// if err := client.Procedures.CreateForJavaScript(ctx, req); err != nil { +// return diag.FromErr(err) +// } +// argTypes := make([]sdk.DataType, 0, len(args)) +// for _, item := range args { +// argTypes = append(argTypes, item.ArgDataType) +// } +// sid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argTypes) +// d.SetId(sid.FullyQualifiedName()) +// return ReadContextProcedure(ctx, d, meta) +//} +// +//func createScalaProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// client := meta.(*provider.Context).Client +// name := d.Get("name").(string) +// schema := d.Get("schema").(string) +// database := d.Get("database").(string) +// id := sdk.NewSchemaObjectIdentifier(database, schema, name) +// +// returns, diags := parseProcedureReturnsRequest(d.Get("return_type").(string)) +// if diags != nil { +// return diags +// } +// procedureDefinition := d.Get("statement").(string) +// runtimeVersion := d.Get("runtime_version").(string) +// packages := []sdk.ProcedurePackageRequest{} +// for _, item := range d.Get("packages").([]interface{}) { +// packages = append(packages, *sdk.NewProcedurePackageRequest(item.(string))) +// } +// handler := d.Get("handler").(string) +// req := sdk.NewCreateForScalaProcedureRequest(id, *returns, runtimeVersion, packages, handler) +// req.WithProcedureDefinition(sdk.String(procedureDefinition)) +// args, diags := getProcedureArguments(d) +// if diags != nil { +// return diags +// } +// if len(args) > 0 { +// req.WithArguments(args) +// } +// +// // read optional params +// if v, ok := d.GetOk("execute_as"); ok { +// if strings.ToUpper(v.(string)) == "OWNER" { +// req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsOwner)) +// } else if strings.ToUpper(v.(string)) == "CALLER" { +// req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsCaller)) +// } +// } +// if v, ok := d.GetOk("comment"); ok { +// req.WithComment(sdk.String(v.(string))) +// } +// if v, ok := d.GetOk("secure"); ok { +// req.WithSecure(sdk.Bool(v.(bool))) +// } +// if _, ok := d.GetOk("imports"); ok { +// imports := []sdk.ProcedureImportRequest{} +// for _, item := range d.Get("imports").([]interface{}) { +// imports = append(imports, *sdk.NewProcedureImportRequest(item.(string))) +// } +// req.WithImports(imports) +// } +// +// if err := client.Procedures.CreateForScala(ctx, req); err != nil { +// return diag.FromErr(err) +// } +// argTypes := make([]sdk.DataType, 0, len(args)) +// for _, item := range args { +// argTypes = append(argTypes, item.ArgDataType) +// } +// sid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argTypes) +// d.SetId(sid.FullyQualifiedName()) +// return ReadContextProcedure(ctx, d, meta) +//} +// +//func createSQLProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// client := meta.(*provider.Context).Client +// name := d.Get("name").(string) +// schema := d.Get("schema").(string) +// database := d.Get("database").(string) +// id := sdk.NewSchemaObjectIdentifier(database, schema, name) +// +// returns, diags := parseProcedureSQLReturnsRequest(d.Get("return_type").(string)) +// if diags != nil { +// return diags +// } +// procedureDefinition := d.Get("statement").(string) +// req := sdk.NewCreateForSQLProcedureRequest(id, *returns, procedureDefinition) +// args, diags := getProcedureArguments(d) +// if diags != nil { +// return diags +// } +// if len(args) > 0 { +// req.WithArguments(args) +// } +// +// // read optional params +// if v, ok := d.GetOk("execute_as"); ok { +// if strings.ToUpper(v.(string)) == "OWNER" { +// req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsOwner)) +// } else if strings.ToUpper(v.(string)) == "CALLER" { +// req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsCaller)) +// } +// } +// if v, ok := d.GetOk("null_input_behavior"); ok { +// req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehavior(v.(string)))) +// } +// if v, ok := d.GetOk("comment"); ok { +// req.WithComment(sdk.String(v.(string))) +// } +// if v, ok := d.GetOk("secure"); ok { +// req.WithSecure(sdk.Bool(v.(bool))) +// } +// +// if err := client.Procedures.CreateForSQL(ctx, req); err != nil { +// return diag.FromErr(err) +// } +// argTypes := make([]sdk.DataType, 0, len(args)) +// for _, item := range args { +// argTypes = append(argTypes, item.ArgDataType) +// } +// sid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argTypes) +// d.SetId(sid.FullyQualifiedName()) +// return ReadContextProcedure(ctx, d, meta) +//} +// +//func createPythonProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// client := meta.(*provider.Context).Client +// name := d.Get("name").(string) +// schema := d.Get("schema").(string) +// database := d.Get("database").(string) +// id := sdk.NewSchemaObjectIdentifier(database, schema, name) +// +// returns, diags := parseProcedureReturnsRequest(d.Get("return_type").(string)) +// if diags != nil { +// return diags +// } +// procedureDefinition := d.Get("statement").(string) +// runtimeVersion := d.Get("runtime_version").(string) +// packages := []sdk.ProcedurePackageRequest{} +// for _, item := range d.Get("packages").([]interface{}) { +// packages = append(packages, *sdk.NewProcedurePackageRequest(item.(string))) +// } +// handler := d.Get("handler").(string) +// req := sdk.NewCreateForPythonProcedureRequest(id, *returns, runtimeVersion, packages, handler) +// req.WithProcedureDefinition(sdk.String(procedureDefinition)) +// args, diags := getProcedureArguments(d) +// if diags != nil { +// return diags +// } +// if len(args) > 0 { +// req.WithArguments(args) +// } +// +// // read optional params +// if v, ok := d.GetOk("execute_as"); ok { +// if strings.ToUpper(v.(string)) == "OWNER" { +// req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsOwner)) +// } else if strings.ToUpper(v.(string)) == "CALLER" { +// req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsCaller)) +// } +// } +// +// // [ { CALLED ON NULL INPUT | { RETURNS NULL ON NULL INPUT | STRICT } } ] does not work for java, scala or python +// // posted in docs-discuss channel, either docs need to be updated to reflect reality or this feature needs to be added +// // https://snowflake.slack.com/archives/C6380540P/p1707511734666249 +// // if v, ok := d.GetOk("null_input_behavior"); ok { +// // req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehavior(v.(string)))) +// // } +// +// if v, ok := d.GetOk("comment"); ok { +// req.WithComment(sdk.String(v.(string))) +// } +// if v, ok := d.GetOk("secure"); ok { +// req.WithSecure(sdk.Bool(v.(bool))) +// } +// if _, ok := d.GetOk("imports"); ok { +// imports := []sdk.ProcedureImportRequest{} +// for _, item := range d.Get("imports").([]interface{}) { +// imports = append(imports, *sdk.NewProcedureImportRequest(item.(string))) +// } +// req.WithImports(imports) +// } +// +// if err := client.Procedures.CreateForPython(ctx, req); err != nil { +// return diag.FromErr(err) +// } +// argTypes := make([]sdk.DataType, 0, len(args)) +// for _, item := range args { +// argTypes = append(argTypes, item.ArgDataType) +// } +// sid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argTypes) +// d.SetId(sid.FullyQualifiedName()) +// return ReadContextProcedure(ctx, d, meta) +//} +// +//func ReadContextProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// diags := diag.Diagnostics{} +// client := meta.(*provider.Context).Client +// +// id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) +// if err := d.Set("name", id.Name()); err != nil { +// return diag.FromErr(err) +// } +// if err := d.Set("database", id.DatabaseName()); err != nil { +// return diag.FromErr(err) +// } +// if err := d.Set("schema", id.SchemaName()); err != nil { +// return diag.FromErr(err) +// } +// args := d.Get("arguments").([]interface{}) +// argTypes := make([]string, len(args)) +// for i, arg := range args { +// argTypes[i] = arg.(map[string]interface{})["type"].(string) +// } +// procedureDetails, err := client.Procedures.Describe(ctx, sdk.NewDescribeProcedureRequest(id.WithoutArguments(), id.Arguments())) +// if err != nil { +// // if procedure is not found then mark resource to be removed from state file during apply or refresh +// d.SetId("") +// return diag.Diagnostics{ +// diag.Diagnostic{ +// Severity: diag.Warning, +// Summary: "Describe procedure failed.", +// Detail: fmt.Sprintf("Describe procedure failed: %v", err), +// }, +// } +// } +// for _, desc := range procedureDetails { +// switch desc.Property { +// case "signature": +// // Format in Snowflake DB is: (argName argType, argName argType, ...) +// args := strings.ReplaceAll(strings.ReplaceAll(desc.Value, "(", ""), ")", "") +// +// if args != "" { // Do nothing for functions without arguments +// argPairs := strings.Split(args, ", ") +// args := []interface{}{} +// +// for _, argPair := range argPairs { +// argItem := strings.Split(argPair, " ") +// +// arg := map[string]interface{}{} +// arg["name"] = argItem[0] +// arg["type"] = argItem[1] +// args = append(args, arg) +// } +// +// if err := d.Set("arguments", args); err != nil { +// return diag.FromErr(err) +// } +// } +// case "null handling": +// if err := d.Set("null_input_behavior", desc.Value); err != nil { +// return diag.FromErr(err) +// } +// case "body": +// if err := d.Set("statement", desc.Value); err != nil { +// return diag.FromErr(err) +// } +// case "execute as": +// if err := d.Set("execute_as", desc.Value); err != nil { +// return diag.FromErr(err) +// } +// case "returns": +// if err := d.Set("return_type", desc.Value); err != nil { +// return diag.FromErr(err) +// } +// case "language": +// if err := d.Set("language", desc.Value); err != nil { +// return diag.FromErr(err) +// } +// case "runtime_version": +// if err := d.Set("runtime_version", desc.Value); err != nil { +// return diag.FromErr(err) +// } +// case "packages": +// packagesString := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(desc.Value, "[", ""), "]", ""), "'", "") +// if packagesString != "" { // Do nothing for Java / Python functions without packages +// packages := strings.Split(packagesString, ",") +// if err := d.Set("packages", packages); err != nil { +// return diag.FromErr(err) +// } +// } +// case "imports": +// importsString := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(desc.Value, "[", ""), "]", ""), "'", ""), " ", "") +// if importsString != "" { // Do nothing for Java functions without imports +// imports := strings.Split(importsString, ",") +// if err := d.Set("imports", imports); err != nil { +// return diag.FromErr(err) +// } +// } +// case "handler": +// if err := d.Set("handler", desc.Value); err != nil { +// return diag.FromErr(err) +// } +// case "volatility": +// if err := d.Set("return_behavior", desc.Value); err != nil { +// return diag.FromErr(err) +// } +// default: +// log.Printf("[INFO] Unexpected procedure property %v returned from Snowflake with value %v", desc.Property, desc.Value) +// } +// } +// +// request := sdk.NewShowProcedureRequest().WithIn(&sdk.In{Schema: sdk.NewDatabaseObjectIdentifier(id.DatabaseName(), id.SchemaName())}).WithLike(&sdk.Like{Pattern: sdk.String(id.Name())}) +// +// procedures, err := client.Procedures.Show(ctx, request) +// if err != nil { +// return diag.FromErr(err) +// } +// // procedure names can be overloaded with different argument types so we iterate over and find the correct one +// // the ShowByID function should probably be updated to also require the list of arg types, like describe procedure +// for _, procedure := range procedures { +// argumentSignature := strings.Split(procedure.Arguments, " RETURN ")[0] +// argumentSignature = strings.ReplaceAll(argumentSignature, " ", "") +// if argumentSignature == id.ArgumentsSignature() { +// if err := d.Set("secure", procedure.IsSecure); err != nil { +// return diag.FromErr(err) +// } +// if err := d.Set("comment", procedure.Description); err != nil { +// return diag.FromErr(err) +// } +// } +// } +// +// return diags +//} +// +//func UpdateContextProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// client := meta.(*provider.Context).Client +// +// id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) +// if d.HasChange("name") { +// newId := sdk.NewSchemaObjectIdentifierWithArguments(id.DatabaseName(), id.SchemaName(), d.Get("name").(string), id.Arguments()) +// +// err := client.Procedures.Alter(ctx, sdk.NewAlterProcedureRequest(id.WithoutArguments(), id.Arguments()).WithRenameTo(sdk.Pointer(newId.WithoutArguments()))) +// if err != nil { +// return diag.FromErr(err) +// } +// +// d.SetId(newId.FullyQualifiedName()) +// id = newId +// } +// +// if d.HasChange("comment") { +// comment := d.Get("comment") +// if comment != "" { +// if err := client.Procedures.Alter(ctx, sdk.NewAlterProcedureRequest(id.WithoutArguments(), id.Arguments()).WithSetComment(sdk.String(comment.(string)))); err != nil { +// return diag.FromErr(err) +// } +// } else { +// if err := client.Procedures.Alter(ctx, sdk.NewAlterProcedureRequest(id.WithoutArguments(), id.Arguments()).WithUnsetComment(sdk.Bool(true))); err != nil { +// return diag.FromErr(err) +// } +// } +// } +// +// if d.HasChange("execute_as") { +// req := sdk.NewAlterProcedureRequest(id.WithoutArguments(), id.Arguments()) +// executeAs := d.Get("execute_as").(string) +// if strings.ToUpper(executeAs) == "OWNER" { +// req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsOwner)) +// } else if strings.ToUpper(executeAs) == "CALLER" { +// req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsCaller)) +// } +// if err := client.Procedures.Alter(ctx, req); err != nil { +// return diag.FromErr(err) +// } +// } +// +// return ReadContextProcedure(ctx, d, meta) +//} +// +//func DeleteContextProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// client := meta.(*provider.Context).Client +// +// id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) +// if err := client.Procedures.Drop(ctx, sdk.NewDropProcedureRequest(id.WithoutArguments(), id.Arguments())); err != nil { +// return diag.FromErr(err) +// } +// d.SetId("") +// return nil +//} +// +//func getProcedureArguments(d *schema.ResourceData) ([]sdk.ProcedureArgumentRequest, diag.Diagnostics) { +// args := make([]sdk.ProcedureArgumentRequest, 0) +// if v, ok := d.GetOk("arguments"); ok { +// for _, arg := range v.([]interface{}) { +// argName := arg.(map[string]interface{})["name"].(string) +// argType := arg.(map[string]interface{})["type"].(string) +// argDataType, diags := convertProcedureDataType(argType) +// if diags != nil { +// return nil, diags +// } +// args = append(args, sdk.ProcedureArgumentRequest{ArgName: argName, ArgDataType: argDataType}) +// } +// } +// return args, nil +//} +// +//func convertProcedureDataType(s string) (sdk.DataType, diag.Diagnostics) { +// dataType, err := sdk.ToDataType(s) +// if err != nil { +// return dataType, diag.FromErr(err) +// } +// return dataType, nil +//} +// +//func convertProcedureColumns(s string) ([]sdk.ProcedureColumn, diag.Diagnostics) { +// pattern := regexp.MustCompile(`(\w+)\s+(\w+)`) +// matches := pattern.FindAllStringSubmatch(s, -1) +// var columns []sdk.ProcedureColumn +// for _, match := range matches { +// if len(match) == 3 { +// dataType, err := sdk.ToDataType(match[2]) +// if err != nil { +// return nil, diag.FromErr(err) +// } +// columns = append(columns, sdk.ProcedureColumn{ +// ColumnName: match[1], +// ColumnDataType: dataType, +// }) +// } +// } +// return columns, nil +//} +// +//func parseProcedureReturnsRequest(s string) (*sdk.ProcedureReturnsRequest, diag.Diagnostics) { +// returns := sdk.NewProcedureReturnsRequest() +// if strings.HasPrefix(strings.ToLower(s), "table") { +// columns, diags := convertProcedureColumns(s) +// if diags != nil { +// return nil, diags +// } +// var cr []sdk.ProcedureColumnRequest +// for _, item := range columns { +// cr = append(cr, *sdk.NewProcedureColumnRequest(item.ColumnName, item.ColumnDataType)) +// } +// returns.WithTable(sdk.NewProcedureReturnsTableRequest().WithColumns(cr)) +// } else { +// returnDataType, diags := convertProcedureDataType(s) +// if diags != nil { +// return nil, diags +// } +// returns.WithResultDataType(sdk.NewProcedureReturnsResultDataTypeRequest(returnDataType)) +// } +// return returns, nil +//} +// +//func parseProcedureSQLReturnsRequest(s string) (*sdk.ProcedureSQLReturnsRequest, diag.Diagnostics) { +// returns := sdk.NewProcedureSQLReturnsRequest() +// if strings.HasPrefix(strings.ToLower(s), "table") { +// columns, diags := convertProcedureColumns(s) +// if diags != nil { +// return nil, diags +// } +// var cr []sdk.ProcedureColumnRequest +// for _, item := range columns { +// cr = append(cr, *sdk.NewProcedureColumnRequest(item.ColumnName, item.ColumnDataType)) +// } +// returns.WithTable(sdk.NewProcedureReturnsTableRequest().WithColumns(cr)) +// } else { +// returnDataType, diags := convertProcedureDataType(s) +// if diags != nil { +// return nil, diags +// } +// returns.WithResultDataType(sdk.NewProcedureReturnsResultDataTypeRequest(returnDataType)) +// } +// return returns, nil +//} diff --git a/pkg/resources/procedure_state_upgraders.go b/pkg/resources/procedure_state_upgraders.go index d162256dfe..1629ac2c35 100644 --- a/pkg/resources/procedure_state_upgraders.go +++ b/pkg/resources/procedure_state_upgraders.go @@ -1,62 +1,54 @@ package resources -import ( - "context" - "fmt" - "strings" - - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" -) - -type v085ProcedureId struct { - DatabaseName string - SchemaName string - ProcedureName string - ArgTypes []string -} - -func parseV085ProcedureId(v string) (*v085ProcedureId, error) { - arr := strings.Split(v, "|") - if len(arr) != 4 { - return nil, sdk.NewError(fmt.Sprintf("ID %v is invalid", v)) - } - - // this is a bit different from V085 state, but it was buggy - var args []string - if arr[3] != "" { - args = strings.Split(arr[3], "-") - } - - return &v085ProcedureId{ - DatabaseName: arr[0], - SchemaName: arr[1], - ProcedureName: arr[2], - ArgTypes: args, - }, nil -} - -func v085ProcedureStateUpgrader(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { - if rawState == nil { - return rawState, nil - } - - oldId := rawState["id"].(string) - parsedV085ProcedureId, err := parseV085ProcedureId(oldId) - if err != nil { - return nil, err - } - - argDataTypes := make([]sdk.DataType, len(parsedV085ProcedureId.ArgTypes)) - for i, argType := range parsedV085ProcedureId.ArgTypes { - argDataType, err := sdk.ToDataType(argType) - if err != nil { - return nil, err - } - argDataTypes[i] = argDataType - } - - schemaObjectIdentifierWithArguments := sdk.NewSchemaObjectIdentifierWithArguments(parsedV085ProcedureId.DatabaseName, parsedV085ProcedureId.SchemaName, parsedV085ProcedureId.ProcedureName, argDataTypes) - rawState["id"] = schemaObjectIdentifierWithArguments.FullyQualifiedName() - - return rawState, nil -} +//type v085ProcedureId struct { +// DatabaseName string +// SchemaName string +// ProcedureName string +// ArgTypes []string +//} +// +//func parseV085ProcedureId(v string) (*v085ProcedureId, error) { +// arr := strings.Split(v, "|") +// if len(arr) != 4 { +// return nil, sdk.NewError(fmt.Sprintf("ID %v is invalid", v)) +// } +// +// // this is a bit different from V085 state, but it was buggy +// var args []string +// if arr[3] != "" { +// args = strings.Split(arr[3], "-") +// } +// +// return &v085ProcedureId{ +// DatabaseName: arr[0], +// SchemaName: arr[1], +// ProcedureName: arr[2], +// ArgTypes: args, +// }, nil +//} +// +//func v085ProcedureStateUpgrader(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { +// if rawState == nil { +// return rawState, nil +// } +// +// oldId := rawState["id"].(string) +// parsedV085ProcedureId, err := parseV085ProcedureId(oldId) +// if err != nil { +// return nil, err +// } +// +// argDataTypes := make([]sdk.DataType, len(parsedV085ProcedureId.ArgTypes)) +// for i, argType := range parsedV085ProcedureId.ArgTypes { +// argDataType, err := sdk.ToDataType(argType) +// if err != nil { +// return nil, err +// } +// argDataTypes[i] = argDataType +// } +// +// schemaObjectIdentifierWithArguments := sdk.NewSchemaObjectIdentifierWithArguments(parsedV085ProcedureId.DatabaseName, parsedV085ProcedureId.SchemaName, parsedV085ProcedureId.ProcedureName, argDataTypes) +// rawState["id"] = schemaObjectIdentifierWithArguments.FullyQualifiedName() +// +// return rawState, nil +//} diff --git a/pkg/sdk/client.go b/pkg/sdk/client.go index dd92fafd95..fe50185a18 100644 --- a/pkg/sdk/client.go +++ b/pkg/sdk/client.go @@ -39,23 +39,23 @@ type Client struct { ReplicationFunctions ReplicationFunctions // DDL Commands - Accounts Accounts - Alerts Alerts - ApiIntegrations ApiIntegrations - ApplicationPackages ApplicationPackages - ApplicationRoles ApplicationRoles - Applications Applications - Comments Comments - CortexSearchServices CortexSearchServices - DatabaseRoles DatabaseRoles - Databases Databases - DynamicTables DynamicTables - ExternalFunctions ExternalFunctions - ExternalTables ExternalTables - EventTables EventTables - FailoverGroups FailoverGroups - FileFormats FileFormats - //Functions Functions + Accounts Accounts + Alerts Alerts + ApiIntegrations ApiIntegrations + ApplicationPackages ApplicationPackages + ApplicationRoles ApplicationRoles + Applications Applications + Comments Comments + CortexSearchServices CortexSearchServices + DatabaseRoles DatabaseRoles + Databases Databases + DynamicTables DynamicTables + ExternalFunctions ExternalFunctions + ExternalTables ExternalTables + EventTables EventTables + FailoverGroups FailoverGroups + FileFormats FileFormats + Functions Functions Grants Grants ManagedAccounts ManagedAccounts MaskingPolicies MaskingPolicies @@ -211,7 +211,7 @@ func (c *Client) initialize() { c.EventTables = &eventTables{client: c} c.FailoverGroups = &failoverGroups{client: c} c.FileFormats = &fileFormats{client: c} - //c.Functions = &functions{client: c} + c.Functions = &functions{client: c} c.Grants = &grants{client: c} c.ManagedAccounts = &managedAccounts{client: c} c.MaskingPolicies = &maskingPolicies{client: c} diff --git a/pkg/sdk/external_functions_impl_gen.go b/pkg/sdk/external_functions_impl_gen.go index ad23a480c6..54b282e8c6 100644 --- a/pkg/sdk/external_functions_impl_gen.go +++ b/pkg/sdk/external_functions_impl_gen.go @@ -73,7 +73,7 @@ func (r *CreateExternalFunctionRequest) toOpts() *CreateExternalFunctionOptions OrReplace: r.OrReplace, Secure: r.Secure, // TODO: - //name: r.name.WithoutArguments(), + // name: r.name.WithoutArguments(), ResultDataType: r.ResultDataType, ReturnNullValues: r.ReturnNullValues, @@ -116,7 +116,7 @@ func (r *AlterExternalFunctionRequest) toOpts() *AlterExternalFunctionOptions { opts := &AlterExternalFunctionOptions{ IfExists: r.IfExists, // TODO: - //name: r.name.WithoutArguments(), + // name: r.name.WithoutArguments(), ArgumentDataTypes: r.ArgumentDataTypes, } if r.Set != nil { diff --git a/pkg/sdk/functions_dto_builders_gen.go b/pkg/sdk/functions_dto_builders_gen.go index ccb0f21602..95747cbc5d 100644 --- a/pkg/sdk/functions_dto_builders_gen.go +++ b/pkg/sdk/functions_dto_builders_gen.go @@ -467,11 +467,9 @@ func (s *CreateForSQLFunctionRequest) WithComment(Comment string) *CreateForSQLF func NewAlterFunctionRequest( name SchemaObjectIdentifierWithArguments, - ArgumentDataTypes []DataType, ) *AlterFunctionRequest { s := AlterFunctionRequest{} s.name = name - s.ArgumentDataTypes = ArgumentDataTypes return &s } @@ -537,11 +535,9 @@ func (s *AlterFunctionRequest) WithUnsetTags(UnsetTags []ObjectIdentifier) *Alte func NewDropFunctionRequest( name SchemaObjectIdentifierWithArguments, - ArgumentDataTypes []DataType, ) *DropFunctionRequest { s := DropFunctionRequest{} s.name = name - s.ArgumentDataTypes = ArgumentDataTypes return &s } @@ -566,10 +562,8 @@ func (s *ShowFunctionRequest) WithIn(In In) *ShowFunctionRequest { func NewDescribeFunctionRequest( name SchemaObjectIdentifierWithArguments, - ArgumentDataTypes []DataType, ) *DescribeFunctionRequest { s := DescribeFunctionRequest{} s.name = name - s.ArgumentDataTypes = ArgumentDataTypes return &s } diff --git a/pkg/sdk/functions_dto_gen.go b/pkg/sdk/functions_dto_gen.go index 02423bf3e2..7942153411 100644 --- a/pkg/sdk/functions_dto_gen.go +++ b/pkg/sdk/functions_dto_gen.go @@ -143,26 +143,24 @@ type CreateForSQLFunctionRequest struct { } type AlterFunctionRequest struct { - IfExists *bool - name SchemaObjectIdentifierWithArguments // required - ArgumentDataTypes []DataType // required - RenameTo *SchemaObjectIdentifier - SetComment *string - SetLogLevel *string - SetTraceLevel *string - SetSecure *bool - UnsetSecure *bool - UnsetLogLevel *bool - UnsetTraceLevel *bool - UnsetComment *bool - SetTags []TagAssociation - UnsetTags []ObjectIdentifier + IfExists *bool + name SchemaObjectIdentifierWithArguments // required + RenameTo *SchemaObjectIdentifier + SetComment *string + SetLogLevel *string + SetTraceLevel *string + SetSecure *bool + UnsetSecure *bool + UnsetLogLevel *bool + UnsetTraceLevel *bool + UnsetComment *bool + SetTags []TagAssociation + UnsetTags []ObjectIdentifier } type DropFunctionRequest struct { - IfExists *bool - name SchemaObjectIdentifierWithArguments // required - ArgumentDataTypes []DataType // required + IfExists *bool + name SchemaObjectIdentifierWithArguments // required } type ShowFunctionRequest struct { @@ -171,6 +169,5 @@ type ShowFunctionRequest struct { } type DescribeFunctionRequest struct { - name SchemaObjectIdentifierWithArguments // required - ArgumentDataTypes []DataType // required + name SchemaObjectIdentifierWithArguments // required } diff --git a/pkg/sdk/functions_gen.go b/pkg/sdk/functions_gen.go index d2c6066699..1b33d5be26 100644 --- a/pkg/sdk/functions_gen.go +++ b/pkg/sdk/functions_gen.go @@ -167,31 +167,29 @@ type CreateForSQLFunctionOptions struct { // AlterFunctionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/alter-function. type AlterFunctionOptions struct { - alter bool `ddl:"static" sql:"ALTER"` - function bool `ddl:"static" sql:"FUNCTION"` - IfExists *bool `ddl:"keyword" sql:"IF EXISTS"` - name SchemaObjectIdentifierWithArguments `ddl:"identifier"` - ArgumentDataTypes []DataType `ddl:"keyword,must_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"` - SetSecure *bool `ddl:"keyword" sql:"SET SECURE"` - UnsetSecure *bool `ddl:"keyword" sql:"UNSET SECURE"` - UnsetLogLevel *bool `ddl:"keyword" sql:"UNSET LOG_LEVEL"` - UnsetTraceLevel *bool `ddl:"keyword" sql:"UNSET TRACE_LEVEL"` - UnsetComment *bool `ddl:"keyword" sql:"UNSET COMMENT"` - SetTags []TagAssociation `ddl:"keyword" sql:"SET TAG"` - UnsetTags []ObjectIdentifier `ddl:"keyword" sql:"UNSET TAG"` + alter bool `ddl:"static" sql:"ALTER"` + function bool `ddl:"static" sql:"FUNCTION"` + IfExists *bool `ddl:"keyword" sql:"IF EXISTS"` + name SchemaObjectIdentifierWithArguments `ddl:"identifier"` + 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"` + SetSecure *bool `ddl:"keyword" sql:"SET SECURE"` + UnsetSecure *bool `ddl:"keyword" sql:"UNSET SECURE"` + UnsetLogLevel *bool `ddl:"keyword" sql:"UNSET LOG_LEVEL"` + UnsetTraceLevel *bool `ddl:"keyword" sql:"UNSET TRACE_LEVEL"` + UnsetComment *bool `ddl:"keyword" sql:"UNSET COMMENT"` + SetTags []TagAssociation `ddl:"keyword" sql:"SET TAG"` + UnsetTags []ObjectIdentifier `ddl:"keyword" sql:"UNSET TAG"` } // DropFunctionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/drop-function. type DropFunctionOptions struct { - drop bool `ddl:"static" sql:"DROP"` - function bool `ddl:"static" sql:"FUNCTION"` - IfExists *bool `ddl:"keyword" sql:"IF EXISTS"` - name SchemaObjectIdentifierWithArguments `ddl:"identifier"` - ArgumentDataTypes []DataType `ddl:"keyword,must_parentheses"` + drop bool `ddl:"static" sql:"DROP"` + function bool `ddl:"static" sql:"FUNCTION"` + IfExists *bool `ddl:"keyword" sql:"IF EXISTS"` + name SchemaObjectIdentifierWithArguments `ddl:"identifier"` } // ShowFunctionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/show-user-functions. @@ -243,17 +241,16 @@ type Function struct { } func (v *Function) ID() SchemaObjectIdentifierWithArguments { - //return NewSchemaObjectIdentifier(v.CatalogName, v.SchemaName, v.Name) + // return NewSchemaObjectIdentifier(v.CatalogName, v.SchemaName, v.Name) // TODO: return SchemaObjectIdentifierWithArguments{} } // DescribeFunctionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/desc-function. type DescribeFunctionOptions struct { - describe bool `ddl:"static" sql:"DESCRIBE"` - function bool `ddl:"static" sql:"FUNCTION"` - name SchemaObjectIdentifierWithArguments `ddl:"identifier"` - ArgumentDataTypes []DataType `ddl:"keyword,must_parentheses"` + describe bool `ddl:"static" sql:"DESCRIBE"` + function bool `ddl:"static" sql:"FUNCTION"` + name SchemaObjectIdentifierWithArguments `ddl:"identifier"` } type functionDetailRow struct { diff --git a/pkg/sdk/functions_gen_test.go b/pkg/sdk/functions_gen_test.go index d32046fc74..06725a9a6e 100644 --- a/pkg/sdk/functions_gen_test.go +++ b/pkg/sdk/functions_gen_test.go @@ -431,7 +431,8 @@ func TestFunctions_CreateForSQL(t *testing.T) { } func TestFunctions_Drop(t *testing.T) { - id := randomSchemaObjectIdentifierWithArguments() + noArgsId := randomSchemaObjectIdentifierWithArguments() + id := randomSchemaObjectIdentifierWithArguments(DataTypeVARCHAR, DataTypeNumber) defaultOpts := func() *DropFunctionOptions { return &DropFunctionOptions{ @@ -452,7 +453,8 @@ func TestFunctions_Drop(t *testing.T) { t.Run("no arguments", func(t *testing.T) { opts := defaultOpts() - assertOptsValidAndSQLEquals(t, opts, `DROP FUNCTION %s ()`, id.FullyQualifiedName()) + opts.name = noArgsId + assertOptsValidAndSQLEquals(t, opts, `DROP FUNCTION %s`, noArgsId.FullyQualifiedName()) }) t.Run("all options", func(t *testing.T) { @@ -460,19 +462,18 @@ func TestFunctions_Drop(t *testing.T) { name: id, } opts.IfExists = Bool(true) - opts.ArgumentDataTypes = []DataType{DataTypeVARCHAR, DataTypeNumber} - assertOptsValidAndSQLEquals(t, opts, `DROP FUNCTION IF EXISTS %s (VARCHAR, NUMBER)`, id.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, opts, `DROP FUNCTION IF EXISTS %s`, id.FullyQualifiedName()) }) } func TestFunctions_Alter(t *testing.T) { - id := randomSchemaObjectIdentifierWithArguments() + id := randomSchemaObjectIdentifierWithArguments(DataTypeVARCHAR, DataTypeNumber) + noArgsId := randomSchemaObjectIdentifierWithArguments() defaultOpts := func() *AlterFunctionOptions { return &AlterFunctionOptions{ - name: id, - IfExists: Bool(true), - ArgumentDataTypes: []DataType{DataTypeVARCHAR, DataTypeNumber}, + name: id, + IfExists: Bool(true), } } @@ -503,62 +504,62 @@ func TestFunctions_Alter(t *testing.T) { opts := defaultOpts() target := randomSchemaObjectIdentifierInSchema(id.SchemaId()) opts.RenameTo = &target - assertOptsValidAndSQLEquals(t, opts, `ALTER FUNCTION IF EXISTS %s (VARCHAR, NUMBER) RENAME TO %s`, id.FullyQualifiedName(), opts.RenameTo.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, opts, `ALTER FUNCTION IF EXISTS %s RENAME TO %s`, id.FullyQualifiedName(), opts.RenameTo.FullyQualifiedName()) }) t.Run("alter: set log level with no arguments", func(t *testing.T) { opts := defaultOpts() - opts.ArgumentDataTypes = nil + opts.name = noArgsId opts.SetLogLevel = String("DEBUG") - assertOptsValidAndSQLEquals(t, opts, `ALTER FUNCTION IF EXISTS %s () SET LOG_LEVEL = 'DEBUG'`, id.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, opts, `ALTER FUNCTION IF EXISTS %s SET LOG_LEVEL = 'DEBUG'`, noArgsId.FullyQualifiedName()) }) t.Run("alter: set log level", func(t *testing.T) { opts := defaultOpts() opts.SetLogLevel = String("DEBUG") - assertOptsValidAndSQLEquals(t, opts, `ALTER FUNCTION IF EXISTS %s (VARCHAR, NUMBER) SET LOG_LEVEL = 'DEBUG'`, id.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, opts, `ALTER FUNCTION IF EXISTS %s 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 FUNCTION IF EXISTS %s (VARCHAR, NUMBER) SET TRACE_LEVEL = 'DEBUG'`, id.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, opts, `ALTER FUNCTION IF EXISTS %s 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 FUNCTION IF EXISTS %s (VARCHAR, NUMBER) SET COMMENT = 'comment'`, id.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, opts, `ALTER FUNCTION IF EXISTS %s SET COMMENT = 'comment'`, id.FullyQualifiedName()) }) t.Run("alter: set secure", func(t *testing.T) { opts := defaultOpts() opts.SetSecure = Bool(true) - assertOptsValidAndSQLEquals(t, opts, `ALTER FUNCTION IF EXISTS %s (VARCHAR, NUMBER) SET SECURE`, id.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, opts, `ALTER FUNCTION IF EXISTS %s SET SECURE`, id.FullyQualifiedName()) }) t.Run("alter: unset log level", func(t *testing.T) { opts := defaultOpts() opts.UnsetLogLevel = Bool(true) - assertOptsValidAndSQLEquals(t, opts, `ALTER FUNCTION IF EXISTS %s (VARCHAR, NUMBER) UNSET LOG_LEVEL`, id.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, opts, `ALTER FUNCTION IF EXISTS %s UNSET LOG_LEVEL`, id.FullyQualifiedName()) }) t.Run("alter: unset trace level", func(t *testing.T) { opts := defaultOpts() opts.UnsetTraceLevel = Bool(true) - assertOptsValidAndSQLEquals(t, opts, `ALTER FUNCTION IF EXISTS %s (VARCHAR, NUMBER) UNSET TRACE_LEVEL`, id.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, opts, `ALTER FUNCTION IF EXISTS %s UNSET TRACE_LEVEL`, id.FullyQualifiedName()) }) t.Run("alter: unset secure", func(t *testing.T) { opts := defaultOpts() opts.UnsetSecure = Bool(true) - assertOptsValidAndSQLEquals(t, opts, `ALTER FUNCTION IF EXISTS %s (VARCHAR, NUMBER) UNSET SECURE`, id.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, opts, `ALTER FUNCTION IF EXISTS %s UNSET SECURE`, id.FullyQualifiedName()) }) t.Run("alter: unset comment", func(t *testing.T) { opts := defaultOpts() opts.UnsetComment = Bool(true) - assertOptsValidAndSQLEquals(t, opts, `ALTER FUNCTION IF EXISTS %s (VARCHAR, NUMBER) UNSET COMMENT`, id.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, opts, `ALTER FUNCTION IF EXISTS %s UNSET COMMENT`, id.FullyQualifiedName()) }) t.Run("alter: set tags", func(t *testing.T) { @@ -569,7 +570,7 @@ func TestFunctions_Alter(t *testing.T) { Value: "value1", }, } - assertOptsValidAndSQLEquals(t, opts, `ALTER FUNCTION IF EXISTS %s (VARCHAR, NUMBER) SET TAG "tag1" = 'value1'`, id.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, opts, `ALTER FUNCTION IF EXISTS %s SET TAG "tag1" = 'value1'`, id.FullyQualifiedName()) }) t.Run("alter: unset tags", func(t *testing.T) { @@ -578,7 +579,7 @@ func TestFunctions_Alter(t *testing.T) { NewAccountObjectIdentifier("tag1"), NewAccountObjectIdentifier("tag2"), } - assertOptsValidAndSQLEquals(t, opts, `ALTER FUNCTION IF EXISTS %s (VARCHAR, NUMBER) UNSET TAG "tag1", "tag2"`, id.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, opts, `ALTER FUNCTION IF EXISTS %s UNSET TAG "tag1", "tag2"`, id.FullyQualifiedName()) }) } @@ -615,7 +616,8 @@ func TestFunctions_Show(t *testing.T) { } func TestFunctions_Describe(t *testing.T) { - id := randomSchemaObjectIdentifierWithArguments() + id := randomSchemaObjectIdentifierWithArguments(DataTypeVARCHAR, DataTypeNumber) + noArgsId := randomSchemaObjectIdentifierWithArguments() defaultOpts := func() *DescribeFunctionOptions { return &DescribeFunctionOptions{ @@ -636,12 +638,12 @@ func TestFunctions_Describe(t *testing.T) { t.Run("no arguments", func(t *testing.T) { opts := defaultOpts() - assertOptsValidAndSQLEquals(t, opts, `DESCRIBE FUNCTION %s ()`, id.FullyQualifiedName()) + opts.name = noArgsId + assertOptsValidAndSQLEquals(t, opts, `DESCRIBE FUNCTION %s`, noArgsId.FullyQualifiedName()) }) t.Run("all options", func(t *testing.T) { opts := defaultOpts() - opts.ArgumentDataTypes = []DataType{DataTypeVARCHAR, DataTypeNumber} - assertOptsValidAndSQLEquals(t, opts, `DESCRIBE FUNCTION %s (VARCHAR, NUMBER)`, id.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, opts, `DESCRIBE FUNCTION %s`, id.FullyQualifiedName()) }) } diff --git a/pkg/sdk/functions_impl_gen.go b/pkg/sdk/functions_impl_gen.go index 33e268a252..03aed370fb 100644 --- a/pkg/sdk/functions_impl_gen.go +++ b/pkg/sdk/functions_impl_gen.go @@ -328,29 +328,27 @@ func (r *CreateForSQLFunctionRequest) toOpts() *CreateForSQLFunctionOptions { func (r *AlterFunctionRequest) toOpts() *AlterFunctionOptions { opts := &AlterFunctionOptions{ - IfExists: r.IfExists, - name: r.name, - ArgumentDataTypes: r.ArgumentDataTypes, - RenameTo: r.RenameTo, - SetComment: r.SetComment, - SetLogLevel: r.SetLogLevel, - SetTraceLevel: r.SetTraceLevel, - SetSecure: r.SetSecure, - UnsetSecure: r.UnsetSecure, - UnsetLogLevel: r.UnsetLogLevel, - UnsetTraceLevel: r.UnsetTraceLevel, - UnsetComment: r.UnsetComment, - SetTags: r.SetTags, - UnsetTags: r.UnsetTags, + IfExists: r.IfExists, + name: r.name, + RenameTo: r.RenameTo, + SetComment: r.SetComment, + SetLogLevel: r.SetLogLevel, + SetTraceLevel: r.SetTraceLevel, + SetSecure: r.SetSecure, + UnsetSecure: r.UnsetSecure, + UnsetLogLevel: r.UnsetLogLevel, + UnsetTraceLevel: r.UnsetTraceLevel, + UnsetComment: r.UnsetComment, + SetTags: r.SetTags, + UnsetTags: r.UnsetTags, } return opts } func (r *DropFunctionRequest) toOpts() *DropFunctionOptions { opts := &DropFunctionOptions{ - IfExists: r.IfExists, - name: r.name, - ArgumentDataTypes: r.ArgumentDataTypes, + IfExists: r.IfExists, + name: r.name, } return opts } @@ -392,8 +390,7 @@ func (r functionRow) convert() *Function { func (r *DescribeFunctionRequest) toOpts() *DescribeFunctionOptions { opts := &DescribeFunctionOptions{ - name: r.name, - ArgumentDataTypes: r.ArgumentDataTypes, + name: r.name, } return opts } diff --git a/pkg/sdk/identifier_helpers.go b/pkg/sdk/identifier_helpers.go index 920adfda2a..31400acc89 100644 --- a/pkg/sdk/identifier_helpers.go +++ b/pkg/sdk/identifier_helpers.go @@ -288,25 +288,30 @@ func (i SchemaObjectIdentifier) FullyQualifiedName() string { // - Handle in the sql_builder // - Use in function,procedure,external_function // - Function (Test, Impl) -// - Fix after arguments removed from SchemaObjectIdentifier +// - Fix after argumentDataTypes removed from SchemaObjectIdentifier // - Look for todos on SNOW-999049 +// TODO: Rename? type SchemaObjectIdentifierWithArguments struct { - databaseName string - schemaName string - name string - arguments []string + databaseName string + schemaName string + name string + argumentDataTypes []DataType } -func NewSchemaObjectIdentifierWithArguments(databaseName, schemaName, name string, arguments ...string) SchemaObjectIdentifierWithArguments { +func NewSchemaObjectIdentifierWithArguments(databaseName, schemaName, name string, argumentDataTypes ...DataType) SchemaObjectIdentifierWithArguments { return SchemaObjectIdentifierWithArguments{ - databaseName: strings.Trim(databaseName, `"`), - schemaName: strings.Trim(schemaName, `"`), - name: strings.Trim(name, `"`), - arguments: arguments, + databaseName: strings.Trim(databaseName, `"`), + schemaName: strings.Trim(schemaName, `"`), + name: strings.Trim(name, `"`), + argumentDataTypes: argumentDataTypes, } } +func NewSchemaObjectIdentifierWithArgumentsInSchema(schemaId DatabaseObjectIdentifier, name string, argumentDataTypes ...DataType) SchemaObjectIdentifierWithArguments { + return NewSchemaObjectIdentifierWithArguments(schemaId.DatabaseName(), schemaId.Name(), name, argumentDataTypes...) +} + // TODO: //func NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(fullyQualifiedName string) SchemaObjectIdentifierWithArguments { // parts := strings.Split(fullyQualifiedName, ".") @@ -331,8 +336,12 @@ func (i SchemaObjectIdentifierWithArguments) Name() string { return i.name } -func (i SchemaObjectIdentifierWithArguments) Arguments() []string { - return i.arguments +func (i SchemaObjectIdentifierWithArguments) ArgumentDataTypes() []DataType { + return i.argumentDataTypes +} + +func (i SchemaObjectIdentifierWithArguments) SchemaObjectId() SchemaObjectIdentifier { + return NewSchemaObjectIdentifier(i.databaseName, i.schemaName, i.name) } func (i SchemaObjectIdentifierWithArguments) SchemaId() DatabaseObjectIdentifier { @@ -344,10 +353,10 @@ func (i SchemaObjectIdentifierWithArguments) DatabaseId() AccountObjectIdentifie } func (i SchemaObjectIdentifierWithArguments) FullyQualifiedName() string { - if i.schemaName == "" && i.databaseName == "" && i.name == "" && len(i.arguments) == 0 { + if i.schemaName == "" && i.databaseName == "" && i.name == "" && len(i.argumentDataTypes) == 0 { return "" } - return fmt.Sprintf(`"%v"."%v"."%v"(%v)`, i.databaseName, i.schemaName, i.name, strings.Join(i.arguments, ", ")) + return fmt.Sprintf(`"%v"."%v"."%v"(%v)`, i.databaseName, i.schemaName, i.name, strings.Join(AsStringList(i.argumentDataTypes), ", ")) } type TableColumnIdentifier struct { diff --git a/pkg/sdk/poc/main.go b/pkg/sdk/poc/main.go index 09db71b74a..7b2db1d395 100644 --- a/pkg/sdk/poc/main.go +++ b/pkg/sdk/poc/main.go @@ -5,12 +5,13 @@ package main import ( "bytes" "fmt" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/genhelpers" "io" "log" "os" "strings" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/genhelpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/poc/example" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/poc/generator" diff --git a/pkg/sdk/random_test.go b/pkg/sdk/random_test.go index 8877b3625e..0f58dc3a79 100644 --- a/pkg/sdk/random_test.go +++ b/pkg/sdk/random_test.go @@ -15,8 +15,8 @@ var ( emptySchemaObjectIdentifierWithArguments = NewSchemaObjectIdentifierWithArguments("", "", "") ) -func randomSchemaObjectIdentifierWithArguments() SchemaObjectIdentifierWithArguments { - return NewSchemaObjectIdentifierWithArguments(random.StringN(12), random.StringN(12), random.StringN(12), random.StringN(12)) +func randomSchemaObjectIdentifierWithArguments(argumentDataTypes ...DataType) SchemaObjectIdentifierWithArguments { + return NewSchemaObjectIdentifierWithArguments(random.StringN(12), random.StringN(12), random.StringN(12), argumentDataTypes...) } func randomSchemaObjectIdentifier() SchemaObjectIdentifier { diff --git a/pkg/sdk/testint/external_functions_integration_test.go b/pkg/sdk/testint/external_functions_integration_test.go index 9787dd7f2e..c901219faa 100644 --- a/pkg/sdk/testint/external_functions_integration_test.go +++ b/pkg/sdk/testint/external_functions_integration_test.go @@ -1,269 +1,260 @@ package testint -import ( - "context" - "fmt" - "testing" - - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" - "github.com/stretchr/testify/require" -) - -func TestInt_ExternalFunctions(t *testing.T) { - client := testClient(t) - ctx := context.Background() - - defaultDataTypes := []sdk.DataType{sdk.DataTypeVARCHAR} - - integration, integrationCleanup := testClientHelper().ApiIntegration.CreateApiIntegration(t) - t.Cleanup(integrationCleanup) - - cleanupExternalFunctionHandle := func(id sdk.SchemaObjectIdentifier, dts []sdk.DataType) func() { - return func() { - err := client.Functions.Drop(ctx, sdk.NewDropFunctionRequest(id.WithoutArguments(), dts).WithIfExists(sdk.Bool(true))) - require.NoError(t, err) - } - } - - // TODO [SNOW-999049]: id returned on purpose; address during identifiers rework - createExternalFunction := func(t *testing.T) (*sdk.ExternalFunction, sdk.SchemaObjectIdentifier) { - t.Helper() - id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(defaultDataTypes) - argument := sdk.NewExternalFunctionArgumentRequest("x", defaultDataTypes[0]) - as := "https://xyz.execute-api.us-west-2.amazonaws.com/production/remote_echo" - request := sdk.NewCreateExternalFunctionRequest(id, sdk.DataTypeVariant, sdk.Pointer(integration.ID()), as). - WithOrReplace(sdk.Bool(true)). - WithSecure(sdk.Bool(true)). - WithArguments([]sdk.ExternalFunctionArgumentRequest{*argument}) - err := client.ExternalFunctions.Create(ctx, request) - require.NoError(t, err) - t.Cleanup(cleanupExternalFunctionHandle(id.WithoutArguments(), []sdk.DataType{sdk.DataTypeVariant})) - - e, err := client.ExternalFunctions.ShowByID(ctx, id) - require.NoError(t, err) - return e, id - } - - assertExternalFunction := func(t *testing.T, id sdk.SchemaObjectIdentifier, secure bool) { - t.Helper() - dts := id.Arguments() - - e, err := client.ExternalFunctions.ShowByID(ctx, id) - require.NoError(t, err) - - require.NotEmpty(t, e.CreatedOn) - require.Equal(t, id.Name(), e.Name) - require.Equal(t, fmt.Sprintf(`"%v"`, id.SchemaName()), e.SchemaName) - require.Equal(t, false, e.IsBuiltin) - require.Equal(t, false, e.IsAggregate) - require.Equal(t, false, e.IsAnsi) - if len(dts) > 0 { - require.Equal(t, 1, e.MinNumArguments) - require.Equal(t, 1, e.MaxNumArguments) - } else { - require.Equal(t, 0, e.MinNumArguments) - require.Equal(t, 0, e.MaxNumArguments) - } - require.NotEmpty(t, e.Arguments) - require.NotEmpty(t, e.Description) - require.NotEmpty(t, e.CatalogName) - require.Equal(t, false, e.IsTableFunction) - require.Equal(t, false, e.ValidForClustering) - require.Equal(t, secure, e.IsSecure) - require.Equal(t, true, e.IsExternalFunction) - require.Equal(t, "EXTERNAL", e.Language) - require.Equal(t, false, e.IsMemoizable) - require.Equal(t, false, e.IsDataMetric) - } - - t.Run("create external function", func(t *testing.T) { - id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(defaultDataTypes) - argument := sdk.NewExternalFunctionArgumentRequest("x", sdk.DataTypeVARCHAR) - headers := []sdk.ExternalFunctionHeaderRequest{ - { - Name: "measure", - Value: "kilometers", - }, - } - ch := []sdk.ExternalFunctionContextHeaderRequest{ - { - ContextFunction: "CURRENT_DATE", - }, - { - ContextFunction: "CURRENT_TIMESTAMP", - }, - } - as := "https://xyz.execute-api.us-west-2.amazonaws.com/production/remote_echo" - request := sdk.NewCreateExternalFunctionRequest(id, sdk.DataTypeVariant, sdk.Pointer(integration.ID()), as). - WithOrReplace(sdk.Bool(true)). - WithSecure(sdk.Bool(true)). - WithArguments([]sdk.ExternalFunctionArgumentRequest{*argument}). - WithNullInputBehavior(sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorCalledOnNullInput)). - WithHeaders(headers). - WithContextHeaders(ch). - WithMaxBatchRows(sdk.Int(10)). - WithCompression(sdk.String("GZIP")) - err := client.ExternalFunctions.Create(ctx, request) - require.NoError(t, err) - t.Cleanup(cleanupExternalFunctionHandle(id.WithoutArguments(), []sdk.DataType{sdk.DataTypeVariant})) - - assertExternalFunction(t, id, true) - }) - - t.Run("create external function without arguments", func(t *testing.T) { - id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(nil) - as := "https://xyz.execute-api.us-west-2.amazonaws.com/production/remote_echo" - request := sdk.NewCreateExternalFunctionRequest(id, sdk.DataTypeVariant, sdk.Pointer(integration.ID()), as) - err := client.ExternalFunctions.Create(ctx, request) - require.NoError(t, err) - t.Cleanup(cleanupExternalFunctionHandle(id, nil)) - - assertExternalFunction(t, id, false) - }) - - t.Run("alter external function: set api integration", func(t *testing.T) { - _, id := createExternalFunction(t) - set := sdk.NewExternalFunctionSetRequest(). - WithApiIntegration(sdk.Pointer(integration.ID())) - request := sdk.NewAlterExternalFunctionRequest(id, defaultDataTypes).WithSet(set) - err := client.ExternalFunctions.Alter(ctx, request) - require.NoError(t, err) - - assertExternalFunction(t, id, true) - }) - - t.Run("alter external function: set headers", func(t *testing.T) { - _, id := createExternalFunction(t) - - headers := []sdk.ExternalFunctionHeaderRequest{ - { - Name: "measure", - Value: "kilometers", - }, - } - set := sdk.NewExternalFunctionSetRequest().WithHeaders(headers) - request := sdk.NewAlterExternalFunctionRequest(id, defaultDataTypes).WithSet(set) - err := client.ExternalFunctions.Alter(ctx, request) - require.NoError(t, err) - assertExternalFunction(t, id, true) - }) - - t.Run("alter external function: set context headers", func(t *testing.T) { - _, id := createExternalFunction(t) - - ch := []sdk.ExternalFunctionContextHeaderRequest{ - { - ContextFunction: "CURRENT_DATE", - }, - { - ContextFunction: "CURRENT_TIMESTAMP", - }, - } - set := sdk.NewExternalFunctionSetRequest().WithContextHeaders(ch) - request := sdk.NewAlterExternalFunctionRequest(id, defaultDataTypes).WithSet(set) - err := client.ExternalFunctions.Alter(ctx, request) - require.NoError(t, err) - assertExternalFunction(t, id, true) - }) - - t.Run("alter external function: set compression", func(t *testing.T) { - _, id := createExternalFunction(t) - - set := sdk.NewExternalFunctionSetRequest().WithCompression(sdk.String("AUTO")) - request := sdk.NewAlterExternalFunctionRequest(id, defaultDataTypes).WithSet(set) - err := client.ExternalFunctions.Alter(ctx, request) - require.NoError(t, err) - assertExternalFunction(t, id, true) - }) - - t.Run("alter external function: set max batch rows", func(t *testing.T) { - _, id := createExternalFunction(t) - - set := sdk.NewExternalFunctionSetRequest().WithMaxBatchRows(sdk.Int(20)) - request := sdk.NewAlterExternalFunctionRequest(id, defaultDataTypes).WithSet(set) - err := client.ExternalFunctions.Alter(ctx, request) - require.NoError(t, err) - assertExternalFunction(t, id, true) - }) - - t.Run("alter external function: unset", func(t *testing.T) { - _, id := createExternalFunction(t) - - unset := sdk.NewExternalFunctionUnsetRequest(). - WithComment(sdk.Bool(true)). - WithHeaders(sdk.Bool(true)) - request := sdk.NewAlterExternalFunctionRequest(id, defaultDataTypes).WithUnset(unset) - err := client.ExternalFunctions.Alter(ctx, request) - require.NoError(t, err) - - assertExternalFunction(t, id, true) - }) - - t.Run("show external function: with like", func(t *testing.T) { - e1, _ := createExternalFunction(t) - e2, _ := createExternalFunction(t) - - es, err := client.ExternalFunctions.Show(ctx, sdk.NewShowExternalFunctionRequest().WithLike(&sdk.Like{Pattern: sdk.String(e1.Name)})) - require.NoError(t, err) - - require.Equal(t, 1, len(es)) - require.Contains(t, es, *e1) - require.NotContains(t, es, *e2) - }) - - t.Run("show external function: with in", func(t *testing.T) { - otherDb, otherDbCleanup := testClientHelper().Database.CreateDatabase(t) - t.Cleanup(otherDbCleanup) - - e1, _ := createExternalFunction(t) - - es, err := client.ExternalFunctions.Show(ctx, sdk.NewShowExternalFunctionRequest().WithIn(&sdk.In{Schema: e1.ID().SchemaId()})) - require.NoError(t, err) - - require.Contains(t, es, *e1) - - es, err = client.ExternalFunctions.Show(ctx, sdk.NewShowExternalFunctionRequest().WithIn(&sdk.In{Database: testClientHelper().Ids.DatabaseId()})) - require.NoError(t, err) - - require.Contains(t, es, *e1) - - es, err = client.ExternalFunctions.Show(ctx, sdk.NewShowExternalFunctionRequest().WithIn(&sdk.In{Database: otherDb.ID()})) - require.NoError(t, err) - - require.Empty(t, es) - }) - - t.Run("show external function: no matches", func(t *testing.T) { - es, err := client.ExternalFunctions.Show(ctx, sdk.NewShowExternalFunctionRequest().WithLike(&sdk.Like{Pattern: sdk.String("non-existing-id-pattern")})) - require.NoError(t, err) - require.Equal(t, 0, len(es)) - }) - - t.Run("show external function by id", func(t *testing.T) { - e, id := createExternalFunction(t) - - es, err := client.ExternalFunctions.ShowByID(ctx, id) - require.NoError(t, err) - require.Equal(t, *e, *es) - - _, err = client.ExternalFunctions.ShowByID(ctx, id.WithoutArguments()) - require.Error(t, err, sdk.ErrObjectNotExistOrAuthorized) - }) - - t.Run("describe external function", func(t *testing.T) { - e, _ := createExternalFunction(t) - - request := sdk.NewDescribeExternalFunctionRequest(e.ID(), []sdk.DataType{sdk.DataTypeVARCHAR}) - details, err := client.ExternalFunctions.Describe(ctx, request) - require.NoError(t, err) - pairs := make(map[string]string) - for _, detail := range details { - pairs[detail.Property] = detail.Value - } - require.Equal(t, "EXTERNAL", pairs["language"]) - require.Equal(t, "VARIANT", pairs["returns"]) - require.Equal(t, "VOLATILE", pairs["volatility"]) - require.Equal(t, "AUTO", pairs["compression"]) - require.Equal(t, "(X VARCHAR)", pairs["signature"]) - }) -} +//func TestInt_ExternalFunctions(t *testing.T) { +// client := testClient(t) +// ctx := context.Background() +// +// defaultDataTypes := []sdk.DataType{sdk.DataTypeVARCHAR} +// +// integration, integrationCleanup := testClientHelper().ApiIntegration.CreateApiIntegration(t) +// t.Cleanup(integrationCleanup) +// +// cleanupExternalFunctionHandle := func(id sdk.SchemaObjectIdentifier, dts []sdk.DataType) func() { +// return func() { +// err := client.Functions.Drop(ctx, sdk.NewDropFunctionRequest(id.WithoutArguments(), dts).WithIfExists(sdk.Bool(true))) +// require.NoError(t, err) +// } +// } +// +// // TODO [SNOW-999049]: id returned on purpose; address during identifiers rework +// createExternalFunction := func(t *testing.T) (*sdk.ExternalFunction, sdk.SchemaObjectIdentifier) { +// t.Helper() +// id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(defaultDataTypes) +// argument := sdk.NewExternalFunctionArgumentRequest("x", defaultDataTypes[0]) +// as := "https://xyz.execute-api.us-west-2.amazonaws.com/production/remote_echo" +// request := sdk.NewCreateExternalFunctionRequest(id, sdk.DataTypeVariant, sdk.Pointer(integration.ID()), as). +// WithOrReplace(sdk.Bool(true)). +// WithSecure(sdk.Bool(true)). +// WithArguments([]sdk.ExternalFunctionArgumentRequest{*argument}) +// err := client.ExternalFunctions.Create(ctx, request) +// require.NoError(t, err) +// t.Cleanup(cleanupExternalFunctionHandle(id.WithoutArguments(), []sdk.DataType{sdk.DataTypeVariant})) +// +// e, err := client.ExternalFunctions.ShowByID(ctx, id) +// require.NoError(t, err) +// return e, id +// } +// +// assertExternalFunction := func(t *testing.T, id sdk.SchemaObjectIdentifier, secure bool) { +// t.Helper() +// dts := id.Arguments() +// +// e, err := client.ExternalFunctions.ShowByID(ctx, id) +// require.NoError(t, err) +// +// require.NotEmpty(t, e.CreatedOn) +// require.Equal(t, id.Name(), e.Name) +// require.Equal(t, fmt.Sprintf(`"%v"`, id.SchemaName()), e.SchemaName) +// require.Equal(t, false, e.IsBuiltin) +// require.Equal(t, false, e.IsAggregate) +// require.Equal(t, false, e.IsAnsi) +// if len(dts) > 0 { +// require.Equal(t, 1, e.MinNumArguments) +// require.Equal(t, 1, e.MaxNumArguments) +// } else { +// require.Equal(t, 0, e.MinNumArguments) +// require.Equal(t, 0, e.MaxNumArguments) +// } +// require.NotEmpty(t, e.Arguments) +// require.NotEmpty(t, e.Description) +// require.NotEmpty(t, e.CatalogName) +// require.Equal(t, false, e.IsTableFunction) +// require.Equal(t, false, e.ValidForClustering) +// require.Equal(t, secure, e.IsSecure) +// require.Equal(t, true, e.IsExternalFunction) +// require.Equal(t, "EXTERNAL", e.Language) +// require.Equal(t, false, e.IsMemoizable) +// require.Equal(t, false, e.IsDataMetric) +// } +// +// t.Run("create external function", func(t *testing.T) { +// id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(defaultDataTypes) +// argument := sdk.NewExternalFunctionArgumentRequest("x", sdk.DataTypeVARCHAR) +// headers := []sdk.ExternalFunctionHeaderRequest{ +// { +// Name: "measure", +// Value: "kilometers", +// }, +// } +// ch := []sdk.ExternalFunctionContextHeaderRequest{ +// { +// ContextFunction: "CURRENT_DATE", +// }, +// { +// ContextFunction: "CURRENT_TIMESTAMP", +// }, +// } +// as := "https://xyz.execute-api.us-west-2.amazonaws.com/production/remote_echo" +// request := sdk.NewCreateExternalFunctionRequest(id, sdk.DataTypeVariant, sdk.Pointer(integration.ID()), as). +// WithOrReplace(sdk.Bool(true)). +// WithSecure(sdk.Bool(true)). +// WithArguments([]sdk.ExternalFunctionArgumentRequest{*argument}). +// WithNullInputBehavior(sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorCalledOnNullInput)). +// WithHeaders(headers). +// WithContextHeaders(ch). +// WithMaxBatchRows(sdk.Int(10)). +// WithCompression(sdk.String("GZIP")) +// err := client.ExternalFunctions.Create(ctx, request) +// require.NoError(t, err) +// t.Cleanup(cleanupExternalFunctionHandle(id.WithoutArguments(), []sdk.DataType{sdk.DataTypeVariant})) +// +// assertExternalFunction(t, id, true) +// }) +// +// t.Run("create external function without arguments", func(t *testing.T) { +// id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(nil) +// as := "https://xyz.execute-api.us-west-2.amazonaws.com/production/remote_echo" +// request := sdk.NewCreateExternalFunctionRequest(id, sdk.DataTypeVariant, sdk.Pointer(integration.ID()), as) +// err := client.ExternalFunctions.Create(ctx, request) +// require.NoError(t, err) +// t.Cleanup(cleanupExternalFunctionHandle(id, nil)) +// +// assertExternalFunction(t, id, false) +// }) +// +// t.Run("alter external function: set api integration", func(t *testing.T) { +// _, id := createExternalFunction(t) +// set := sdk.NewExternalFunctionSetRequest(). +// WithApiIntegration(sdk.Pointer(integration.ID())) +// request := sdk.NewAlterExternalFunctionRequest(id, defaultDataTypes).WithSet(set) +// err := client.ExternalFunctions.Alter(ctx, request) +// require.NoError(t, err) +// +// assertExternalFunction(t, id, true) +// }) +// +// t.Run("alter external function: set headers", func(t *testing.T) { +// _, id := createExternalFunction(t) +// +// headers := []sdk.ExternalFunctionHeaderRequest{ +// { +// Name: "measure", +// Value: "kilometers", +// }, +// } +// set := sdk.NewExternalFunctionSetRequest().WithHeaders(headers) +// request := sdk.NewAlterExternalFunctionRequest(id, defaultDataTypes).WithSet(set) +// err := client.ExternalFunctions.Alter(ctx, request) +// require.NoError(t, err) +// assertExternalFunction(t, id, true) +// }) +// +// t.Run("alter external function: set context headers", func(t *testing.T) { +// _, id := createExternalFunction(t) +// +// ch := []sdk.ExternalFunctionContextHeaderRequest{ +// { +// ContextFunction: "CURRENT_DATE", +// }, +// { +// ContextFunction: "CURRENT_TIMESTAMP", +// }, +// } +// set := sdk.NewExternalFunctionSetRequest().WithContextHeaders(ch) +// request := sdk.NewAlterExternalFunctionRequest(id, defaultDataTypes).WithSet(set) +// err := client.ExternalFunctions.Alter(ctx, request) +// require.NoError(t, err) +// assertExternalFunction(t, id, true) +// }) +// +// t.Run("alter external function: set compression", func(t *testing.T) { +// _, id := createExternalFunction(t) +// +// set := sdk.NewExternalFunctionSetRequest().WithCompression(sdk.String("AUTO")) +// request := sdk.NewAlterExternalFunctionRequest(id, defaultDataTypes).WithSet(set) +// err := client.ExternalFunctions.Alter(ctx, request) +// require.NoError(t, err) +// assertExternalFunction(t, id, true) +// }) +// +// t.Run("alter external function: set max batch rows", func(t *testing.T) { +// _, id := createExternalFunction(t) +// +// set := sdk.NewExternalFunctionSetRequest().WithMaxBatchRows(sdk.Int(20)) +// request := sdk.NewAlterExternalFunctionRequest(id, defaultDataTypes).WithSet(set) +// err := client.ExternalFunctions.Alter(ctx, request) +// require.NoError(t, err) +// assertExternalFunction(t, id, true) +// }) +// +// t.Run("alter external function: unset", func(t *testing.T) { +// _, id := createExternalFunction(t) +// +// unset := sdk.NewExternalFunctionUnsetRequest(). +// WithComment(sdk.Bool(true)). +// WithHeaders(sdk.Bool(true)) +// request := sdk.NewAlterExternalFunctionRequest(id, defaultDataTypes).WithUnset(unset) +// err := client.ExternalFunctions.Alter(ctx, request) +// require.NoError(t, err) +// +// assertExternalFunction(t, id, true) +// }) +// +// t.Run("show external function: with like", func(t *testing.T) { +// e1, _ := createExternalFunction(t) +// e2, _ := createExternalFunction(t) +// +// es, err := client.ExternalFunctions.Show(ctx, sdk.NewShowExternalFunctionRequest().WithLike(&sdk.Like{Pattern: sdk.String(e1.Name)})) +// require.NoError(t, err) +// +// require.Equal(t, 1, len(es)) +// require.Contains(t, es, *e1) +// require.NotContains(t, es, *e2) +// }) +// +// t.Run("show external function: with in", func(t *testing.T) { +// otherDb, otherDbCleanup := testClientHelper().Database.CreateDatabase(t) +// t.Cleanup(otherDbCleanup) +// +// e1, _ := createExternalFunction(t) +// +// es, err := client.ExternalFunctions.Show(ctx, sdk.NewShowExternalFunctionRequest().WithIn(&sdk.In{Schema: e1.ID().SchemaId()})) +// require.NoError(t, err) +// +// require.Contains(t, es, *e1) +// +// es, err = client.ExternalFunctions.Show(ctx, sdk.NewShowExternalFunctionRequest().WithIn(&sdk.In{Database: testClientHelper().Ids.DatabaseId()})) +// require.NoError(t, err) +// +// require.Contains(t, es, *e1) +// +// es, err = client.ExternalFunctions.Show(ctx, sdk.NewShowExternalFunctionRequest().WithIn(&sdk.In{Database: otherDb.ID()})) +// require.NoError(t, err) +// +// require.Empty(t, es) +// }) +// +// t.Run("show external function: no matches", func(t *testing.T) { +// es, err := client.ExternalFunctions.Show(ctx, sdk.NewShowExternalFunctionRequest().WithLike(&sdk.Like{Pattern: sdk.String("non-existing-id-pattern")})) +// require.NoError(t, err) +// require.Equal(t, 0, len(es)) +// }) +// +// t.Run("show external function by id", func(t *testing.T) { +// e, id := createExternalFunction(t) +// +// es, err := client.ExternalFunctions.ShowByID(ctx, id) +// require.NoError(t, err) +// require.Equal(t, *e, *es) +// +// _, err = client.ExternalFunctions.ShowByID(ctx, id.WithoutArguments()) +// require.Error(t, err, sdk.ErrObjectNotExistOrAuthorized) +// }) +// +// t.Run("describe external function", func(t *testing.T) { +// e, _ := createExternalFunction(t) +// +// request := sdk.NewDescribeExternalFunctionRequest(e.ID(), []sdk.DataType{sdk.DataTypeVARCHAR}) +// details, err := client.ExternalFunctions.Describe(ctx, request) +// require.NoError(t, err) +// pairs := make(map[string]string) +// for _, detail := range details { +// pairs[detail.Property] = detail.Value +// } +// require.Equal(t, "EXTERNAL", pairs["language"]) +// require.Equal(t, "VARIANT", pairs["returns"]) +// require.Equal(t, "VOLATILE", pairs["volatility"]) +// require.Equal(t, "AUTO", pairs["compression"]) +// require.Equal(t, "(X VARCHAR)", pairs["signature"]) +// }) +//} diff --git a/pkg/sdk/testint/functions_integration_test.go b/pkg/sdk/testint/functions_integration_test.go index 205689be40..82d7a2fd1b 100644 --- a/pkg/sdk/testint/functions_integration_test.go +++ b/pkg/sdk/testint/functions_integration_test.go @@ -23,9 +23,9 @@ func TestInt_CreateFunctions(t *testing.T) { client := testClient(t) ctx := context.Background() - cleanupFunctionHandle := func(id sdk.SchemaObjectIdentifier, dts []sdk.DataType) func() { + cleanupFunctionHandle := func(id sdk.SchemaObjectIdentifierWithArguments) func() { return func() { - err := client.Functions.Drop(ctx, sdk.NewDropFunctionRequest(id, dts)) + err := client.Functions.Drop(ctx, sdk.NewDropFunctionRequest(id)) if errors.Is(err, sdk.ErrObjectNotExistOrAuthorized) { return } @@ -35,7 +35,7 @@ func TestInt_CreateFunctions(t *testing.T) { t.Run("create function for Java", func(t *testing.T) { name := "echo_varchar" - id := testClientHelper().Ids.NewSchemaObjectIdentifier(name) + id := testClientHelper().Ids.NewSchemaObjectIdentifierWithArguments(name, sdk.DataTypeVARCHAR) definition := ` class TestFunc { @@ -45,17 +45,17 @@ func TestInt_CreateFunctions(t *testing.T) { }` target := fmt.Sprintf("@~/tf-%d.jar", time.Now().Unix()) dt := sdk.NewFunctionReturnsResultDataTypeRequest(sdk.DataTypeVARCHAR) - returns := sdk.NewFunctionReturnsRequest().WithResultDataType(dt) - argument := sdk.NewFunctionArgumentRequest("x", sdk.DataTypeVARCHAR).WithDefaultValue(sdk.String("'abc'")) + returns := sdk.NewFunctionReturnsRequest().WithResultDataType(*dt) + argument := sdk.NewFunctionArgumentRequest("x", sdk.DataTypeVARCHAR).WithDefaultValue("'abc'") request := sdk.NewCreateForJavaFunctionRequest(id, *returns, "TestFunc.echoVarchar"). - WithOrReplace(sdk.Bool(true)). + WithOrReplace(true). WithArguments([]sdk.FunctionArgumentRequest{*argument}). - WithNullInputBehavior(sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorCalledOnNullInput)). - WithTargetPath(&target). - WithFunctionDefinition(&definition) + WithNullInputBehavior(*sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorCalledOnNullInput)). + WithTargetPath(target). + WithFunctionDefinition(definition) err := client.Functions.CreateForJava(ctx, request) require.NoError(t, err) - t.Cleanup(cleanupFunctionHandle(id, []sdk.DataType{"VARCHAR"})) + t.Cleanup(cleanupFunctionHandle(id)) function, err := client.Functions.ShowByID(ctx, id) require.NoError(t, err) @@ -65,7 +65,7 @@ func TestInt_CreateFunctions(t *testing.T) { t.Run("create function for Javascript", func(t *testing.T) { name := "js_factorial" - id := testClientHelper().Ids.NewSchemaObjectIdentifier(name) + id := testClientHelper().Ids.NewSchemaObjectIdentifierWithArguments(name, sdk.DataTypeFloat) definition := ` if (D <= 0) { @@ -79,15 +79,15 @@ func TestInt_CreateFunctions(t *testing.T) { }` dt := sdk.NewFunctionReturnsResultDataTypeRequest(sdk.DataTypeFloat) - returns := sdk.NewFunctionReturnsRequest().WithResultDataType(dt) + returns := sdk.NewFunctionReturnsRequest().WithResultDataType(*dt) argument := sdk.NewFunctionArgumentRequest("d", sdk.DataTypeFloat) request := sdk.NewCreateForJavascriptFunctionRequest(id, *returns, definition). - WithOrReplace(sdk.Bool(true)). + WithOrReplace(true). WithArguments([]sdk.FunctionArgumentRequest{*argument}). - WithNullInputBehavior(sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorCalledOnNullInput)) + WithNullInputBehavior(*sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorCalledOnNullInput)) err := client.Functions.CreateForJavascript(ctx, request) require.NoError(t, err) - t.Cleanup(cleanupFunctionHandle(id, []sdk.DataType{sdk.DataTypeFloat})) + t.Cleanup(cleanupFunctionHandle(id)) function, err := client.Functions.ShowByID(ctx, id) require.NoError(t, err) @@ -96,21 +96,21 @@ func TestInt_CreateFunctions(t *testing.T) { }) t.Run("create function for Python", func(t *testing.T) { - id := testClientHelper().Ids.RandomSchemaObjectIdentifier() + id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(sdk.DataTypeNumber) definition := ` def dump(i): print("Hello World!")` dt := sdk.NewFunctionReturnsResultDataTypeRequest(sdk.DataTypeVariant) - returns := sdk.NewFunctionReturnsRequest().WithResultDataType(dt) + returns := sdk.NewFunctionReturnsRequest().WithResultDataType(*dt) argument := sdk.NewFunctionArgumentRequest("i", sdk.DataTypeNumber) request := sdk.NewCreateForPythonFunctionRequest(id, *returns, "3.8", "dump"). - WithOrReplace(sdk.Bool(true)). + WithOrReplace(true). WithArguments([]sdk.FunctionArgumentRequest{*argument}). - WithFunctionDefinition(&definition) + WithFunctionDefinition(definition) err := client.Functions.CreateForPython(ctx, request) require.NoError(t, err) - t.Cleanup(cleanupFunctionHandle(id, []sdk.DataType{"int"})) + t.Cleanup(cleanupFunctionHandle(id)) function, err := client.Functions.ShowByID(ctx, id) require.NoError(t, err) @@ -120,7 +120,7 @@ def dump(i): t.Run("create function for Scala", func(t *testing.T) { name := "echo_varchar" - id := testClientHelper().Ids.NewSchemaObjectIdentifier(name) + id := testClientHelper().Ids.NewSchemaObjectIdentifierWithArguments(name, sdk.DataTypeVARCHAR) definition := ` class Echo { @@ -131,13 +131,13 @@ def dump(i): argument := sdk.NewFunctionArgumentRequest("x", sdk.DataTypeVARCHAR) request := sdk.NewCreateForScalaFunctionRequest(id, sdk.DataTypeVARCHAR, "Echo.echoVarchar"). - WithOrReplace(sdk.Bool(true)). + WithOrReplace(true). WithArguments([]sdk.FunctionArgumentRequest{*argument}). - WithRuntimeVersion(sdk.String("2.12")). - WithFunctionDefinition(&definition) + WithRuntimeVersion("2.12"). + WithFunctionDefinition(definition) err := client.Functions.CreateForScala(ctx, request) require.NoError(t, err) - t.Cleanup(cleanupFunctionHandle(id, []sdk.DataType{sdk.DataTypeVARCHAR})) + t.Cleanup(cleanupFunctionHandle(id)) function, err := client.Functions.ShowByID(ctx, id) require.NoError(t, err) @@ -146,20 +146,20 @@ def dump(i): }) t.Run("create function for SQL", func(t *testing.T) { - id := testClientHelper().Ids.RandomSchemaObjectIdentifier() + id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(sdk.DataTypeFloat) definition := "3.141592654::FLOAT" dt := sdk.NewFunctionReturnsResultDataTypeRequest(sdk.DataTypeFloat) - returns := sdk.NewFunctionReturnsRequest().WithResultDataType(dt) + returns := sdk.NewFunctionReturnsRequest().WithResultDataType(*dt) argument := sdk.NewFunctionArgumentRequest("x", sdk.DataTypeFloat) request := sdk.NewCreateForSQLFunctionRequest(id, *returns, definition). WithArguments([]sdk.FunctionArgumentRequest{*argument}). - WithOrReplace(sdk.Bool(true)). - WithComment(sdk.String("comment")) + WithOrReplace(true). + WithComment("comment") err := client.Functions.CreateForSQL(ctx, request) require.NoError(t, err) - t.Cleanup(cleanupFunctionHandle(id, []sdk.DataType{sdk.DataTypeFloat})) + t.Cleanup(cleanupFunctionHandle(id)) function, err := client.Functions.ShowByID(ctx, id) require.NoError(t, err) @@ -168,18 +168,18 @@ def dump(i): }) t.Run("create function for SQL with no arguments", func(t *testing.T) { - id := testClientHelper().Ids.RandomSchemaObjectIdentifier() + id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments() definition := "3.141592654::FLOAT" dt := sdk.NewFunctionReturnsResultDataTypeRequest(sdk.DataTypeFloat) - returns := sdk.NewFunctionReturnsRequest().WithResultDataType(dt) + returns := sdk.NewFunctionReturnsRequest().WithResultDataType(*dt) request := sdk.NewCreateForSQLFunctionRequest(id, *returns, definition). - WithOrReplace(sdk.Bool(true)). - WithComment(sdk.String("comment")) + WithOrReplace(true). + WithComment("comment") err := client.Functions.CreateForSQL(ctx, request) require.NoError(t, err) - t.Cleanup(cleanupFunctionHandle(id, nil)) + t.Cleanup(cleanupFunctionHandle(id)) function, err := client.Functions.ShowByID(ctx, id) require.NoError(t, err) @@ -195,7 +195,7 @@ func TestInt_OtherFunctions(t *testing.T) { tagTest, tagCleanup := testClientHelper().Tag.CreateTag(t) t.Cleanup(tagCleanup) - assertFunction := func(t *testing.T, id sdk.SchemaObjectIdentifier, secure bool, withArguments bool) { + assertFunction := func(t *testing.T, id sdk.SchemaObjectIdentifierWithArguments, secure bool, withArguments bool) { t.Helper() function, err := client.Functions.ShowByID(ctx, id) @@ -224,9 +224,9 @@ func TestInt_OtherFunctions(t *testing.T) { assert.Equal(t, false, function.IsMemoizable) } - cleanupFunctionHandle := func(id sdk.SchemaObjectIdentifier, dts []sdk.DataType) func() { + cleanupFunctionHandle := func(id sdk.SchemaObjectIdentifierWithArguments) func() { return func() { - err := client.Functions.Drop(ctx, sdk.NewDropFunctionRequest(id, dts)) + err := client.Functions.Drop(ctx, sdk.NewDropFunctionRequest(id)) if errors.Is(err, sdk.ErrObjectNotExistOrAuthorized) { return } @@ -236,14 +236,14 @@ func TestInt_OtherFunctions(t *testing.T) { createFunctionForSQLHandle := func(t *testing.T, cleanup bool, withArguments bool) *sdk.Function { t.Helper() - id := testClientHelper().Ids.RandomSchemaObjectIdentifier() + id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(sdk.DataTypeFloat) definition := "3.141592654::FLOAT" dt := sdk.NewFunctionReturnsResultDataTypeRequest(sdk.DataTypeFloat) - returns := sdk.NewFunctionReturnsRequest().WithResultDataType(dt) + returns := sdk.NewFunctionReturnsRequest().WithResultDataType(*dt) request := sdk.NewCreateForSQLFunctionRequest(id, *returns, definition). - WithOrReplace(sdk.Bool(true)) + WithOrReplace(true) if withArguments { argument := sdk.NewFunctionArgumentRequest("x", sdk.DataTypeFloat) request = request.WithArguments([]sdk.FunctionArgumentRequest{*argument}) @@ -251,31 +251,27 @@ func TestInt_OtherFunctions(t *testing.T) { err := client.Functions.CreateForSQL(ctx, request) require.NoError(t, err) if cleanup { - if withArguments { - t.Cleanup(cleanupFunctionHandle(id, []sdk.DataType{sdk.DataTypeFloat})) - } else { - t.Cleanup(cleanupFunctionHandle(id, nil)) - } + t.Cleanup(cleanupFunctionHandle(id)) } function, err := client.Functions.ShowByID(ctx, id) require.NoError(t, err) return function } - defaultAlterRequest := func(id sdk.SchemaObjectIdentifier) *sdk.AlterFunctionRequest { - return sdk.NewAlterFunctionRequest(id, []sdk.DataType{sdk.DataTypeFloat}) + defaultAlterRequest := func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { + return sdk.NewAlterFunctionRequest(id) } t.Run("alter function: rename", func(t *testing.T) { f := createFunctionForSQLHandle(t, false, true) id := f.ID() - nid := testClientHelper().Ids.RandomSchemaObjectIdentifier() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithRenameTo(&nid)) + nid := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments() + err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithRenameTo(nid.SchemaObjectId())) if err != nil { - t.Cleanup(cleanupFunctionHandle(id, []sdk.DataType{sdk.DataTypeFloat})) + t.Cleanup(cleanupFunctionHandle(id)) } else { - t.Cleanup(cleanupFunctionHandle(nid, []sdk.DataType{sdk.DataTypeFloat})) + t.Cleanup(cleanupFunctionHandle(nid)) } require.NoError(t, err) @@ -291,7 +287,7 @@ func TestInt_OtherFunctions(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) id := f.ID() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetLogLevel(sdk.String("DEBUG"))) + err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetLogLevel(string(sdk.LogLevelDebug))) require.NoError(t, err) assertFunction(t, id, false, true) }) @@ -300,7 +296,7 @@ func TestInt_OtherFunctions(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) id := f.ID() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetLogLevel(sdk.Bool(true))) + err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetLogLevel(true)) require.NoError(t, err) assertFunction(t, id, false, true) }) @@ -309,7 +305,7 @@ func TestInt_OtherFunctions(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) id := f.ID() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetTraceLevel(sdk.String("ALWAYS"))) + err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetTraceLevel(string(sdk.TraceLevelAlways))) require.NoError(t, err) assertFunction(t, id, false, true) }) @@ -318,7 +314,7 @@ func TestInt_OtherFunctions(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) id := f.ID() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetTraceLevel(sdk.Bool(true))) + err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetTraceLevel(true)) require.NoError(t, err) assertFunction(t, id, false, true) }) @@ -327,7 +323,7 @@ func TestInt_OtherFunctions(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) id := f.ID() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetComment(sdk.String("test comment"))) + err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetComment("test comment")) require.NoError(t, err) assertFunction(t, id, false, true) }) @@ -336,7 +332,7 @@ func TestInt_OtherFunctions(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) id := f.ID() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetComment(sdk.Bool(true))) + err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetComment(true)) require.NoError(t, err) assertFunction(t, id, false, true) }) @@ -345,7 +341,7 @@ func TestInt_OtherFunctions(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) id := f.ID() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetSecure(sdk.Bool(true))) + err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetSecure(true)) require.NoError(t, err) assertFunction(t, id, true, true) }) @@ -353,7 +349,7 @@ func TestInt_OtherFunctions(t *testing.T) { t.Run("alter function: set secure with no arguments", func(t *testing.T) { f := createFunctionForSQLHandle(t, true, false) id := f.ID() - err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id, nil).WithSetSecure(sdk.Bool(true))) + err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithSetSecure(true)) require.NoError(t, err) assertFunction(t, id, true, false) }) @@ -362,7 +358,7 @@ func TestInt_OtherFunctions(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) id := f.ID() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetSecure(sdk.Bool(true))) + err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetSecure(true)) require.NoError(t, err) assertFunction(t, id, false, true) }) @@ -404,7 +400,7 @@ func TestInt_OtherFunctions(t *testing.T) { f1 := createFunctionForSQLHandle(t, true, true) f2 := createFunctionForSQLHandle(t, true, true) - functions, err := client.Functions.Show(ctx, sdk.NewShowFunctionRequest().WithLike(&sdk.Like{Pattern: &f1.Name})) + functions, err := client.Functions.Show(ctx, sdk.NewShowFunctionRequest().WithLike(sdk.Like{Pattern: &f1.Name})) require.NoError(t, err) require.Equal(t, 1, len(functions)) @@ -413,17 +409,15 @@ func TestInt_OtherFunctions(t *testing.T) { }) t.Run("show function for SQL: no matches", func(t *testing.T) { - functions, err := client.Functions.Show(ctx, sdk.NewShowFunctionRequest().WithLike(&sdk.Like{Pattern: sdk.String("non-existing-id-pattern")})) + functions, err := client.Functions.Show(ctx, sdk.NewShowFunctionRequest().WithLike(sdk.Like{Pattern: sdk.String("non-existing-id-pattern")})) require.NoError(t, err) require.Equal(t, 0, len(functions)) }) t.Run("describe function for SQL", func(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) - id := f.ID() - request := sdk.NewDescribeFunctionRequest(id, []sdk.DataType{sdk.DataTypeFloat}) - details, err := client.Functions.Describe(ctx, request) + details, err := client.Functions.Describe(ctx, f.ID()) require.NoError(t, err) pairs := make(map[string]string) for _, detail := range details { @@ -437,10 +431,8 @@ func TestInt_OtherFunctions(t *testing.T) { t.Run("describe function for SQL: no arguments", func(t *testing.T) { f := createFunctionForSQLHandle(t, true, false) - id := f.ID() - request := sdk.NewDescribeFunctionRequest(id, nil) - details, err := client.Functions.Describe(ctx, request) + details, err := client.Functions.Describe(ctx, f.ID()) require.NoError(t, err) pairs := make(map[string]string) for _, detail := range details { @@ -457,9 +449,9 @@ func TestInt_FunctionsShowByID(t *testing.T) { client := testClient(t) ctx := testContext(t) - cleanupFunctionHandle := func(id sdk.SchemaObjectIdentifier, dts []sdk.DataType) func() { + cleanupFunctionHandle := func(id sdk.SchemaObjectIdentifierWithArguments, dts []sdk.DataType) func() { return func() { - err := client.Functions.Drop(ctx, sdk.NewDropFunctionRequest(id, dts)) + err := client.Functions.Drop(ctx, sdk.NewDropFunctionRequest(id)) if errors.Is(err, sdk.ErrObjectNotExistOrAuthorized) { return } @@ -467,13 +459,13 @@ func TestInt_FunctionsShowByID(t *testing.T) { } } - createFunctionForSQLHandle := func(t *testing.T, id sdk.SchemaObjectIdentifier) { + createFunctionForSQLHandle := func(t *testing.T, id sdk.SchemaObjectIdentifierWithArguments) { t.Helper() definition := "3.141592654::FLOAT" dt := sdk.NewFunctionReturnsResultDataTypeRequest(sdk.DataTypeFloat) - returns := sdk.NewFunctionReturnsRequest().WithResultDataType(dt) - request := sdk.NewCreateForSQLFunctionRequest(id, *returns, definition).WithOrReplace(sdk.Bool(true)) + returns := sdk.NewFunctionReturnsRequest().WithResultDataType(*dt) + request := sdk.NewCreateForSQLFunctionRequest(id, *returns, definition).WithOrReplace(true) argument := sdk.NewFunctionArgumentRequest("x", sdk.DataTypeFloat) request = request.WithArguments([]sdk.FunctionArgumentRequest{*argument}) @@ -486,8 +478,8 @@ func TestInt_FunctionsShowByID(t *testing.T) { schema, schemaCleanup := testClientHelper().Schema.CreateSchema(t) t.Cleanup(schemaCleanup) - id1 := testClientHelper().Ids.RandomSchemaObjectIdentifier() - id2 := testClientHelper().Ids.NewSchemaObjectIdentifierInSchema(id1.Name(), schema.ID()) + id1 := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments() + id2 := testClientHelper().Ids.NewSchemaObjectIdentifierWithArgumentsInSchema(id1.Name(), schema.ID()) createFunctionForSQLHandle(t, id1) createFunctionForSQLHandle(t, id2) diff --git a/pkg/sdk/testint/procedures_integration_test.go b/pkg/sdk/testint/procedures_integration_test.go index ea4df83a82..ae48927d08 100644 --- a/pkg/sdk/testint/procedures_integration_test.go +++ b/pkg/sdk/testint/procedures_integration_test.go @@ -1,1027 +1,1016 @@ package testint -import ( - "errors" - "fmt" - "testing" - - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" - "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) - - 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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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/developer-guide/stored-procedure/stored-procedures-javascript#basic-examples - name := "stproc1" - id := testClientHelper().Ids.NewSchemaObjectIdentifier(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/developer-guide/stored-procedure/stored-procedures-javascript#basic-examples - name := "sp_pi" - id := testClientHelper().Ids.NewSchemaObjectIdentifier(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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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{"INT"})) - - 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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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) - 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("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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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)). - // Suddenly this is erroring out, when it used to not have an problem. Must be an error with the Snowflake API. - // Created issue in docs-discuss channel. https://snowflake.slack.com/archives/C6380540P/p1707511734666249 - // Error: Received unexpected error: - // 001003 (42000): SQL compilation error: - // syntax error line 1 at position 210 unexpected 'NULL'. - // syntax error line 1 at position 215 unexpected 'ON'. - // WithNullInputBehavior(sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorReturnNullInput)). - 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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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)). - // SNOW-1051627 todo: uncomment once null input behavior working again - // WithNullInputBehavior(sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorReturnNullInput)). - 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) - - tagTest, tagCleanup := testClientHelper().Tag.CreateTag(t) - 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 := testClientHelper().Ids.RandomSchemaObjectIdentifier() - 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 := f.ID() - nid := testClientHelper().Ids.RandomSchemaObjectIdentifier() - 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 := f.ID() - 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 := f.ID() - 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 := f.ID() - 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 := f.ID() - 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 := f.ID() - 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 := f.ID() - 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("non-existing-id-pattern")})) - 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 := f.ID() - - 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 := testClientHelper().Ids.RandomSchemaObjectIdentifier() - 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) - }) -} - -func TestInt_CallProcedure(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) - } - } - - createTableHandle := func(t *testing.T, table sdk.SchemaObjectIdentifier) { - t.Helper() - - _, err := client.ExecForTests(ctx, fmt.Sprintf(`CREATE OR REPLACE TABLE %s (id NUMBER, name VARCHAR, role VARCHAR)`, table.FullyQualifiedName())) - require.NoError(t, err) - _, err = client.ExecForTests(ctx, fmt.Sprintf(`INSERT INTO %s (id, name, role) VALUES (1, 'Alice', 'op'), (2, 'Bob', 'dev'), (3, 'Cindy', 'dev')`, table.FullyQualifiedName())) - require.NoError(t, err) - t.Cleanup(func() { - _, err := client.ExecForTests(ctx, fmt.Sprintf(`DROP TABLE %s`, table.FullyQualifiedName())) - require.NoError(t, err) - }) - } - - // create a employees table - tid := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, "employees") - createTableHandle(t, tid) - - createProcedureForSQLHandle := func(t *testing.T, cleanup bool) *sdk.Procedure { - t.Helper() - - definition := ` - BEGIN - RETURN message; - END;` - id := testClientHelper().Ids.RandomSchemaObjectIdentifier() - 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 - } - - t.Run("call procedure for SQL: argument positions", func(t *testing.T) { - f := createProcedureForSQLHandle(t, true) - err := client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(f.ID()).WithCallArguments([]string{"'hi'"})) - require.NoError(t, err) - }) - - t.Run("call procedure for SQL: argument names", func(t *testing.T) { - f := createProcedureForSQLHandle(t, true) - err := client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(f.ID()).WithCallArguments([]string{"message => 'hi'"})) - require.NoError(t, err) - }) - - t.Run("call procedure for Java: returns table", func(t *testing.T) { - // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-java#omitting-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 name, String role) { - DataFrame table = session.table(name); - 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("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})) - - ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} - err = client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(id).WithCallArguments(ca)) - require.NoError(t, err) - }) - - t.Run("call procedure for Scala: returns table", func(t *testing.T) { - // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-scala#omitting-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, name: String, role: String): DataFrame = { - val table = session.table(name) - val filteredRows = table.filter(col("role") === role) - return filteredRows - } - }` - returnsTable := sdk.NewProcedureReturnsTableRequest().WithColumns([]sdk.ProcedureColumnRequest{}) - returns := sdk.NewProcedureReturnsRequest().WithTable(returnsTable) - arg1 := sdk.NewProcedureArgumentRequest("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})) - - ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} - err = client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(id).WithCallArguments(ca)) - require.NoError(t, err) - }) - - t.Run("call procedure for Javascript", func(t *testing.T) { - // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-javascript#basic-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. - }` - arg := sdk.NewProcedureArgumentRequest("FLOAT_PARAM1", sdk.DataTypeFloat) - request := sdk.NewCreateForJavaScriptProcedureRequest(id, sdk.DataTypeString, definition). - WithOrReplace(sdk.Bool(true)). - WithArguments([]sdk.ProcedureArgumentRequest{*arg}). - WithNullInputBehavior(sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorStrict)). - WithExecuteAs(sdk.ExecuteAsPointer(sdk.ExecuteAsOwner)) - err := client.Procedures.CreateForJavaScript(ctx, request) - require.NoError(t, err) - t.Cleanup(cleanupProcedureHandle(id, []sdk.DataType{sdk.DataTypeFloat})) - - err = client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(id).WithCallArguments([]string{"5.14::FLOAT"})) - require.NoError(t, err) - }) - - t.Run("call procedure for Javascript: no arguments", func(t *testing.T) { - // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-javascript#basic-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)) - - err = client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(id)) - require.NoError(t, err) - }) - - t.Run("call procedure for Python: returns table", func(t *testing.T) { - // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-python#omitting-return-column-names-and-types - id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, "filterByRole") - - definition := ` -from snowflake.snowpark.functions import col -def filter_by_role(session, name, role): - df = session.table(name) - return df.filter(col("role") == role)` - returnsTable := sdk.NewProcedureReturnsTableRequest().WithColumns([]sdk.ProcedureColumnRequest{}) - returns := sdk.NewProcedureReturnsRequest().WithTable(returnsTable) - arg1 := sdk.NewProcedureArgumentRequest("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})) - - id = sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, "filterByRole") - ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} - err = client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(id).WithCallArguments(ca)) - require.NoError(t, err) - }) -} - -func TestInt_CreateAndCallProcedures(t *testing.T) { - client := testClient(t) - ctx := testContext(t) - - databaseTest, schemaTest := testDb(t), testSchema(t) - createTableHandle := func(t *testing.T, table sdk.SchemaObjectIdentifier) { - t.Helper() - - _, err := client.ExecForTests(ctx, fmt.Sprintf(`CREATE OR REPLACE TABLE %s (id NUMBER, name VARCHAR, role VARCHAR)`, table.FullyQualifiedName())) - require.NoError(t, err) - _, err = client.ExecForTests(ctx, fmt.Sprintf(`INSERT INTO %s (id, name, role) VALUES (1, 'Alice', 'op'), (2, 'Bob', 'dev'), (3, 'Cindy', 'dev')`, table.FullyQualifiedName())) - require.NoError(t, err) - t.Cleanup(func() { - _, err := client.ExecForTests(ctx, fmt.Sprintf(`DROP TABLE %s`, table.FullyQualifiedName())) - require.NoError(t, err) - }) - } - - // create a employees table - tid := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, "employees") - createTableHandle(t, tid) - - t.Run("create and call procedure for Java: returns table", func(t *testing.T) { - // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-java#omitting-return-column-names-and-types - // TODO [SNOW-1348106]: make random with procedures rework - name := sdk.NewAccountObjectIdentifier("filter_by_role") - - definition := ` - import com.snowflake.snowpark_java.*; - public class Filter { - public DataFrame filterByRole(Session session, String name, String role) { - DataFrame table = session.table(name); - 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("name", sdk.DataTypeVARCHAR) - arg2 := sdk.NewProcedureArgumentRequest("role", sdk.DataTypeVARCHAR) - packages := []sdk.ProcedurePackageRequest{*sdk.NewProcedurePackageRequest("com.snowflake:snowpark:latest")} - ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} - request := sdk.NewCreateAndCallForJavaProcedureRequest(name, *returns, "11", packages, "Filter.filterByRole", name). - WithArguments([]sdk.ProcedureArgumentRequest{*arg1, *arg2}). - WithProcedureDefinition(sdk.String(definition)). - WithCallArguments(ca) - err := client.Procedures.CreateAndCallForJava(ctx, request) - require.NoError(t, err) - }) - - t.Run("create and call procedure for Scala: returns table", func(t *testing.T) { - // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-scala#omitting-return-column-names-and-types - // TODO [SNOW-1348106]: make random with procedures rework - name := sdk.NewAccountObjectIdentifier("filter_by_role") - - definition := ` - import com.snowflake.snowpark.functions._ - import com.snowflake.snowpark._ - - object Filter { - def filterByRole(session: Session, name: String, role: String): DataFrame = { - val table = session.table(name) - 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("name", sdk.DataTypeVARCHAR) - arg2 := sdk.NewProcedureArgumentRequest("role", sdk.DataTypeVARCHAR) - packages := []sdk.ProcedurePackageRequest{*sdk.NewProcedurePackageRequest("com.snowflake:snowpark:latest")} - ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} - request := sdk.NewCreateAndCallForScalaProcedureRequest(name, *returns, "2.12", packages, "Filter.filterByRole", name). - WithArguments([]sdk.ProcedureArgumentRequest{*arg1, *arg2}). - WithProcedureDefinition(sdk.String(definition)). - WithCallArguments(ca) - err := client.Procedures.CreateAndCallForScala(ctx, request) - require.NoError(t, err) - }) - - t.Run("create and call procedure for Javascript", func(t *testing.T) { - // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-javascript#basic-examples - // TODO [SNOW-1348106]: make random with procedures rework - name := sdk.NewAccountObjectIdentifier("stproc1") - - 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. - }` - arg := sdk.NewProcedureArgumentRequest("FLOAT_PARAM1", sdk.DataTypeFloat) - request := sdk.NewCreateAndCallForJavaScriptProcedureRequest(name, sdk.DataTypeString, definition, name). - WithArguments([]sdk.ProcedureArgumentRequest{*arg}). - WithNullInputBehavior(sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorStrict)). - WithCallArguments([]string{"5.14::FLOAT"}) - err := client.Procedures.CreateAndCallForJavaScript(ctx, request) - require.NoError(t, err) - }) - - t.Run("create and call procedure for Javascript: no arguments", func(t *testing.T) { - // https://docs.snowflake.com/en/sql-reference/sql/create-procedure#examples - // TODO [SNOW-1348106]: make random with procedures rework - name := sdk.NewAccountObjectIdentifier("sp_pi") - - definition := `return 3.1415926;` - request := sdk.NewCreateAndCallForJavaScriptProcedureRequest(name, sdk.DataTypeFloat, definition, name).WithNotNull(sdk.Bool(true)) - err := client.Procedures.CreateAndCallForJavaScript(ctx, request) - require.NoError(t, err) - }) - - t.Run("create and call procedure for SQL: argument positions", func(t *testing.T) { - definition := ` - BEGIN - RETURN message; - END;` - - name := testClientHelper().Ids.RandomAccountObjectIdentifier() - dt := sdk.NewProcedureReturnsResultDataTypeRequest(sdk.DataTypeVARCHAR) - returns := sdk.NewProcedureReturnsRequest().WithResultDataType(dt) - argument := sdk.NewProcedureArgumentRequest("message", sdk.DataTypeVARCHAR) - request := sdk.NewCreateAndCallForSQLProcedureRequest(name, *returns, definition, name). - WithArguments([]sdk.ProcedureArgumentRequest{*argument}). - WithCallArguments([]string{"message => 'hi'"}) - err := client.Procedures.CreateAndCallForSQL(ctx, request) - require.NoError(t, err) - }) - - t.Run("create and call procedure for Python: returns table", func(t *testing.T) { - // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-python#omitting-return-column-names-and-types - // TODO [SNOW-1348106]: make random with procedures rework - name := sdk.NewAccountObjectIdentifier("filterByRole") - definition := ` -from snowflake.snowpark.functions import col -def filter_by_role(session, name, role): - df = session.table(name) - return df.filter(col("role") == role)` - returnsTable := sdk.NewProcedureReturnsTableRequest().WithColumns([]sdk.ProcedureColumnRequest{}) - returns := sdk.NewProcedureReturnsRequest().WithTable(returnsTable) - arg1 := sdk.NewProcedureArgumentRequest("name", sdk.DataTypeVARCHAR) - arg2 := sdk.NewProcedureArgumentRequest("role", sdk.DataTypeVARCHAR) - packages := []sdk.ProcedurePackageRequest{*sdk.NewProcedurePackageRequest("snowflake-snowpark-python")} - ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} - request := sdk.NewCreateAndCallForPythonProcedureRequest(name, *returns, "3.8", packages, "filter_by_role", name). - WithArguments([]sdk.ProcedureArgumentRequest{*arg1, *arg2}). - WithProcedureDefinition(sdk.String(definition)). - WithCallArguments(ca) - err := client.Procedures.CreateAndCallForPython(ctx, request) - require.NoError(t, err) - }) - - t.Run("create and call procedure for Java: returns table and with clause", func(t *testing.T) { - // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-java#omitting-return-column-names-and-types - // TODO [SNOW-1348106]: make random with procedures rework - name := sdk.NewAccountObjectIdentifier("filter_by_role") - definition := ` - import com.snowflake.snowpark_java.*; - public class Filter { - public DataFrame filterByRole(Session session, String name, String role) { - DataFrame table = session.table(name); - 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("name", sdk.DataTypeVARCHAR) - arg2 := sdk.NewProcedureArgumentRequest("role", sdk.DataTypeVARCHAR) - packages := []sdk.ProcedurePackageRequest{*sdk.NewProcedurePackageRequest("com.snowflake:snowpark:latest")} - - ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} - // TODO [SNOW-1348106]: make random with procedures rework - cte := sdk.NewAccountObjectIdentifier("records") - statement := fmt.Sprintf(`(SELECT name, role FROM %s WHERE name = 'Bob')`, tid.FullyQualifiedName()) - clause := sdk.NewProcedureWithClauseRequest(cte, statement).WithCteColumns([]string{"name", "role"}) - request := sdk.NewCreateAndCallForJavaProcedureRequest(name, *returns, "11", packages, "Filter.filterByRole", name). - WithArguments([]sdk.ProcedureArgumentRequest{*arg1, *arg2}). - WithProcedureDefinition(sdk.String(definition)). - WithWithClause(clause). - WithCallArguments(ca) - err := client.Procedures.CreateAndCallForJava(ctx, request) - require.NoError(t, err) - }) -} - -func TestInt_ProceduresShowByID(t *testing.T) { - client := testClient(t) - ctx := testContext(t) - - cleanupProcedureHandle := func(id sdk.SchemaObjectIdentifier, dts []sdk.DataType) func() { - return func() { - err := client.Procedures.Drop(ctx, sdk.NewDropProcedureRequest(id, dts)) - if errors.Is(err, sdk.ErrObjectNotExistOrAuthorized) { - return - } - require.NoError(t, err) - } - } - - createProcedureForSQLHandle := func(t *testing.T, id sdk.SchemaObjectIdentifier) { - t.Helper() - - 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). - WithArguments([]sdk.ProcedureArgumentRequest{*argument}). - WithExecuteAs(sdk.ExecuteAsPointer(sdk.ExecuteAsCaller)) - err := client.Procedures.CreateForSQL(ctx, request) - require.NoError(t, err) - t.Cleanup(cleanupProcedureHandle(id, []sdk.DataType{sdk.DataTypeVARCHAR})) - } - - t.Run("show by id - same name in different schemas", func(t *testing.T) { - schema, schemaCleanup := testClientHelper().Schema.CreateSchema(t) - t.Cleanup(schemaCleanup) - - id1 := testClientHelper().Ids.RandomSchemaObjectIdentifier() - id2 := testClientHelper().Ids.NewSchemaObjectIdentifierInSchema(id1.Name(), schema.ID()) - - createProcedureForSQLHandle(t, id1) - createProcedureForSQLHandle(t, id2) - - e1, err := client.Procedures.ShowByID(ctx, id1) - require.NoError(t, err) - require.Equal(t, id1, e1.ID()) - - e2, err := client.Procedures.ShowByID(ctx, id2) - require.NoError(t, err) - require.Equal(t, id2, e2.ID()) - }) -} +//func TestInt_CreateProcedures(t *testing.T) { +// client := testClient(t) +// ctx := testContext(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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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/developer-guide/stored-procedure/stored-procedures-javascript#basic-examples +// name := "stproc1" +// id := testClientHelper().Ids.NewSchemaObjectIdentifier(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/developer-guide/stored-procedure/stored-procedures-javascript#basic-examples +// name := "sp_pi" +// id := testClientHelper().Ids.NewSchemaObjectIdentifier(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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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{"INT"})) +// +// 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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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) +// 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("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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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)). +// // Suddenly this is erroring out, when it used to not have an problem. Must be an error with the Snowflake API. +// // Created issue in docs-discuss channel. https://snowflake.slack.com/archives/C6380540P/p1707511734666249 +// // Error: Received unexpected error: +// // 001003 (42000): SQL compilation error: +// // syntax error line 1 at position 210 unexpected 'NULL'. +// // syntax error line 1 at position 215 unexpected 'ON'. +// // WithNullInputBehavior(sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorReturnNullInput)). +// 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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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)). +// // SNOW-1051627 todo: uncomment once null input behavior working again +// // WithNullInputBehavior(sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorReturnNullInput)). +// 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) +// +// tagTest, tagCleanup := testClientHelper().Tag.CreateTag(t) +// 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 := testClientHelper().Ids.RandomSchemaObjectIdentifier() +// 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 := f.ID() +// nid := testClientHelper().Ids.RandomSchemaObjectIdentifier() +// 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 := f.ID() +// 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 := f.ID() +// 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 := f.ID() +// 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 := f.ID() +// 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 := f.ID() +// 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 := f.ID() +// 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("non-existing-id-pattern")})) +// 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 := f.ID() +// +// 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 := testClientHelper().Ids.RandomSchemaObjectIdentifier() +// 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) +// }) +//} +// +//func TestInt_CallProcedure(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) +// } +// } +// +// createTableHandle := func(t *testing.T, table sdk.SchemaObjectIdentifier) { +// t.Helper() +// +// _, err := client.ExecForTests(ctx, fmt.Sprintf(`CREATE OR REPLACE TABLE %s (id NUMBER, name VARCHAR, role VARCHAR)`, table.FullyQualifiedName())) +// require.NoError(t, err) +// _, err = client.ExecForTests(ctx, fmt.Sprintf(`INSERT INTO %s (id, name, role) VALUES (1, 'Alice', 'op'), (2, 'Bob', 'dev'), (3, 'Cindy', 'dev')`, table.FullyQualifiedName())) +// require.NoError(t, err) +// t.Cleanup(func() { +// _, err := client.ExecForTests(ctx, fmt.Sprintf(`DROP TABLE %s`, table.FullyQualifiedName())) +// require.NoError(t, err) +// }) +// } +// +// // create a employees table +// tid := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, "employees") +// createTableHandle(t, tid) +// +// createProcedureForSQLHandle := func(t *testing.T, cleanup bool) *sdk.Procedure { +// t.Helper() +// +// definition := ` +// BEGIN +// RETURN message; +// END;` +// id := testClientHelper().Ids.RandomSchemaObjectIdentifier() +// 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 +// } +// +// t.Run("call procedure for SQL: argument positions", func(t *testing.T) { +// f := createProcedureForSQLHandle(t, true) +// err := client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(f.ID()).WithCallArguments([]string{"'hi'"})) +// require.NoError(t, err) +// }) +// +// t.Run("call procedure for SQL: argument names", func(t *testing.T) { +// f := createProcedureForSQLHandle(t, true) +// err := client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(f.ID()).WithCallArguments([]string{"message => 'hi'"})) +// require.NoError(t, err) +// }) +// +// t.Run("call procedure for Java: returns table", func(t *testing.T) { +// // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-java#omitting-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 name, String role) { +// DataFrame table = session.table(name); +// 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("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})) +// +// ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} +// err = client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(id).WithCallArguments(ca)) +// require.NoError(t, err) +// }) +// +// t.Run("call procedure for Scala: returns table", func(t *testing.T) { +// // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-scala#omitting-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, name: String, role: String): DataFrame = { +// val table = session.table(name) +// val filteredRows = table.filter(col("role") === role) +// return filteredRows +// } +// }` +// returnsTable := sdk.NewProcedureReturnsTableRequest().WithColumns([]sdk.ProcedureColumnRequest{}) +// returns := sdk.NewProcedureReturnsRequest().WithTable(returnsTable) +// arg1 := sdk.NewProcedureArgumentRequest("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})) +// +// ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} +// err = client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(id).WithCallArguments(ca)) +// require.NoError(t, err) +// }) +// +// t.Run("call procedure for Javascript", func(t *testing.T) { +// // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-javascript#basic-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. +// }` +// arg := sdk.NewProcedureArgumentRequest("FLOAT_PARAM1", sdk.DataTypeFloat) +// request := sdk.NewCreateForJavaScriptProcedureRequest(id, sdk.DataTypeString, definition). +// WithOrReplace(sdk.Bool(true)). +// WithArguments([]sdk.ProcedureArgumentRequest{*arg}). +// WithNullInputBehavior(sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorStrict)). +// WithExecuteAs(sdk.ExecuteAsPointer(sdk.ExecuteAsOwner)) +// err := client.Procedures.CreateForJavaScript(ctx, request) +// require.NoError(t, err) +// t.Cleanup(cleanupProcedureHandle(id, []sdk.DataType{sdk.DataTypeFloat})) +// +// err = client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(id).WithCallArguments([]string{"5.14::FLOAT"})) +// require.NoError(t, err) +// }) +// +// t.Run("call procedure for Javascript: no arguments", func(t *testing.T) { +// // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-javascript#basic-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)) +// +// err = client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(id)) +// require.NoError(t, err) +// }) +// +// t.Run("call procedure for Python: returns table", func(t *testing.T) { +// // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-python#omitting-return-column-names-and-types +// id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, "filterByRole") +// +// definition := ` +//from snowflake.snowpark.functions import col +//def filter_by_role(session, name, role): +// df = session.table(name) +// return df.filter(col("role") == role)` +// returnsTable := sdk.NewProcedureReturnsTableRequest().WithColumns([]sdk.ProcedureColumnRequest{}) +// returns := sdk.NewProcedureReturnsRequest().WithTable(returnsTable) +// arg1 := sdk.NewProcedureArgumentRequest("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})) +// +// id = sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, "filterByRole") +// ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} +// err = client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(id).WithCallArguments(ca)) +// require.NoError(t, err) +// }) +//} +// +//func TestInt_CreateAndCallProcedures(t *testing.T) { +// client := testClient(t) +// ctx := testContext(t) +// +// databaseTest, schemaTest := testDb(t), testSchema(t) +// createTableHandle := func(t *testing.T, table sdk.SchemaObjectIdentifier) { +// t.Helper() +// +// _, err := client.ExecForTests(ctx, fmt.Sprintf(`CREATE OR REPLACE TABLE %s (id NUMBER, name VARCHAR, role VARCHAR)`, table.FullyQualifiedName())) +// require.NoError(t, err) +// _, err = client.ExecForTests(ctx, fmt.Sprintf(`INSERT INTO %s (id, name, role) VALUES (1, 'Alice', 'op'), (2, 'Bob', 'dev'), (3, 'Cindy', 'dev')`, table.FullyQualifiedName())) +// require.NoError(t, err) +// t.Cleanup(func() { +// _, err := client.ExecForTests(ctx, fmt.Sprintf(`DROP TABLE %s`, table.FullyQualifiedName())) +// require.NoError(t, err) +// }) +// } +// +// // create a employees table +// tid := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, "employees") +// createTableHandle(t, tid) +// +// t.Run("create and call procedure for Java: returns table", func(t *testing.T) { +// // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-java#omitting-return-column-names-and-types +// // TODO [SNOW-1348106]: make random with procedures rework +// name := sdk.NewAccountObjectIdentifier("filter_by_role") +// +// definition := ` +// import com.snowflake.snowpark_java.*; +// public class Filter { +// public DataFrame filterByRole(Session session, String name, String role) { +// DataFrame table = session.table(name); +// 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("name", sdk.DataTypeVARCHAR) +// arg2 := sdk.NewProcedureArgumentRequest("role", sdk.DataTypeVARCHAR) +// packages := []sdk.ProcedurePackageRequest{*sdk.NewProcedurePackageRequest("com.snowflake:snowpark:latest")} +// ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} +// request := sdk.NewCreateAndCallForJavaProcedureRequest(name, *returns, "11", packages, "Filter.filterByRole", name). +// WithArguments([]sdk.ProcedureArgumentRequest{*arg1, *arg2}). +// WithProcedureDefinition(sdk.String(definition)). +// WithCallArguments(ca) +// err := client.Procedures.CreateAndCallForJava(ctx, request) +// require.NoError(t, err) +// }) +// +// t.Run("create and call procedure for Scala: returns table", func(t *testing.T) { +// // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-scala#omitting-return-column-names-and-types +// // TODO [SNOW-1348106]: make random with procedures rework +// name := sdk.NewAccountObjectIdentifier("filter_by_role") +// +// definition := ` +// import com.snowflake.snowpark.functions._ +// import com.snowflake.snowpark._ +// +// object Filter { +// def filterByRole(session: Session, name: String, role: String): DataFrame = { +// val table = session.table(name) +// 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("name", sdk.DataTypeVARCHAR) +// arg2 := sdk.NewProcedureArgumentRequest("role", sdk.DataTypeVARCHAR) +// packages := []sdk.ProcedurePackageRequest{*sdk.NewProcedurePackageRequest("com.snowflake:snowpark:latest")} +// ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} +// request := sdk.NewCreateAndCallForScalaProcedureRequest(name, *returns, "2.12", packages, "Filter.filterByRole", name). +// WithArguments([]sdk.ProcedureArgumentRequest{*arg1, *arg2}). +// WithProcedureDefinition(sdk.String(definition)). +// WithCallArguments(ca) +// err := client.Procedures.CreateAndCallForScala(ctx, request) +// require.NoError(t, err) +// }) +// +// t.Run("create and call procedure for Javascript", func(t *testing.T) { +// // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-javascript#basic-examples +// // TODO [SNOW-1348106]: make random with procedures rework +// name := sdk.NewAccountObjectIdentifier("stproc1") +// +// 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. +// }` +// arg := sdk.NewProcedureArgumentRequest("FLOAT_PARAM1", sdk.DataTypeFloat) +// request := sdk.NewCreateAndCallForJavaScriptProcedureRequest(name, sdk.DataTypeString, definition, name). +// WithArguments([]sdk.ProcedureArgumentRequest{*arg}). +// WithNullInputBehavior(sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorStrict)). +// WithCallArguments([]string{"5.14::FLOAT"}) +// err := client.Procedures.CreateAndCallForJavaScript(ctx, request) +// require.NoError(t, err) +// }) +// +// t.Run("create and call procedure for Javascript: no arguments", func(t *testing.T) { +// // https://docs.snowflake.com/en/sql-reference/sql/create-procedure#examples +// // TODO [SNOW-1348106]: make random with procedures rework +// name := sdk.NewAccountObjectIdentifier("sp_pi") +// +// definition := `return 3.1415926;` +// request := sdk.NewCreateAndCallForJavaScriptProcedureRequest(name, sdk.DataTypeFloat, definition, name).WithNotNull(sdk.Bool(true)) +// err := client.Procedures.CreateAndCallForJavaScript(ctx, request) +// require.NoError(t, err) +// }) +// +// t.Run("create and call procedure for SQL: argument positions", func(t *testing.T) { +// definition := ` +// BEGIN +// RETURN message; +// END;` +// +// name := testClientHelper().Ids.RandomAccountObjectIdentifier() +// dt := sdk.NewProcedureReturnsResultDataTypeRequest(sdk.DataTypeVARCHAR) +// returns := sdk.NewProcedureReturnsRequest().WithResultDataType(dt) +// argument := sdk.NewProcedureArgumentRequest("message", sdk.DataTypeVARCHAR) +// request := sdk.NewCreateAndCallForSQLProcedureRequest(name, *returns, definition, name). +// WithArguments([]sdk.ProcedureArgumentRequest{*argument}). +// WithCallArguments([]string{"message => 'hi'"}) +// err := client.Procedures.CreateAndCallForSQL(ctx, request) +// require.NoError(t, err) +// }) +// +// t.Run("create and call procedure for Python: returns table", func(t *testing.T) { +// // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-python#omitting-return-column-names-and-types +// // TODO [SNOW-1348106]: make random with procedures rework +// name := sdk.NewAccountObjectIdentifier("filterByRole") +// definition := ` +//from snowflake.snowpark.functions import col +//def filter_by_role(session, name, role): +// df = session.table(name) +// return df.filter(col("role") == role)` +// returnsTable := sdk.NewProcedureReturnsTableRequest().WithColumns([]sdk.ProcedureColumnRequest{}) +// returns := sdk.NewProcedureReturnsRequest().WithTable(returnsTable) +// arg1 := sdk.NewProcedureArgumentRequest("name", sdk.DataTypeVARCHAR) +// arg2 := sdk.NewProcedureArgumentRequest("role", sdk.DataTypeVARCHAR) +// packages := []sdk.ProcedurePackageRequest{*sdk.NewProcedurePackageRequest("snowflake-snowpark-python")} +// ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} +// request := sdk.NewCreateAndCallForPythonProcedureRequest(name, *returns, "3.8", packages, "filter_by_role", name). +// WithArguments([]sdk.ProcedureArgumentRequest{*arg1, *arg2}). +// WithProcedureDefinition(sdk.String(definition)). +// WithCallArguments(ca) +// err := client.Procedures.CreateAndCallForPython(ctx, request) +// require.NoError(t, err) +// }) +// +// t.Run("create and call procedure for Java: returns table and with clause", func(t *testing.T) { +// // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-java#omitting-return-column-names-and-types +// // TODO [SNOW-1348106]: make random with procedures rework +// name := sdk.NewAccountObjectIdentifier("filter_by_role") +// definition := ` +// import com.snowflake.snowpark_java.*; +// public class Filter { +// public DataFrame filterByRole(Session session, String name, String role) { +// DataFrame table = session.table(name); +// 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("name", sdk.DataTypeVARCHAR) +// arg2 := sdk.NewProcedureArgumentRequest("role", sdk.DataTypeVARCHAR) +// packages := []sdk.ProcedurePackageRequest{*sdk.NewProcedurePackageRequest("com.snowflake:snowpark:latest")} +// +// ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} +// // TODO [SNOW-1348106]: make random with procedures rework +// cte := sdk.NewAccountObjectIdentifier("records") +// statement := fmt.Sprintf(`(SELECT name, role FROM %s WHERE name = 'Bob')`, tid.FullyQualifiedName()) +// clause := sdk.NewProcedureWithClauseRequest(cte, statement).WithCteColumns([]string{"name", "role"}) +// request := sdk.NewCreateAndCallForJavaProcedureRequest(name, *returns, "11", packages, "Filter.filterByRole", name). +// WithArguments([]sdk.ProcedureArgumentRequest{*arg1, *arg2}). +// WithProcedureDefinition(sdk.String(definition)). +// WithWithClause(clause). +// WithCallArguments(ca) +// err := client.Procedures.CreateAndCallForJava(ctx, request) +// require.NoError(t, err) +// }) +//} +// +//func TestInt_ProceduresShowByID(t *testing.T) { +// client := testClient(t) +// ctx := testContext(t) +// +// cleanupProcedureHandle := func(id sdk.SchemaObjectIdentifier, dts []sdk.DataType) func() { +// return func() { +// err := client.Procedures.Drop(ctx, sdk.NewDropProcedureRequest(id, dts)) +// if errors.Is(err, sdk.ErrObjectNotExistOrAuthorized) { +// return +// } +// require.NoError(t, err) +// } +// } +// +// createProcedureForSQLHandle := func(t *testing.T, id sdk.SchemaObjectIdentifier) { +// t.Helper() +// +// 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). +// WithArguments([]sdk.ProcedureArgumentRequest{*argument}). +// WithExecuteAs(sdk.ExecuteAsPointer(sdk.ExecuteAsCaller)) +// err := client.Procedures.CreateForSQL(ctx, request) +// require.NoError(t, err) +// t.Cleanup(cleanupProcedureHandle(id, []sdk.DataType{sdk.DataTypeVARCHAR})) +// } +// +// t.Run("show by id - same name in different schemas", func(t *testing.T) { +// schema, schemaCleanup := testClientHelper().Schema.CreateSchema(t) +// t.Cleanup(schemaCleanup) +// +// id1 := testClientHelper().Ids.RandomSchemaObjectIdentifier() +// id2 := testClientHelper().Ids.NewSchemaObjectIdentifierInSchema(id1.Name(), schema.ID()) +// +// createProcedureForSQLHandle(t, id1) +// createProcedureForSQLHandle(t, id2) +// +// e1, err := client.Procedures.ShowByID(ctx, id1) +// require.NoError(t, err) +// require.Equal(t, id1, e1.ID()) +// +// e2, err := client.Procedures.ShowByID(ctx, id2) +// require.NoError(t, err) +// require.Equal(t, id2, e2.ID()) +// }) +//} From ed3947eff8988317fa008602ec098dbe31ccb87e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Thu, 25 Jul 2024 17:07:26 +0200 Subject: [PATCH 04/13] very nice save create function for java works ;) --- pkg/sdk/functions_def.go | 10 +- pkg/sdk/functions_dto_builders_gen.go | 10 +- pkg/sdk/functions_dto_gen.go | 10 +- pkg/sdk/functions_gen.go | 190 +++++++++--------- pkg/sdk/functions_gen_test.go | 20 +- pkg/sdk/testint/functions_integration_test.go | 29 ++- 6 files changed, 133 insertions(+), 136 deletions(-) diff --git a/pkg/sdk/functions_def.go b/pkg/sdk/functions_def.go index d098f89fe3..4411f6a389 100644 --- a/pkg/sdk/functions_def.go +++ b/pkg/sdk/functions_def.go @@ -50,7 +50,7 @@ var FunctionsDef = g.NewInterface( OptionalSQL("SECURE"). SQL("FUNCTION"). IfNotExists(). - Name(). + Identifier("name", g.KindOfT[SchemaObjectIdentifier](), g.IdentifierOptions().Required()). ListQueryStructField( "Arguments", functionArgument, @@ -94,7 +94,7 @@ var FunctionsDef = g.NewInterface( OptionalSQL("TEMPORARY"). OptionalSQL("SECURE"). SQL("FUNCTION"). - Name(). + Identifier("name", g.KindOfT[SchemaObjectIdentifier](), g.IdentifierOptions().Required()). ListQueryStructField( "Arguments", functionArgument, @@ -123,7 +123,7 @@ var FunctionsDef = g.NewInterface( OptionalSQL("SECURE"). SQL("FUNCTION"). IfNotExists(). - Name(). + Identifier("name", g.KindOfT[SchemaObjectIdentifier](), g.IdentifierOptions().Required()). ListQueryStructField( "Arguments", functionArgument, @@ -168,7 +168,7 @@ var FunctionsDef = g.NewInterface( OptionalSQL("SECURE"). SQL("FUNCTION"). IfNotExists(). - Name(). + Identifier("name", g.KindOfT[SchemaObjectIdentifier](), g.IdentifierOptions().Required()). ListQueryStructField( "Arguments", functionArgument, @@ -206,7 +206,7 @@ var FunctionsDef = g.NewInterface( OptionalSQL("TEMPORARY"). OptionalSQL("SECURE"). SQL("FUNCTION"). - Name(). + Identifier("name", g.KindOfT[SchemaObjectIdentifier](), g.IdentifierOptions().Required()). ListQueryStructField( "Arguments", functionArgument, diff --git a/pkg/sdk/functions_dto_builders_gen.go b/pkg/sdk/functions_dto_builders_gen.go index 95747cbc5d..0a72ce0fdc 100644 --- a/pkg/sdk/functions_dto_builders_gen.go +++ b/pkg/sdk/functions_dto_builders_gen.go @@ -5,7 +5,7 @@ package sdk import () func NewCreateForJavaFunctionRequest( - name SchemaObjectIdentifierWithArguments, + name SchemaObjectIdentifier, Returns FunctionReturnsRequest, Handler string, ) *CreateForJavaFunctionRequest { @@ -176,7 +176,7 @@ func (s *FunctionPackageRequest) WithPackage(Package string) *FunctionPackageReq } func NewCreateForJavascriptFunctionRequest( - name SchemaObjectIdentifierWithArguments, + name SchemaObjectIdentifier, Returns FunctionReturnsRequest, FunctionDefinition string, ) *CreateForJavascriptFunctionRequest { @@ -233,7 +233,7 @@ func (s *CreateForJavascriptFunctionRequest) WithComment(Comment string) *Create } func NewCreateForPythonFunctionRequest( - name SchemaObjectIdentifierWithArguments, + name SchemaObjectIdentifier, Returns FunctionReturnsRequest, RuntimeVersion string, Handler string, @@ -322,7 +322,7 @@ func (s *CreateForPythonFunctionRequest) WithFunctionDefinition(FunctionDefiniti } func NewCreateForScalaFunctionRequest( - name SchemaObjectIdentifierWithArguments, + name SchemaObjectIdentifier, ResultDataType DataType, Handler string, ) *CreateForScalaFunctionRequest { @@ -409,7 +409,7 @@ func (s *CreateForScalaFunctionRequest) WithFunctionDefinition(FunctionDefinitio } func NewCreateForSQLFunctionRequest( - name SchemaObjectIdentifierWithArguments, + name SchemaObjectIdentifier, Returns FunctionReturnsRequest, FunctionDefinition string, ) *CreateForSQLFunctionRequest { diff --git a/pkg/sdk/functions_dto_gen.go b/pkg/sdk/functions_dto_gen.go index 7942153411..6376eea28b 100644 --- a/pkg/sdk/functions_dto_gen.go +++ b/pkg/sdk/functions_dto_gen.go @@ -19,7 +19,7 @@ type CreateForJavaFunctionRequest struct { Temporary *bool Secure *bool IfNotExists *bool - name SchemaObjectIdentifierWithArguments // required + name SchemaObjectIdentifier // required Arguments []FunctionArgumentRequest CopyGrants *bool Returns FunctionReturnsRequest // required @@ -73,7 +73,7 @@ type CreateForJavascriptFunctionRequest struct { OrReplace *bool Temporary *bool Secure *bool - name SchemaObjectIdentifierWithArguments // required + name SchemaObjectIdentifier // required Arguments []FunctionArgumentRequest CopyGrants *bool Returns FunctionReturnsRequest // required @@ -89,7 +89,7 @@ type CreateForPythonFunctionRequest struct { Temporary *bool Secure *bool IfNotExists *bool - name SchemaObjectIdentifierWithArguments // required + name SchemaObjectIdentifier // required Arguments []FunctionArgumentRequest CopyGrants *bool Returns FunctionReturnsRequest // required @@ -111,7 +111,7 @@ type CreateForScalaFunctionRequest struct { Temporary *bool Secure *bool IfNotExists *bool - name SchemaObjectIdentifierWithArguments // required + name SchemaObjectIdentifier // required Arguments []FunctionArgumentRequest CopyGrants *bool ResultDataType DataType // required @@ -131,7 +131,7 @@ type CreateForSQLFunctionRequest struct { OrReplace *bool Temporary *bool Secure *bool - name SchemaObjectIdentifierWithArguments // required + name SchemaObjectIdentifier // required Arguments []FunctionArgumentRequest CopyGrants *bool Returns FunctionReturnsRequest // required diff --git a/pkg/sdk/functions_gen.go b/pkg/sdk/functions_gen.go index 1b33d5be26..84291b6d2e 100644 --- a/pkg/sdk/functions_gen.go +++ b/pkg/sdk/functions_gen.go @@ -20,29 +20,29 @@ type Functions interface { // CreateForJavaFunctionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/create-function#java-handler. type CreateForJavaFunctionOptions struct { - create bool `ddl:"static" sql:"CREATE"` - OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` - Temporary *bool `ddl:"keyword" sql:"TEMPORARY"` - Secure *bool `ddl:"keyword" sql:"SECURE"` - function bool `ddl:"static" sql:"FUNCTION"` - IfNotExists *bool `ddl:"keyword" sql:"IF NOT EXISTS"` - name SchemaObjectIdentifierWithArguments `ddl:"identifier"` - Arguments []FunctionArgument `ddl:"list,must_parentheses"` - CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` - Returns FunctionReturns `ddl:"keyword" sql:"RETURNS"` - ReturnNullValues *ReturnNullValues `ddl:"keyword"` - languageJava bool `ddl:"static" sql:"LANGUAGE JAVA"` - NullInputBehavior *NullInputBehavior `ddl:"keyword"` - ReturnResultsBehavior *ReturnResultsBehavior `ddl:"keyword"` - RuntimeVersion *string `ddl:"parameter,single_quotes" sql:"RUNTIME_VERSION"` - Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` - Imports []FunctionImport `ddl:"parameter,parentheses" sql:"IMPORTS"` - Packages []FunctionPackage `ddl:"parameter,parentheses" sql:"PACKAGES"` - 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"` - FunctionDefinition *string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` + create bool `ddl:"static" sql:"CREATE"` + OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` + Temporary *bool `ddl:"keyword" sql:"TEMPORARY"` + Secure *bool `ddl:"keyword" sql:"SECURE"` + function bool `ddl:"static" sql:"FUNCTION"` + IfNotExists *bool `ddl:"keyword" sql:"IF NOT EXISTS"` + name SchemaObjectIdentifier `ddl:"identifier"` + Arguments []FunctionArgument `ddl:"list,must_parentheses"` + CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` + Returns FunctionReturns `ddl:"keyword" sql:"RETURNS"` + ReturnNullValues *ReturnNullValues `ddl:"keyword"` + languageJava bool `ddl:"static" sql:"LANGUAGE JAVA"` + NullInputBehavior *NullInputBehavior `ddl:"keyword"` + ReturnResultsBehavior *ReturnResultsBehavior `ddl:"keyword"` + RuntimeVersion *string `ddl:"parameter,single_quotes" sql:"RUNTIME_VERSION"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + Imports []FunctionImport `ddl:"parameter,parentheses" sql:"IMPORTS"` + Packages []FunctionPackage `ddl:"parameter,parentheses" sql:"PACKAGES"` + 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"` + FunctionDefinition *string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` } type FunctionArgument struct { @@ -79,90 +79,90 @@ type FunctionPackage struct { // CreateForJavascriptFunctionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/create-function#javascript-handler. type CreateForJavascriptFunctionOptions struct { - create bool `ddl:"static" sql:"CREATE"` - OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` - Temporary *bool `ddl:"keyword" sql:"TEMPORARY"` - Secure *bool `ddl:"keyword" sql:"SECURE"` - function bool `ddl:"static" sql:"FUNCTION"` - name SchemaObjectIdentifierWithArguments `ddl:"identifier"` - Arguments []FunctionArgument `ddl:"list,must_parentheses"` - CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` - Returns FunctionReturns `ddl:"keyword" sql:"RETURNS"` - ReturnNullValues *ReturnNullValues `ddl:"keyword"` - languageJavascript bool `ddl:"static" sql:"LANGUAGE JAVASCRIPT"` - NullInputBehavior *NullInputBehavior `ddl:"keyword"` - ReturnResultsBehavior *ReturnResultsBehavior `ddl:"keyword"` - Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` - FunctionDefinition string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` + create bool `ddl:"static" sql:"CREATE"` + OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` + Temporary *bool `ddl:"keyword" sql:"TEMPORARY"` + Secure *bool `ddl:"keyword" sql:"SECURE"` + function bool `ddl:"static" sql:"FUNCTION"` + name SchemaObjectIdentifier `ddl:"identifier"` + Arguments []FunctionArgument `ddl:"list,must_parentheses"` + CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` + Returns FunctionReturns `ddl:"keyword" sql:"RETURNS"` + ReturnNullValues *ReturnNullValues `ddl:"keyword"` + languageJavascript bool `ddl:"static" sql:"LANGUAGE JAVASCRIPT"` + NullInputBehavior *NullInputBehavior `ddl:"keyword"` + ReturnResultsBehavior *ReturnResultsBehavior `ddl:"keyword"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + FunctionDefinition string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` } // CreateForPythonFunctionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/create-function#python-handler. type CreateForPythonFunctionOptions struct { - create bool `ddl:"static" sql:"CREATE"` - OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` - Temporary *bool `ddl:"keyword" sql:"TEMPORARY"` - Secure *bool `ddl:"keyword" sql:"SECURE"` - function bool `ddl:"static" sql:"FUNCTION"` - IfNotExists *bool `ddl:"keyword" sql:"IF NOT EXISTS"` - name SchemaObjectIdentifierWithArguments `ddl:"identifier"` - Arguments []FunctionArgument `ddl:"list,must_parentheses"` - CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` - Returns FunctionReturns `ddl:"keyword" sql:"RETURNS"` - ReturnNullValues *ReturnNullValues `ddl:"keyword"` - languagePython bool `ddl:"static" sql:"LANGUAGE PYTHON"` - NullInputBehavior *NullInputBehavior `ddl:"keyword"` - ReturnResultsBehavior *ReturnResultsBehavior `ddl:"keyword"` - RuntimeVersion string `ddl:"parameter,single_quotes" sql:"RUNTIME_VERSION"` - Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` - Imports []FunctionImport `ddl:"parameter,parentheses" sql:"IMPORTS"` - Packages []FunctionPackage `ddl:"parameter,parentheses" sql:"PACKAGES"` - Handler string `ddl:"parameter,single_quotes" sql:"HANDLER"` - ExternalAccessIntegrations []AccountObjectIdentifier `ddl:"parameter,parentheses" sql:"EXTERNAL_ACCESS_INTEGRATIONS"` - Secrets []Secret `ddl:"parameter,parentheses" sql:"SECRETS"` - FunctionDefinition *string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` + create bool `ddl:"static" sql:"CREATE"` + OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` + Temporary *bool `ddl:"keyword" sql:"TEMPORARY"` + Secure *bool `ddl:"keyword" sql:"SECURE"` + function bool `ddl:"static" sql:"FUNCTION"` + IfNotExists *bool `ddl:"keyword" sql:"IF NOT EXISTS"` + name SchemaObjectIdentifier `ddl:"identifier"` + Arguments []FunctionArgument `ddl:"list,must_parentheses"` + CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` + Returns FunctionReturns `ddl:"keyword" sql:"RETURNS"` + ReturnNullValues *ReturnNullValues `ddl:"keyword"` + languagePython bool `ddl:"static" sql:"LANGUAGE PYTHON"` + NullInputBehavior *NullInputBehavior `ddl:"keyword"` + ReturnResultsBehavior *ReturnResultsBehavior `ddl:"keyword"` + RuntimeVersion string `ddl:"parameter,single_quotes" sql:"RUNTIME_VERSION"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + Imports []FunctionImport `ddl:"parameter,parentheses" sql:"IMPORTS"` + Packages []FunctionPackage `ddl:"parameter,parentheses" sql:"PACKAGES"` + Handler string `ddl:"parameter,single_quotes" sql:"HANDLER"` + ExternalAccessIntegrations []AccountObjectIdentifier `ddl:"parameter,parentheses" sql:"EXTERNAL_ACCESS_INTEGRATIONS"` + Secrets []Secret `ddl:"parameter,parentheses" sql:"SECRETS"` + FunctionDefinition *string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` } // CreateForScalaFunctionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/create-function#scala-handler. type CreateForScalaFunctionOptions struct { - create bool `ddl:"static" sql:"CREATE"` - OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` - Temporary *bool `ddl:"keyword" sql:"TEMPORARY"` - Secure *bool `ddl:"keyword" sql:"SECURE"` - function bool `ddl:"static" sql:"FUNCTION"` - IfNotExists *bool `ddl:"keyword" sql:"IF NOT EXISTS"` - name SchemaObjectIdentifierWithArguments `ddl:"identifier"` - Arguments []FunctionArgument `ddl:"list,must_parentheses"` - CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` - ResultDataType DataType `ddl:"parameter,no_equals" sql:"RETURNS"` - ReturnNullValues *ReturnNullValues `ddl:"keyword"` - languageScala bool `ddl:"static" sql:"LANGUAGE SCALA"` - NullInputBehavior *NullInputBehavior `ddl:"keyword"` - ReturnResultsBehavior *ReturnResultsBehavior `ddl:"keyword"` - RuntimeVersion *string `ddl:"parameter,single_quotes" sql:"RUNTIME_VERSION"` - Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` - Imports []FunctionImport `ddl:"parameter,parentheses" sql:"IMPORTS"` - Packages []FunctionPackage `ddl:"parameter,parentheses" sql:"PACKAGES"` - Handler string `ddl:"parameter,single_quotes" sql:"HANDLER"` - TargetPath *string `ddl:"parameter,single_quotes" sql:"TARGET_PATH"` - FunctionDefinition *string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` + create bool `ddl:"static" sql:"CREATE"` + OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` + Temporary *bool `ddl:"keyword" sql:"TEMPORARY"` + Secure *bool `ddl:"keyword" sql:"SECURE"` + function bool `ddl:"static" sql:"FUNCTION"` + IfNotExists *bool `ddl:"keyword" sql:"IF NOT EXISTS"` + name SchemaObjectIdentifier `ddl:"identifier"` + Arguments []FunctionArgument `ddl:"list,must_parentheses"` + CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` + ResultDataType DataType `ddl:"parameter,no_equals" sql:"RETURNS"` + ReturnNullValues *ReturnNullValues `ddl:"keyword"` + languageScala bool `ddl:"static" sql:"LANGUAGE SCALA"` + NullInputBehavior *NullInputBehavior `ddl:"keyword"` + ReturnResultsBehavior *ReturnResultsBehavior `ddl:"keyword"` + RuntimeVersion *string `ddl:"parameter,single_quotes" sql:"RUNTIME_VERSION"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + Imports []FunctionImport `ddl:"parameter,parentheses" sql:"IMPORTS"` + Packages []FunctionPackage `ddl:"parameter,parentheses" sql:"PACKAGES"` + Handler string `ddl:"parameter,single_quotes" sql:"HANDLER"` + TargetPath *string `ddl:"parameter,single_quotes" sql:"TARGET_PATH"` + FunctionDefinition *string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` } // CreateForSQLFunctionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/create-function#sql-handler. type CreateForSQLFunctionOptions struct { - create bool `ddl:"static" sql:"CREATE"` - OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` - Temporary *bool `ddl:"keyword" sql:"TEMPORARY"` - Secure *bool `ddl:"keyword" sql:"SECURE"` - function bool `ddl:"static" sql:"FUNCTION"` - name SchemaObjectIdentifierWithArguments `ddl:"identifier"` - Arguments []FunctionArgument `ddl:"list,must_parentheses"` - CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` - Returns FunctionReturns `ddl:"keyword" sql:"RETURNS"` - ReturnNullValues *ReturnNullValues `ddl:"keyword"` - ReturnResultsBehavior *ReturnResultsBehavior `ddl:"keyword"` - Memoizable *bool `ddl:"keyword" sql:"MEMOIZABLE"` - Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` - FunctionDefinition string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` + create bool `ddl:"static" sql:"CREATE"` + OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` + Temporary *bool `ddl:"keyword" sql:"TEMPORARY"` + Secure *bool `ddl:"keyword" sql:"SECURE"` + function bool `ddl:"static" sql:"FUNCTION"` + name SchemaObjectIdentifier `ddl:"identifier"` + Arguments []FunctionArgument `ddl:"list,must_parentheses"` + CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` + Returns FunctionReturns `ddl:"keyword" sql:"RETURNS"` + ReturnNullValues *ReturnNullValues `ddl:"keyword"` + ReturnResultsBehavior *ReturnResultsBehavior `ddl:"keyword"` + Memoizable *bool `ddl:"keyword" sql:"MEMOIZABLE"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + FunctionDefinition string `ddl:"parameter,single_quotes,no_equals" sql:"AS"` } // AlterFunctionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/alter-function. diff --git a/pkg/sdk/functions_gen_test.go b/pkg/sdk/functions_gen_test.go index 06725a9a6e..0bb4778832 100644 --- a/pkg/sdk/functions_gen_test.go +++ b/pkg/sdk/functions_gen_test.go @@ -5,7 +5,7 @@ import ( ) func TestFunctions_CreateForJava(t *testing.T) { - id := randomSchemaObjectIdentifierWithArguments() + id := randomSchemaObjectIdentifier() defaultOpts := func() *CreateForJavaFunctionOptions { return &CreateForJavaFunctionOptions{ @@ -20,7 +20,7 @@ func TestFunctions_CreateForJava(t *testing.T) { t.Run("validation: incorrect identifier", func(t *testing.T) { opts := defaultOpts() - opts.name = emptySchemaObjectIdentifierWithArguments + opts.name = emptySchemaObjectIdentifier assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) }) @@ -120,7 +120,7 @@ func TestFunctions_CreateForJava(t *testing.T) { } func TestFunctions_CreateForJavascript(t *testing.T) { - id := randomSchemaObjectIdentifierWithArguments() + id := randomSchemaObjectIdentifier() defaultOpts := func() *CreateForJavascriptFunctionOptions { return &CreateForJavascriptFunctionOptions{ @@ -135,7 +135,7 @@ func TestFunctions_CreateForJavascript(t *testing.T) { t.Run("validation: incorrect identifier", func(t *testing.T) { opts := defaultOpts() - opts.name = emptySchemaObjectIdentifierWithArguments + opts.name = emptySchemaObjectIdentifier assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) }) @@ -183,7 +183,7 @@ func TestFunctions_CreateForJavascript(t *testing.T) { } func TestFunctions_CreateForPython(t *testing.T) { - id := randomSchemaObjectIdentifierWithArguments() + id := randomSchemaObjectIdentifier() defaultOpts := func() *CreateForPythonFunctionOptions { return &CreateForPythonFunctionOptions{ @@ -198,7 +198,7 @@ func TestFunctions_CreateForPython(t *testing.T) { t.Run("validation: incorrect identifier", func(t *testing.T) { opts := defaultOpts() - opts.name = emptySchemaObjectIdentifierWithArguments + opts.name = emptySchemaObjectIdentifier assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) }) @@ -288,7 +288,7 @@ func TestFunctions_CreateForPython(t *testing.T) { } func TestFunctions_CreateForScala(t *testing.T) { - id := randomSchemaObjectIdentifierWithArguments() + id := randomSchemaObjectIdentifier() defaultOpts := func() *CreateForScalaFunctionOptions { return &CreateForScalaFunctionOptions{ @@ -303,7 +303,7 @@ func TestFunctions_CreateForScala(t *testing.T) { t.Run("validation: incorrect identifier", func(t *testing.T) { opts := defaultOpts() - opts.name = emptySchemaObjectIdentifierWithArguments + opts.name = emptySchemaObjectIdentifier assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) }) @@ -357,7 +357,7 @@ func TestFunctions_CreateForScala(t *testing.T) { } func TestFunctions_CreateForSQL(t *testing.T) { - id := randomSchemaObjectIdentifierWithArguments() + id := randomSchemaObjectIdentifier() defaultOpts := func() *CreateForSQLFunctionOptions { return &CreateForSQLFunctionOptions{ @@ -372,7 +372,7 @@ func TestFunctions_CreateForSQL(t *testing.T) { t.Run("validation: incorrect identifier", func(t *testing.T) { opts := defaultOpts() - opts.name = emptySchemaObjectIdentifierWithArguments + opts.name = emptySchemaObjectIdentifier assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) }) diff --git a/pkg/sdk/testint/functions_integration_test.go b/pkg/sdk/testint/functions_integration_test.go index 82d7a2fd1b..a237d77df6 100644 --- a/pkg/sdk/testint/functions_integration_test.go +++ b/pkg/sdk/testint/functions_integration_test.go @@ -34,8 +34,7 @@ func TestInt_CreateFunctions(t *testing.T) { } t.Run("create function for Java", func(t *testing.T) { - name := "echo_varchar" - id := testClientHelper().Ids.NewSchemaObjectIdentifierWithArguments(name, sdk.DataTypeVARCHAR) + id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(sdk.DataTypeVARCHAR) definition := ` class TestFunc { @@ -47,7 +46,7 @@ func TestInt_CreateFunctions(t *testing.T) { dt := sdk.NewFunctionReturnsResultDataTypeRequest(sdk.DataTypeVARCHAR) returns := sdk.NewFunctionReturnsRequest().WithResultDataType(*dt) argument := sdk.NewFunctionArgumentRequest("x", sdk.DataTypeVARCHAR).WithDefaultValue("'abc'") - request := sdk.NewCreateForJavaFunctionRequest(id, *returns, "TestFunc.echoVarchar"). + request := sdk.NewCreateForJavaFunctionRequest(id.SchemaObjectId(), *returns, "TestFunc.echoVarchar"). WithOrReplace(true). WithArguments([]sdk.FunctionArgumentRequest{*argument}). WithNullInputBehavior(*sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorCalledOnNullInput)). @@ -64,8 +63,7 @@ func TestInt_CreateFunctions(t *testing.T) { }) t.Run("create function for Javascript", func(t *testing.T) { - name := "js_factorial" - id := testClientHelper().Ids.NewSchemaObjectIdentifierWithArguments(name, sdk.DataTypeFloat) + id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(sdk.DataTypeFloat) definition := ` if (D <= 0) { @@ -81,7 +79,7 @@ func TestInt_CreateFunctions(t *testing.T) { dt := sdk.NewFunctionReturnsResultDataTypeRequest(sdk.DataTypeFloat) returns := sdk.NewFunctionReturnsRequest().WithResultDataType(*dt) argument := sdk.NewFunctionArgumentRequest("d", sdk.DataTypeFloat) - request := sdk.NewCreateForJavascriptFunctionRequest(id, *returns, definition). + request := sdk.NewCreateForJavascriptFunctionRequest(id.SchemaObjectId(), *returns, definition). WithOrReplace(true). WithArguments([]sdk.FunctionArgumentRequest{*argument}). WithNullInputBehavior(*sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorCalledOnNullInput)) @@ -104,7 +102,7 @@ def dump(i): dt := sdk.NewFunctionReturnsResultDataTypeRequest(sdk.DataTypeVariant) returns := sdk.NewFunctionReturnsRequest().WithResultDataType(*dt) argument := sdk.NewFunctionArgumentRequest("i", sdk.DataTypeNumber) - request := sdk.NewCreateForPythonFunctionRequest(id, *returns, "3.8", "dump"). + request := sdk.NewCreateForPythonFunctionRequest(id.SchemaObjectId(), *returns, "3.8", "dump"). WithOrReplace(true). WithArguments([]sdk.FunctionArgumentRequest{*argument}). WithFunctionDefinition(definition) @@ -119,8 +117,7 @@ def dump(i): }) t.Run("create function for Scala", func(t *testing.T) { - name := "echo_varchar" - id := testClientHelper().Ids.NewSchemaObjectIdentifierWithArguments(name, sdk.DataTypeVARCHAR) + id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(sdk.DataTypeVARCHAR) definition := ` class Echo { @@ -130,7 +127,7 @@ def dump(i): }` argument := sdk.NewFunctionArgumentRequest("x", sdk.DataTypeVARCHAR) - request := sdk.NewCreateForScalaFunctionRequest(id, sdk.DataTypeVARCHAR, "Echo.echoVarchar"). + request := sdk.NewCreateForScalaFunctionRequest(id.SchemaObjectId(), sdk.DataTypeVARCHAR, "Echo.echoVarchar"). WithOrReplace(true). WithArguments([]sdk.FunctionArgumentRequest{*argument}). WithRuntimeVersion("2.12"). @@ -153,7 +150,7 @@ def dump(i): dt := sdk.NewFunctionReturnsResultDataTypeRequest(sdk.DataTypeFloat) returns := sdk.NewFunctionReturnsRequest().WithResultDataType(*dt) argument := sdk.NewFunctionArgumentRequest("x", sdk.DataTypeFloat) - request := sdk.NewCreateForSQLFunctionRequest(id, *returns, definition). + request := sdk.NewCreateForSQLFunctionRequest(id.SchemaObjectId(), *returns, definition). WithArguments([]sdk.FunctionArgumentRequest{*argument}). WithOrReplace(true). WithComment("comment") @@ -174,7 +171,7 @@ def dump(i): dt := sdk.NewFunctionReturnsResultDataTypeRequest(sdk.DataTypeFloat) returns := sdk.NewFunctionReturnsRequest().WithResultDataType(*dt) - request := sdk.NewCreateForSQLFunctionRequest(id, *returns, definition). + request := sdk.NewCreateForSQLFunctionRequest(id.SchemaObjectId(), *returns, definition). WithOrReplace(true). WithComment("comment") err := client.Functions.CreateForSQL(ctx, request) @@ -242,7 +239,7 @@ func TestInt_OtherFunctions(t *testing.T) { dt := sdk.NewFunctionReturnsResultDataTypeRequest(sdk.DataTypeFloat) returns := sdk.NewFunctionReturnsRequest().WithResultDataType(*dt) - request := sdk.NewCreateForSQLFunctionRequest(id, *returns, definition). + request := sdk.NewCreateForSQLFunctionRequest(id.SchemaObjectId(), *returns, definition). WithOrReplace(true) if withArguments { argument := sdk.NewFunctionArgumentRequest("x", sdk.DataTypeFloat) @@ -449,7 +446,7 @@ func TestInt_FunctionsShowByID(t *testing.T) { client := testClient(t) ctx := testContext(t) - cleanupFunctionHandle := func(id sdk.SchemaObjectIdentifierWithArguments, dts []sdk.DataType) func() { + cleanupFunctionHandle := func(id sdk.SchemaObjectIdentifierWithArguments) func() { return func() { err := client.Functions.Drop(ctx, sdk.NewDropFunctionRequest(id)) if errors.Is(err, sdk.ErrObjectNotExistOrAuthorized) { @@ -465,13 +462,13 @@ func TestInt_FunctionsShowByID(t *testing.T) { definition := "3.141592654::FLOAT" dt := sdk.NewFunctionReturnsResultDataTypeRequest(sdk.DataTypeFloat) returns := sdk.NewFunctionReturnsRequest().WithResultDataType(*dt) - request := sdk.NewCreateForSQLFunctionRequest(id, *returns, definition).WithOrReplace(true) + request := sdk.NewCreateForSQLFunctionRequest(id.SchemaObjectId(), *returns, definition).WithOrReplace(true) argument := sdk.NewFunctionArgumentRequest("x", sdk.DataTypeFloat) request = request.WithArguments([]sdk.FunctionArgumentRequest{*argument}) err := client.Functions.CreateForSQL(ctx, request) require.NoError(t, err) - t.Cleanup(cleanupFunctionHandle(id, []sdk.DataType{sdk.DataTypeFloat})) + t.Cleanup(cleanupFunctionHandle(id)) } t.Run("show by id - same name in different schemas", func(t *testing.T) { From 39be3822afa98f70f65f95e2783b45c1a573f4c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Thu, 1 Aug 2024 14:18:18 +0200 Subject: [PATCH 05/13] wip --- pkg/acceptance/check_destroy.go | 25 +- pkg/datasources/functions.go | 2 +- pkg/provider/provider.go | 10 +- pkg/resources/function.go | 1528 +++++++++-------- pkg/resources/function_acceptance_test.go | 2 +- pkg/resources/function_state_upgraders.go | 110 +- pkg/resources/procedure_acceptance_test.go | 763 ++++---- pkg/schemas/function_gen.go | 2 +- pkg/schemas/gen/main/main.go | 1 + pkg/sdk/data_types.go | 5 + pkg/sdk/functions_gen.go | 59 +- pkg/sdk/functions_impl_gen.go | 4 +- pkg/sdk/identifier_helpers.go | 67 +- pkg/sdk/identifier_helpers_test.go | 20 + pkg/sdk/testint/functions_integration_test.go | 595 ++++--- 15 files changed, 1691 insertions(+), 1502 deletions(-) diff --git a/pkg/acceptance/check_destroy.go b/pkg/acceptance/check_destroy.go index 0c4b8f0e65..e5ae067b60 100644 --- a/pkg/acceptance/check_destroy.go +++ b/pkg/acceptance/check_destroy.go @@ -27,7 +27,10 @@ func CheckDestroy(t *testing.T, resource resources.Resource) func(*terraform.Sta } t.Logf("found resource %s in state", resource) ctx := context.Background() - id := decodeSnowflakeId(rs, resource) + id, err := decodeSnowflakeId(rs, resource) + if err != nil { + return err + } if id == nil { return fmt.Errorf("could not get the id of %s", resource) } @@ -45,16 +48,16 @@ func CheckDestroy(t *testing.T, resource resources.Resource) func(*terraform.Sta } } -func decodeSnowflakeId(rs *terraform.ResourceState, resource resources.Resource) sdk.ObjectIdentifier { +func decodeSnowflakeId(rs *terraform.ResourceState, resource resources.Resource) (sdk.ObjectIdentifier, error) { switch resource { case resources.ExternalFunction: - return sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(rs.Primary.ID) + return sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(rs.Primary.ID), nil case resources.Function: - return sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(rs.Primary.ID) + return sdk.NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(rs.Primary.ID) case resources.Procedure: - return sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(rs.Primary.ID) + return sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(rs.Primary.ID), nil default: - return helpers.DecodeSnowflakeID(rs.Primary.ID) + return helpers.DecodeSnowflakeID(rs.Primary.ID), nil } } @@ -115,9 +118,9 @@ var showByIdFunctions = map[resources.Resource]showByIdFunc{ resources.FileFormat: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { return runShowById(ctx, id, client.FileFormats.ShowByID) }, - //resources.Function: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { - // return runShowById(ctx, id, client.Functions.ShowByID) - //}, + resources.Function: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { + return runShowById(ctx, id, client.Functions.ShowByID) + }, resources.ManagedAccount: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { return runShowById(ctx, id, client.ManagedAccounts.ShowByID) }, @@ -213,7 +216,7 @@ var showByIdFunctions = map[resources.Resource]showByIdFunc{ }, } -func runShowById[T any, U sdk.AccountObjectIdentifier | sdk.DatabaseObjectIdentifier | sdk.SchemaObjectIdentifier | sdk.TableColumnIdentifier](ctx context.Context, id sdk.ObjectIdentifier, show func(ctx context.Context, id U) (T, error)) error { +func runShowById[T any, U sdk.AccountObjectIdentifier | sdk.DatabaseObjectIdentifier | sdk.SchemaObjectIdentifier | sdk.TableColumnIdentifier | sdk.SchemaObjectIdentifierWithArguments](ctx context.Context, id sdk.ObjectIdentifier, show func(ctx context.Context, id U) (T, error)) error { idCast, err := asId[U](id) if err != nil { return err @@ -222,7 +225,7 @@ func runShowById[T any, U sdk.AccountObjectIdentifier | sdk.DatabaseObjectIdenti return err } -func asId[T sdk.AccountObjectIdentifier | sdk.DatabaseObjectIdentifier | sdk.SchemaObjectIdentifier | sdk.TableColumnIdentifier](id sdk.ObjectIdentifier) (*T, error) { +func asId[T sdk.AccountObjectIdentifier | sdk.DatabaseObjectIdentifier | sdk.SchemaObjectIdentifier | sdk.TableColumnIdentifier | sdk.SchemaObjectIdentifierWithArguments](id sdk.ObjectIdentifier) (*T, error) { if idCast, ok := id.(T); !ok { return nil, fmt.Errorf("expected %s identifier type, but got: %T", reflect.TypeOf(new(T)).Elem().Name(), id) } else { diff --git a/pkg/datasources/functions.go b/pkg/datasources/functions.go index 44a92b4568..548f18ec5f 100644 --- a/pkg/datasources/functions.go +++ b/pkg/datasources/functions.go @@ -92,7 +92,7 @@ func ReadContextFunctions(ctx context.Context, d *schema.ResourceData, meta inte entities := []map[string]interface{}{} for _, item := range functions { - signature, err := parseArguments(item.Arguments) + signature, err := parseArguments(item.ArgumentsRaw) if err != nil { return diag.FromErr(err) } diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 371158f5d6..f5c8f2d76a 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -437,11 +437,11 @@ func getResources() map[string]*schema.Resource { "snowflake_dynamic_table": resources.DynamicTable(), "snowflake_email_notification_integration": resources.EmailNotificationIntegration(), //"snowflake_external_function": resources.ExternalFunction(), - "snowflake_external_oauth_integration": resources.ExternalOauthIntegration(), - "snowflake_external_table": resources.ExternalTable(), - "snowflake_failover_group": resources.FailoverGroup(), - "snowflake_file_format": resources.FileFormat(), - //"snowflake_function": resources.Function(), + "snowflake_external_oauth_integration": resources.ExternalOauthIntegration(), + "snowflake_external_table": resources.ExternalTable(), + "snowflake_failover_group": resources.FailoverGroup(), + "snowflake_file_format": resources.FileFormat(), + "snowflake_function": resources.Function(), "snowflake_grant_account_role": resources.GrantAccountRole(), "snowflake_grant_application_role": resources.GrantApplicationRole(), "snowflake_grant_database_role": resources.GrantDatabaseRole(), diff --git a/pkg/resources/function.go b/pkg/resources/function.go index 1e9c309472..747429a270 100644 --- a/pkg/resources/function.go +++ b/pkg/resources/function.go @@ -1,757 +1,775 @@ package resources -//var languages = []string{"javascript", "scala", "java", "sql", "python"} -// -//var functionSchema = map[string]*schema.Schema{ -// "name": { -// Type: schema.TypeString, -// Required: true, -// Description: "Specifies the identifier for the function; does not have to be unique for the schema in which the function is created. Don't use the | character.", -// }, -// "database": { -// Type: schema.TypeString, -// Required: true, -// Description: "The database in which to create the function. Don't use the | character.", -// ForceNew: true, -// }, -// "schema": { -// Type: schema.TypeString, -// Required: true, -// Description: "The schema in which to create the function. Don't use the | character.", -// ForceNew: true, -// }, -// "arguments": { -// Type: schema.TypeList, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "name": { -// Type: schema.TypeString, -// Required: true, -// // Suppress the diff shown if the values are equal when both compared in lower case. -// DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { -// return strings.EqualFold(old, new) -// }, -// Description: "The argument name", -// }, -// "type": { -// Type: schema.TypeString, -// Required: true, -// // Suppress the diff shown if the values are equal when both compared in lower case. -// DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { -// return strings.EqualFold(old, new) -// }, -// Description: "The argument type", -// }, -// }, -// }, -// Optional: true, -// Description: "List of the arguments for the function", -// ForceNew: true, -// }, -// "return_type": { -// Type: schema.TypeString, -// Description: "The return type of the function", -// // Suppress the diff shown if the values are equal when both compared in lower case. -// DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { -// return strings.EqualFold(old, new) -// }, -// Required: true, -// ForceNew: true, -// }, -// "statement": { -// Type: schema.TypeString, -// Required: true, -// Description: "Specifies the javascript / java / scala / sql / python code used to create the function.", -// ForceNew: true, -// DiffSuppressFunc: DiffSuppressStatement, -// }, -// "language": { -// Type: schema.TypeString, -// Optional: true, -// Default: "SQL", -// DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { -// return strings.EqualFold(old, new) -// }, -// ValidateFunc: validation.StringInSlice(languages, true), -// Description: "Specifies the language of the stored function code.", -// }, -// "null_input_behavior": { -// Type: schema.TypeString, -// Optional: true, -// Default: "CALLED ON NULL INPUT", -// ForceNew: true, -// // We do not use STRICT, because Snowflake then in the Read phase returns RETURNS NULL ON NULL INPUT -// ValidateFunc: validation.StringInSlice([]string{"CALLED ON NULL INPUT", "RETURNS NULL ON NULL INPUT"}, false), -// Description: "Specifies the behavior of the function when called with null inputs.", -// }, -// "return_behavior": { -// Type: schema.TypeString, -// Optional: true, -// Default: "VOLATILE", -// ForceNew: true, -// ValidateFunc: validation.StringInSlice([]string{"VOLATILE", "IMMUTABLE"}, false), -// Description: "Specifies the behavior of the function when returning results", -// }, -// "is_secure": { -// Type: schema.TypeBool, -// Optional: true, -// Default: false, -// Description: "Specifies that the function is secure.", -// }, -// "comment": { -// Type: schema.TypeString, -// Optional: true, -// Default: "user-defined function", -// Description: "Specifies a comment for the function.", -// }, -// "runtime_version": { -// Type: schema.TypeString, -// Optional: true, -// ForceNew: true, -// Description: "Required for Python functions. Specifies Python runtime version.", -// }, -// "packages": { -// Type: schema.TypeList, -// Elem: &schema.Schema{ -// Type: schema.TypeString, -// }, -// Optional: true, -// ForceNew: true, -// Description: "List of package imports to use for Java / Python functions. For Java, package imports should be of the form: package_name:version_number, where package_name is snowflake_domain:package. For Python use it should be: ('numpy','pandas','xgboost==1.5.0').", -// }, -// "imports": { -// Type: schema.TypeList, -// Elem: &schema.Schema{ -// Type: schema.TypeString, -// }, -// Optional: true, -// ForceNew: true, -// Description: "Imports for Java / Python functions. For Java this a list of jar files, for Python this is a list of Python files.", -// }, -// "handler": { -// Type: schema.TypeString, -// Optional: true, -// ForceNew: true, -// Description: "The handler method for Java / Python function.", -// }, -// "target_path": { -// Type: schema.TypeString, -// Optional: true, -// ForceNew: true, -// Description: "The target path for the Java / Python functions. For Java, it is the path of compiled jar files and for the Python it is the path of the Python files.", -// }, -//} -// -//// Function returns a pointer to the resource representing a stored function. -//func Function() *schema.Resource { -// return &schema.Resource{ -// SchemaVersion: 1, -// -// CreateContext: CreateContextFunction, -// ReadContext: ReadContextFunction, -// UpdateContext: UpdateContextFunction, -// DeleteContext: DeleteContextFunction, -// -// Schema: functionSchema, -// Importer: &schema.ResourceImporter{ -// StateContext: schema.ImportStatePassthroughContext, -// }, -// -// StateUpgraders: []schema.StateUpgrader{ -// { -// Version: 0, -// // setting type to cty.EmptyObject is a bit hacky here but following https://developer.hashicorp.com/terraform/plugin/framework/migrating/resources/state-upgrade#sdkv2-1 would require lots of repetitive code; this should work with cty.EmptyObject -// Type: cty.EmptyObject, -// Upgrade: v085FunctionIdStateUpgrader, -// }, -// }, -// } -//} -// -//func CreateContextFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// lang := strings.ToUpper(d.Get("language").(string)) -// switch lang { -// case "JAVA": -// return createJavaFunction(ctx, d, meta) -// case "JAVASCRIPT": -// return createJavascriptFunction(ctx, d, meta) -// case "PYTHON": -// return createPythonFunction(ctx, d, meta) -// case "SCALA": -// return createScalaFunction(ctx, d, meta) -// case "", "SQL": // SQL if language is not set -// return createSQLFunction(ctx, d, meta) -// default: -// return diag.Diagnostics{ -// diag.Diagnostic{ -// Severity: diag.Error, -// Summary: "Invalid language", -// Detail: fmt.Sprintf("Language %s is not supported", lang), -// }, -// } -// } -//} -// -//func createJavaFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// client := meta.(*provider.Context).Client -// name := d.Get("name").(string) -// schema := d.Get("schema").(string) -// database := d.Get("database").(string) -// id := sdk.NewSchemaObjectIdentifier(database, schema, name) -// -// // Set required -// returns, diags := parseFunctionReturnsRequest(d.Get("return_type").(string)) -// if diags != nil { -// return diags -// } -// handler := d.Get("handler").(string) -// // create request with required -// request := sdk.NewCreateForJavaFunctionRequest(id, *returns, handler) -// functionDefinition := d.Get("statement").(string) -// request.WithFunctionDefinition(sdk.String(functionDefinition)) -// -// // Set optionals -// if v, ok := d.GetOk("is_secure"); ok { -// request.WithSecure(sdk.Bool(v.(bool))) -// } -// arguments, diags := parseFunctionArguments(d) -// if diags != nil { -// return diags -// } -// if len(arguments) > 0 { -// request.WithArguments(arguments) -// } -// if v, ok := d.GetOk("null_input_behavior"); ok { -// request.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehavior(v.(string)))) -// } -// if v, ok := d.GetOk("return_behavior"); ok { -// request.WithReturnResultsBehavior(sdk.Pointer(sdk.ReturnResultsBehavior(v.(string)))) -// } -// if v, ok := d.GetOk("runtime_version"); ok { -// request.WithRuntimeVersion(sdk.String(v.(string))) -// } -// if v, ok := d.GetOk("comment"); ok { -// request.WithComment(sdk.String(v.(string))) -// } -// if _, ok := d.GetOk("imports"); ok { -// imports := []sdk.FunctionImportRequest{} -// for _, item := range d.Get("imports").([]interface{}) { -// imports = append(imports, *sdk.NewFunctionImportRequest().WithImport(item.(string))) -// } -// request.WithImports(imports) -// } -// if _, ok := d.GetOk("packages"); ok { -// packages := []sdk.FunctionPackageRequest{} -// for _, item := range d.Get("packages").([]interface{}) { -// packages = append(packages, *sdk.NewFunctionPackageRequest().WithPackage(item.(string))) -// } -// request.WithPackages(packages) -// } -// if v, ok := d.GetOk("target_path"); ok { -// request.WithTargetPath(sdk.String(v.(string))) -// } -// -// if err := client.Functions.CreateForJava(ctx, request); err != nil { -// return diag.FromErr(err) -// } -// argumentTypes := make([]sdk.DataType, 0, len(arguments)) -// for _, item := range arguments { -// argumentTypes = append(argumentTypes, item.ArgDataType) -// } -// nid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argumentTypes) -// d.SetId(nid.FullyQualifiedName()) -// return ReadContextFunction(ctx, d, meta) -//} -// -//func createScalaFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// client := meta.(*provider.Context).Client -// name := d.Get("name").(string) -// schema := d.Get("schema").(string) -// database := d.Get("database").(string) -// id := sdk.NewSchemaObjectIdentifier(database, schema, name) -// -// // Set required -// returnType := d.Get("return_type").(string) -// returnDataType, diags := convertFunctionDataType(returnType) -// if diags != nil { -// return diags -// } -// functionDefinition := d.Get("statement").(string) -// handler := d.Get("handler").(string) -// // create request with required -// request := sdk.NewCreateForScalaFunctionRequest(id, returnDataType, handler) -// request.WithFunctionDefinition(sdk.String(functionDefinition)) -// -// // Set optionals -// if v, ok := d.GetOk("is_secure"); ok { -// request.WithSecure(sdk.Bool(v.(bool))) -// } -// arguments, diags := parseFunctionArguments(d) -// if diags != nil { -// return diags -// } -// if len(arguments) > 0 { -// request.WithArguments(arguments) -// } -// if v, ok := d.GetOk("null_input_behavior"); ok { -// request.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehavior(v.(string)))) -// } -// if v, ok := d.GetOk("return_behavior"); ok { -// request.WithReturnResultsBehavior(sdk.Pointer(sdk.ReturnResultsBehavior(v.(string)))) -// } -// if v, ok := d.GetOk("runtime_version"); ok { -// request.WithRuntimeVersion(sdk.String(v.(string))) -// } -// if v, ok := d.GetOk("comment"); ok { -// request.WithComment(sdk.String(v.(string))) -// } -// if _, ok := d.GetOk("imports"); ok { -// imports := []sdk.FunctionImportRequest{} -// for _, item := range d.Get("imports").([]interface{}) { -// imports = append(imports, *sdk.NewFunctionImportRequest().WithImport(item.(string))) -// } -// request.WithImports(imports) -// } -// if _, ok := d.GetOk("packages"); ok { -// packages := []sdk.FunctionPackageRequest{} -// for _, item := range d.Get("packages").([]interface{}) { -// packages = append(packages, *sdk.NewFunctionPackageRequest().WithPackage(item.(string))) -// } -// request.WithPackages(packages) -// } -// if v, ok := d.GetOk("target_path"); ok { -// request.WithTargetPath(sdk.String(v.(string))) -// } -// -// if err := client.Functions.CreateForScala(ctx, request); err != nil { -// return diag.FromErr(err) -// } -// argumentTypes := make([]sdk.DataType, 0, len(arguments)) -// for _, item := range arguments { -// argumentTypes = append(argumentTypes, item.ArgDataType) -// } -// nid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argumentTypes) -// d.SetId(nid.FullyQualifiedName()) -// return ReadContextFunction(ctx, d, meta) -//} -// -//func createSQLFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// client := meta.(*provider.Context).Client -// name := d.Get("name").(string) -// schema := d.Get("schema").(string) -// database := d.Get("database").(string) -// id := sdk.NewSchemaObjectIdentifier(database, schema, name) -// -// // Set required -// returns, diags := parseFunctionReturnsRequest(d.Get("return_type").(string)) -// if diags != nil { -// return diags -// } -// functionDefinition := d.Get("statement").(string) -// // create request with required -// request := sdk.NewCreateForSQLFunctionRequest(id, *returns, functionDefinition) -// -// // Set optionals -// if v, ok := d.GetOk("is_secure"); ok { -// request.WithSecure(sdk.Bool(v.(bool))) -// } -// arguments, diags := parseFunctionArguments(d) -// if diags != nil { -// return diags -// } -// if len(arguments) > 0 { -// request.WithArguments(arguments) -// } -// if v, ok := d.GetOk("return_behavior"); ok { -// request.WithReturnResultsBehavior(sdk.Pointer(sdk.ReturnResultsBehavior(v.(string)))) -// } -// if v, ok := d.GetOk("comment"); ok { -// request.WithComment(sdk.String(v.(string))) -// } -// -// if err := client.Functions.CreateForSQL(ctx, request); err != nil { -// return diag.FromErr(err) -// } -// argumentTypes := make([]sdk.DataType, 0, len(arguments)) -// for _, item := range arguments { -// argumentTypes = append(argumentTypes, item.ArgDataType) -// } -// nid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argumentTypes) -// d.SetId(nid.FullyQualifiedName()) -// return ReadContextFunction(ctx, d, meta) -//} -// -//func createPythonFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// client := meta.(*provider.Context).Client -// name := d.Get("name").(string) -// schema := d.Get("schema").(string) -// database := d.Get("database").(string) -// id := sdk.NewSchemaObjectIdentifier(database, schema, name) -// -// // Set required -// returns, diags := parseFunctionReturnsRequest(d.Get("return_type").(string)) -// if diags != nil { -// return diags -// } -// functionDefinition := d.Get("statement").(string) -// version := d.Get("runtime_version").(string) -// handler := d.Get("handler").(string) -// // create request with required -// request := sdk.NewCreateForPythonFunctionRequest(id, *returns, version, handler) -// request.WithFunctionDefinition(sdk.String(functionDefinition)) -// -// // Set optionals -// if v, ok := d.GetOk("is_secure"); ok { -// request.WithSecure(sdk.Bool(v.(bool))) -// } -// arguments, diags := parseFunctionArguments(d) -// if diags != nil { -// return diags -// } -// if len(arguments) > 0 { -// request.WithArguments(arguments) -// } -// if v, ok := d.GetOk("null_input_behavior"); ok { -// request.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehavior(v.(string)))) -// } -// if v, ok := d.GetOk("return_behavior"); ok { -// request.WithReturnResultsBehavior(sdk.Pointer(sdk.ReturnResultsBehavior(v.(string)))) -// } -// -// if v, ok := d.GetOk("comment"); ok { -// request.WithComment(sdk.String(v.(string))) -// } -// if _, ok := d.GetOk("imports"); ok { -// imports := []sdk.FunctionImportRequest{} -// for _, item := range d.Get("imports").([]interface{}) { -// imports = append(imports, *sdk.NewFunctionImportRequest().WithImport(item.(string))) -// } -// request.WithImports(imports) -// } -// if _, ok := d.GetOk("packages"); ok { -// packages := []sdk.FunctionPackageRequest{} -// for _, item := range d.Get("packages").([]interface{}) { -// packages = append(packages, *sdk.NewFunctionPackageRequest().WithPackage(item.(string))) -// } -// request.WithPackages(packages) -// } -// -// if err := client.Functions.CreateForPython(ctx, request); err != nil { -// return diag.FromErr(err) -// } -// argumentTypes := make([]sdk.DataType, 0, len(arguments)) -// for _, item := range arguments { -// argumentTypes = append(argumentTypes, item.ArgDataType) -// } -// nid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argumentTypes) -// d.SetId(nid.FullyQualifiedName()) -// return ReadContextFunction(ctx, d, meta) -//} -// -//func createJavascriptFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// client := meta.(*provider.Context).Client -// name := d.Get("name").(string) -// schema := d.Get("schema").(string) -// database := d.Get("database").(string) -// id := sdk.NewSchemaObjectIdentifier(database, schema, name) -// -// // Set required -// returns, diags := parseFunctionReturnsRequest(d.Get("return_type").(string)) -// if diags != nil { -// return diags -// } -// functionDefinition := d.Get("statement").(string) -// // create request with required -// request := sdk.NewCreateForJavascriptFunctionRequest(id, *returns, functionDefinition) -// -// // Set optionals -// if v, ok := d.GetOk("is_secure"); ok { -// request.WithSecure(sdk.Bool(v.(bool))) -// } -// arguments, diags := parseFunctionArguments(d) -// if diags != nil { -// return diags -// } -// if len(arguments) > 0 { -// request.WithArguments(arguments) -// } -// if v, ok := d.GetOk("null_input_behavior"); ok { -// request.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehavior(v.(string)))) -// } -// if v, ok := d.GetOk("return_behavior"); ok { -// request.WithReturnResultsBehavior(sdk.Pointer(sdk.ReturnResultsBehavior(v.(string)))) -// } -// if v, ok := d.GetOk("comment"); ok { -// request.WithComment(sdk.String(v.(string))) -// } -// -// if err := client.Functions.CreateForJavascript(ctx, request); err != nil { -// return diag.FromErr(err) -// } -// argumentTypes := make([]sdk.DataType, 0, len(arguments)) -// for _, item := range arguments { -// argumentTypes = append(argumentTypes, item.ArgDataType) -// } -// nid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argumentTypes) -// d.SetId(nid.FullyQualifiedName()) -// return ReadContextFunction(ctx, d, meta) -//} -// -//func ReadContextFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// diags := diag.Diagnostics{} -// client := meta.(*provider.Context).Client -// -// id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) -// if err := d.Set("name", id.Name()); err != nil { -// return diag.FromErr(err) -// } -// if err := d.Set("database", id.DatabaseName()); err != nil { -// return diag.FromErr(err) -// } -// if err := d.Set("schema", id.SchemaName()); err != nil { -// return diag.FromErr(err) -// } -// -// arguments := d.Get("arguments").([]interface{}) -// argumentTypes := make([]string, len(arguments)) -// for i, arg := range arguments { -// argumentTypes[i] = arg.(map[string]interface{})["type"].(string) -// } -// functionDetails, err := client.Functions.Describe(ctx, sdk.NewDescribeFunctionRequest(id.WithoutArguments(), id.Arguments())) -// if err != nil { -// // if function is not found then mark resource to be removed from state file during apply or refresh -// d.SetId("") -// return diag.Diagnostics{ -// diag.Diagnostic{ -// Severity: diag.Warning, -// Summary: "Describe function failed.", -// Detail: "See our document on design decisions for functions: ", -// }, -// } -// } -// for _, desc := range functionDetails { -// switch desc.Property { -// case "signature": -// // Format in Snowflake DB is: (argName argType, argName argType, ...) -// value := strings.ReplaceAll(strings.ReplaceAll(desc.Value, "(", ""), ")", "") -// if value != "" { // Do nothing for functions without arguments -// pairs := strings.Split(value, ", ") -// -// arguments := []interface{}{} -// for _, pair := range pairs { -// item := strings.Split(pair, " ") -// argument := map[string]interface{}{} -// argument["name"] = item[0] -// argument["type"] = item[1] -// arguments = append(arguments, argument) -// } -// if err := d.Set("arguments", arguments); err != nil { -// diag.FromErr(err) -// } -// } -// case "null handling": -// if err := d.Set("null_input_behavior", desc.Value); err != nil { -// diag.FromErr(err) -// } -// case "volatility": -// if err := d.Set("return_behavior", desc.Value); err != nil { -// diag.FromErr(err) -// } -// case "body": -// if err := d.Set("statement", desc.Value); err != nil { -// diag.FromErr(err) -// } -// case "returns": -// // Format in Snowflake DB is returnType() -// re := regexp.MustCompile(`^(.*)\([0-9]*\)$`) -// match := re.FindStringSubmatch(desc.Value) -// rt := desc.Value -// if match != nil { -// rt = match[1] -// } -// if err := d.Set("return_type", rt); err != nil { -// diag.FromErr(err) -// } -// case "language": -// if snowflake.Contains(languages, strings.ToLower(desc.Value)) { -// if err := d.Set("language", desc.Value); err != nil { -// diag.FromErr(err) -// } -// } else { -// log.Printf("[INFO] Unexpected language for function %v returned from Snowflake", desc.Value) -// } -// case "packages": -// value := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(desc.Value, "[", ""), "]", ""), "'", "") -// if value != "" { // Do nothing for Java / Python functions without packages -// packages := strings.Split(value, ",") -// if err := d.Set("packages", packages); err != nil { -// diag.FromErr(err) -// } -// } -// case "imports": -// value := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(desc.Value, "[", ""), "]", ""), "'", "") -// if value != "" { // Do nothing for Java functions without imports -// imports := strings.Split(value, ",") -// if err := d.Set("imports", imports); err != nil { -// diag.FromErr(err) -// } -// } -// case "handler": -// if err := d.Set("handler", desc.Value); err != nil { -// diag.FromErr(err) -// } -// case "target_path": -// if err := d.Set("target_path", desc.Value); err != nil { -// diag.FromErr(err) -// } -// case "runtime_version": -// if err := d.Set("runtime_version", desc.Value); err != nil { -// diag.FromErr(err) -// } -// default: -// log.Printf("[INFO] Unexpected function property %v returned from Snowflake with value %v", desc.Property, desc.Value) -// } -// } -// -// // Show functions to set is_secure and comment -// request := sdk.NewShowFunctionRequest().WithIn(&sdk.In{Schema: sdk.NewDatabaseObjectIdentifier(id.DatabaseName(), id.SchemaName())}).WithLike(&sdk.Like{Pattern: sdk.String(id.Name())}) -// functions, err := client.Functions.Show(ctx, request) -// if err != nil { -// return diag.FromErr(err) -// } -// for _, function := range functions { -// signature := strings.Split(function.Arguments, " RETURN ")[0] -// signature = strings.ReplaceAll(signature, " ", "") -// id.FullyQualifiedName() -// if signature == id.ArgumentsSignature() { -// if err := d.Set("is_secure", function.IsSecure); err != nil { -// return diag.FromErr(err) -// } -// if err := d.Set("comment", function.Description); err != nil { -// return diag.FromErr(err) -// } -// } -// } -// return diags -//} -// -//func UpdateContextFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// client := meta.(*provider.Context).Client -// -// id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) -// if d.HasChange("name") { -// name := d.Get("name").(string) -// newId := sdk.NewSchemaObjectIdentifierWithArguments(id.DatabaseName(), id.SchemaName(), name, id.Arguments()) -// -// if err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id.WithoutArguments(), id.Arguments()).WithRenameTo(sdk.Pointer(newId.WithoutArguments()))); err != nil { -// return diag.FromErr(err) -// } -// -// d.SetId(newId.FullyQualifiedName()) -// id = newId -// } -// -// if d.HasChange("is_secure") { -// secure := d.Get("is_secure") -// if secure.(bool) { -// if err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id.WithoutArguments(), id.Arguments()).WithSetSecure(sdk.Bool(true))); err != nil { -// return diag.FromErr(err) -// } -// } else { -// if err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id.WithoutArguments(), id.Arguments()).WithUnsetSecure(sdk.Bool(true))); err != nil { -// return diag.FromErr(err) -// } -// } -// } -// -// if d.HasChange("comment") { -// comment := d.Get("comment") -// if comment != "" { -// if err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id.WithoutArguments(), id.Arguments()).WithSetComment(sdk.String(comment.(string)))); err != nil { -// return diag.FromErr(err) -// } -// } else { -// if err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id.WithoutArguments(), id.Arguments()).WithUnsetComment(sdk.Bool(true))); err != nil { -// return diag.FromErr(err) -// } -// } -// } -// -// return ReadContextFunction(ctx, d, meta) -//} -// -//func DeleteContextFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// client := meta.(*provider.Context).Client -// -// id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) -// if err := client.Functions.Drop(ctx, sdk.NewDropFunctionRequest(id.WithoutArguments(), id.Arguments())); err != nil { -// return diag.FromErr(err) -// } -// d.SetId("") -// return nil -//} -// -//func parseFunctionArguments(d *schema.ResourceData) ([]sdk.FunctionArgumentRequest, diag.Diagnostics) { -// args := make([]sdk.FunctionArgumentRequest, 0) -// if v, ok := d.GetOk("arguments"); ok { -// for _, arg := range v.([]interface{}) { -// argName := arg.(map[string]interface{})["name"].(string) -// argType := arg.(map[string]interface{})["type"].(string) -// argDataType, diags := convertFunctionDataType(argType) -// if diags != nil { -// return nil, diags -// } -// args = append(args, sdk.FunctionArgumentRequest{ArgName: argName, ArgDataType: argDataType}) -// } -// } -// return args, nil -//} -// -//func convertFunctionDataType(s string) (sdk.DataType, diag.Diagnostics) { -// dataType, err := sdk.ToDataType(s) -// if err != nil { -// return dataType, diag.FromErr(err) -// } -// return dataType, nil -//} -// -//func convertFunctionColumns(s string) ([]sdk.FunctionColumn, diag.Diagnostics) { -// pattern := regexp.MustCompile(`(\w+)\s+(\w+)`) -// matches := pattern.FindAllStringSubmatch(s, -1) -// var columns []sdk.FunctionColumn -// for _, match := range matches { -// if len(match) == 3 { -// dataType, err := sdk.ToDataType(match[2]) -// if err != nil { -// return nil, diag.FromErr(err) -// } -// columns = append(columns, sdk.FunctionColumn{ -// ColumnName: match[1], -// ColumnDataType: dataType, -// }) -// } -// } -// return columns, nil -//} -// -//func parseFunctionReturnsRequest(s string) (*sdk.FunctionReturnsRequest, diag.Diagnostics) { -// returns := sdk.NewFunctionReturnsRequest() -// if strings.HasPrefix(strings.ToLower(s), "table") { -// columns, diags := convertFunctionColumns(s) -// if diags != nil { -// return nil, diags -// } -// var cr []sdk.FunctionColumnRequest -// for _, item := range columns { -// cr = append(cr, *sdk.NewFunctionColumnRequest(item.ColumnName, item.ColumnDataType)) -// } -// returns.WithTable(sdk.NewFunctionReturnsTableRequest().WithColumns(cr)) -// } else { -// returnDataType, diags := convertFunctionDataType(s) -// if diags != nil { -// return nil, diags -// } -// returns.WithResultDataType(sdk.NewFunctionReturnsResultDataTypeRequest(returnDataType)) -// } -// return returns, nil -//} +import ( + "context" + "fmt" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/snowflake" + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "log" + "regexp" + "strings" +) + +var languages = []string{"javascript", "scala", "java", "sql", "python"} + +var functionSchema = map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "Specifies the identifier for the function; does not have to be unique for the schema in which the function is created. Don't use the | character.", + }, + "database": { + Type: schema.TypeString, + Required: true, + Description: "The database in which to create the function. Don't use the | character.", + ForceNew: true, + }, + "schema": { + Type: schema.TypeString, + Required: true, + Description: "The schema in which to create the function. Don't use the | character.", + ForceNew: true, + }, + "arguments": { + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + // Suppress the diff shown if the values are equal when both compared in lower case. + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return strings.EqualFold(old, new) + }, + Description: "The argument name", + }, + "type": { + Type: schema.TypeString, + Required: true, + // Suppress the diff shown if the values are equal when both compared in lower case. + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return strings.EqualFold(old, new) + }, + Description: "The argument type", + }, + }, + }, + Optional: true, + Description: "List of the arguments for the function", + ForceNew: true, + }, + "return_type": { + Type: schema.TypeString, + Description: "The return type of the function", + // Suppress the diff shown if the values are equal when both compared in lower case. + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return strings.EqualFold(old, new) + }, + Required: true, + ForceNew: true, + }, + "statement": { + Type: schema.TypeString, + Required: true, + Description: "Specifies the javascript / java / scala / sql / python code used to create the function.", + ForceNew: true, + DiffSuppressFunc: DiffSuppressStatement, + }, + "language": { + Type: schema.TypeString, + Optional: true, + Default: "SQL", + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return strings.EqualFold(old, new) + }, + ValidateFunc: validation.StringInSlice(languages, true), + Description: "Specifies the language of the stored function code.", + }, + "null_input_behavior": { + Type: schema.TypeString, + Optional: true, + Default: "CALLED ON NULL INPUT", + ForceNew: true, + // We do not use STRICT, because Snowflake then in the Read phase returns RETURNS NULL ON NULL INPUT + ValidateFunc: validation.StringInSlice([]string{"CALLED ON NULL INPUT", "RETURNS NULL ON NULL INPUT"}, false), + Description: "Specifies the behavior of the function when called with null inputs.", + }, + "return_behavior": { + Type: schema.TypeString, + Optional: true, + Default: "VOLATILE", + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"VOLATILE", "IMMUTABLE"}, false), + Description: "Specifies the behavior of the function when returning results", + }, + "is_secure": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Specifies that the function is secure.", + }, + "comment": { + Type: schema.TypeString, + Optional: true, + Default: "user-defined function", + Description: "Specifies a comment for the function.", + }, + "runtime_version": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "Required for Python functions. Specifies Python runtime version.", + }, + "packages": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + ForceNew: true, + Description: "List of package imports to use for Java / Python functions. For Java, package imports should be of the form: package_name:version_number, where package_name is snowflake_domain:package. For Python use it should be: ('numpy','pandas','xgboost==1.5.0').", + }, + "imports": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + ForceNew: true, + Description: "Imports for Java / Python functions. For Java this a list of jar files, for Python this is a list of Python files.", + }, + "handler": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The handler method for Java / Python function.", + }, + "target_path": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The target path for the Java / Python functions. For Java, it is the path of compiled jar files and for the Python it is the path of the Python files.", + }, +} + +func Function() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + CreateContext: CreateContextFunction, + ReadContext: ReadContextFunction, + UpdateContext: UpdateContextFunction, + DeleteContext: DeleteContextFunction, + + Schema: functionSchema, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + StateUpgraders: []schema.StateUpgrader{ + { + Version: 0, + // setting type to cty.EmptyObject is a bit hacky here but following https://developer.hashicorp.com/terraform/plugin/framework/migrating/resources/state-upgrade#sdkv2-1 would require lots of repetitive code; this should work with cty.EmptyObject + Type: cty.EmptyObject, + Upgrade: v085FunctionIdStateUpgrader, + }, + }, + } +} + +func CreateContextFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + lang := strings.ToUpper(d.Get("language").(string)) + switch lang { + case "JAVA": + return createJavaFunction(ctx, d, meta) + case "JAVASCRIPT": + return createJavascriptFunction(ctx, d, meta) + case "PYTHON": + return createPythonFunction(ctx, d, meta) + case "SCALA": + return createScalaFunction(ctx, d, meta) + case "", "SQL": // SQL if language is not set + return createSQLFunction(ctx, d, meta) + default: + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Invalid language", + Detail: fmt.Sprintf("Language %s is not supported", lang), + }, + } + } +} + +func createJavaFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + name := d.Get("name").(string) + schema := d.Get("schema").(string) + database := d.Get("database").(string) + id := sdk.NewSchemaObjectIdentifier(database, schema, name) + + // Set required + returns, diags := parseFunctionReturnsRequest(d.Get("return_type").(string)) + if diags != nil { + return diags + } + handler := d.Get("handler").(string) + // create request with required + request := sdk.NewCreateForJavaFunctionRequest(id, *returns, handler) + functionDefinition := d.Get("statement").(string) + request.WithFunctionDefinition(functionDefinition) + + // Set optionals + if v, ok := d.GetOk("is_secure"); ok { + request.WithSecure(v.(bool)) + } + arguments, diags := parseFunctionArguments(d) + if diags != nil { + return diags + } + if len(arguments) > 0 { + request.WithArguments(arguments) + } + if v, ok := d.GetOk("null_input_behavior"); ok { + request.WithNullInputBehavior(sdk.NullInputBehavior(v.(string))) + } + if v, ok := d.GetOk("return_behavior"); ok { + request.WithReturnResultsBehavior(sdk.ReturnResultsBehavior(v.(string))) + } + if v, ok := d.GetOk("runtime_version"); ok { + request.WithRuntimeVersion(v.(string)) + } + if v, ok := d.GetOk("comment"); ok { + request.WithComment(v.(string)) + } + if _, ok := d.GetOk("imports"); ok { + imports := []sdk.FunctionImportRequest{} + for _, item := range d.Get("imports").([]interface{}) { + imports = append(imports, *sdk.NewFunctionImportRequest().WithImport(item.(string))) + } + request.WithImports(imports) + } + if _, ok := d.GetOk("packages"); ok { + packages := []sdk.FunctionPackageRequest{} + for _, item := range d.Get("packages").([]interface{}) { + packages = append(packages, *sdk.NewFunctionPackageRequest().WithPackage(item.(string))) + } + request.WithPackages(packages) + } + if v, ok := d.GetOk("target_path"); ok { + request.WithTargetPath(v.(string)) + } + + if err := client.Functions.CreateForJava(ctx, request); err != nil { + return diag.FromErr(err) + } + argumentTypes := make([]sdk.DataType, 0, len(arguments)) + for _, item := range arguments { + argumentTypes = append(argumentTypes, item.ArgDataType) + } + nid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argumentTypes...) + d.SetId(nid.FullyQualifiedName()) + return ReadContextFunction(ctx, d, meta) +} + +func createScalaFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + name := d.Get("name").(string) + schema := d.Get("schema").(string) + database := d.Get("database").(string) + id := sdk.NewSchemaObjectIdentifier(database, schema, name) + + // Set required + returnType := d.Get("return_type").(string) + returnDataType, diags := convertFunctionDataType(returnType) + if diags != nil { + return diags + } + functionDefinition := d.Get("statement").(string) + handler := d.Get("handler").(string) + // create request with required + request := sdk.NewCreateForScalaFunctionRequest(id, returnDataType, handler) + request.WithFunctionDefinition(functionDefinition) + + // Set optionals + if v, ok := d.GetOk("is_secure"); ok { + request.WithSecure(v.(bool)) + } + arguments, diags := parseFunctionArguments(d) + if diags != nil { + return diags + } + if len(arguments) > 0 { + request.WithArguments(arguments) + } + if v, ok := d.GetOk("null_input_behavior"); ok { + request.WithNullInputBehavior(sdk.NullInputBehavior(v.(string))) + } + if v, ok := d.GetOk("return_behavior"); ok { + request.WithReturnResultsBehavior(sdk.ReturnResultsBehavior(v.(string))) + } + if v, ok := d.GetOk("runtime_version"); ok { + request.WithRuntimeVersion(v.(string)) + } + if v, ok := d.GetOk("comment"); ok { + request.WithComment(v.(string)) + } + if _, ok := d.GetOk("imports"); ok { + imports := []sdk.FunctionImportRequest{} + for _, item := range d.Get("imports").([]interface{}) { + imports = append(imports, *sdk.NewFunctionImportRequest().WithImport(item.(string))) + } + request.WithImports(imports) + } + if _, ok := d.GetOk("packages"); ok { + packages := []sdk.FunctionPackageRequest{} + for _, item := range d.Get("packages").([]interface{}) { + packages = append(packages, *sdk.NewFunctionPackageRequest().WithPackage(item.(string))) + } + request.WithPackages(packages) + } + if v, ok := d.GetOk("target_path"); ok { + request.WithTargetPath(v.(string)) + } + + if err := client.Functions.CreateForScala(ctx, request); err != nil { + return diag.FromErr(err) + } + argumentTypes := make([]sdk.DataType, 0, len(arguments)) + for _, item := range arguments { + argumentTypes = append(argumentTypes, item.ArgDataType) + } + nid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argumentTypes...) + d.SetId(nid.FullyQualifiedName()) + return ReadContextFunction(ctx, d, meta) +} + +func createSQLFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + name := d.Get("name").(string) + schema := d.Get("schema").(string) + database := d.Get("database").(string) + id := sdk.NewSchemaObjectIdentifier(database, schema, name) + + // Set required + returns, diags := parseFunctionReturnsRequest(d.Get("return_type").(string)) + if diags != nil { + return diags + } + functionDefinition := d.Get("statement").(string) + // create request with required + request := sdk.NewCreateForSQLFunctionRequest(id, *returns, functionDefinition) + + // Set optionals + if v, ok := d.GetOk("is_secure"); ok { + request.WithSecure(v.(bool)) + } + arguments, diags := parseFunctionArguments(d) + if diags != nil { + return diags + } + if len(arguments) > 0 { + request.WithArguments(arguments) + } + if v, ok := d.GetOk("return_behavior"); ok { + request.WithReturnResultsBehavior(sdk.ReturnResultsBehavior(v.(string))) + } + if v, ok := d.GetOk("comment"); ok { + request.WithComment(v.(string)) + } + + if err := client.Functions.CreateForSQL(ctx, request); err != nil { + return diag.FromErr(err) + } + argumentTypes := make([]sdk.DataType, 0, len(arguments)) + for _, item := range arguments { + argumentTypes = append(argumentTypes, item.ArgDataType) + } + nid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argumentTypes...) + d.SetId(nid.FullyQualifiedName()) + return ReadContextFunction(ctx, d, meta) +} + +func createPythonFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + name := d.Get("name").(string) + schema := d.Get("schema").(string) + database := d.Get("database").(string) + id := sdk.NewSchemaObjectIdentifier(database, schema, name) + + // Set required + returns, diags := parseFunctionReturnsRequest(d.Get("return_type").(string)) + if diags != nil { + return diags + } + functionDefinition := d.Get("statement").(string) + version := d.Get("runtime_version").(string) + handler := d.Get("handler").(string) + // create request with required + request := sdk.NewCreateForPythonFunctionRequest(id, *returns, version, handler) + request.WithFunctionDefinition(functionDefinition) + + // Set optionals + if v, ok := d.GetOk("is_secure"); ok { + request.WithSecure(v.(bool)) + } + arguments, diags := parseFunctionArguments(d) + if diags != nil { + return diags + } + if len(arguments) > 0 { + request.WithArguments(arguments) + } + if v, ok := d.GetOk("null_input_behavior"); ok { + request.WithNullInputBehavior(sdk.NullInputBehavior(v.(string))) + } + if v, ok := d.GetOk("return_behavior"); ok { + request.WithReturnResultsBehavior(sdk.ReturnResultsBehavior(v.(string))) + } + + if v, ok := d.GetOk("comment"); ok { + request.WithComment(v.(string)) + } + if _, ok := d.GetOk("imports"); ok { + imports := []sdk.FunctionImportRequest{} + for _, item := range d.Get("imports").([]interface{}) { + imports = append(imports, *sdk.NewFunctionImportRequest().WithImport(item.(string))) + } + request.WithImports(imports) + } + if _, ok := d.GetOk("packages"); ok { + packages := []sdk.FunctionPackageRequest{} + for _, item := range d.Get("packages").([]interface{}) { + packages = append(packages, *sdk.NewFunctionPackageRequest().WithPackage(item.(string))) + } + request.WithPackages(packages) + } + + if err := client.Functions.CreateForPython(ctx, request); err != nil { + return diag.FromErr(err) + } + argumentTypes := make([]sdk.DataType, 0, len(arguments)) + for _, item := range arguments { + argumentTypes = append(argumentTypes, item.ArgDataType) + } + nid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argumentTypes...) + d.SetId(nid.FullyQualifiedName()) + return ReadContextFunction(ctx, d, meta) +} + +func createJavascriptFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + name := d.Get("name").(string) + schema := d.Get("schema").(string) + database := d.Get("database").(string) + id := sdk.NewSchemaObjectIdentifier(database, schema, name) + + // Set required + returns, diags := parseFunctionReturnsRequest(d.Get("return_type").(string)) + if diags != nil { + return diags + } + functionDefinition := d.Get("statement").(string) + // create request with required + request := sdk.NewCreateForJavascriptFunctionRequest(id, *returns, functionDefinition) + + // Set optionals + if v, ok := d.GetOk("is_secure"); ok { + request.WithSecure(v.(bool)) + } + arguments, diags := parseFunctionArguments(d) + if diags != nil { + return diags + } + if len(arguments) > 0 { + request.WithArguments(arguments) + } + if v, ok := d.GetOk("null_input_behavior"); ok { + request.WithNullInputBehavior(sdk.NullInputBehavior(v.(string))) + } + if v, ok := d.GetOk("return_behavior"); ok { + request.WithReturnResultsBehavior(sdk.ReturnResultsBehavior(v.(string))) + } + if v, ok := d.GetOk("comment"); ok { + request.WithComment(v.(string)) + } + + if err := client.Functions.CreateForJavascript(ctx, request); err != nil { + return diag.FromErr(err) + } + argumentTypes := make([]sdk.DataType, 0, len(arguments)) + for _, item := range arguments { + argumentTypes = append(argumentTypes, item.ArgDataType) + } + nid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argumentTypes...) + // TODO: Create upgrader for id migration + d.SetId(nid.FullyQualifiedName()) + return ReadContextFunction(ctx, d, meta) +} + +func ReadContextFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + diags := diag.Diagnostics{} + client := meta.(*provider.Context).Client + + id, err := sdk.NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(d.Id()) + if err != nil { + return diag.FromErr(err) + } + if err := d.Set("name", id.Name()); err != nil { + return diag.FromErr(err) + } + if err := d.Set("database", id.DatabaseName()); err != nil { + return diag.FromErr(err) + } + if err := d.Set("schema", id.SchemaName()); err != nil { + return diag.FromErr(err) + } + + arguments := d.Get("arguments").([]interface{}) + argumentTypes := make([]string, len(arguments)) + for i, arg := range arguments { + argumentTypes[i] = arg.(map[string]interface{})["type"].(string) + } + functionDetails, err := client.Functions.Describe(ctx, id) + if err != nil { + // if function is not found then mark resource to be removed from state file during apply or refresh + d.SetId("") + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Describe function failed.", + Detail: "See our document on design decisions for functions: ", + }, + } + } + for _, desc := range functionDetails { + switch desc.Property { + case "signature": + // Format in Snowflake DB is: (argName argType, argName argType, ...) + value := strings.ReplaceAll(strings.ReplaceAll(desc.Value, "(", ""), ")", "") + if value != "" { // Do nothing for functions without arguments + pairs := strings.Split(value, ", ") + + arguments := []interface{}{} + for _, pair := range pairs { + item := strings.Split(pair, " ") + argument := map[string]interface{}{} + argument["name"] = item[0] + argument["type"] = item[1] + arguments = append(arguments, argument) + } + if err := d.Set("arguments", arguments); err != nil { + diag.FromErr(err) + } + } + case "null handling": + if err := d.Set("null_input_behavior", desc.Value); err != nil { + diag.FromErr(err) + } + case "volatility": + if err := d.Set("return_behavior", desc.Value); err != nil { + diag.FromErr(err) + } + case "body": + if err := d.Set("statement", desc.Value); err != nil { + diag.FromErr(err) + } + case "returns": + // Format in Snowflake DB is returnType() + re := regexp.MustCompile(`^(.*)\([0-9]*\)$`) + match := re.FindStringSubmatch(desc.Value) + rt := desc.Value + if match != nil { + rt = match[1] + } + if err := d.Set("return_type", rt); err != nil { + diag.FromErr(err) + } + case "language": + if snowflake.Contains(languages, strings.ToLower(desc.Value)) { + if err := d.Set("language", desc.Value); err != nil { + diag.FromErr(err) + } + } else { + log.Printf("[INFO] Unexpected language for function %v returned from Snowflake", desc.Value) + } + case "packages": + value := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(desc.Value, "[", ""), "]", ""), "'", "") + if value != "" { // Do nothing for Java / Python functions without packages + packages := strings.Split(value, ",") + if err := d.Set("packages", packages); err != nil { + diag.FromErr(err) + } + } + case "imports": + value := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(desc.Value, "[", ""), "]", ""), "'", "") + if value != "" { // Do nothing for Java functions without imports + imports := strings.Split(value, ",") + if err := d.Set("imports", imports); err != nil { + diag.FromErr(err) + } + } + case "handler": + if err := d.Set("handler", desc.Value); err != nil { + diag.FromErr(err) + } + case "target_path": + if err := d.Set("target_path", desc.Value); err != nil { + diag.FromErr(err) + } + case "runtime_version": + if err := d.Set("runtime_version", desc.Value); err != nil { + diag.FromErr(err) + } + default: + log.Printf("[INFO] Unexpected function property %v returned from Snowflake with value %v", desc.Property, desc.Value) + } + } + + function, err := client.Functions.ShowByID(ctx, id) + if err != nil { + return diag.FromErr(err) + } + + if err := d.Set("is_secure", function.IsSecure); err != nil { + return diag.FromErr(err) + } + + if err := d.Set("comment", function.Description); err != nil { + return diag.FromErr(err) + } + + return diags +} + +func UpdateContextFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + + id, err := sdk.NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(d.Id()) + if err != nil { + return diag.FromErr(err) + } + if d.HasChange("name") { + name := d.Get("name").(string) + newId := sdk.NewSchemaObjectIdentifierWithArguments(id.DatabaseName(), id.SchemaName(), name, id.ArgumentDataTypes()...) + + if err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithRenameTo(newId.SchemaObjectId())); err != nil { + return diag.FromErr(err) + } + + d.SetId(newId.FullyQualifiedName()) + id = newId + } + + if d.HasChange("is_secure") { + secure := d.Get("is_secure") + if secure.(bool) { + if err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithSetSecure(true)); err != nil { + return diag.FromErr(err) + } + } else { + if err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithUnsetSecure(true)); err != nil { + return diag.FromErr(err) + } + } + } + + if d.HasChange("comment") { + comment := d.Get("comment") + if comment != "" { + if err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithSetComment(comment.(string))); err != nil { + return diag.FromErr(err) + } + } else { + if err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithUnsetComment(true)); err != nil { + return diag.FromErr(err) + } + } + } + + return ReadContextFunction(ctx, d, meta) +} + +func DeleteContextFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + + id, err := sdk.NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(d.Id()) + if err != nil { + return diag.FromErr(err) + } + if err := client.Functions.Drop(ctx, sdk.NewDropFunctionRequest(id)); err != nil { + return diag.FromErr(err) + } + d.SetId("") + return nil +} + +func parseFunctionArguments(d *schema.ResourceData) ([]sdk.FunctionArgumentRequest, diag.Diagnostics) { + args := make([]sdk.FunctionArgumentRequest, 0) + if v, ok := d.GetOk("arguments"); ok { + for _, arg := range v.([]interface{}) { + argName := arg.(map[string]interface{})["name"].(string) + argType := arg.(map[string]interface{})["type"].(string) + argDataType, diags := convertFunctionDataType(argType) + if diags != nil { + return nil, diags + } + args = append(args, sdk.FunctionArgumentRequest{ArgName: argName, ArgDataType: argDataType}) + } + } + return args, nil +} + +func convertFunctionDataType(s string) (sdk.DataType, diag.Diagnostics) { + dataType, err := sdk.ToDataType(s) + if err != nil { + return dataType, diag.FromErr(err) + } + return dataType, nil +} + +func convertFunctionColumns(s string) ([]sdk.FunctionColumn, diag.Diagnostics) { + pattern := regexp.MustCompile(`(\w+)\s+(\w+)`) + matches := pattern.FindAllStringSubmatch(s, -1) + var columns []sdk.FunctionColumn + for _, match := range matches { + if len(match) == 3 { + dataType, err := sdk.ToDataType(match[2]) + if err != nil { + return nil, diag.FromErr(err) + } + columns = append(columns, sdk.FunctionColumn{ + ColumnName: match[1], + ColumnDataType: dataType, + }) + } + } + return columns, nil +} + +func parseFunctionReturnsRequest(s string) (*sdk.FunctionReturnsRequest, diag.Diagnostics) { + returns := sdk.NewFunctionReturnsRequest() + if strings.HasPrefix(strings.ToLower(s), "table") { + columns, diags := convertFunctionColumns(s) + if diags != nil { + return nil, diags + } + var cr []sdk.FunctionColumnRequest + for _, item := range columns { + cr = append(cr, *sdk.NewFunctionColumnRequest(item.ColumnName, item.ColumnDataType)) + } + returns.WithTable(*sdk.NewFunctionReturnsTableRequest().WithColumns(cr)) + } else { + returnDataType, diags := convertFunctionDataType(s) + if diags != nil { + return nil, diags + } + returns.WithResultDataType(*sdk.NewFunctionReturnsResultDataTypeRequest(returnDataType)) + } + return returns, nil +} diff --git a/pkg/resources/function_acceptance_test.go b/pkg/resources/function_acceptance_test.go index 224d5985a7..fe32ca0f76 100644 --- a/pkg/resources/function_acceptance_test.go +++ b/pkg/resources/function_acceptance_test.go @@ -185,7 +185,7 @@ func TestAcc_Function_complex(t *testing.T) { // proves issue https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2490 func TestAcc_Function_migrateFromVersion085(t *testing.T) { - id := acc.TestClient().Ids.RandomSchemaObjectIdentifierWithArguments([]sdk.DataType{sdk.DataTypeVARCHAR}) + id := acc.TestClient().Ids.RandomSchemaObjectIdentifierWithArguments(sdk.DataTypeVARCHAR) name := id.Name() comment := random.Comment() resourceName := "snowflake_function.f" diff --git a/pkg/resources/function_state_upgraders.go b/pkg/resources/function_state_upgraders.go index 46687f1bf9..26e6debc1e 100644 --- a/pkg/resources/function_state_upgraders.go +++ b/pkg/resources/function_state_upgraders.go @@ -1,54 +1,60 @@ package resources -//type v085FunctionId struct { -// DatabaseName string -// SchemaName string -// FunctionName string -// ArgTypes []string -//} -// -//func parseV085FunctionId(v string) (*v085FunctionId, error) { -// arr := strings.Split(v, "|") -// if len(arr) != 4 { -// return nil, sdk.NewError(fmt.Sprintf("ID %v is invalid", v)) -// } -// -// // this is a bit different from V085 state, but it was buggy -// var args []string -// if arr[3] != "" { -// args = strings.Split(arr[3], "-") -// } -// -// return &v085FunctionId{ -// DatabaseName: arr[0], -// SchemaName: arr[1], -// FunctionName: arr[2], -// ArgTypes: args, -// }, nil -//} -// -//func v085FunctionIdStateUpgrader(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { -// if rawState == nil { -// return rawState, nil -// } -// -// oldId := rawState["id"].(string) -// parsedV085FunctionId, err := parseV085FunctionId(oldId) -// if err != nil { -// return nil, err -// } -// -// argDataTypes := make([]sdk.DataType, len(parsedV085FunctionId.ArgTypes)) -// for i, argType := range parsedV085FunctionId.ArgTypes { -// argDataType, err := sdk.ToDataType(argType) -// if err != nil { -// return nil, err -// } -// argDataTypes[i] = argDataType -// } -// -// schemaObjectIdentifierWithArguments := sdk.NewSchemaObjectIdentifierWithArguments(parsedV085FunctionId.DatabaseName, parsedV085FunctionId.SchemaName, parsedV085FunctionId.FunctionName, argDataTypes) -// rawState["id"] = schemaObjectIdentifierWithArguments.FullyQualifiedName() -// -// return rawState, nil -//} +import ( + "context" + "fmt" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "strings" +) + +type v085FunctionId struct { + DatabaseName string + SchemaName string + FunctionName string + ArgTypes []string +} + +func parseV085FunctionId(v string) (*v085FunctionId, error) { + arr := strings.Split(v, "|") + if len(arr) != 4 { + return nil, sdk.NewError(fmt.Sprintf("ID %v is invalid", v)) + } + + // this is a bit different from V085 state, but it was buggy + var args []string + if arr[3] != "" { + args = strings.Split(arr[3], "-") + } + + return &v085FunctionId{ + DatabaseName: arr[0], + SchemaName: arr[1], + FunctionName: arr[2], + ArgTypes: args, + }, nil +} +func v085FunctionIdStateUpgrader(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { + if rawState == nil { + return rawState, nil + } + + oldId := rawState["id"].(string) + parsedV085FunctionId, err := parseV085FunctionId(oldId) + if err != nil { + return nil, err + } + + argDataTypes := make([]sdk.DataType, len(parsedV085FunctionId.ArgTypes)) + for i, argType := range parsedV085FunctionId.ArgTypes { + argDataType, err := sdk.ToDataType(argType) + if err != nil { + return nil, err + } + argDataTypes[i] = argDataType + } + + schemaObjectIdentifierWithArguments := sdk.NewSchemaObjectIdentifierWithArguments(parsedV085FunctionId.DatabaseName, parsedV085FunctionId.SchemaName, parsedV085FunctionId.FunctionName, argDataTypes...) + rawState["id"] = schemaObjectIdentifierWithArguments.FullyQualifiedName() + + return rawState, nil +} diff --git a/pkg/resources/procedure_acceptance_test.go b/pkg/resources/procedure_acceptance_test.go index 4eeecabfd8..1f13f783a2 100644 --- a/pkg/resources/procedure_acceptance_test.go +++ b/pkg/resources/procedure_acceptance_test.go @@ -1,391 +1,376 @@ package resources_test -import ( - "fmt" - "strings" - "testing" - - acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" - - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/provider/resources" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" - "github.com/hashicorp/terraform-plugin-testing/config" - "github.com/hashicorp/terraform-plugin-testing/helper/resource" - "github.com/hashicorp/terraform-plugin-testing/plancheck" - "github.com/hashicorp/terraform-plugin-testing/tfversion" -) - -func testAccProcedure(t *testing.T, configDirectory string) { - t.Helper() - - name := acc.TestClient().Ids.Alpha() - newName := acc.TestClient().Ids.Alpha() - - resourceName := "snowflake_procedure.p" - m := func() map[string]config.Variable { - return map[string]config.Variable{ - "name": config.StringVariable(name), - "database": config.StringVariable(acc.TestDatabaseName), - "schema": config.StringVariable(acc.TestSchemaName), - "comment": config.StringVariable("Terraform acceptance test"), - "execute_as": config.StringVariable("CALLER"), - } - } - variableSet2 := m() - variableSet2["name"] = config.StringVariable(newName) - variableSet2["comment"] = config.StringVariable("Terraform acceptance test - updated") - variableSet2["execute_as"] = config.StringVariable("OWNER") - - ignoreDuringImport := []string{"null_input_behavior"} - if strings.Contains(configDirectory, "/sql") { - ignoreDuringImport = append(ignoreDuringImport, "return_behavior") - } - - resource.Test(t, resource.TestCase{ - ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, - PreCheck: func() { acc.TestAccPreCheck(t) }, - TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.RequireAbove(tfversion.Version1_5_0), - }, - CheckDestroy: acc.CheckDestroy(t, resources.Procedure), - Steps: []resource.TestStep{ - { - ConfigDirectory: acc.ConfigurationDirectory(configDirectory), - ConfigVariables: m(), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "name", name), - resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), - resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), - resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test"), - resource.TestCheckResourceAttr(resourceName, "return_behavior", "VOLATILE"), - resource.TestCheckResourceAttr(resourceName, "execute_as", "CALLER"), - - // computed attributes - resource.TestCheckResourceAttrSet(resourceName, "return_type"), - resource.TestCheckResourceAttrSet(resourceName, "statement"), - resource.TestCheckResourceAttrSet(resourceName, "secure"), - ), - }, - - // test - rename + change comment and caller (proves https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2642) - { - ConfigDirectory: acc.ConfigurationDirectory(configDirectory), - ConfigVariables: variableSet2, - ConfigPlanChecks: resource.ConfigPlanChecks{ - PreApply: []plancheck.PlanCheck{ - plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate), - }, - }, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "name", newName), - resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), - resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), - resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test - updated"), - resource.TestCheckResourceAttr(resourceName, "execute_as", "OWNER"), - ), - }, - - // test - import - { - ConfigDirectory: acc.ConfigurationDirectory(configDirectory), - ConfigVariables: variableSet2, - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: ignoreDuringImport, - }, - }, - }) -} - -func TestAcc_Procedure_SQL(t *testing.T) { - testAccProcedure(t, "TestAcc_Procedure/sql") -} - -/* -Error: 391531 (42601): SQL compilation error: An active warehouse is required for creating Python stored procedures. -func TestAcc_Procedure_Python(t *testing.T) { - testAccProcedure(t, "TestAcc_Procedure/python") -} -*/ - -func TestAcc_Procedure_Javascript(t *testing.T) { - testAccProcedure(t, "TestAcc_Procedure/javascript") -} - -func TestAcc_Procedure_Java(t *testing.T) { - testAccProcedure(t, "TestAcc_Procedure/java") -} - -func TestAcc_Procedure_Scala(t *testing.T) { - testAccProcedure(t, "TestAcc_Procedure/scala") -} - -func TestAcc_Procedure_complex(t *testing.T) { - name := acc.TestClient().Ids.Alpha() - resourceName := "snowflake_procedure.p" - m := func() map[string]config.Variable { - return map[string]config.Variable{ - "name": config.StringVariable(name), - "database": config.StringVariable(acc.TestDatabaseName), - "schema": config.StringVariable(acc.TestSchemaName), - "comment": config.StringVariable("Terraform acceptance test"), - "execute_as": config.StringVariable("CALLER"), - } - } - variableSet2 := m() - variableSet2["comment"] = config.StringVariable("Terraform acceptance test - updated") - - statement := "var x = 1\nreturn x\n" - resource.Test(t, resource.TestCase{ - ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, - PreCheck: func() { acc.TestAccPreCheck(t) }, - TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.RequireAbove(tfversion.Version1_5_0), - }, - CheckDestroy: acc.CheckDestroy(t, resources.Procedure), - Steps: []resource.TestStep{ - { - ConfigDirectory: acc.ConfigurationDirectory("TestAcc_Procedure/complex"), - ConfigVariables: m(), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "name", name), - resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), - resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), - resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test"), - resource.TestCheckResourceAttr(resourceName, "statement", statement), - resource.TestCheckResourceAttr(resourceName, "execute_as", "CALLER"), - resource.TestCheckResourceAttr(resourceName, "arguments.#", "2"), - resource.TestCheckResourceAttr(resourceName, "arguments.0.name", "ARG1"), - resource.TestCheckResourceAttr(resourceName, "arguments.0.type", "VARCHAR"), - resource.TestCheckResourceAttr(resourceName, "arguments.1.name", "ARG2"), - resource.TestCheckResourceAttr(resourceName, "arguments.1.type", "DATE"), - resource.TestCheckResourceAttr(resourceName, "null_input_behavior", "RETURNS NULL ON NULL INPUT"), - - // computed attributes - resource.TestCheckResourceAttrSet(resourceName, "return_type"), - resource.TestCheckResourceAttrSet(resourceName, "statement"), - resource.TestCheckResourceAttrSet(resourceName, "execute_as"), - resource.TestCheckResourceAttrSet(resourceName, "secure"), - ), - }, - - // test - change comment - { - ConfigDirectory: acc.ConfigurationDirectory("TestAcc_Procedure/complex"), - ConfigVariables: variableSet2, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "name", name), - resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), - resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), - resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test - updated"), - ), - }, - - // test - import - { - ConfigDirectory: acc.ConfigurationDirectory("TestAcc_Procedure/complex"), - ConfigVariables: variableSet2, - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "return_behavior", - }, - }, - }, - }) -} - -func TestAcc_Procedure_migrateFromVersion085(t *testing.T) { - id := acc.TestClient().Ids.RandomSchemaObjectIdentifier() - name := id.Name() - resourceName := "snowflake_procedure.p" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acc.TestAccPreCheck(t) }, - TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.RequireAbove(tfversion.Version1_5_0), - }, - CheckDestroy: acc.CheckDestroy(t, resources.Procedure), - - Steps: []resource.TestStep{ - { - ExternalProviders: map[string]resource.ExternalProvider{ - "snowflake": { - VersionConstraint: "=0.85.0", - Source: "Snowflake-Labs/snowflake", - }, - }, - Config: procedureConfig(acc.TestDatabaseName, acc.TestSchemaName, name), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|%s|%s|", acc.TestDatabaseName, acc.TestSchemaName, name)), - resource.TestCheckResourceAttr(resourceName, "name", name), - resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), - resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), - ), - }, - { - ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, - Config: procedureConfig(acc.TestDatabaseName, acc.TestSchemaName, name), - ConfigPlanChecks: resource.ConfigPlanChecks{ - PreApply: []plancheck.PlanCheck{plancheck.ExpectEmptyPlan()}, - }, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "id", id.FullyQualifiedName()), - resource.TestCheckResourceAttr(resourceName, "name", name), - resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), - resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), - ), - }, - }, - }) -} - -func procedureConfig(database string, schema string, name string) string { - return fmt.Sprintf(` -resource "snowflake_procedure" "p" { - database = "%[1]s" - schema = "%[2]s" - name = "%[3]s" - language = "JAVASCRIPT" - return_type = "VARCHAR" - statement = < , ...); column names can contain commas and other special characters, + // and they're not escaped, meaning for names such as `"a,b.c|d e" NUMBER` the signature will contain `(a,b.c|d e NUMBER)`. + arguments := make([]DataType, 0) + // TODO(TODO - ticket number): Handle arguments with comma in the name (right now this could break for arguments containing dots) + for _, arg := range strings.Split(strings.Trim(signatureProperty.Value, "()"), ",") { + // single argument has a structure of + argumentSignatureParts := strings.Split(strings.TrimSpace(arg), " ") + arguments = append(arguments, DataType(argumentSignatureParts[len(argumentSignatureParts)-1])) + } + return arguments, nil +} + +func parseFunctionArgumentsFromString(arguments string) []DataType { + // TODO what about data types with parentheses + argListBegin := strings.LastIndex(arguments, "(") + argListEnd := strings.LastIndex(arguments, ")") + return collections.Map( + ParseCommaSeparatedStringArray(arguments[argListBegin+1:argListEnd], false), + func(dataType string) DataType { return DataType(dataType) }, + ) +} + +func (v *Function) ID(details []FunctionDetail) (SchemaObjectIdentifierWithArguments, error) { + arguments, err := parseFunctionArgumentsFromDetails(details) + if err != nil { + return SchemaObjectIdentifierWithArguments{}, err + } + return NewSchemaObjectIdentifierWithArguments(v.CatalogName, v.SchemaName, v.Name, arguments...), nil } // DescribeFunctionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/desc-function. diff --git a/pkg/sdk/functions_impl_gen.go b/pkg/sdk/functions_impl_gen.go index 03aed370fb..3fca482739 100644 --- a/pkg/sdk/functions_impl_gen.go +++ b/pkg/sdk/functions_impl_gen.go @@ -2,7 +2,6 @@ package sdk import ( "context" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" ) @@ -63,6 +62,7 @@ func (v *functions) ShowByID(ctx context.Context, id SchemaObjectIdentifierWithA if err != nil { return nil, err } + // TODO: Should compare arguments return collections.FindOne(functions, func(r Function) bool { return r.Name == id.Name() }) } @@ -371,7 +371,7 @@ func (r functionRow) convert() *Function { IsAnsi: r.IsAnsi == "Y", MinNumArguments: r.MinNumArguments, MaxNumArguments: r.MaxNumArguments, - Arguments: r.Arguments, + ArgumentsRaw: r.Arguments, Description: r.Description, CatalogName: r.CatalogName, IsTableFunction: r.IsTableFunction == "Y", diff --git a/pkg/sdk/identifier_helpers.go b/pkg/sdk/identifier_helpers.go index 31400acc89..99c4036436 100644 --- a/pkg/sdk/identifier_helpers.go +++ b/pkg/sdk/identifier_helpers.go @@ -312,16 +312,53 @@ func NewSchemaObjectIdentifierWithArgumentsInSchema(schemaId DatabaseObjectIdent return NewSchemaObjectIdentifierWithArguments(schemaId.DatabaseName(), schemaId.Name(), name, argumentDataTypes...) } -// TODO: -//func NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(fullyQualifiedName string) SchemaObjectIdentifierWithArguments { -// parts := strings.Split(fullyQualifiedName, ".") -// id := SchemaObjectIdentifierWithArguments{ -// databaseName: strings.Trim(parts[0], `"`), -// schemaName: strings.Trim(parts[1], `"`), -// name: strings.Trim(parts[2], `"`), -// // TODO: Arguments +func NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(fullyQualifiedName string) (SchemaObjectIdentifierWithArguments, error) { + // TODO: This is wrong + argsBegin := strings.LastIndex(fullyQualifiedName, "(") + parts, err := parseIdentifierStringWithOpts(fullyQualifiedName[:argsBegin], func(r *csv.Reader) { + r.Comma = '.' + }) + if err != nil { + return SchemaObjectIdentifierWithArguments{}, err + } + return NewSchemaObjectIdentifierWithArguments( + parts[0], + parts[1], + parts[2], + parseFunctionArgumentsFromString(fullyQualifiedName[argsBegin:])..., + ), nil +} + +// TODO: Remove this func +func parseIdentifierStringWithOpts(identifier string, opts func(*csv.Reader)) ([]string, error) { + reader := csv.NewReader(strings.NewReader(identifier)) + if opts != nil { + opts(reader) + } + lines, err := reader.ReadAll() + if err != nil { + return nil, fmt.Errorf("unable to read identifier: %s, err = %w", identifier, err) + } + if lines == nil { + return make([]string, 0), nil + } + if len(lines) != 1 { + return nil, fmt.Errorf("incompatible identifier: %s", identifier) + } + return lines[0], nil +} + +// TODO: Move to resource package (or use FullyQUalifiedName and NewFromFullyQualifiedName because it will be needed anyway for things like returned ids from SHOW GRANTS) +//func NewSchemaObjectIdentifierWithArgumentsFromResourceIdentifier(resourceId string) SchemaObjectIdentifierWithArguments { +// // TODO: use standard parsing method +// resourceIdParts := strings.Split(resourceId, "|") +// schemaObjectId := NewSchemaObjectIdentifierFromFullyQualifiedName(resourceIdParts[0]) +// argumentSlice := resourceIdParts[1:] +// arguments := make([]DataType, len(argumentSlice)) +// for i, argument := range argumentSlice { +// arguments[i] = DataType(argument) // } -// return id +// return NewSchemaObjectIdentifierWithArguments(schemaObjectId.DatabaseName(), schemaObjectId.SchemaName(), schemaObjectId.Name(), arguments...) //} func (i SchemaObjectIdentifierWithArguments) DatabaseName() string { @@ -356,9 +393,19 @@ func (i SchemaObjectIdentifierWithArguments) FullyQualifiedName() string { if i.schemaName == "" && i.databaseName == "" && i.name == "" && len(i.argumentDataTypes) == 0 { return "" } - return fmt.Sprintf(`"%v"."%v"."%v"(%v)`, i.databaseName, i.schemaName, i.name, strings.Join(AsStringList(i.argumentDataTypes), ", ")) + return fmt.Sprintf(`"%v"."%v"."%v"(%v)`, i.databaseName, i.schemaName, i.name, strings.Join(AsStringList(i.argumentDataTypes), ",")) } +// TODO: Move to resource package +//func (i SchemaObjectIdentifierWithArguments) AsResourceIdentifier() string { +// // TODO: use standard encoding method +// resourceId := []string{ +// i.SchemaObjectId().FullyQualifiedName(), +// } +// resourceId = append(resourceId, AsStringList(i.ArgumentDataTypes())...) +// return strings.Join(resourceId, "|") +//} + type TableColumnIdentifier struct { databaseName string schemaName string diff --git a/pkg/sdk/identifier_helpers_test.go b/pkg/sdk/identifier_helpers_test.go index 3b84c372f5..a034efff9d 100644 --- a/pkg/sdk/identifier_helpers_test.go +++ b/pkg/sdk/identifier_helpers_test.go @@ -1,6 +1,7 @@ package sdk import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -83,3 +84,22 @@ func TestDatabaseObjectIdentifier(t *testing.T) { assert.Equal(t, `"aaa"."bbb"`, identifier.FullyQualifiedName()) }) } + +// TODO: test cases +func TestSchemaObjectIdentifierWithArguments(t *testing.T) { + t.Run("create new from fully qualified name", func(t *testing.T) { + testCases := []struct { + Input SchemaObjectIdentifierWithArguments + }{ + {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeFloat, DataTypeNumber, DataTypeTimestampTZ)}, + } + + for _, testCase := range testCases { + t.Run(fmt.Sprintf("processing %s", testCase.Input.FullyQualifiedName()), func(t *testing.T) { + parsedId, err := NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(testCase.Input.FullyQualifiedName()) + require.NoError(t, err) + require.Equal(t, testCase.Input.FullyQualifiedName(), parsedId.FullyQualifiedName()) + }) + } + }) +} diff --git a/pkg/sdk/testint/functions_integration_test.go b/pkg/sdk/testint/functions_integration_test.go index a237d77df6..a1d57a79ee 100644 --- a/pkg/sdk/testint/functions_integration_test.go +++ b/pkg/sdk/testint/functions_integration_test.go @@ -8,8 +8,6 @@ import ( "time" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -185,262 +183,268 @@ def dump(i): }) } -func TestInt_OtherFunctions(t *testing.T) { - client := testClient(t) - ctx := testContext(t) - - tagTest, tagCleanup := testClientHelper().Tag.CreateTag(t) - t.Cleanup(tagCleanup) - - assertFunction := func(t *testing.T, id sdk.SchemaObjectIdentifierWithArguments, secure bool, withArguments bool) { - t.Helper() - - function, err := client.Functions.ShowByID(ctx, id) - require.NoError(t, err) - - assert.NotEmpty(t, function.CreatedOn) - assert.Equal(t, id.Name(), function.Name) - assert.Equal(t, false, function.IsBuiltin) - assert.Equal(t, false, function.IsAggregate) - assert.Equal(t, false, function.IsAnsi) - if withArguments { - assert.Equal(t, 1, function.MinNumArguments) - assert.Equal(t, 1, function.MaxNumArguments) - } else { - assert.Equal(t, 0, function.MinNumArguments) - assert.Equal(t, 0, function.MaxNumArguments) - } - assert.NotEmpty(t, function.Arguments) - assert.NotEmpty(t, function.Description) - assert.NotEmpty(t, function.CatalogName) - assert.Equal(t, false, function.IsTableFunction) - assert.Equal(t, false, function.ValidForClustering) - assert.Equal(t, secure, function.IsSecure) - assert.Equal(t, false, function.IsExternalFunction) - assert.Equal(t, "SQL", function.Language) - assert.Equal(t, false, function.IsMemoizable) - } - - cleanupFunctionHandle := func(id sdk.SchemaObjectIdentifierWithArguments) func() { - return func() { - err := client.Functions.Drop(ctx, sdk.NewDropFunctionRequest(id)) - if errors.Is(err, sdk.ErrObjectNotExistOrAuthorized) { - return - } - require.NoError(t, err) - } - } - - createFunctionForSQLHandle := func(t *testing.T, cleanup bool, withArguments bool) *sdk.Function { - t.Helper() - id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(sdk.DataTypeFloat) - - definition := "3.141592654::FLOAT" - - dt := sdk.NewFunctionReturnsResultDataTypeRequest(sdk.DataTypeFloat) - returns := sdk.NewFunctionReturnsRequest().WithResultDataType(*dt) - request := sdk.NewCreateForSQLFunctionRequest(id.SchemaObjectId(), *returns, definition). - WithOrReplace(true) - if withArguments { - argument := sdk.NewFunctionArgumentRequest("x", sdk.DataTypeFloat) - request = request.WithArguments([]sdk.FunctionArgumentRequest{*argument}) - } - err := client.Functions.CreateForSQL(ctx, request) - require.NoError(t, err) - if cleanup { - t.Cleanup(cleanupFunctionHandle(id)) - } - function, err := client.Functions.ShowByID(ctx, id) - require.NoError(t, err) - return function - } - - defaultAlterRequest := func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { - return sdk.NewAlterFunctionRequest(id) - } - - t.Run("alter function: rename", func(t *testing.T) { - f := createFunctionForSQLHandle(t, false, true) - - id := f.ID() - nid := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithRenameTo(nid.SchemaObjectId())) - if err != nil { - t.Cleanup(cleanupFunctionHandle(id)) - } else { - t.Cleanup(cleanupFunctionHandle(nid)) - } - require.NoError(t, err) - - _, err = client.Functions.ShowByID(ctx, id) - assert.ErrorIs(t, err, collections.ErrObjectNotFound) - - e, err := client.Functions.ShowByID(ctx, nid) - require.NoError(t, err) - require.Equal(t, nid.Name(), e.Name) - }) - - t.Run("alter function: set log level", func(t *testing.T) { - f := createFunctionForSQLHandle(t, true, true) - - id := f.ID() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetLogLevel(string(sdk.LogLevelDebug))) - require.NoError(t, err) - assertFunction(t, id, false, true) - }) - - t.Run("alter function: unset log level", func(t *testing.T) { - f := createFunctionForSQLHandle(t, true, true) - - id := f.ID() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetLogLevel(true)) - require.NoError(t, err) - assertFunction(t, id, false, true) - }) - - t.Run("alter function: set trace level", func(t *testing.T) { - f := createFunctionForSQLHandle(t, true, true) - - id := f.ID() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetTraceLevel(string(sdk.TraceLevelAlways))) - require.NoError(t, err) - assertFunction(t, id, false, true) - }) - - t.Run("alter function: unset trace level", func(t *testing.T) { - f := createFunctionForSQLHandle(t, true, true) - - id := f.ID() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetTraceLevel(true)) - require.NoError(t, err) - assertFunction(t, id, false, true) - }) - - t.Run("alter function: set comment", func(t *testing.T) { - f := createFunctionForSQLHandle(t, true, true) - - id := f.ID() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetComment("test comment")) - require.NoError(t, err) - assertFunction(t, id, false, true) - }) - - t.Run("alter function: unset comment", func(t *testing.T) { - f := createFunctionForSQLHandle(t, true, true) - - id := f.ID() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetComment(true)) - require.NoError(t, err) - assertFunction(t, id, false, true) - }) - - t.Run("alter function: set secure", func(t *testing.T) { - f := createFunctionForSQLHandle(t, true, true) - - id := f.ID() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetSecure(true)) - require.NoError(t, err) - assertFunction(t, id, true, true) - }) - - t.Run("alter function: set secure with no arguments", func(t *testing.T) { - f := createFunctionForSQLHandle(t, true, false) - id := f.ID() - err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithSetSecure(true)) - require.NoError(t, err) - assertFunction(t, id, true, false) - }) - - t.Run("alter function: unset secure", func(t *testing.T) { - f := createFunctionForSQLHandle(t, true, true) - - id := f.ID() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetSecure(true)) - require.NoError(t, err) - assertFunction(t, id, false, true) - }) - - t.Run("alter function: set and unset tags", func(t *testing.T) { - f := createFunctionForSQLHandle(t, true, true) - - id := f.ID() - setTags := []sdk.TagAssociation{ - { - Name: tagTest.ID(), - Value: "v1", - }, - } - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetTags(setTags)) - require.NoError(t, err) - assertFunction(t, id, false, true) - - unsetTags := []sdk.ObjectIdentifier{ - tagTest.ID(), - } - err = client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetTags(unsetTags)) - require.NoError(t, err) - assertFunction(t, id, false, true) - }) - - t.Run("show function for SQL: without like", func(t *testing.T) { - f1 := createFunctionForSQLHandle(t, true, true) - f2 := createFunctionForSQLHandle(t, true, true) - - functions, err := client.Functions.Show(ctx, sdk.NewShowFunctionRequest()) - require.NoError(t, err) - - require.Contains(t, functions, *f1) - require.Contains(t, functions, *f2) - }) - - t.Run("show function for SQL: with like", func(t *testing.T) { - f1 := createFunctionForSQLHandle(t, true, true) - f2 := createFunctionForSQLHandle(t, true, true) - - functions, err := client.Functions.Show(ctx, sdk.NewShowFunctionRequest().WithLike(sdk.Like{Pattern: &f1.Name})) - require.NoError(t, err) - - require.Equal(t, 1, len(functions)) - require.Contains(t, functions, *f1) - require.NotContains(t, functions, *f2) - }) - - t.Run("show function for SQL: no matches", func(t *testing.T) { - functions, err := client.Functions.Show(ctx, sdk.NewShowFunctionRequest().WithLike(sdk.Like{Pattern: sdk.String("non-existing-id-pattern")})) - require.NoError(t, err) - require.Equal(t, 0, len(functions)) - }) - - t.Run("describe function for SQL", func(t *testing.T) { - f := createFunctionForSQLHandle(t, true, true) - - details, err := client.Functions.Describe(ctx, f.ID()) - 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, "FLOAT", pairs["returns"]) - require.Equal(t, "3.141592654::FLOAT", pairs["body"]) - require.Equal(t, "(X FLOAT)", pairs["signature"]) - }) - - t.Run("describe function for SQL: no arguments", func(t *testing.T) { - f := createFunctionForSQLHandle(t, true, false) - - details, err := client.Functions.Describe(ctx, f.ID()) - 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, "FLOAT", pairs["returns"]) - require.Equal(t, "3.141592654::FLOAT", pairs["body"]) - require.Equal(t, "()", pairs["signature"]) - }) -} +//func TestInt_OtherFunctions(t *testing.T) { +// client := testClient(t) +// ctx := testContext(t) +// +// tagTest, tagCleanup := testClientHelper().Tag.CreateTag(t) +// t.Cleanup(tagCleanup) +// +// assertFunction := func(t *testing.T, id sdk.SchemaObjectIdentifierWithArguments, secure bool, withArguments bool) { +// t.Helper() +// +// function, err := client.Functions.ShowByID(ctx, id) +// require.NoError(t, err) +// +// assert.NotEmpty(t, function.CreatedOn) +// assert.Equal(t, id.Name(), function.Name) +// assert.Equal(t, false, function.IsBuiltin) +// assert.Equal(t, false, function.IsAggregate) +// assert.Equal(t, false, function.IsAnsi) +// if withArguments { +// assert.Equal(t, 1, function.MinNumArguments) +// assert.Equal(t, 1, function.MaxNumArguments) +// } else { +// assert.Equal(t, 0, function.MinNumArguments) +// assert.Equal(t, 0, function.MaxNumArguments) +// } +// assert.NotEmpty(t, function.ArgumentsRaw) +// assert.NotEmpty(t, function.Arguments) +// assert.NotEmpty(t, function.Description) +// assert.NotEmpty(t, function.CatalogName) +// assert.Equal(t, false, function.IsTableFunction) +// assert.Equal(t, false, function.ValidForClustering) +// assert.Equal(t, secure, function.IsSecure) +// assert.Equal(t, false, function.IsExternalFunction) +// assert.Equal(t, "SQL", function.Language) +// assert.Equal(t, false, function.IsMemoizable) +// } +// +// cleanupFunctionHandle := func(id sdk.SchemaObjectIdentifierWithArguments) func() { +// return func() { +// err := client.Functions.Drop(ctx, sdk.NewDropFunctionRequest(id)) +// if errors.Is(err, sdk.ErrObjectNotExistOrAuthorized) { +// return +// } +// require.NoError(t, err) +// } +// } +// +// createFunctionForSQLHandle := func(t *testing.T, cleanup bool, withArguments bool) *sdk.Function { +// t.Helper() +// var id sdk.SchemaObjectIdentifierWithArguments +// if withArguments { +// id = testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(sdk.DataTypeFloat) +// } else { +// id = testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments() +// } +// +// definition := "3.141592654::FLOAT" +// +// dt := sdk.NewFunctionReturnsResultDataTypeRequest(sdk.DataTypeFloat) +// returns := sdk.NewFunctionReturnsRequest().WithResultDataType(*dt) +// request := sdk.NewCreateForSQLFunctionRequest(id.SchemaObjectId(), *returns, definition). +// WithOrReplace(true) +// if withArguments { +// argument := sdk.NewFunctionArgumentRequest("x", sdk.DataTypeFloat) +// request = request.WithArguments([]sdk.FunctionArgumentRequest{*argument}) +// } +// err := client.Functions.CreateForSQL(ctx, request) +// require.NoError(t, err) +// if cleanup { +// t.Cleanup(cleanupFunctionHandle(id)) +// } +// function, err := client.Functions.ShowByID(ctx, id) +// require.NoError(t, err) +// return function +// } +// +// defaultAlterRequest := func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { +// return sdk.NewAlterFunctionRequest(id) +// } +// +// t.Run("alter function: rename", func(t *testing.T) { +// f := createFunctionForSQLHandle(t, false, true) +// +// id := f.ID() +// nid := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments() +// err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithRenameTo(nid.SchemaObjectId())) +// if err != nil { +// t.Cleanup(cleanupFunctionHandle(id)) +// } else { +// t.Cleanup(cleanupFunctionHandle(nid)) +// } +// require.NoError(t, err) +// +// _, err = client.Functions.ShowByID(ctx, id) +// assert.ErrorIs(t, err, collections.ErrObjectNotFound) +// +// e, err := client.Functions.ShowByID(ctx, nid) +// require.NoError(t, err) +// require.Equal(t, nid.Name(), e.Name) +// }) +// +// t.Run("alter function: set log level", func(t *testing.T) { +// f := createFunctionForSQLHandle(t, true, true) +// +// id := f.ID() +// err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetLogLevel(string(sdk.LogLevelDebug))) +// require.NoError(t, err) +// assertFunction(t, id, false, true) +// }) +// +// t.Run("alter function: unset log level", func(t *testing.T) { +// f := createFunctionForSQLHandle(t, true, true) +// +// id := f.ID() +// err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetLogLevel(true)) +// require.NoError(t, err) +// assertFunction(t, id, false, true) +// }) +// +// t.Run("alter function: set trace level", func(t *testing.T) { +// f := createFunctionForSQLHandle(t, true, true) +// +// id := f.ID() +// err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetTraceLevel(string(sdk.TraceLevelAlways))) +// require.NoError(t, err) +// assertFunction(t, id, false, true) +// }) +// +// t.Run("alter function: unset trace level", func(t *testing.T) { +// f := createFunctionForSQLHandle(t, true, true) +// +// id := f.ID() +// err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetTraceLevel(true)) +// require.NoError(t, err) +// assertFunction(t, id, false, true) +// }) +// +// t.Run("alter function: set comment", func(t *testing.T) { +// f := createFunctionForSQLHandle(t, true, true) +// +// id := f.ID() +// err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetComment("test comment")) +// require.NoError(t, err) +// assertFunction(t, id, false, true) +// }) +// +// t.Run("alter function: unset comment", func(t *testing.T) { +// f := createFunctionForSQLHandle(t, true, true) +// +// id := f.ID() +// err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetComment(true)) +// require.NoError(t, err) +// assertFunction(t, id, false, true) +// }) +// +// t.Run("alter function: set secure", func(t *testing.T) { +// f := createFunctionForSQLHandle(t, true, true) +// +// id := f.ID() +// err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetSecure(true)) +// require.NoError(t, err) +// assertFunction(t, id, true, true) +// }) +// +// t.Run("alter function: set secure with no arguments", func(t *testing.T) { +// f := createFunctionForSQLHandle(t, true, true) +// id := f.ID() +// err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithSetSecure(true)) +// require.NoError(t, err) +// assertFunction(t, id, true, true) +// }) +// +// t.Run("alter function: unset secure", func(t *testing.T) { +// f := createFunctionForSQLHandle(t, true, true) +// +// id := f.ID() +// err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetSecure(true)) +// require.NoError(t, err) +// assertFunction(t, id, false, true) +// }) +// +// t.Run("alter function: set and unset tags", func(t *testing.T) { +// f := createFunctionForSQLHandle(t, true, true) +// +// id := f.ID() +// setTags := []sdk.TagAssociation{ +// { +// Name: tagTest.ID(), +// Value: "v1", +// }, +// } +// err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetTags(setTags)) +// require.NoError(t, err) +// assertFunction(t, id, false, true) +// +// unsetTags := []sdk.ObjectIdentifier{ +// tagTest.ID(), +// } +// err = client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetTags(unsetTags)) +// require.NoError(t, err) +// assertFunction(t, id, false, true) +// }) +// +// t.Run("show function for SQL: without like", func(t *testing.T) { +// f1 := createFunctionForSQLHandle(t, true, true) +// f2 := createFunctionForSQLHandle(t, true, true) +// +// functions, err := client.Functions.Show(ctx, sdk.NewShowFunctionRequest()) +// require.NoError(t, err) +// +// require.Contains(t, functions, *f1) +// require.Contains(t, functions, *f2) +// }) +// +// t.Run("show function for SQL: with like", func(t *testing.T) { +// f1 := createFunctionForSQLHandle(t, true, true) +// f2 := createFunctionForSQLHandle(t, true, true) +// +// functions, err := client.Functions.Show(ctx, sdk.NewShowFunctionRequest().WithLike(sdk.Like{Pattern: &f1.Name})) +// require.NoError(t, err) +// +// require.Equal(t, 1, len(functions)) +// require.Contains(t, functions, *f1) +// require.NotContains(t, functions, *f2) +// }) +// +// t.Run("show function for SQL: no matches", func(t *testing.T) { +// functions, err := client.Functions.Show(ctx, sdk.NewShowFunctionRequest().WithLike(sdk.Like{Pattern: sdk.String("non-existing-id-pattern")})) +// require.NoError(t, err) +// require.Equal(t, 0, len(functions)) +// }) +// +// t.Run("describe function for SQL", func(t *testing.T) { +// f := createFunctionForSQLHandle(t, true, true) +// +// details, err := client.Functions.Describe(ctx, f.ID()) +// 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, "FLOAT", pairs["returns"]) +// require.Equal(t, "3.141592654::FLOAT", pairs["body"]) +// require.Equal(t, "(X FLOAT)", pairs["signature"]) +// }) +// +// t.Run("describe function for SQL: no arguments", func(t *testing.T) { +// f := createFunctionForSQLHandle(t, true, false) +// +// details, err := client.Functions.Describe(ctx, f.ID()) +// 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, "FLOAT", pairs["returns"]) +// require.Equal(t, "3.141592654::FLOAT", pairs["body"]) +// require.Equal(t, "()", pairs["signature"]) +// }) +//} func TestInt_FunctionsShowByID(t *testing.T) { client := testClient(t) @@ -475,18 +479,85 @@ func TestInt_FunctionsShowByID(t *testing.T) { schema, schemaCleanup := testClientHelper().Schema.CreateSchema(t) t.Cleanup(schemaCleanup) - id1 := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments() - id2 := testClientHelper().Ids.NewSchemaObjectIdentifierWithArgumentsInSchema(id1.Name(), schema.ID()) + id1 := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(sdk.DataTypeFloat) + id2 := testClientHelper().Ids.NewSchemaObjectIdentifierWithArgumentsInSchema(id1.Name(), schema.ID(), sdk.DataTypeFloat) createFunctionForSQLHandle(t, id1) createFunctionForSQLHandle(t, id2) e1, err := client.Functions.ShowByID(ctx, id1) require.NoError(t, err) - require.Equal(t, id1, e1.ID()) + + e1Details, err := client.Functions.Describe(ctx, id1) + require.NoError(t, err) + + e1Id, err := e1.ID(e1Details) + require.NoError(t, err) + require.Equal(t, id1, e1Id) e2, err := client.Functions.ShowByID(ctx, id2) require.NoError(t, err) - require.Equal(t, id2, e2.ID()) + + e2Details, err := client.Functions.Describe(ctx, id2) + require.NoError(t, err) + + e2Id, err := e2.ID(e2Details) + require.NoError(t, err) + require.Equal(t, id2, e2Id) + }) + + t.Run("function returns non detailed data types of arguments", func(t *testing.T) { + // This test proves that every detailed data type (e.g. VARCHAR(20) and NUMBER(10, 0)) is generalized + // (to e.g. VARCHAR and NUMBER) and that sdk.ToDataType mapping function maps detailed types correctly to + // their generalized counterparts. + + id := testClientHelper().Ids.RandomSchemaObjectIdentifier() + args := []sdk.FunctionArgumentRequest{ + *sdk.NewFunctionArgumentRequest("A", "NUMBER(2, 0)"), + *sdk.NewFunctionArgumentRequest("B", "DECIMAL"), + *sdk.NewFunctionArgumentRequest("C", "INTEGER"), + *sdk.NewFunctionArgumentRequest("D", sdk.DataTypeFloat), + *sdk.NewFunctionArgumentRequest("E", "DOUBLE"), + *sdk.NewFunctionArgumentRequest("F", "VARCHAR(20)"), + *sdk.NewFunctionArgumentRequest("G", "CHAR"), + *sdk.NewFunctionArgumentRequest("H", sdk.DataTypeString), + *sdk.NewFunctionArgumentRequest("I", "TEXT"), + *sdk.NewFunctionArgumentRequest("J", sdk.DataTypeBinary), + *sdk.NewFunctionArgumentRequest("K", "VARBINARY"), + *sdk.NewFunctionArgumentRequest("L", sdk.DataTypeBoolean), + *sdk.NewFunctionArgumentRequest("M", sdk.DataTypeDate), + *sdk.NewFunctionArgumentRequest("N", "DATETIME"), + *sdk.NewFunctionArgumentRequest("O", sdk.DataTypeTime), + *sdk.NewFunctionArgumentRequest("P", sdk.DataTypeTimestamp), + *sdk.NewFunctionArgumentRequest("R", sdk.DataTypeTimestampLTZ), + *sdk.NewFunctionArgumentRequest("S", sdk.DataTypeTimestampNTZ), + *sdk.NewFunctionArgumentRequest("T", sdk.DataTypeTimestampTZ), + *sdk.NewFunctionArgumentRequest("U", sdk.DataTypeVariant), + *sdk.NewFunctionArgumentRequest("V", sdk.DataTypeObject), + *sdk.NewFunctionArgumentRequest("W", sdk.DataTypeArray), + *sdk.NewFunctionArgumentRequest("X", sdk.DataTypeGeography), + *sdk.NewFunctionArgumentRequest("Y", sdk.DataTypeGeometry), + *sdk.NewFunctionArgumentRequest("Z", "VECTOR(INT, 16)"), + } + err := client.Functions.CreateForPython(ctx, sdk.NewCreateForPythonFunctionRequest( + id, + *sdk.NewFunctionReturnsRequest().WithResultDataType(*sdk.NewFunctionReturnsResultDataTypeRequest(sdk.DataTypeVariant)), + "3.8", + "add", + ). + WithArguments(args). + WithFunctionDefinition("def add(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, R, S, T, U, V, W, X, Y, Z): A + A"), + ) + require.NoError(t, err) + + dataTypes := make([]sdk.DataType, len(args)) + for i, arg := range args { + dataTypes[i], err = sdk.ToDataType(string(arg.ArgDataType)) + require.NoError(t, err) + } + idWithArguments := sdk.NewSchemaObjectIdentifierWithArguments(id.DatabaseName(), id.SchemaName(), id.Name(), dataTypes...) + + _, err = client.Functions.ShowByID(ctx, idWithArguments) + require.NoError(t, err) }) } From 8328e325a55feb0272827437ff05b93b01ad7ae0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Mon, 5 Aug 2024 10:43:38 +0200 Subject: [PATCH 06/13] wip --- pkg/sdk/functions_gen.go | 51 +++++++++++++++++++++++----- pkg/sdk/identifier_helpers.go | 21 +++++++++--- pkg/sdk/identifier_helpers_test.go | 54 ++++++++++++++++++++---------- 3 files changed, 97 insertions(+), 29 deletions(-) diff --git a/pkg/sdk/functions_gen.go b/pkg/sdk/functions_gen.go index 459758f70d..81d9a38614 100644 --- a/pkg/sdk/functions_gen.go +++ b/pkg/sdk/functions_gen.go @@ -1,9 +1,12 @@ package sdk import ( + "bytes" "context" "database/sql" + "fmt" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" + "log" "strings" ) @@ -261,14 +264,46 @@ func parseFunctionArgumentsFromDetails(details []FunctionDetail) ([]DataType, er return arguments, nil } -func parseFunctionArgumentsFromString(arguments string) []DataType { - // TODO what about data types with parentheses - argListBegin := strings.LastIndex(arguments, "(") - argListEnd := strings.LastIndex(arguments, ")") - return collections.Map( - ParseCommaSeparatedStringArray(arguments[argListBegin+1:argListEnd], false), - func(dataType string) DataType { return DataType(dataType) }, - ) +// Move to sdk/identifier_parsers.go +func parseFunctionArgumentsFromString(arguments string) ([]DataType, error) { + dataTypes := make([]DataType, 0) + + stringBuffer := bytes.NewBufferString(arguments) + for stringBuffer.Len() > 0 { + + // we use another buffer to peek into next data type + peekBuffer := bytes.NewBufferString(stringBuffer.String()) + peekDataType, _ := peekBuffer.ReadString(',') + peekDataType = strings.TrimSpace(peekDataType) + + // For function purposes only Vector needs special case + switch { + case strings.HasPrefix(peekDataType, "VECTOR"): + vectorDataType, _ := stringBuffer.ReadString(')') + vectorDataType = strings.TrimSpace(vectorDataType) + if stringBuffer.Len() > 0 { + commaByte, err := stringBuffer.ReadByte() + if commaByte != ',' { + return nil, fmt.Errorf("expected a comma delimited string but found %s", string(commaByte)) + } + if err != nil { + return nil, err + } + } + log.Println("Adding vec:", vectorDataType) + dataTypes = append(dataTypes, DataType(vectorDataType)) + default: + dataType, err := stringBuffer.ReadString(',') + if err == nil { + dataType = dataType[:len(dataType)-1] + } + dataType = strings.TrimSpace(dataType) + log.Println("Adding:", dataType) + dataTypes = append(dataTypes, DataType(dataType)) + } + } + + return dataTypes, nil } func (v *Function) ID(details []FunctionDetail) (SchemaObjectIdentifierWithArguments, error) { diff --git a/pkg/sdk/identifier_helpers.go b/pkg/sdk/identifier_helpers.go index 99c4036436..1ca037b8df 100644 --- a/pkg/sdk/identifier_helpers.go +++ b/pkg/sdk/identifier_helpers.go @@ -313,19 +313,23 @@ func NewSchemaObjectIdentifierWithArgumentsInSchema(schemaId DatabaseObjectIdent } func NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(fullyQualifiedName string) (SchemaObjectIdentifierWithArguments, error) { - // TODO: This is wrong - argsBegin := strings.LastIndex(fullyQualifiedName, "(") - parts, err := parseIdentifierStringWithOpts(fullyQualifiedName[:argsBegin], func(r *csv.Reader) { + splitIdIndex := strings.IndexRune(fullyQualifiedName, '(') + parts, err := parseIdentifierStringWithOpts(fullyQualifiedName[:splitIdIndex], func(r *csv.Reader) { r.Comma = '.' }) if err != nil { return SchemaObjectIdentifierWithArguments{}, err } + arguments := fullyQualifiedName[splitIdIndex:] + dataTypes, err := parseFunctionArgumentsFromString(arguments[1 : len(arguments)-1]) + if err != nil { + return SchemaObjectIdentifierWithArguments{}, err + } return NewSchemaObjectIdentifierWithArguments( parts[0], parts[1], parts[2], - parseFunctionArgumentsFromString(fullyQualifiedName[argsBegin:])..., + dataTypes..., ), nil } @@ -345,6 +349,15 @@ func parseIdentifierStringWithOpts(identifier string, opts func(*csv.Reader)) ([ if len(lines) != 1 { return nil, fmt.Errorf("incompatible identifier: %s", identifier) } + for _, part := range lines[0] { + // TODO(SNOW-1571674): Remove the validation + if strings.Contains(part, `"`) { + return nil, fmt.Errorf(`unable to parse identifier: %s, currently identifiers containing double quotes are not supported in the provider`, identifier) + } + if strings.ContainsAny(part, `()`) { + return nil, fmt.Errorf(`unable to parse identifier: %s, currently identifiers containing '(' or ')' parentheses are not supported in the provider`, identifier) + } + } return lines[0], nil } diff --git a/pkg/sdk/identifier_helpers_test.go b/pkg/sdk/identifier_helpers_test.go index a034efff9d..33800a1967 100644 --- a/pkg/sdk/identifier_helpers_test.go +++ b/pkg/sdk/identifier_helpers_test.go @@ -85,21 +85,41 @@ func TestDatabaseObjectIdentifier(t *testing.T) { }) } -// TODO: test cases -func TestSchemaObjectIdentifierWithArguments(t *testing.T) { - t.Run("create new from fully qualified name", func(t *testing.T) { - testCases := []struct { - Input SchemaObjectIdentifierWithArguments - }{ - {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeFloat, DataTypeNumber, DataTypeTimestampTZ)}, - } - - for _, testCase := range testCases { - t.Run(fmt.Sprintf("processing %s", testCase.Input.FullyQualifiedName()), func(t *testing.T) { - parsedId, err := NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(testCase.Input.FullyQualifiedName()) - require.NoError(t, err) - require.Equal(t, testCase.Input.FullyQualifiedName(), parsedId.FullyQualifiedName()) - }) - } - }) +func TestNewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(t *testing.T) { + testCases := []struct { + RawInput string + Input SchemaObjectIdentifierWithArguments + Error string + }{ + {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeFloat, DataTypeNumber, DataTypeTimestampTZ)}, + {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeFloat, "VECTOR(INT, 20)")}, + {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, "VECTOR(INT, 20)", DataTypeFloat)}, + {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeFloat, "VECTOR(INT, 20)", "VECTOR(INT, 10)")}, + {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeTime, "VECTOR(INT, 20)", "VECTOR(FLOAT, 10)", DataTypeFloat)}, + // TODO(): Won't work, because of the assumption that identifiers are not containing '(' and ')' parentheses + {Input: NewSchemaObjectIdentifierWithArguments(`ab()c`, `def()`, `()ghi`, DataTypeTime, "VECTOR(INT, 20)", "VECTOR(FLOAT, 10)", DataTypeFloat), Error: `unable to read identifier: "ab`}, + {Input: NewSchemaObjectIdentifierWithArguments(`ab(,)c`, `,def()`, `()ghi,`, DataTypeTime, "VECTOR(INT, 20)", "VECTOR(FLOAT, 10)", DataTypeFloat), Error: `unable to read identifier: "ab`}, + {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`)}, + {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`), RawInput: `abc.def.ghi()`}, + {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeFloat, "VECTOR(INT, 20)"), RawInput: `abc.def.ghi(FLOAT, VECTOR(INT, 20))`}, + } + + for _, testCase := range testCases { + t.Run(fmt.Sprintf("processing %s", testCase.Input.FullyQualifiedName()), func(t *testing.T) { + var id SchemaObjectIdentifierWithArguments + var err error + if testCase.RawInput != "" { + id, err = NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(testCase.RawInput) + } else { + id, err = NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(testCase.Input.FullyQualifiedName()) + } + + if testCase.Error != "" { + assert.ErrorContains(t, err, testCase.Error) + } else { + assert.NoError(t, err) + assert.Equal(t, testCase.Input.FullyQualifiedName(), id.FullyQualifiedName()) + } + }) + } } From 5767204b3df6a58595dd9d2a4378a853c0af867d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Mon, 5 Aug 2024 14:07:19 +0200 Subject: [PATCH 07/13] wip --- pkg/resources/external_function.go | 991 ++++++++------- .../external_function_acceptance_test.go | 1106 ++++++++--------- pkg/resources/function_acceptance_test.go | 80 +- pkg/resources/function_state_upgraders.go | 1 + pkg/sdk/functions_gen.go | 71 +- pkg/sdk/functions_impl_gen.go | 11 + pkg/sdk/identifier_helpers.go | 3 +- pkg/sdk/identifier_parsers.go | 52 + pkg/sdk/identifier_parsers_test.go | 49 + pkg/sdk/testint/functions_integration_test.go | 592 +++++---- 10 files changed, 1558 insertions(+), 1398 deletions(-) diff --git a/pkg/resources/external_function.go b/pkg/resources/external_function.go index 5bdff6e3b3..a78e9c5d6b 100644 --- a/pkg/resources/external_function.go +++ b/pkg/resources/external_function.go @@ -1,506 +1,489 @@ package resources -import ( - "context" - "encoding/json" - "log" - "regexp" - "strconv" - "strings" - - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" - - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" - "github.com/hashicorp/go-cty/cty" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" -) - -var externalFunctionSchema = map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - Description: "Specifies the identifier for the external function. The identifier can contain the schema name and database name, as well as the function name. The function's signature (name and argument data types) must be unique within the schema.", - }, - "schema": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - Description: "The schema in which to create the external function.", - }, - "database": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - Description: "The database in which to create the external function.", - }, - "arg": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, - Description: "Specifies the arguments/inputs for the external function. These should correspond to the arguments that the remote service expects.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - // Suppress the diff shown if the values are equal when both compared in lower case. - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - return strings.EqualFold(strings.ToLower(old), strings.ToLower(new)) - }, - Description: "Argument name", - }, - "type": { - Type: schema.TypeString, - Required: true, - // Suppress the diff shown if the values are equal when both compared in lower case. - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - return strings.EqualFold(strings.ToLower(old), strings.ToLower(new)) - }, - Description: "Argument type, e.g. VARCHAR", - }, - }, - }, - }, - "null_input_behavior": { - Type: schema.TypeString, - Optional: true, - Default: "CALLED ON NULL INPUT", - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{"CALLED ON NULL INPUT", "RETURNS NULL ON NULL INPUT", "STRICT"}, false), - Description: "Specifies the behavior of the external function when called with null inputs.", - }, - "return_type": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - // Suppress the diff shown if the values are equal when both compared in lower case. - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - return strings.EqualFold(strings.ToLower(old), strings.ToLower(new)) - }, - Description: "Specifies the data type returned by the external function.", - }, - "return_null_allowed": { - Type: schema.TypeBool, - Optional: true, - ForceNew: true, - Description: "Indicates whether the function can return NULL values (true) or must return only NON-NULL values (false).", - Default: true, - }, - "return_behavior": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{"VOLATILE", "IMMUTABLE"}, false), - Description: "Specifies the behavior of the function when returning results", - }, - "api_integration": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - Description: "The name of the API integration object that should be used to authenticate the call to the proxy service.", - }, - "header": { - Type: schema.TypeSet, - Optional: true, - ForceNew: true, - Description: "Allows users to specify key-value metadata that is sent with every request as HTTP headers.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - Description: "Header name", - }, - "value": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - Description: "Header value", - }, - }, - }, - }, - "context_headers": { - Type: schema.TypeList, - Elem: &schema.Schema{Type: schema.TypeString}, - Optional: true, - ForceNew: true, - // Suppress the diff shown if the values are equal when both compared in lower case. - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - return strings.EqualFold(strings.ToLower(old), strings.ToLower(new)) - }, - Description: "Binds Snowflake context function results to HTTP headers.", - }, - "max_batch_rows": { - Type: schema.TypeInt, - Optional: true, - ForceNew: true, - Description: "This specifies the maximum number of rows in each batch sent to the proxy service.", - }, - "compression": { - Type: schema.TypeString, - Optional: true, - Default: "AUTO", - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{"NONE", "AUTO", "GZIP", "DEFLATE"}, false), - Description: "If specified, the JSON payload is compressed when sent from Snowflake to the proxy service, and when sent back from the proxy service to Snowflake.", - }, - "request_translator": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Description: "This specifies the name of the request translator function", - }, - "response_translator": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Description: "This specifies the name of the response translator function.", - }, - "url_of_proxy_and_resource": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - Description: "This is the invocation URL of the proxy service and resource through which Snowflake calls the remote service.", - }, - "comment": { - Type: schema.TypeString, - Optional: true, - Default: "user-defined function", - Description: "A description of the external function.", - }, - "created_on": { - Type: schema.TypeString, - Computed: true, - Description: "Date and time when the external function was created.", - }, -} - -// ExternalFunction returns a pointer to the resource representing an external function. -func ExternalFunction() *schema.Resource { - return &schema.Resource{ - SchemaVersion: 1, - - CreateContext: CreateContextExternalFunction, - ReadContext: ReadContextExternalFunction, - UpdateContext: UpdateContextExternalFunction, - DeleteContext: DeleteContextExternalFunction, - - Schema: externalFunctionSchema, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - - StateUpgraders: []schema.StateUpgrader{ - { - Version: 0, - // setting type to cty.EmptyObject is a bit hacky here but following https://developer.hashicorp.com/terraform/plugin/framework/migrating/resources/state-upgrade#sdkv2-1 would require lots of repetitive code; this should work with cty.EmptyObject - Type: cty.EmptyObject, - Upgrade: v085ExternalFunctionStateUpgrader, - }, - }, - } -} - -func CreateContextExternalFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*provider.Context).Client - database := d.Get("database").(string) - schemaName := d.Get("schema").(string) - name := d.Get("name").(string) - id := sdk.NewSchemaObjectIdentifier(database, schemaName, name) - - returnType := d.Get("return_type").(string) - resultDataType, err := sdk.ToDataType(returnType) - if err != nil { - return diag.FromErr(err) - } - apiIntegration := sdk.NewAccountObjectIdentifier(d.Get("api_integration").(string)) - urlOfProxyAndResource := d.Get("url_of_proxy_and_resource").(string) - req := sdk.NewCreateExternalFunctionRequest(id, resultDataType, &apiIntegration, urlOfProxyAndResource) - - // Set optionals - args := make([]sdk.ExternalFunctionArgumentRequest, 0) - if v, ok := d.GetOk("arg"); ok { - for _, arg := range v.([]interface{}) { - argName := arg.(map[string]interface{})["name"].(string) - argType := arg.(map[string]interface{})["type"].(string) - argDataType, err := sdk.ToDataType(argType) - if err != nil { - return diag.FromErr(err) - } - args = append(args, sdk.ExternalFunctionArgumentRequest{ArgName: argName, ArgDataType: argDataType}) - } - } - if len(args) > 0 { - req.WithArguments(args) - } - - if v, ok := d.GetOk("return_null_allowed"); ok { - if v.(bool) { - req.WithReturnNullValues(&sdk.ReturnNullValuesNull) - } else { - req.WithReturnNullValues(&sdk.ReturnNullValuesNotNull) - } - } - - if v, ok := d.GetOk("return_behavior"); ok { - if v.(string) == "VOLATILE" { - req.WithReturnResultsBehavior(&sdk.ReturnResultsBehaviorVolatile) - } else { - req.WithReturnResultsBehavior(&sdk.ReturnResultsBehaviorImmutable) - } - } - - if v, ok := d.GetOk("null_input_behavior"); ok { - switch { - case v.(string) == "CALLED ON NULL INPUT": - req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehaviorCalledOnNullInput)) - case v.(string) == "RETURNS NULL ON NULL INPUT": - req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehaviorReturnNullInput)) - default: - req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehaviorStrict)) - } - } - - if v, ok := d.GetOk("comment"); ok { - req.WithComment(sdk.String(v.(string))) - } - - if _, ok := d.GetOk("header"); ok { - headers := make([]sdk.ExternalFunctionHeaderRequest, 0) - for _, header := range d.Get("header").(*schema.Set).List() { - m := header.(map[string]interface{}) - headerName := m["name"].(string) - headerValue := m["value"].(string) - headers = append(headers, sdk.ExternalFunctionHeaderRequest{ - Name: headerName, - Value: headerValue, - }) - } - req.WithHeaders(headers) - } - - if v, ok := d.GetOk("context_headers"); ok { - contextHeadersList := expandStringList(v.([]interface{})) - contextHeaders := make([]sdk.ExternalFunctionContextHeaderRequest, 0) - for _, header := range contextHeadersList { - contextHeaders = append(contextHeaders, sdk.ExternalFunctionContextHeaderRequest{ - ContextFunction: header, - }) - } - req.WithContextHeaders(contextHeaders) - } - - if v, ok := d.GetOk("max_batch_rows"); ok { - req.WithMaxBatchRows(sdk.Int(v.(int))) - } - - if v, ok := d.GetOk("compression"); ok { - req.WithCompression(sdk.String(v.(string))) - } - - if v, ok := d.GetOk("request_translator"); ok { - req.WithRequestTranslator(sdk.Pointer(sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(v.(string)))) - } - - if v, ok := d.GetOk("response_translator"); ok { - req.WithResponseTranslator(sdk.Pointer(sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(v.(string)))) - } - - if err := client.ExternalFunctions.Create(ctx, req); err != nil { - return diag.FromErr(err) - } - argTypes := make([]sdk.DataType, 0, len(args)) - for _, item := range args { - argTypes = append(argTypes, item.ArgDataType) - } - sid := sdk.NewSchemaObjectIdentifierWithArguments(database, schemaName, name, argTypes) - d.SetId(sid.FullyQualifiedName()) - return ReadContextExternalFunction(ctx, d, meta) -} - -func ReadContextExternalFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*provider.Context).Client - - id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) - externalFunction, err := client.ExternalFunctions.ShowByID(ctx, id) - if err != nil { - d.SetId("") - return nil - } - - // Some properties can come from the SHOW EXTERNAL FUNCTION call - if err := d.Set("name", externalFunction.Name); err != nil { - return diag.FromErr(err) - } - - if err := d.Set("schema", strings.Trim(externalFunction.SchemaName, "\"")); err != nil { - return diag.FromErr(err) - } - - if err := d.Set("database", strings.Trim(externalFunction.CatalogName, "\"")); err != nil { - return diag.FromErr(err) - } - - if err := d.Set("comment", externalFunction.Description); err != nil { - return diag.FromErr(err) - } - - if err := d.Set("created_on", externalFunction.CreatedOn); err != nil { - return diag.FromErr(err) - } - - // Some properties come from the DESCRIBE FUNCTION call - externalFunctionPropertyRows, err := client.ExternalFunctions.Describe(ctx, sdk.NewDescribeExternalFunctionRequest(id.WithoutArguments(), id.Arguments())) - if err != nil { - d.SetId("") - return nil - } - - for _, row := range externalFunctionPropertyRows { - switch row.Property { - case "signature": - // Format in Snowflake DB is: (argName argType, argName argType, ...) - args := strings.ReplaceAll(strings.ReplaceAll(row.Value, "(", ""), ")", "") - - if args != "" { // Do nothing for functions without arguments - argPairs := strings.Split(args, ", ") - args := []interface{}{} - - for _, argPair := range argPairs { - argItem := strings.Split(argPair, " ") - - arg := map[string]interface{}{} - arg["name"] = argItem[0] - arg["type"] = argItem[1] - args = append(args, arg) - } - - if err := d.Set("arg", args); err != nil { - return diag.Errorf("error setting arg: %v", err) - } - } - case "returns": - returnType := row.Value - // We first check for VARIANT or OBJECT - if returnType == "VARIANT" || returnType == "OBJECT" { - if err := d.Set("return_type", returnType); err != nil { - return diag.Errorf("error setting return_type: %v", err) - } - break - } - - // otherwise, format in Snowflake DB is returnType() - re := regexp.MustCompile(`^(\w+)\([0-9]*\)$`) - match := re.FindStringSubmatch(row.Value) - if len(match) < 2 { - return diag.Errorf("return_type %s not recognized", returnType) - } - if err := d.Set("return_type", match[1]); err != nil { - return diag.Errorf("error setting return_type: %v", err) - } - - case "null handling": - if err := d.Set("null_input_behavior", row.Value); err != nil { - return diag.Errorf("error setting null_input_behavior: %v", err) - } - case "volatility": - if err := d.Set("return_behavior", row.Value); err != nil { - return diag.Errorf("error setting return_behavior: %v", err) - } - case "headers": - if row.Value != "" && row.Value != "null" { - // Format in Snowflake DB is: {"head1":"val1","head2":"val2"} - var jsonHeaders map[string]string - err := json.Unmarshal([]byte(row.Value), &jsonHeaders) - if err != nil { - return diag.Errorf("error unmarshalling headers: %v", err) - } - - headers := make([]any, 0, len(jsonHeaders)) - for key, value := range jsonHeaders { - headers = append(headers, map[string]any{ - "name": key, - "value": value, - }) - } - - if err := d.Set("header", headers); err != nil { - return diag.Errorf("error setting return_behavior: %v", err) - } - } - case "context_headers": - if row.Value != "" && row.Value != "null" { - // Format in Snowflake DB is: ["CONTEXT_FUNCTION_1","CONTEXT_FUNCTION_2"] - contextHeaders := strings.Split(strings.Trim(row.Value, "[]"), ",") - for i, v := range contextHeaders { - contextHeaders[i] = strings.Trim(v, "\"") - } - if err := d.Set("context_headers", contextHeaders); err != nil { - return diag.Errorf("error setting context_headers: %v", err) - } - } - case "max_batch_rows": - if row.Value != "not set" { - maxBatchRows, err := strconv.ParseInt(row.Value, 10, 64) - if err != nil { - return diag.Errorf("error parsing max_batch_rows: %v", err) - } - if err := d.Set("max_batch_rows", maxBatchRows); err != nil { - return diag.Errorf("error setting max_batch_rows: %v", err) - } - } - case "compression": - if err := d.Set("compression", row.Value); err != nil { - return diag.Errorf("error setting compression: %v", err) - } - case "body": - if err := d.Set("url_of_proxy_and_resource", row.Value); err != nil { - return diag.Errorf("error setting url_of_proxy_and_resource: %v", err) - } - case "language": - // To ignore - default: - log.Printf("[WARN] unexpected external function property %v returned from Snowflake", row.Property) - } - } - - return nil -} - -func UpdateContextExternalFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*provider.Context).Client - - id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) - req := sdk.NewAlterFunctionRequest(id.WithoutArguments(), id.Arguments()) - if d.HasChange("comment") { - _, new := d.GetChange("comment") - if new == "" { - req.UnsetComment = sdk.Bool(true) - } else { - req.SetComment = sdk.String(new.(string)) - } - err := client.Functions.Alter(ctx, req) - if err != nil { - return diag.FromErr(err) - } - } - return ReadContextExternalFunction(ctx, d, meta) -} - -func DeleteContextExternalFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*provider.Context).Client - - id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) - req := sdk.NewDropFunctionRequest(id.WithoutArguments(), id.Arguments()) - if err := client.Functions.Drop(ctx, req); err != nil { - return diag.FromErr(err) - } - - d.SetId("") - return nil -} +//var externalFunctionSchema = map[string]*schema.Schema{ +// "name": { +// Type: schema.TypeString, +// Required: true, +// ForceNew: true, +// Description: "Specifies the identifier for the external function. The identifier can contain the schema name and database name, as well as the function name. The function's signature (name and argument data types) must be unique within the schema.", +// }, +// "schema": { +// Type: schema.TypeString, +// Required: true, +// ForceNew: true, +// Description: "The schema in which to create the external function.", +// }, +// "database": { +// Type: schema.TypeString, +// Required: true, +// ForceNew: true, +// Description: "The database in which to create the external function.", +// }, +// "arg": { +// Type: schema.TypeList, +// Optional: true, +// ForceNew: true, +// Description: "Specifies the arguments/inputs for the external function. These should correspond to the arguments that the remote service expects.", +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "name": { +// Type: schema.TypeString, +// Required: true, +// // Suppress the diff shown if the values are equal when both compared in lower case. +// DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { +// return strings.EqualFold(strings.ToLower(old), strings.ToLower(new)) +// }, +// Description: "Argument name", +// }, +// "type": { +// Type: schema.TypeString, +// Required: true, +// // Suppress the diff shown if the values are equal when both compared in lower case. +// DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { +// return strings.EqualFold(strings.ToLower(old), strings.ToLower(new)) +// }, +// Description: "Argument type, e.g. VARCHAR", +// }, +// }, +// }, +// }, +// "null_input_behavior": { +// Type: schema.TypeString, +// Optional: true, +// Default: "CALLED ON NULL INPUT", +// ForceNew: true, +// ValidateFunc: validation.StringInSlice([]string{"CALLED ON NULL INPUT", "RETURNS NULL ON NULL INPUT", "STRICT"}, false), +// Description: "Specifies the behavior of the external function when called with null inputs.", +// }, +// "return_type": { +// Type: schema.TypeString, +// Required: true, +// ForceNew: true, +// // Suppress the diff shown if the values are equal when both compared in lower case. +// DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { +// return strings.EqualFold(strings.ToLower(old), strings.ToLower(new)) +// }, +// Description: "Specifies the data type returned by the external function.", +// }, +// "return_null_allowed": { +// Type: schema.TypeBool, +// Optional: true, +// ForceNew: true, +// Description: "Indicates whether the function can return NULL values (true) or must return only NON-NULL values (false).", +// Default: true, +// }, +// "return_behavior": { +// Type: schema.TypeString, +// Required: true, +// ForceNew: true, +// ValidateFunc: validation.StringInSlice([]string{"VOLATILE", "IMMUTABLE"}, false), +// Description: "Specifies the behavior of the function when returning results", +// }, +// "api_integration": { +// Type: schema.TypeString, +// Required: true, +// ForceNew: true, +// Description: "The name of the API integration object that should be used to authenticate the call to the proxy service.", +// }, +// "header": { +// Type: schema.TypeSet, +// Optional: true, +// ForceNew: true, +// Description: "Allows users to specify key-value metadata that is sent with every request as HTTP headers.", +// Elem: &schema.Resource{ +// Schema: map[string]*schema.Schema{ +// "name": { +// Type: schema.TypeString, +// Required: true, +// ForceNew: true, +// Description: "Header name", +// }, +// "value": { +// Type: schema.TypeString, +// Required: true, +// ForceNew: true, +// Description: "Header value", +// }, +// }, +// }, +// }, +// "context_headers": { +// Type: schema.TypeList, +// Elem: &schema.Schema{Type: schema.TypeString}, +// Optional: true, +// ForceNew: true, +// // Suppress the diff shown if the values are equal when both compared in lower case. +// DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { +// return strings.EqualFold(strings.ToLower(old), strings.ToLower(new)) +// }, +// Description: "Binds Snowflake context function results to HTTP headers.", +// }, +// "max_batch_rows": { +// Type: schema.TypeInt, +// Optional: true, +// ForceNew: true, +// Description: "This specifies the maximum number of rows in each batch sent to the proxy service.", +// }, +// "compression": { +// Type: schema.TypeString, +// Optional: true, +// Default: "AUTO", +// ForceNew: true, +// ValidateFunc: validation.StringInSlice([]string{"NONE", "AUTO", "GZIP", "DEFLATE"}, false), +// Description: "If specified, the JSON payload is compressed when sent from Snowflake to the proxy service, and when sent back from the proxy service to Snowflake.", +// }, +// "request_translator": { +// Type: schema.TypeString, +// Optional: true, +// ForceNew: true, +// Description: "This specifies the name of the request translator function", +// }, +// "response_translator": { +// Type: schema.TypeString, +// Optional: true, +// ForceNew: true, +// Description: "This specifies the name of the response translator function.", +// }, +// "url_of_proxy_and_resource": { +// Type: schema.TypeString, +// Required: true, +// ForceNew: true, +// Description: "This is the invocation URL of the proxy service and resource through which Snowflake calls the remote service.", +// }, +// "comment": { +// Type: schema.TypeString, +// Optional: true, +// Default: "user-defined function", +// Description: "A description of the external function.", +// }, +// "created_on": { +// Type: schema.TypeString, +// Computed: true, +// Description: "Date and time when the external function was created.", +// }, +//} +// +//// ExternalFunction returns a pointer to the resource representing an external function. +//func ExternalFunction() *schema.Resource { +// return &schema.Resource{ +// SchemaVersion: 1, +// +// CreateContext: CreateContextExternalFunction, +// ReadContext: ReadContextExternalFunction, +// UpdateContext: UpdateContextExternalFunction, +// DeleteContext: DeleteContextExternalFunction, +// +// Schema: externalFunctionSchema, +// Importer: &schema.ResourceImporter{ +// StateContext: schema.ImportStatePassthroughContext, +// }, +// +// StateUpgraders: []schema.StateUpgrader{ +// { +// Version: 0, +// // setting type to cty.EmptyObject is a bit hacky here but following https://developer.hashicorp.com/terraform/plugin/framework/migrating/resources/state-upgrade#sdkv2-1 would require lots of repetitive code; this should work with cty.EmptyObject +// Type: cty.EmptyObject, +// Upgrade: v085ExternalFunctionStateUpgrader, +// }, +// }, +// } +//} +// +//func CreateContextExternalFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// client := meta.(*provider.Context).Client +// database := d.Get("database").(string) +// schemaName := d.Get("schema").(string) +// name := d.Get("name").(string) +// id := sdk.NewSchemaObjectIdentifier(database, schemaName, name) +// +// returnType := d.Get("return_type").(string) +// resultDataType, err := sdk.ToDataType(returnType) +// if err != nil { +// return diag.FromErr(err) +// } +// apiIntegration := sdk.NewAccountObjectIdentifier(d.Get("api_integration").(string)) +// urlOfProxyAndResource := d.Get("url_of_proxy_and_resource").(string) +// req := sdk.NewCreateExternalFunctionRequest(id, resultDataType, &apiIntegration, urlOfProxyAndResource) +// +// // Set optionals +// args := make([]sdk.ExternalFunctionArgumentRequest, 0) +// if v, ok := d.GetOk("arg"); ok { +// for _, arg := range v.([]interface{}) { +// argName := arg.(map[string]interface{})["name"].(string) +// argType := arg.(map[string]interface{})["type"].(string) +// argDataType, err := sdk.ToDataType(argType) +// if err != nil { +// return diag.FromErr(err) +// } +// args = append(args, sdk.ExternalFunctionArgumentRequest{ArgName: argName, ArgDataType: argDataType}) +// } +// } +// if len(args) > 0 { +// req.WithArguments(args) +// } +// +// if v, ok := d.GetOk("return_null_allowed"); ok { +// if v.(bool) { +// req.WithReturnNullValues(&sdk.ReturnNullValuesNull) +// } else { +// req.WithReturnNullValues(&sdk.ReturnNullValuesNotNull) +// } +// } +// +// if v, ok := d.GetOk("return_behavior"); ok { +// if v.(string) == "VOLATILE" { +// req.WithReturnResultsBehavior(&sdk.ReturnResultsBehaviorVolatile) +// } else { +// req.WithReturnResultsBehavior(&sdk.ReturnResultsBehaviorImmutable) +// } +// } +// +// if v, ok := d.GetOk("null_input_behavior"); ok { +// switch { +// case v.(string) == "CALLED ON NULL INPUT": +// req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehaviorCalledOnNullInput)) +// case v.(string) == "RETURNS NULL ON NULL INPUT": +// req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehaviorReturnNullInput)) +// default: +// req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehaviorStrict)) +// } +// } +// +// if v, ok := d.GetOk("comment"); ok { +// req.WithComment(sdk.String(v.(string))) +// } +// +// if _, ok := d.GetOk("header"); ok { +// headers := make([]sdk.ExternalFunctionHeaderRequest, 0) +// for _, header := range d.Get("header").(*schema.Set).List() { +// m := header.(map[string]interface{}) +// headerName := m["name"].(string) +// headerValue := m["value"].(string) +// headers = append(headers, sdk.ExternalFunctionHeaderRequest{ +// Name: headerName, +// Value: headerValue, +// }) +// } +// req.WithHeaders(headers) +// } +// +// if v, ok := d.GetOk("context_headers"); ok { +// contextHeadersList := expandStringList(v.([]interface{})) +// contextHeaders := make([]sdk.ExternalFunctionContextHeaderRequest, 0) +// for _, header := range contextHeadersList { +// contextHeaders = append(contextHeaders, sdk.ExternalFunctionContextHeaderRequest{ +// ContextFunction: header, +// }) +// } +// req.WithContextHeaders(contextHeaders) +// } +// +// if v, ok := d.GetOk("max_batch_rows"); ok { +// req.WithMaxBatchRows(sdk.Int(v.(int))) +// } +// +// if v, ok := d.GetOk("compression"); ok { +// req.WithCompression(sdk.String(v.(string))) +// } +// +// if v, ok := d.GetOk("request_translator"); ok { +// req.WithRequestTranslator(sdk.Pointer(sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(v.(string)))) +// } +// +// if v, ok := d.GetOk("response_translator"); ok { +// req.WithResponseTranslator(sdk.Pointer(sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(v.(string)))) +// } +// +// if err := client.ExternalFunctions.Create(ctx, req); err != nil { +// return diag.FromErr(err) +// } +// argTypes := make([]sdk.DataType, 0, len(args)) +// for _, item := range args { +// argTypes = append(argTypes, item.ArgDataType) +// } +// sid := sdk.NewSchemaObjectIdentifierWithArguments(database, schemaName, name, argTypes) +// d.SetId(sid.FullyQualifiedName()) +// return ReadContextExternalFunction(ctx, d, meta) +//} +// +//func ReadContextExternalFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// client := meta.(*provider.Context).Client +// +// id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) +// externalFunction, err := client.ExternalFunctions.ShowByID(ctx, id) +// if err != nil { +// d.SetId("") +// return nil +// } +// +// // Some properties can come from the SHOW EXTERNAL FUNCTION call +// if err := d.Set("name", externalFunction.Name); err != nil { +// return diag.FromErr(err) +// } +// +// if err := d.Set("schema", strings.Trim(externalFunction.SchemaName, "\"")); err != nil { +// return diag.FromErr(err) +// } +// +// if err := d.Set("database", strings.Trim(externalFunction.CatalogName, "\"")); err != nil { +// return diag.FromErr(err) +// } +// +// if err := d.Set("comment", externalFunction.Description); err != nil { +// return diag.FromErr(err) +// } +// +// if err := d.Set("created_on", externalFunction.CreatedOn); err != nil { +// return diag.FromErr(err) +// } +// +// // Some properties come from the DESCRIBE FUNCTION call +// externalFunctionPropertyRows, err := client.ExternalFunctions.Describe(ctx, sdk.NewDescribeExternalFunctionRequest(id.WithoutArguments(), id.Arguments())) +// if err != nil { +// d.SetId("") +// return nil +// } +// +// for _, row := range externalFunctionPropertyRows { +// switch row.Property { +// case "signature": +// // Format in Snowflake DB is: (argName argType, argName argType, ...) +// args := strings.ReplaceAll(strings.ReplaceAll(row.Value, "(", ""), ")", "") +// +// if args != "" { // Do nothing for functions without arguments +// argPairs := strings.Split(args, ", ") +// args := []interface{}{} +// +// for _, argPair := range argPairs { +// argItem := strings.Split(argPair, " ") +// +// arg := map[string]interface{}{} +// arg["name"] = argItem[0] +// arg["type"] = argItem[1] +// args = append(args, arg) +// } +// +// if err := d.Set("arg", args); err != nil { +// return diag.Errorf("error setting arg: %v", err) +// } +// } +// case "returns": +// returnType := row.Value +// // We first check for VARIANT or OBJECT +// if returnType == "VARIANT" || returnType == "OBJECT" { +// if err := d.Set("return_type", returnType); err != nil { +// return diag.Errorf("error setting return_type: %v", err) +// } +// break +// } +// +// // otherwise, format in Snowflake DB is returnType() +// re := regexp.MustCompile(`^(\w+)\([0-9]*\)$`) +// match := re.FindStringSubmatch(row.Value) +// if len(match) < 2 { +// return diag.Errorf("return_type %s not recognized", returnType) +// } +// if err := d.Set("return_type", match[1]); err != nil { +// return diag.Errorf("error setting return_type: %v", err) +// } +// +// case "null handling": +// if err := d.Set("null_input_behavior", row.Value); err != nil { +// return diag.Errorf("error setting null_input_behavior: %v", err) +// } +// case "volatility": +// if err := d.Set("return_behavior", row.Value); err != nil { +// return diag.Errorf("error setting return_behavior: %v", err) +// } +// case "headers": +// if row.Value != "" && row.Value != "null" { +// // Format in Snowflake DB is: {"head1":"val1","head2":"val2"} +// var jsonHeaders map[string]string +// err := json.Unmarshal([]byte(row.Value), &jsonHeaders) +// if err != nil { +// return diag.Errorf("error unmarshalling headers: %v", err) +// } +// +// headers := make([]any, 0, len(jsonHeaders)) +// for key, value := range jsonHeaders { +// headers = append(headers, map[string]any{ +// "name": key, +// "value": value, +// }) +// } +// +// if err := d.Set("header", headers); err != nil { +// return diag.Errorf("error setting return_behavior: %v", err) +// } +// } +// case "context_headers": +// if row.Value != "" && row.Value != "null" { +// // Format in Snowflake DB is: ["CONTEXT_FUNCTION_1","CONTEXT_FUNCTION_2"] +// contextHeaders := strings.Split(strings.Trim(row.Value, "[]"), ",") +// for i, v := range contextHeaders { +// contextHeaders[i] = strings.Trim(v, "\"") +// } +// if err := d.Set("context_headers", contextHeaders); err != nil { +// return diag.Errorf("error setting context_headers: %v", err) +// } +// } +// case "max_batch_rows": +// if row.Value != "not set" { +// maxBatchRows, err := strconv.ParseInt(row.Value, 10, 64) +// if err != nil { +// return diag.Errorf("error parsing max_batch_rows: %v", err) +// } +// if err := d.Set("max_batch_rows", maxBatchRows); err != nil { +// return diag.Errorf("error setting max_batch_rows: %v", err) +// } +// } +// case "compression": +// if err := d.Set("compression", row.Value); err != nil { +// return diag.Errorf("error setting compression: %v", err) +// } +// case "body": +// if err := d.Set("url_of_proxy_and_resource", row.Value); err != nil { +// return diag.Errorf("error setting url_of_proxy_and_resource: %v", err) +// } +// case "language": +// // To ignore +// default: +// log.Printf("[WARN] unexpected external function property %v returned from Snowflake", row.Property) +// } +// } +// +// return nil +//} +// +//func UpdateContextExternalFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// client := meta.(*provider.Context).Client +// +// id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) +// req := sdk.NewAlterFunctionRequest(id.WithoutArguments(), id.Arguments()) +// if d.HasChange("comment") { +// _, new := d.GetChange("comment") +// if new == "" { +// req.UnsetComment = sdk.Bool(true) +// } else { +// req.SetComment = sdk.String(new.(string)) +// } +// err := client.Functions.Alter(ctx, req) +// if err != nil { +// return diag.FromErr(err) +// } +// } +// return ReadContextExternalFunction(ctx, d, meta) +//} +// +//func DeleteContextExternalFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +// client := meta.(*provider.Context).Client +// +// id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) +// req := sdk.NewDropFunctionRequest(id.WithoutArguments(), id.Arguments()) +// if err := client.Functions.Drop(ctx, req); err != nil { +// return diag.FromErr(err) +// } +// +// d.SetId("") +// return nil +//} diff --git a/pkg/resources/external_function_acceptance_test.go b/pkg/resources/external_function_acceptance_test.go index 66513f390d..ecb8c43f11 100644 --- a/pkg/resources/external_function_acceptance_test.go +++ b/pkg/resources/external_function_acceptance_test.go @@ -1,562 +1,548 @@ package resources_test -import ( - "fmt" - "testing" - - acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" - - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/provider/resources" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" - "github.com/hashicorp/terraform-plugin-testing/config" - "github.com/hashicorp/terraform-plugin-testing/helper/resource" - "github.com/hashicorp/terraform-plugin-testing/plancheck" - "github.com/hashicorp/terraform-plugin-testing/tfversion" -) - -func TestAcc_ExternalFunction_basic(t *testing.T) { - accName := acc.TestClient().Ids.Alpha() - - m := func() map[string]config.Variable { - return map[string]config.Variable{ - "database": config.StringVariable(acc.TestDatabaseName), - "schema": config.StringVariable(acc.TestSchemaName), - "name": config.StringVariable(accName), - "api_allowed_prefixes": config.ListVariable(config.StringVariable("https://123456.execute-api.us-west-2.amazonaws.com/prod/")), - "url_of_proxy_and_resource": config.StringVariable("https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"), - "comment": config.StringVariable("Terraform acceptance test"), - } - } - - resourceName := "snowflake_external_function.external_function" - configVariables := m() - configVariables2 := m() - configVariables2["comment"] = config.StringVariable("Terraform acceptance test - updated") - - resource.Test(t, resource.TestCase{ - ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, - PreCheck: func() { acc.TestAccPreCheck(t) }, - TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.RequireAbove(tfversion.Version1_5_0), - }, - CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), - Steps: []resource.TestStep{ - { - ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/basic"), - ConfigVariables: configVariables, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "name", accName), - resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), - resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), - resource.TestCheckResourceAttr(resourceName, "arg.#", "2"), - resource.TestCheckResourceAttr(resourceName, "arg.0.name", "ARG1"), - resource.TestCheckResourceAttr(resourceName, "arg.0.type", "VARCHAR"), - resource.TestCheckResourceAttr(resourceName, "arg.1.name", "ARG2"), - resource.TestCheckResourceAttr(resourceName, "arg.1.type", "VARCHAR"), - resource.TestCheckResourceAttr(resourceName, "null_input_behavior", "CALLED ON NULL INPUT"), - resource.TestCheckResourceAttr(resourceName, "return_type", "VARIANT"), - resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), - resource.TestCheckResourceAttr(resourceName, "return_behavior", "IMMUTABLE"), - resource.TestCheckResourceAttrSet(resourceName, "api_integration"), - resource.TestCheckResourceAttr(resourceName, "compression", "AUTO"), - resource.TestCheckResourceAttr(resourceName, "url_of_proxy_and_resource", "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"), - resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test"), - resource.TestCheckResourceAttrSet(resourceName, "created_on"), - ), - }, - { - ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/basic"), - ConfigVariables: configVariables2, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test - updated"), - ), - }, - // IMPORT - { - ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/basic"), - ConfigVariables: configVariables2, - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - // these two are not found in either the show or describe command - ImportStateVerifyIgnore: []string{"return_null_allowed", "api_integration"}, - }, - }, - }) -} - -func TestAcc_ExternalFunction_no_arguments(t *testing.T) { - accName := acc.TestClient().Ids.Alpha() - - m := func() map[string]config.Variable { - return map[string]config.Variable{ - "database": config.StringVariable(acc.TestDatabaseName), - "schema": config.StringVariable(acc.TestSchemaName), - "name": config.StringVariable(accName), - "api_allowed_prefixes": config.ListVariable(config.StringVariable("https://123456.execute-api.us-west-2.amazonaws.com/prod/")), - "url_of_proxy_and_resource": config.StringVariable("https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"), - "comment": config.StringVariable("Terraform acceptance test"), - } - } - - resourceName := "snowflake_external_function.external_function" - configVariables := m() - configVariables2 := m() - configVariables2["comment"] = config.StringVariable("Terraform acceptance test - updated") - - resource.Test(t, resource.TestCase{ - ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, - PreCheck: func() { acc.TestAccPreCheck(t) }, - TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.RequireAbove(tfversion.Version1_5_0), - }, - CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), - Steps: []resource.TestStep{ - { - ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/no_arguments"), - ConfigVariables: configVariables, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "name", accName), - resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), - resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), - resource.TestCheckResourceAttr(resourceName, "arg.#", "0"), - resource.TestCheckResourceAttr(resourceName, "null_input_behavior", "CALLED ON NULL INPUT"), - resource.TestCheckResourceAttr(resourceName, "return_type", "VARIANT"), - resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), - resource.TestCheckResourceAttr(resourceName, "return_behavior", "IMMUTABLE"), - resource.TestCheckResourceAttrSet(resourceName, "api_integration"), - resource.TestCheckResourceAttr(resourceName, "compression", "AUTO"), - resource.TestCheckResourceAttr(resourceName, "url_of_proxy_and_resource", "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"), - resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test"), - resource.TestCheckResourceAttrSet(resourceName, "created_on"), - ), - }, - { - ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/no_arguments"), - ConfigVariables: configVariables2, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test - updated"), - ), - }, - // IMPORT - { - ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/no_arguments"), - ConfigVariables: configVariables2, - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - // these two are not found in either the show or describe command - ImportStateVerifyIgnore: []string{"return_null_allowed", "api_integration"}, - }, - }, - }) -} - -func TestAcc_ExternalFunction_complete(t *testing.T) { - accName := acc.TestClient().Ids.Alpha() - - m := func() map[string]config.Variable { - return map[string]config.Variable{ - "database": config.StringVariable(acc.TestDatabaseName), - "schema": config.StringVariable(acc.TestSchemaName), - "name": config.StringVariable(accName), - "api_allowed_prefixes": config.ListVariable(config.StringVariable("https://123456.execute-api.us-west-2.amazonaws.com/prod/")), - "url_of_proxy_and_resource": config.StringVariable("https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"), - "comment": config.StringVariable("Terraform acceptance test"), - } - } - - resourceName := "snowflake_external_function.external_function" - configVariables := m() - configVariables2 := m() - configVariables2["comment"] = config.StringVariable("Terraform acceptance test - updated") - - resource.Test(t, resource.TestCase{ - ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, - PreCheck: func() { acc.TestAccPreCheck(t) }, - TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.RequireAbove(tfversion.Version1_5_0), - }, - CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), - Steps: []resource.TestStep{ - { - ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/complete"), - ConfigVariables: configVariables, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "name", accName), - resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), - resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), - resource.TestCheckResourceAttr(resourceName, "arg.#", "0"), - resource.TestCheckResourceAttr(resourceName, "null_input_behavior", "CALLED ON NULL INPUT"), - resource.TestCheckResourceAttr(resourceName, "return_type", "VARIANT"), - resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), - resource.TestCheckResourceAttr(resourceName, "return_behavior", "IMMUTABLE"), - resource.TestCheckResourceAttrSet(resourceName, "api_integration"), - resource.TestCheckResourceAttr(resourceName, "compression", "AUTO"), - resource.TestCheckResourceAttr(resourceName, "url_of_proxy_and_resource", "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"), - resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test"), - resource.TestCheckResourceAttrSet(resourceName, "created_on"), - resource.TestCheckResourceAttr(resourceName, "header.#", "1"), - resource.TestCheckResourceAttr(resourceName, "header.0.name", "x-custom-header"), - resource.TestCheckResourceAttr(resourceName, "header.0.value", "snowflake"), - resource.TestCheckResourceAttr(resourceName, "max_batch_rows", "500"), - resource.TestCheckResourceAttr(resourceName, "request_translator", fmt.Sprintf("%s.%s.%s%s", acc.TestDatabaseName, acc.TestSchemaName, accName, "_request_translator")), - resource.TestCheckResourceAttr(resourceName, "response_translator", fmt.Sprintf("%s.%s.%s%s", acc.TestDatabaseName, acc.TestSchemaName, accName, "_response_translator")), - ), - }, - { - ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/complete"), - ConfigVariables: configVariables2, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test - updated"), - ), - }, - // IMPORT - { - ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/complete"), - ConfigVariables: configVariables2, - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - // these four are not found in either the show or describe command - ImportStateVerifyIgnore: []string{"return_null_allowed", "api_integration", "request_translator", "response_translator"}, - }, - }, - }) -} - -func TestAcc_ExternalFunction_migrateFromVersion085(t *testing.T) { - id := acc.TestClient().Ids.RandomSchemaObjectIdentifierWithArguments([]sdk.DataType{sdk.DataTypeVARCHAR, sdk.DataTypeVARCHAR}) - name := id.Name() - resourceName := "snowflake_external_function.f" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acc.TestAccPreCheck(t) }, - TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.RequireAbove(tfversion.Version1_5_0), - }, - CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), - - Steps: []resource.TestStep{ - { - ExternalProviders: map[string]resource.ExternalProvider{ - "snowflake": { - VersionConstraint: "=0.85.0", - Source: "Snowflake-Labs/snowflake", - }, - }, - Config: externalFunctionConfig(acc.TestDatabaseName, acc.TestSchemaName, name), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|%s|%s|VARCHAR-VARCHAR", acc.TestDatabaseName, acc.TestSchemaName, name)), - resource.TestCheckResourceAttr(resourceName, "name", name), - resource.TestCheckResourceAttr(resourceName, "database", "\""+acc.TestDatabaseName+"\""), - resource.TestCheckResourceAttr(resourceName, "schema", "\""+acc.TestSchemaName+"\""), - resource.TestCheckNoResourceAttr(resourceName, "return_null_allowed"), - ), - ExpectNonEmptyPlan: true, - }, - { - ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, - Config: externalFunctionConfig(acc.TestDatabaseName, acc.TestSchemaName, name), - ConfigPlanChecks: resource.ConfigPlanChecks{ - PreApply: []plancheck.PlanCheck{plancheck.ExpectEmptyPlan()}, - }, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "id", id.FullyQualifiedName()), - resource.TestCheckResourceAttr(resourceName, "name", name), - resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), - resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), - resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), - ), - }, - }, - }) -} - -func TestAcc_ExternalFunction_migrateFromVersion085_issue2694_previousValuePresent(t *testing.T) { - name := acc.TestClient().Ids.Alpha() - resourceName := "snowflake_external_function.f" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acc.TestAccPreCheck(t) }, - TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.RequireAbove(tfversion.Version1_5_0), - }, - CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), - - Steps: []resource.TestStep{ - { - ExternalProviders: map[string]resource.ExternalProvider{ - "snowflake": { - VersionConstraint: "=0.85.0", - Source: "Snowflake-Labs/snowflake", - }, - }, - Config: externalFunctionConfigWithReturnNullAllowed(acc.TestDatabaseName, acc.TestSchemaName, name, sdk.Bool(true)), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), - ), - ExpectNonEmptyPlan: true, - }, - { - ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, - Config: externalFunctionConfig(acc.TestDatabaseName, acc.TestSchemaName, name), - ConfigPlanChecks: resource.ConfigPlanChecks{ - PreApply: []plancheck.PlanCheck{plancheck.ExpectEmptyPlan()}, - }, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), - ), - }, - }, - }) -} - -func TestAcc_ExternalFunction_migrateFromVersion085_issue2694_previousValueRemoved(t *testing.T) { - name := acc.TestClient().Ids.Alpha() - resourceName := "snowflake_external_function.f" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acc.TestAccPreCheck(t) }, - TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.RequireAbove(tfversion.Version1_5_0), - }, - CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), - - Steps: []resource.TestStep{ - { - ExternalProviders: map[string]resource.ExternalProvider{ - "snowflake": { - VersionConstraint: "=0.85.0", - Source: "Snowflake-Labs/snowflake", - }, - }, - Config: externalFunctionConfigWithReturnNullAllowed(acc.TestDatabaseName, acc.TestSchemaName, name, sdk.Bool(true)), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), - ), - ExpectNonEmptyPlan: true, - }, - { - ExternalProviders: map[string]resource.ExternalProvider{ - "snowflake": { - VersionConstraint: "=0.85.0", - Source: "Snowflake-Labs/snowflake", - }, - }, - Config: externalFunctionConfig(acc.TestDatabaseName, acc.TestSchemaName, name), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckNoResourceAttr(resourceName, "return_null_allowed"), - ), - ExpectNonEmptyPlan: true, - }, - { - ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, - Config: externalFunctionConfig(acc.TestDatabaseName, acc.TestSchemaName, name), - ConfigPlanChecks: resource.ConfigPlanChecks{ - PreApply: []plancheck.PlanCheck{plancheck.ExpectEmptyPlan()}, - }, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), - ), - }, - }, - }) -} - -// Proves issue https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2528. -// The problem originated from ShowById without IN clause. There was no IN clause in the docs at the time. -// It was raised with the appropriate team in Snowflake. -func TestAcc_ExternalFunction_issue2528(t *testing.T) { - accName := acc.TestClient().Ids.Alpha() - secondSchema := acc.TestClient().Ids.Alpha() - - resourceName := "snowflake_external_function.f" - - resource.Test(t, resource.TestCase{ - ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, - PreCheck: func() { acc.TestAccPreCheck(t) }, - TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.RequireAbove(tfversion.Version1_5_0), - }, - CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), - Steps: []resource.TestStep{ - { - Config: externalFunctionConfigIssue2528(acc.TestDatabaseName, acc.TestSchemaName, accName, secondSchema), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "name", accName), - ), - }, - }, - }) -} - -// Proves that header parsing handles values wrapped in curly braces, e.g. `value = "{1}"` -func TestAcc_ExternalFunction_HeaderParsing(t *testing.T) { - id := acc.TestClient().Ids.RandomSchemaObjectIdentifier() - - resourceName := "snowflake_external_function.f" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acc.TestAccPreCheck(t) }, - TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.RequireAbove(tfversion.Version1_5_0), - }, - CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), - Steps: []resource.TestStep{ - { - ExternalProviders: map[string]resource.ExternalProvider{ - "snowflake": { - VersionConstraint: "=0.93.0", - Source: "Snowflake-Labs/snowflake", - }, - }, - Config: externalFunctionConfigIssueCurlyHeader(id), - // Previous implementation produces a plan with the following changes - // - // - header { # forces replacement - // - name = "name" -> null - // - value = "0" -> null - // } - // - // + header { # forces replacement - // + name = "name" - // + value = "{0}" - // } - ExpectNonEmptyPlan: true, - }, - { - ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, - Config: externalFunctionConfigIssueCurlyHeader(id), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "header.#", "1"), - resource.TestCheckResourceAttr(resourceName, "header.0.name", "name"), - resource.TestCheckResourceAttr(resourceName, "header.0.value", "{0}"), - ), - }, - }, - }) -} - -func externalFunctionConfig(database string, schema string, name string) string { - return externalFunctionConfigWithReturnNullAllowed(database, schema, name, nil) -} - -func externalFunctionConfigWithReturnNullAllowed(database string, schema string, name string, returnNullAllowed *bool) string { - returnNullAllowedText := "" - if returnNullAllowed != nil { - returnNullAllowedText = fmt.Sprintf("return_null_allowed = \"%t\"", *returnNullAllowed) - } - - return fmt.Sprintf(` -resource "snowflake_api_integration" "test_api_int" { - name = "%[3]s" - api_provider = "aws_api_gateway" - api_aws_role_arn = "arn:aws:iam::000000000001:/role/test" - api_allowed_prefixes = ["https://123456.execute-api.us-west-2.amazonaws.com/prod/"] - enabled = true -} - -resource "snowflake_external_function" "f" { - name = "%[3]s" - database = "%[1]s" - schema = "%[2]s" - arg { - name = "ARG1" - type = "VARCHAR" - } - arg { - name = "ARG2" - type = "VARCHAR" - } - return_type = "VARIANT" - return_behavior = "IMMUTABLE" - api_integration = snowflake_api_integration.test_api_int.name - url_of_proxy_and_resource = "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func" - %[4]s -} - -`, database, schema, name, returnNullAllowedText) -} - -func externalFunctionConfigIssue2528(database string, schema string, name string, schema2 string) string { - return fmt.Sprintf(` -resource "snowflake_api_integration" "test_api_int" { - name = "%[3]s" - api_provider = "aws_api_gateway" - api_aws_role_arn = "arn:aws:iam::000000000001:/role/test" - api_allowed_prefixes = ["https://123456.execute-api.us-west-2.amazonaws.com/prod/"] - enabled = true -} - -resource "snowflake_schema" "s2" { - database = "%[1]s" - name = "%[4]s" -} - -resource "snowflake_external_function" "f" { - name = "%[3]s" - database = "%[1]s" - schema = "%[2]s" - arg { - name = "SNS_NOTIF" - type = "OBJECT" - } - return_type = "VARIANT" - return_behavior = "VOLATILE" - api_integration = snowflake_api_integration.test_api_int.name - url_of_proxy_and_resource = "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func" -} - -resource "snowflake_external_function" "f2" { - depends_on = [snowflake_schema.s2] - - name = "%[3]s" - database = "%[1]s" - schema = "%[4]s" - arg { - name = "SNS_NOTIF" - type = "OBJECT" - } - return_type = "VARIANT" - return_behavior = "VOLATILE" - api_integration = snowflake_api_integration.test_api_int.name - url_of_proxy_and_resource = "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func" -} -`, database, schema, name, schema2) -} - -func externalFunctionConfigIssueCurlyHeader(id sdk.SchemaObjectIdentifier) string { - return fmt.Sprintf(` -resource "snowflake_api_integration" "test_api_int" { - name = "%[3]s" - api_provider = "aws_api_gateway" - api_aws_role_arn = "arn:aws:iam::000000000001:/role/test" - api_allowed_prefixes = ["https://123456.execute-api.us-west-2.amazonaws.com/prod/"] - enabled = true -} - -resource "snowflake_external_function" "f" { - name = "%[3]s" - database = "%[1]s" - schema = "%[2]s" - arg { - name = "ARG1" - type = "VARCHAR" - } - arg { - name = "ARG2" - type = "VARCHAR" - } - header { - name = "name" - value = "{0}" - } - return_type = "VARIANT" - return_behavior = "IMMUTABLE" - api_integration = snowflake_api_integration.test_api_int.name - url_of_proxy_and_resource = "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func" -} - -`, id.DatabaseName(), id.SchemaName(), id.Name()) -} +//func TestAcc_ExternalFunction_basic(t *testing.T) { +// accName := acc.TestClient().Ids.Alpha() +// +// m := func() map[string]config.Variable { +// return map[string]config.Variable{ +// "database": config.StringVariable(acc.TestDatabaseName), +// "schema": config.StringVariable(acc.TestSchemaName), +// "name": config.StringVariable(accName), +// "api_allowed_prefixes": config.ListVariable(config.StringVariable("https://123456.execute-api.us-west-2.amazonaws.com/prod/")), +// "url_of_proxy_and_resource": config.StringVariable("https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"), +// "comment": config.StringVariable("Terraform acceptance test"), +// } +// } +// +// resourceName := "snowflake_external_function.external_function" +// configVariables := m() +// configVariables2 := m() +// configVariables2["comment"] = config.StringVariable("Terraform acceptance test - updated") +// +// resource.Test(t, resource.TestCase{ +// ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, +// PreCheck: func() { acc.TestAccPreCheck(t) }, +// TerraformVersionChecks: []tfversion.TerraformVersionCheck{ +// tfversion.RequireAbove(tfversion.Version1_5_0), +// }, +// CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), +// Steps: []resource.TestStep{ +// { +// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/basic"), +// ConfigVariables: configVariables, +// Check: resource.ComposeTestCheckFunc( +// resource.TestCheckResourceAttr(resourceName, "name", accName), +// resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), +// resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), +// resource.TestCheckResourceAttr(resourceName, "arg.#", "2"), +// resource.TestCheckResourceAttr(resourceName, "arg.0.name", "ARG1"), +// resource.TestCheckResourceAttr(resourceName, "arg.0.type", "VARCHAR"), +// resource.TestCheckResourceAttr(resourceName, "arg.1.name", "ARG2"), +// resource.TestCheckResourceAttr(resourceName, "arg.1.type", "VARCHAR"), +// resource.TestCheckResourceAttr(resourceName, "null_input_behavior", "CALLED ON NULL INPUT"), +// resource.TestCheckResourceAttr(resourceName, "return_type", "VARIANT"), +// resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), +// resource.TestCheckResourceAttr(resourceName, "return_behavior", "IMMUTABLE"), +// resource.TestCheckResourceAttrSet(resourceName, "api_integration"), +// resource.TestCheckResourceAttr(resourceName, "compression", "AUTO"), +// resource.TestCheckResourceAttr(resourceName, "url_of_proxy_and_resource", "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"), +// resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test"), +// resource.TestCheckResourceAttrSet(resourceName, "created_on"), +// ), +// }, +// { +// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/basic"), +// ConfigVariables: configVariables2, +// Check: resource.ComposeTestCheckFunc( +// resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test - updated"), +// ), +// }, +// // IMPORT +// { +// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/basic"), +// ConfigVariables: configVariables2, +// ResourceName: resourceName, +// ImportState: true, +// ImportStateVerify: true, +// // these two are not found in either the show or describe command +// ImportStateVerifyIgnore: []string{"return_null_allowed", "api_integration"}, +// }, +// }, +// }) +//} +// +//func TestAcc_ExternalFunction_no_arguments(t *testing.T) { +// accName := acc.TestClient().Ids.Alpha() +// +// m := func() map[string]config.Variable { +// return map[string]config.Variable{ +// "database": config.StringVariable(acc.TestDatabaseName), +// "schema": config.StringVariable(acc.TestSchemaName), +// "name": config.StringVariable(accName), +// "api_allowed_prefixes": config.ListVariable(config.StringVariable("https://123456.execute-api.us-west-2.amazonaws.com/prod/")), +// "url_of_proxy_and_resource": config.StringVariable("https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"), +// "comment": config.StringVariable("Terraform acceptance test"), +// } +// } +// +// resourceName := "snowflake_external_function.external_function" +// configVariables := m() +// configVariables2 := m() +// configVariables2["comment"] = config.StringVariable("Terraform acceptance test - updated") +// +// resource.Test(t, resource.TestCase{ +// ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, +// PreCheck: func() { acc.TestAccPreCheck(t) }, +// TerraformVersionChecks: []tfversion.TerraformVersionCheck{ +// tfversion.RequireAbove(tfversion.Version1_5_0), +// }, +// CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), +// Steps: []resource.TestStep{ +// { +// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/no_arguments"), +// ConfigVariables: configVariables, +// Check: resource.ComposeTestCheckFunc( +// resource.TestCheckResourceAttr(resourceName, "name", accName), +// resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), +// resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), +// resource.TestCheckResourceAttr(resourceName, "arg.#", "0"), +// resource.TestCheckResourceAttr(resourceName, "null_input_behavior", "CALLED ON NULL INPUT"), +// resource.TestCheckResourceAttr(resourceName, "return_type", "VARIANT"), +// resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), +// resource.TestCheckResourceAttr(resourceName, "return_behavior", "IMMUTABLE"), +// resource.TestCheckResourceAttrSet(resourceName, "api_integration"), +// resource.TestCheckResourceAttr(resourceName, "compression", "AUTO"), +// resource.TestCheckResourceAttr(resourceName, "url_of_proxy_and_resource", "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"), +// resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test"), +// resource.TestCheckResourceAttrSet(resourceName, "created_on"), +// ), +// }, +// { +// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/no_arguments"), +// ConfigVariables: configVariables2, +// Check: resource.ComposeTestCheckFunc( +// resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test - updated"), +// ), +// }, +// // IMPORT +// { +// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/no_arguments"), +// ConfigVariables: configVariables2, +// ResourceName: resourceName, +// ImportState: true, +// ImportStateVerify: true, +// // these two are not found in either the show or describe command +// ImportStateVerifyIgnore: []string{"return_null_allowed", "api_integration"}, +// }, +// }, +// }) +//} +// +//func TestAcc_ExternalFunction_complete(t *testing.T) { +// accName := acc.TestClient().Ids.Alpha() +// +// m := func() map[string]config.Variable { +// return map[string]config.Variable{ +// "database": config.StringVariable(acc.TestDatabaseName), +// "schema": config.StringVariable(acc.TestSchemaName), +// "name": config.StringVariable(accName), +// "api_allowed_prefixes": config.ListVariable(config.StringVariable("https://123456.execute-api.us-west-2.amazonaws.com/prod/")), +// "url_of_proxy_and_resource": config.StringVariable("https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"), +// "comment": config.StringVariable("Terraform acceptance test"), +// } +// } +// +// resourceName := "snowflake_external_function.external_function" +// configVariables := m() +// configVariables2 := m() +// configVariables2["comment"] = config.StringVariable("Terraform acceptance test - updated") +// +// resource.Test(t, resource.TestCase{ +// ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, +// PreCheck: func() { acc.TestAccPreCheck(t) }, +// TerraformVersionChecks: []tfversion.TerraformVersionCheck{ +// tfversion.RequireAbove(tfversion.Version1_5_0), +// }, +// CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), +// Steps: []resource.TestStep{ +// { +// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/complete"), +// ConfigVariables: configVariables, +// Check: resource.ComposeTestCheckFunc( +// resource.TestCheckResourceAttr(resourceName, "name", accName), +// resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), +// resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), +// resource.TestCheckResourceAttr(resourceName, "arg.#", "0"), +// resource.TestCheckResourceAttr(resourceName, "null_input_behavior", "CALLED ON NULL INPUT"), +// resource.TestCheckResourceAttr(resourceName, "return_type", "VARIANT"), +// resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), +// resource.TestCheckResourceAttr(resourceName, "return_behavior", "IMMUTABLE"), +// resource.TestCheckResourceAttrSet(resourceName, "api_integration"), +// resource.TestCheckResourceAttr(resourceName, "compression", "AUTO"), +// resource.TestCheckResourceAttr(resourceName, "url_of_proxy_and_resource", "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"), +// resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test"), +// resource.TestCheckResourceAttrSet(resourceName, "created_on"), +// resource.TestCheckResourceAttr(resourceName, "header.#", "1"), +// resource.TestCheckResourceAttr(resourceName, "header.0.name", "x-custom-header"), +// resource.TestCheckResourceAttr(resourceName, "header.0.value", "snowflake"), +// resource.TestCheckResourceAttr(resourceName, "max_batch_rows", "500"), +// resource.TestCheckResourceAttr(resourceName, "request_translator", fmt.Sprintf("%s.%s.%s%s", acc.TestDatabaseName, acc.TestSchemaName, accName, "_request_translator")), +// resource.TestCheckResourceAttr(resourceName, "response_translator", fmt.Sprintf("%s.%s.%s%s", acc.TestDatabaseName, acc.TestSchemaName, accName, "_response_translator")), +// ), +// }, +// { +// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/complete"), +// ConfigVariables: configVariables2, +// Check: resource.ComposeTestCheckFunc( +// resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test - updated"), +// ), +// }, +// // IMPORT +// { +// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/complete"), +// ConfigVariables: configVariables2, +// ResourceName: resourceName, +// ImportState: true, +// ImportStateVerify: true, +// // these four are not found in either the show or describe command +// ImportStateVerifyIgnore: []string{"return_null_allowed", "api_integration", "request_translator", "response_translator"}, +// }, +// }, +// }) +//} +// +//func TestAcc_ExternalFunction_migrateFromVersion085(t *testing.T) { +// id := acc.TestClient().Ids.RandomSchemaObjectIdentifierWithArguments([]sdk.DataType{sdk.DataTypeVARCHAR, sdk.DataTypeVARCHAR}) +// name := id.Name() +// resourceName := "snowflake_external_function.f" +// +// resource.Test(t, resource.TestCase{ +// PreCheck: func() { acc.TestAccPreCheck(t) }, +// TerraformVersionChecks: []tfversion.TerraformVersionCheck{ +// tfversion.RequireAbove(tfversion.Version1_5_0), +// }, +// CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), +// +// Steps: []resource.TestStep{ +// { +// ExternalProviders: map[string]resource.ExternalProvider{ +// "snowflake": { +// VersionConstraint: "=0.85.0", +// Source: "Snowflake-Labs/snowflake", +// }, +// }, +// Config: externalFunctionConfig(acc.TestDatabaseName, acc.TestSchemaName, name), +// Check: resource.ComposeTestCheckFunc( +// resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|%s|%s|VARCHAR-VARCHAR", acc.TestDatabaseName, acc.TestSchemaName, name)), +// resource.TestCheckResourceAttr(resourceName, "name", name), +// resource.TestCheckResourceAttr(resourceName, "database", "\""+acc.TestDatabaseName+"\""), +// resource.TestCheckResourceAttr(resourceName, "schema", "\""+acc.TestSchemaName+"\""), +// resource.TestCheckNoResourceAttr(resourceName, "return_null_allowed"), +// ), +// ExpectNonEmptyPlan: true, +// }, +// { +// ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, +// Config: externalFunctionConfig(acc.TestDatabaseName, acc.TestSchemaName, name), +// ConfigPlanChecks: resource.ConfigPlanChecks{ +// PreApply: []plancheck.PlanCheck{plancheck.ExpectEmptyPlan()}, +// }, +// Check: resource.ComposeTestCheckFunc( +// resource.TestCheckResourceAttr(resourceName, "id", id.FullyQualifiedName()), +// resource.TestCheckResourceAttr(resourceName, "name", name), +// resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), +// resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), +// resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), +// ), +// }, +// }, +// }) +//} +// +//func TestAcc_ExternalFunction_migrateFromVersion085_issue2694_previousValuePresent(t *testing.T) { +// name := acc.TestClient().Ids.Alpha() +// resourceName := "snowflake_external_function.f" +// +// resource.Test(t, resource.TestCase{ +// PreCheck: func() { acc.TestAccPreCheck(t) }, +// TerraformVersionChecks: []tfversion.TerraformVersionCheck{ +// tfversion.RequireAbove(tfversion.Version1_5_0), +// }, +// CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), +// +// Steps: []resource.TestStep{ +// { +// ExternalProviders: map[string]resource.ExternalProvider{ +// "snowflake": { +// VersionConstraint: "=0.85.0", +// Source: "Snowflake-Labs/snowflake", +// }, +// }, +// Config: externalFunctionConfigWithReturnNullAllowed(acc.TestDatabaseName, acc.TestSchemaName, name, sdk.Bool(true)), +// Check: resource.ComposeTestCheckFunc( +// resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), +// ), +// ExpectNonEmptyPlan: true, +// }, +// { +// ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, +// Config: externalFunctionConfig(acc.TestDatabaseName, acc.TestSchemaName, name), +// ConfigPlanChecks: resource.ConfigPlanChecks{ +// PreApply: []plancheck.PlanCheck{plancheck.ExpectEmptyPlan()}, +// }, +// Check: resource.ComposeTestCheckFunc( +// resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), +// ), +// }, +// }, +// }) +//} +// +//func TestAcc_ExternalFunction_migrateFromVersion085_issue2694_previousValueRemoved(t *testing.T) { +// name := acc.TestClient().Ids.Alpha() +// resourceName := "snowflake_external_function.f" +// +// resource.Test(t, resource.TestCase{ +// PreCheck: func() { acc.TestAccPreCheck(t) }, +// TerraformVersionChecks: []tfversion.TerraformVersionCheck{ +// tfversion.RequireAbove(tfversion.Version1_5_0), +// }, +// CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), +// +// Steps: []resource.TestStep{ +// { +// ExternalProviders: map[string]resource.ExternalProvider{ +// "snowflake": { +// VersionConstraint: "=0.85.0", +// Source: "Snowflake-Labs/snowflake", +// }, +// }, +// Config: externalFunctionConfigWithReturnNullAllowed(acc.TestDatabaseName, acc.TestSchemaName, name, sdk.Bool(true)), +// Check: resource.ComposeTestCheckFunc( +// resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), +// ), +// ExpectNonEmptyPlan: true, +// }, +// { +// ExternalProviders: map[string]resource.ExternalProvider{ +// "snowflake": { +// VersionConstraint: "=0.85.0", +// Source: "Snowflake-Labs/snowflake", +// }, +// }, +// Config: externalFunctionConfig(acc.TestDatabaseName, acc.TestSchemaName, name), +// Check: resource.ComposeTestCheckFunc( +// resource.TestCheckNoResourceAttr(resourceName, "return_null_allowed"), +// ), +// ExpectNonEmptyPlan: true, +// }, +// { +// ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, +// Config: externalFunctionConfig(acc.TestDatabaseName, acc.TestSchemaName, name), +// ConfigPlanChecks: resource.ConfigPlanChecks{ +// PreApply: []plancheck.PlanCheck{plancheck.ExpectEmptyPlan()}, +// }, +// Check: resource.ComposeTestCheckFunc( +// resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), +// ), +// }, +// }, +// }) +//} +// +//// Proves issue https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2528. +//// The problem originated from ShowById without IN clause. There was no IN clause in the docs at the time. +//// It was raised with the appropriate team in Snowflake. +//func TestAcc_ExternalFunction_issue2528(t *testing.T) { +// accName := acc.TestClient().Ids.Alpha() +// secondSchema := acc.TestClient().Ids.Alpha() +// +// resourceName := "snowflake_external_function.f" +// +// resource.Test(t, resource.TestCase{ +// ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, +// PreCheck: func() { acc.TestAccPreCheck(t) }, +// TerraformVersionChecks: []tfversion.TerraformVersionCheck{ +// tfversion.RequireAbove(tfversion.Version1_5_0), +// }, +// CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), +// Steps: []resource.TestStep{ +// { +// Config: externalFunctionConfigIssue2528(acc.TestDatabaseName, acc.TestSchemaName, accName, secondSchema), +// Check: resource.ComposeTestCheckFunc( +// resource.TestCheckResourceAttr(resourceName, "name", accName), +// ), +// }, +// }, +// }) +//} +// +//// Proves that header parsing handles values wrapped in curly braces, e.g. `value = "{1}"` +//func TestAcc_ExternalFunction_HeaderParsing(t *testing.T) { +// id := acc.TestClient().Ids.RandomSchemaObjectIdentifier() +// +// resourceName := "snowflake_external_function.f" +// +// resource.Test(t, resource.TestCase{ +// PreCheck: func() { acc.TestAccPreCheck(t) }, +// TerraformVersionChecks: []tfversion.TerraformVersionCheck{ +// tfversion.RequireAbove(tfversion.Version1_5_0), +// }, +// CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), +// Steps: []resource.TestStep{ +// { +// ExternalProviders: map[string]resource.ExternalProvider{ +// "snowflake": { +// VersionConstraint: "=0.93.0", +// Source: "Snowflake-Labs/snowflake", +// }, +// }, +// Config: externalFunctionConfigIssueCurlyHeader(id), +// // Previous implementation produces a plan with the following changes +// // +// // - header { # forces replacement +// // - name = "name" -> null +// // - value = "0" -> null +// // } +// // +// // + header { # forces replacement +// // + name = "name" +// // + value = "{0}" +// // } +// ExpectNonEmptyPlan: true, +// }, +// { +// ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, +// Config: externalFunctionConfigIssueCurlyHeader(id), +// Check: resource.ComposeTestCheckFunc( +// resource.TestCheckResourceAttr(resourceName, "header.#", "1"), +// resource.TestCheckResourceAttr(resourceName, "header.0.name", "name"), +// resource.TestCheckResourceAttr(resourceName, "header.0.value", "{0}"), +// ), +// }, +// }, +// }) +//} +// +//func externalFunctionConfig(database string, schema string, name string) string { +// return externalFunctionConfigWithReturnNullAllowed(database, schema, name, nil) +//} +// +//func externalFunctionConfigWithReturnNullAllowed(database string, schema string, name string, returnNullAllowed *bool) string { +// returnNullAllowedText := "" +// if returnNullAllowed != nil { +// returnNullAllowedText = fmt.Sprintf("return_null_allowed = \"%t\"", *returnNullAllowed) +// } +// +// return fmt.Sprintf(` +//resource "snowflake_api_integration" "test_api_int" { +// name = "%[3]s" +// api_provider = "aws_api_gateway" +// api_aws_role_arn = "arn:aws:iam::000000000001:/role/test" +// api_allowed_prefixes = ["https://123456.execute-api.us-west-2.amazonaws.com/prod/"] +// enabled = true +//} +// +//resource "snowflake_external_function" "f" { +// name = "%[3]s" +// database = "%[1]s" +// schema = "%[2]s" +// arg { +// name = "ARG1" +// type = "VARCHAR" +// } +// arg { +// name = "ARG2" +// type = "VARCHAR" +// } +// return_type = "VARIANT" +// return_behavior = "IMMUTABLE" +// api_integration = snowflake_api_integration.test_api_int.name +// url_of_proxy_and_resource = "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func" +// %[4]s +//} +// +//`, database, schema, name, returnNullAllowedText) +//} +// +//func externalFunctionConfigIssue2528(database string, schema string, name string, schema2 string) string { +// return fmt.Sprintf(` +//resource "snowflake_api_integration" "test_api_int" { +// name = "%[3]s" +// api_provider = "aws_api_gateway" +// api_aws_role_arn = "arn:aws:iam::000000000001:/role/test" +// api_allowed_prefixes = ["https://123456.execute-api.us-west-2.amazonaws.com/prod/"] +// enabled = true +//} +// +//resource "snowflake_schema" "s2" { +// database = "%[1]s" +// name = "%[4]s" +//} +// +//resource "snowflake_external_function" "f" { +// name = "%[3]s" +// database = "%[1]s" +// schema = "%[2]s" +// arg { +// name = "SNS_NOTIF" +// type = "OBJECT" +// } +// return_type = "VARIANT" +// return_behavior = "VOLATILE" +// api_integration = snowflake_api_integration.test_api_int.name +// url_of_proxy_and_resource = "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func" +//} +// +//resource "snowflake_external_function" "f2" { +// depends_on = [snowflake_schema.s2] +// +// name = "%[3]s" +// database = "%[1]s" +// schema = "%[4]s" +// arg { +// name = "SNS_NOTIF" +// type = "OBJECT" +// } +// return_type = "VARIANT" +// return_behavior = "VOLATILE" +// api_integration = snowflake_api_integration.test_api_int.name +// url_of_proxy_and_resource = "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func" +//} +//`, database, schema, name, schema2) +//} +// +//func externalFunctionConfigIssueCurlyHeader(id sdk.SchemaObjectIdentifier) string { +// return fmt.Sprintf(` +//resource "snowflake_api_integration" "test_api_int" { +// name = "%[3]s" +// api_provider = "aws_api_gateway" +// api_aws_role_arn = "arn:aws:iam::000000000001:/role/test" +// api_allowed_prefixes = ["https://123456.execute-api.us-west-2.amazonaws.com/prod/"] +// enabled = true +//} +// +//resource "snowflake_external_function" "f" { +// name = "%[3]s" +// database = "%[1]s" +// schema = "%[2]s" +// arg { +// name = "ARG1" +// type = "VARCHAR" +// } +// arg { +// name = "ARG2" +// type = "VARCHAR" +// } +// header { +// name = "name" +// value = "{0}" +// } +// return_type = "VARIANT" +// return_behavior = "IMMUTABLE" +// api_integration = snowflake_api_integration.test_api_int.name +// url_of_proxy_and_resource = "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func" +//} +// +//`, id.DatabaseName(), id.SchemaName(), id.Name()) +//} diff --git a/pkg/resources/function_acceptance_test.go b/pkg/resources/function_acceptance_test.go index fe32ca0f76..2a41bff811 100644 --- a/pkg/resources/function_acceptance_test.go +++ b/pkg/resources/function_acceptance_test.go @@ -2,6 +2,7 @@ package resources_test import ( "fmt" + "regexp" "strings" "testing" @@ -218,8 +219,13 @@ func TestAcc_Function_migrateFromVersion085(t *testing.T) { ), }, { - ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, - Config: functionConfig(acc.TestDatabaseName, acc.TestSchemaName, name, comment), + ExternalProviders: map[string]resource.ExternalProvider{ + "snowflake": { + VersionConstraint: "=0.94.1", + Source: "Snowflake-Labs/snowflake", + }, + }, + Config: functionConfig(acc.TestDatabaseName, acc.TestSchemaName, name, comment), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "id", id.FullyQualifiedName()), resource.TestCheckResourceAttr(resourceName, "name", name), @@ -231,6 +237,41 @@ func TestAcc_Function_migrateFromVersion085(t *testing.T) { }) } +func TestAcc_Function_Version0941_ResourceIdMigration(t *testing.T) { + name := acc.TestClient().Ids.RandomAccountObjectIdentifier().Name() + resourceName := "snowflake_function.f" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.CheckDestroy(t, resources.Function), + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "snowflake": { + VersionConstraint: "=0.94.1", + Source: "Snowflake-Labs/snowflake", + }, + }, + Config: functionConfigWithVector(acc.TestDatabaseName, acc.TestSchemaName, name, ""), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf(`"%s"."%s"."%s"(VARCHAR, VECTOR(INT, 20), FLOAT, NUMBER, VECTOR(FLOAT, 10))`, acc.TestDatabaseName, acc.TestSchemaName, name)), + ), + ExpectError: regexp.MustCompile("Error: invalid data type: VECTOR\\(INT, 20\\)"), + }, + { + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + Config: functionConfigWithVector(acc.TestDatabaseName, acc.TestSchemaName, name, ""), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf(`"%s"."%s"."%s"(VARCHAR, VECTOR(INT, 20), FLOAT, NUMBER, VECTOR(FLOAT, 10))`, acc.TestDatabaseName, acc.TestSchemaName, name)), + ), + }, + }, + }) +} + func TestAcc_Function_Rename(t *testing.T) { name := acc.TestClient().Ids.Alpha() newName := acc.TestClient().Ids.Alpha() @@ -272,6 +313,41 @@ func TestAcc_Function_Rename(t *testing.T) { }) } +func functionConfigWithVector(database string, schema string, name string, comment string) string { + return fmt.Sprintf(` +resource "snowflake_function" "f" { + database = "%[1]s" + schema = "%[2]s" + name = "%[3]s" + comment = "%[4]s" + return_type = "VARCHAR" + return_behavior = "IMMUTABLE" + statement = "SELECT A" + + arguments { + name = "A" + type = "VARCHAR(200)" + } + arguments { + name = "B" + type = "VECTOR(INT, 20)" + } + arguments { + name = "C" + type = "FLOAT" + } + arguments { + name = "D" + type = "NUMBER(10, 2)" + } + arguments { + name = "E" + type = "VECTOR(FLOAT, 10)" + } +} +`, database, schema, name, comment) +} + func functionConfig(database string, schema string, name string, comment string) string { return fmt.Sprintf(` resource "snowflake_function" "f" { diff --git a/pkg/resources/function_state_upgraders.go b/pkg/resources/function_state_upgraders.go index 26e6debc1e..ac7c1bd20a 100644 --- a/pkg/resources/function_state_upgraders.go +++ b/pkg/resources/function_state_upgraders.go @@ -33,6 +33,7 @@ func parseV085FunctionId(v string) (*v085FunctionId, error) { ArgTypes: args, }, nil } + func v085FunctionIdStateUpgrader(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { if rawState == nil { return rawState, nil diff --git a/pkg/sdk/functions_gen.go b/pkg/sdk/functions_gen.go index 81d9a38614..6ff4dc9632 100644 --- a/pkg/sdk/functions_gen.go +++ b/pkg/sdk/functions_gen.go @@ -1,12 +1,9 @@ package sdk import ( - "bytes" "context" "database/sql" - "fmt" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" - "log" "strings" ) @@ -226,15 +223,15 @@ type functionRow struct { } type Function struct { - CreatedOn string - Name string - SchemaName string - IsBuiltin bool - IsAggregate bool - IsAnsi bool - MinNumArguments int - MaxNumArguments int - // TODO(SNOW-function refactor): Remove raw arguments + CreatedOn string + Name string + SchemaName string + IsBuiltin bool + IsAggregate bool + IsAnsi bool + MinNumArguments int + MaxNumArguments int + Arguments []DataType ArgumentsRaw string Description string CatalogName string @@ -264,54 +261,8 @@ func parseFunctionArgumentsFromDetails(details []FunctionDetail) ([]DataType, er return arguments, nil } -// Move to sdk/identifier_parsers.go -func parseFunctionArgumentsFromString(arguments string) ([]DataType, error) { - dataTypes := make([]DataType, 0) - - stringBuffer := bytes.NewBufferString(arguments) - for stringBuffer.Len() > 0 { - - // we use another buffer to peek into next data type - peekBuffer := bytes.NewBufferString(stringBuffer.String()) - peekDataType, _ := peekBuffer.ReadString(',') - peekDataType = strings.TrimSpace(peekDataType) - - // For function purposes only Vector needs special case - switch { - case strings.HasPrefix(peekDataType, "VECTOR"): - vectorDataType, _ := stringBuffer.ReadString(')') - vectorDataType = strings.TrimSpace(vectorDataType) - if stringBuffer.Len() > 0 { - commaByte, err := stringBuffer.ReadByte() - if commaByte != ',' { - return nil, fmt.Errorf("expected a comma delimited string but found %s", string(commaByte)) - } - if err != nil { - return nil, err - } - } - log.Println("Adding vec:", vectorDataType) - dataTypes = append(dataTypes, DataType(vectorDataType)) - default: - dataType, err := stringBuffer.ReadString(',') - if err == nil { - dataType = dataType[:len(dataType)-1] - } - dataType = strings.TrimSpace(dataType) - log.Println("Adding:", dataType) - dataTypes = append(dataTypes, DataType(dataType)) - } - } - - return dataTypes, nil -} - -func (v *Function) ID(details []FunctionDetail) (SchemaObjectIdentifierWithArguments, error) { - arguments, err := parseFunctionArgumentsFromDetails(details) - if err != nil { - return SchemaObjectIdentifierWithArguments{}, err - } - return NewSchemaObjectIdentifierWithArguments(v.CatalogName, v.SchemaName, v.Name, arguments...), nil +func (v *Function) ID() SchemaObjectIdentifierWithArguments { + return NewSchemaObjectIdentifierWithArguments(v.CatalogName, v.SchemaName, v.Name, v.Arguments...) } // DescribeFunctionOptions is based on https://docs.snowflake.com/en/sql-reference/sql/desc-function. diff --git a/pkg/sdk/functions_impl_gen.go b/pkg/sdk/functions_impl_gen.go index 3fca482739..a082969111 100644 --- a/pkg/sdk/functions_impl_gen.go +++ b/pkg/sdk/functions_impl_gen.go @@ -3,6 +3,8 @@ package sdk import ( "context" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" + "log" + "strings" ) var _ Functions = (*functions)(nil) @@ -379,6 +381,15 @@ func (r functionRow) convert() *Function { IsExternalFunction: r.IsExternalFunction == "Y", Language: r.Language, } + arguments := strings.TrimLeft(r.Arguments, r.Name) + returnIndex := strings.Index(arguments, ") RETURN ") + dataTypes, err := ParseFunctionArgumentsFromString(arguments[:returnIndex+1]) + if err != nil { + log.Printf("[DEBUG] failed to parse function arguments, err = %s", err) + } else { + e.Arguments = dataTypes + } + if r.IsSecure.Valid { e.IsSecure = r.IsSecure.String == "Y" } diff --git a/pkg/sdk/identifier_helpers.go b/pkg/sdk/identifier_helpers.go index 1ca037b8df..c4f7ef39e4 100644 --- a/pkg/sdk/identifier_helpers.go +++ b/pkg/sdk/identifier_helpers.go @@ -320,8 +320,7 @@ func NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(fullyQualified if err != nil { return SchemaObjectIdentifierWithArguments{}, err } - arguments := fullyQualifiedName[splitIdIndex:] - dataTypes, err := parseFunctionArgumentsFromString(arguments[1 : len(arguments)-1]) + dataTypes, err := ParseFunctionArgumentsFromString(fullyQualifiedName[splitIdIndex:]) if err != nil { return SchemaObjectIdentifierWithArguments{}, err } diff --git a/pkg/sdk/identifier_parsers.go b/pkg/sdk/identifier_parsers.go index 584761b0be..3fc4904907 100644 --- a/pkg/sdk/identifier_parsers.go +++ b/pkg/sdk/identifier_parsers.go @@ -1,6 +1,7 @@ package sdk import ( + "bytes" "encoding/csv" "fmt" "strings" @@ -37,6 +38,10 @@ func ParseIdentifierString(identifier string) ([]string, error) { if strings.Contains(part, `"`) { return nil, fmt.Errorf(`unable to parse identifier: %s, currently identifiers containing double quotes are not supported in the provider`, identifier) } + // TODO(SNOW-1571674): Remove the validation + if strings.ContainsAny(part, `()`) { + return nil, fmt.Errorf(`unable to parse identifier: %s, currently identifiers containing opening and closing parentheses '()' are not supported in the provider`, identifier) + } } return parts, nil } @@ -140,3 +145,50 @@ func ParseExternalObjectIdentifier(identifier string) (ExternalObjectIdentifier, }, ) } + +// ParseFunctionArgumentsFromString parses function argument from arguments string with optional argument names. +// Varying types are not supported (e.g. VARCHAR(200)), because Snowflake outputs them in shortened version +// (VARCHAR in this case). The only exception is newly added type VECTOR which has the following structure +// VECTOR(, n) where right now can be either INT or FLOAT and n is the number of elements in the VECTOR. +// Snowflake returns vectors with their exact type and this function supports it. +func ParseFunctionArgumentsFromString(arguments string) ([]DataType, error) { + dataTypes := make([]DataType, 0) + + if len(arguments) > 0 && arguments[0] == '(' && arguments[len(arguments)-1] == ')' { + arguments = arguments[1 : len(arguments)-1] + } + stringBuffer := bytes.NewBufferString(arguments) + + for stringBuffer.Len() > 0 { + // We use another buffer to peek into next data type (needed for vector parsing) + peekDataType, _ := bytes.NewBufferString(stringBuffer.String()).ReadString(',') + peekDataType = strings.TrimSpace(peekDataType) + + switch { + // For now, only vectors need special parsing behavior + case strings.HasPrefix(peekDataType, "VECTOR"): + vectorDataType, _ := stringBuffer.ReadString(')') + vectorDataType = strings.TrimSpace(vectorDataType) + + if stringBuffer.Len() > 0 { + commaByte, err := stringBuffer.ReadByte() + if commaByte != ',' { + return nil, fmt.Errorf("expected a comma delimited string but found %s", string(commaByte)) + } + if err != nil { + return nil, err + } + } + dataTypes = append(dataTypes, DataType(vectorDataType)) + default: + dataType, err := stringBuffer.ReadString(',') + if err == nil { + dataType = dataType[:len(dataType)-1] + } + dataType = strings.TrimSpace(dataType) + dataTypes = append(dataTypes, DataType(dataType)) + } + } + + return dataTypes, nil +} diff --git a/pkg/sdk/identifier_parsers_test.go b/pkg/sdk/identifier_parsers_test.go index de86bbc9dc..cecb87db90 100644 --- a/pkg/sdk/identifier_parsers_test.go +++ b/pkg/sdk/identifier_parsers_test.go @@ -80,6 +80,27 @@ func Test_ParseIdentifierString(t *testing.T) { require.ErrorContains(t, err, `unable to parse identifier: "ab""c".def, currently identifiers containing double quotes are not supported in the provider`) }) + t.Run("returns error when identifier contains opening parenthesis", func(t *testing.T) { + input := `"ab(c".def` + _, err := ParseIdentifierString(input) + + require.ErrorContains(t, err, `unable to parse identifier: "ab(c".def, currently identifiers containing opening and closing parentheses '()' are not supported in the provider`) + }) + + t.Run("returns error when identifier contains closing parenthesis", func(t *testing.T) { + input := `"ab)c".def` + _, err := ParseIdentifierString(input) + + require.ErrorContains(t, err, `unable to parse identifier: "ab)c".def, currently identifiers containing opening and closing parentheses '()' are not supported in the provider`) + }) + + t.Run("returns error when identifier contains opening and closing parentheses", func(t *testing.T) { + input := `"ab()c".def` + _, err := ParseIdentifierString(input) + + require.ErrorContains(t, err, `unable to parse identifier: "ab()c".def, currently identifiers containing opening and closing parentheses '()' are not supported in the provider`) + }) + t.Run("returns parts correctly with dots inside", func(t *testing.T) { input := `"ab.c".def` expected := []string{`ab.c`, "def"} @@ -250,3 +271,31 @@ func Test_ParseObjectIdentifierString(t *testing.T) { }) } } + +func Test_ParseFunctionArgumentsFromString(t *testing.T) { + testCases := []struct { + Arguments string + Expected []DataType + Error string + }{ + {Arguments: `()`, Expected: []DataType{}}, + {Arguments: `(FLOAT, NUMBER, TIME)`, Expected: []DataType{DataTypeFloat, DataTypeNumber, DataTypeTime}}, + {Arguments: `FLOAT, NUMBER, TIME`, Expected: []DataType{DataTypeFloat, DataTypeNumber, DataTypeTime}}, + {Arguments: `(FLOAT, NUMBER, VECTOR(FLOAT, 20))`, Expected: []DataType{DataTypeFloat, DataTypeNumber, DataType("VECTOR(FLOAT, 20)")}}, + {Arguments: `FLOAT, NUMBER, VECTOR(FLOAT, 20)`, Expected: []DataType{DataTypeFloat, DataTypeNumber, DataType("VECTOR(FLOAT, 20)")}}, + {Arguments: `(VECTOR(FLOAT, 10), NUMBER, VECTOR(FLOAT, 20))`, Expected: []DataType{DataType("VECTOR(FLOAT, 10)"), DataTypeNumber, DataType("VECTOR(FLOAT, 20)")}}, + {Arguments: `VECTOR(FLOAT, 10)| NUMBER, VECTOR(FLOAT, 20)`, Error: "expected a comma delimited string but found |"}, + } + + for _, testCase := range testCases { + t.Run(fmt.Sprintf("parsing function arguments %s", testCase.Arguments), func(t *testing.T) { + dataTypes, err := ParseFunctionArgumentsFromString(testCase.Arguments) + if testCase.Error != "" { + assert.ErrorContains(t, err, testCase.Error) + } else { + assert.NoError(t, err) + assert.Equal(t, testCase.Expected, dataTypes) + } + }) + } +} diff --git a/pkg/sdk/testint/functions_integration_test.go b/pkg/sdk/testint/functions_integration_test.go index a1d57a79ee..883a90c6bd 100644 --- a/pkg/sdk/testint/functions_integration_test.go +++ b/pkg/sdk/testint/functions_integration_test.go @@ -4,6 +4,8 @@ import ( "context" "errors" "fmt" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" + "github.com/stretchr/testify/assert" "testing" "time" @@ -183,268 +185,268 @@ def dump(i): }) } -//func TestInt_OtherFunctions(t *testing.T) { -// client := testClient(t) -// ctx := testContext(t) -// -// tagTest, tagCleanup := testClientHelper().Tag.CreateTag(t) -// t.Cleanup(tagCleanup) -// -// assertFunction := func(t *testing.T, id sdk.SchemaObjectIdentifierWithArguments, secure bool, withArguments bool) { -// t.Helper() -// -// function, err := client.Functions.ShowByID(ctx, id) -// require.NoError(t, err) -// -// assert.NotEmpty(t, function.CreatedOn) -// assert.Equal(t, id.Name(), function.Name) -// assert.Equal(t, false, function.IsBuiltin) -// assert.Equal(t, false, function.IsAggregate) -// assert.Equal(t, false, function.IsAnsi) -// if withArguments { -// assert.Equal(t, 1, function.MinNumArguments) -// assert.Equal(t, 1, function.MaxNumArguments) -// } else { -// assert.Equal(t, 0, function.MinNumArguments) -// assert.Equal(t, 0, function.MaxNumArguments) -// } -// assert.NotEmpty(t, function.ArgumentsRaw) -// assert.NotEmpty(t, function.Arguments) -// assert.NotEmpty(t, function.Description) -// assert.NotEmpty(t, function.CatalogName) -// assert.Equal(t, false, function.IsTableFunction) -// assert.Equal(t, false, function.ValidForClustering) -// assert.Equal(t, secure, function.IsSecure) -// assert.Equal(t, false, function.IsExternalFunction) -// assert.Equal(t, "SQL", function.Language) -// assert.Equal(t, false, function.IsMemoizable) -// } -// -// cleanupFunctionHandle := func(id sdk.SchemaObjectIdentifierWithArguments) func() { -// return func() { -// err := client.Functions.Drop(ctx, sdk.NewDropFunctionRequest(id)) -// if errors.Is(err, sdk.ErrObjectNotExistOrAuthorized) { -// return -// } -// require.NoError(t, err) -// } -// } -// -// createFunctionForSQLHandle := func(t *testing.T, cleanup bool, withArguments bool) *sdk.Function { -// t.Helper() -// var id sdk.SchemaObjectIdentifierWithArguments -// if withArguments { -// id = testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(sdk.DataTypeFloat) -// } else { -// id = testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments() -// } -// -// definition := "3.141592654::FLOAT" -// -// dt := sdk.NewFunctionReturnsResultDataTypeRequest(sdk.DataTypeFloat) -// returns := sdk.NewFunctionReturnsRequest().WithResultDataType(*dt) -// request := sdk.NewCreateForSQLFunctionRequest(id.SchemaObjectId(), *returns, definition). -// WithOrReplace(true) -// if withArguments { -// argument := sdk.NewFunctionArgumentRequest("x", sdk.DataTypeFloat) -// request = request.WithArguments([]sdk.FunctionArgumentRequest{*argument}) -// } -// err := client.Functions.CreateForSQL(ctx, request) -// require.NoError(t, err) -// if cleanup { -// t.Cleanup(cleanupFunctionHandle(id)) -// } -// function, err := client.Functions.ShowByID(ctx, id) -// require.NoError(t, err) -// return function -// } -// -// defaultAlterRequest := func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { -// return sdk.NewAlterFunctionRequest(id) -// } -// -// t.Run("alter function: rename", func(t *testing.T) { -// f := createFunctionForSQLHandle(t, false, true) -// -// id := f.ID() -// nid := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments() -// err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithRenameTo(nid.SchemaObjectId())) -// if err != nil { -// t.Cleanup(cleanupFunctionHandle(id)) -// } else { -// t.Cleanup(cleanupFunctionHandle(nid)) -// } -// require.NoError(t, err) -// -// _, err = client.Functions.ShowByID(ctx, id) -// assert.ErrorIs(t, err, collections.ErrObjectNotFound) -// -// e, err := client.Functions.ShowByID(ctx, nid) -// require.NoError(t, err) -// require.Equal(t, nid.Name(), e.Name) -// }) -// -// t.Run("alter function: set log level", func(t *testing.T) { -// f := createFunctionForSQLHandle(t, true, true) -// -// id := f.ID() -// err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetLogLevel(string(sdk.LogLevelDebug))) -// require.NoError(t, err) -// assertFunction(t, id, false, true) -// }) -// -// t.Run("alter function: unset log level", func(t *testing.T) { -// f := createFunctionForSQLHandle(t, true, true) -// -// id := f.ID() -// err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetLogLevel(true)) -// require.NoError(t, err) -// assertFunction(t, id, false, true) -// }) -// -// t.Run("alter function: set trace level", func(t *testing.T) { -// f := createFunctionForSQLHandle(t, true, true) -// -// id := f.ID() -// err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetTraceLevel(string(sdk.TraceLevelAlways))) -// require.NoError(t, err) -// assertFunction(t, id, false, true) -// }) -// -// t.Run("alter function: unset trace level", func(t *testing.T) { -// f := createFunctionForSQLHandle(t, true, true) -// -// id := f.ID() -// err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetTraceLevel(true)) -// require.NoError(t, err) -// assertFunction(t, id, false, true) -// }) -// -// t.Run("alter function: set comment", func(t *testing.T) { -// f := createFunctionForSQLHandle(t, true, true) -// -// id := f.ID() -// err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetComment("test comment")) -// require.NoError(t, err) -// assertFunction(t, id, false, true) -// }) -// -// t.Run("alter function: unset comment", func(t *testing.T) { -// f := createFunctionForSQLHandle(t, true, true) -// -// id := f.ID() -// err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetComment(true)) -// require.NoError(t, err) -// assertFunction(t, id, false, true) -// }) -// -// t.Run("alter function: set secure", func(t *testing.T) { -// f := createFunctionForSQLHandle(t, true, true) -// -// id := f.ID() -// err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetSecure(true)) -// require.NoError(t, err) -// assertFunction(t, id, true, true) -// }) -// -// t.Run("alter function: set secure with no arguments", func(t *testing.T) { -// f := createFunctionForSQLHandle(t, true, true) -// id := f.ID() -// err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithSetSecure(true)) -// require.NoError(t, err) -// assertFunction(t, id, true, true) -// }) -// -// t.Run("alter function: unset secure", func(t *testing.T) { -// f := createFunctionForSQLHandle(t, true, true) -// -// id := f.ID() -// err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetSecure(true)) -// require.NoError(t, err) -// assertFunction(t, id, false, true) -// }) -// -// t.Run("alter function: set and unset tags", func(t *testing.T) { -// f := createFunctionForSQLHandle(t, true, true) -// -// id := f.ID() -// setTags := []sdk.TagAssociation{ -// { -// Name: tagTest.ID(), -// Value: "v1", -// }, -// } -// err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetTags(setTags)) -// require.NoError(t, err) -// assertFunction(t, id, false, true) -// -// unsetTags := []sdk.ObjectIdentifier{ -// tagTest.ID(), -// } -// err = client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetTags(unsetTags)) -// require.NoError(t, err) -// assertFunction(t, id, false, true) -// }) -// -// t.Run("show function for SQL: without like", func(t *testing.T) { -// f1 := createFunctionForSQLHandle(t, true, true) -// f2 := createFunctionForSQLHandle(t, true, true) -// -// functions, err := client.Functions.Show(ctx, sdk.NewShowFunctionRequest()) -// require.NoError(t, err) -// -// require.Contains(t, functions, *f1) -// require.Contains(t, functions, *f2) -// }) -// -// t.Run("show function for SQL: with like", func(t *testing.T) { -// f1 := createFunctionForSQLHandle(t, true, true) -// f2 := createFunctionForSQLHandle(t, true, true) -// -// functions, err := client.Functions.Show(ctx, sdk.NewShowFunctionRequest().WithLike(sdk.Like{Pattern: &f1.Name})) -// require.NoError(t, err) -// -// require.Equal(t, 1, len(functions)) -// require.Contains(t, functions, *f1) -// require.NotContains(t, functions, *f2) -// }) -// -// t.Run("show function for SQL: no matches", func(t *testing.T) { -// functions, err := client.Functions.Show(ctx, sdk.NewShowFunctionRequest().WithLike(sdk.Like{Pattern: sdk.String("non-existing-id-pattern")})) -// require.NoError(t, err) -// require.Equal(t, 0, len(functions)) -// }) -// -// t.Run("describe function for SQL", func(t *testing.T) { -// f := createFunctionForSQLHandle(t, true, true) -// -// details, err := client.Functions.Describe(ctx, f.ID()) -// 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, "FLOAT", pairs["returns"]) -// require.Equal(t, "3.141592654::FLOAT", pairs["body"]) -// require.Equal(t, "(X FLOAT)", pairs["signature"]) -// }) -// -// t.Run("describe function for SQL: no arguments", func(t *testing.T) { -// f := createFunctionForSQLHandle(t, true, false) -// -// details, err := client.Functions.Describe(ctx, f.ID()) -// 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, "FLOAT", pairs["returns"]) -// require.Equal(t, "3.141592654::FLOAT", pairs["body"]) -// require.Equal(t, "()", pairs["signature"]) -// }) -//} +func TestInt_OtherFunctions(t *testing.T) { + client := testClient(t) + ctx := testContext(t) + + tagTest, tagCleanup := testClientHelper().Tag.CreateTag(t) + t.Cleanup(tagCleanup) + + assertFunction := func(t *testing.T, id sdk.SchemaObjectIdentifierWithArguments, secure bool, withArguments bool) { + t.Helper() + + function, err := client.Functions.ShowByID(ctx, id) + require.NoError(t, err) + + assert.NotEmpty(t, function.CreatedOn) + assert.Equal(t, id.Name(), function.Name) + assert.Equal(t, false, function.IsBuiltin) + assert.Equal(t, false, function.IsAggregate) + assert.Equal(t, false, function.IsAnsi) + if withArguments { + assert.Equal(t, 1, function.MinNumArguments) + assert.Equal(t, 1, function.MaxNumArguments) + } else { + assert.Equal(t, 0, function.MinNumArguments) + assert.Equal(t, 0, function.MaxNumArguments) + } + assert.NotEmpty(t, function.ArgumentsRaw) + assert.NotEmpty(t, function.Arguments) + assert.NotEmpty(t, function.Description) + assert.NotEmpty(t, function.CatalogName) + assert.Equal(t, false, function.IsTableFunction) + assert.Equal(t, false, function.ValidForClustering) + assert.Equal(t, secure, function.IsSecure) + assert.Equal(t, false, function.IsExternalFunction) + assert.Equal(t, "SQL", function.Language) + assert.Equal(t, false, function.IsMemoizable) + } + + cleanupFunctionHandle := func(id sdk.SchemaObjectIdentifierWithArguments) func() { + return func() { + err := client.Functions.Drop(ctx, sdk.NewDropFunctionRequest(id)) + if errors.Is(err, sdk.ErrObjectNotExistOrAuthorized) { + return + } + require.NoError(t, err) + } + } + + createFunctionForSQLHandle := func(t *testing.T, cleanup bool, withArguments bool) *sdk.Function { + t.Helper() + var id sdk.SchemaObjectIdentifierWithArguments + if withArguments { + id = testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(sdk.DataTypeFloat) + } else { + id = testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments() + } + + definition := "3.141592654::FLOAT" + + dt := sdk.NewFunctionReturnsResultDataTypeRequest(sdk.DataTypeFloat) + returns := sdk.NewFunctionReturnsRequest().WithResultDataType(*dt) + request := sdk.NewCreateForSQLFunctionRequest(id.SchemaObjectId(), *returns, definition). + WithOrReplace(true) + if withArguments { + argument := sdk.NewFunctionArgumentRequest("x", sdk.DataTypeFloat) + request = request.WithArguments([]sdk.FunctionArgumentRequest{*argument}) + } + err := client.Functions.CreateForSQL(ctx, request) + require.NoError(t, err) + if cleanup { + t.Cleanup(cleanupFunctionHandle(id)) + } + function, err := client.Functions.ShowByID(ctx, id) + require.NoError(t, err) + return function + } + + defaultAlterRequest := func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { + return sdk.NewAlterFunctionRequest(id) + } + + t.Run("alter function: rename", func(t *testing.T) { + f := createFunctionForSQLHandle(t, false, true) + + id := f.ID() + nid := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments() + err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithRenameTo(nid.SchemaObjectId())) + if err != nil { + t.Cleanup(cleanupFunctionHandle(id)) + } else { + t.Cleanup(cleanupFunctionHandle(nid)) + } + require.NoError(t, err) + + _, err = client.Functions.ShowByID(ctx, id) + assert.ErrorIs(t, err, collections.ErrObjectNotFound) + + e, err := client.Functions.ShowByID(ctx, nid) + require.NoError(t, err) + require.Equal(t, nid.Name(), e.Name) + }) + + t.Run("alter function: set log level", func(t *testing.T) { + f := createFunctionForSQLHandle(t, true, true) + + id := f.ID() + err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetLogLevel(string(sdk.LogLevelDebug))) + require.NoError(t, err) + assertFunction(t, id, false, true) + }) + + t.Run("alter function: unset log level", func(t *testing.T) { + f := createFunctionForSQLHandle(t, true, true) + + id := f.ID() + err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetLogLevel(true)) + require.NoError(t, err) + assertFunction(t, id, false, true) + }) + + t.Run("alter function: set trace level", func(t *testing.T) { + f := createFunctionForSQLHandle(t, true, true) + + id := f.ID() + err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetTraceLevel(string(sdk.TraceLevelAlways))) + require.NoError(t, err) + assertFunction(t, id, false, true) + }) + + t.Run("alter function: unset trace level", func(t *testing.T) { + f := createFunctionForSQLHandle(t, true, true) + + id := f.ID() + err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetTraceLevel(true)) + require.NoError(t, err) + assertFunction(t, id, false, true) + }) + + t.Run("alter function: set comment", func(t *testing.T) { + f := createFunctionForSQLHandle(t, true, true) + + id := f.ID() + err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetComment("test comment")) + require.NoError(t, err) + assertFunction(t, id, false, true) + }) + + t.Run("alter function: unset comment", func(t *testing.T) { + f := createFunctionForSQLHandle(t, true, true) + + id := f.ID() + err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetComment(true)) + require.NoError(t, err) + assertFunction(t, id, false, true) + }) + + t.Run("alter function: set secure", func(t *testing.T) { + f := createFunctionForSQLHandle(t, true, true) + + id := f.ID() + err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetSecure(true)) + require.NoError(t, err) + assertFunction(t, id, true, true) + }) + + t.Run("alter function: set secure with no arguments", func(t *testing.T) { + f := createFunctionForSQLHandle(t, true, true) + id := f.ID() + err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithSetSecure(true)) + require.NoError(t, err) + assertFunction(t, id, true, true) + }) + + t.Run("alter function: unset secure", func(t *testing.T) { + f := createFunctionForSQLHandle(t, true, true) + + id := f.ID() + err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetSecure(true)) + require.NoError(t, err) + assertFunction(t, id, false, true) + }) + + t.Run("alter function: set and unset tags", func(t *testing.T) { + f := createFunctionForSQLHandle(t, true, true) + + id := f.ID() + setTags := []sdk.TagAssociation{ + { + Name: tagTest.ID(), + Value: "v1", + }, + } + err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetTags(setTags)) + require.NoError(t, err) + assertFunction(t, id, false, true) + + unsetTags := []sdk.ObjectIdentifier{ + tagTest.ID(), + } + err = client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetTags(unsetTags)) + require.NoError(t, err) + assertFunction(t, id, false, true) + }) + + t.Run("show function for SQL: without like", func(t *testing.T) { + f1 := createFunctionForSQLHandle(t, true, true) + f2 := createFunctionForSQLHandle(t, true, true) + + functions, err := client.Functions.Show(ctx, sdk.NewShowFunctionRequest()) + require.NoError(t, err) + + require.Contains(t, functions, *f1) + require.Contains(t, functions, *f2) + }) + + t.Run("show function for SQL: with like", func(t *testing.T) { + f1 := createFunctionForSQLHandle(t, true, true) + f2 := createFunctionForSQLHandle(t, true, true) + + functions, err := client.Functions.Show(ctx, sdk.NewShowFunctionRequest().WithLike(sdk.Like{Pattern: &f1.Name})) + require.NoError(t, err) + + require.Equal(t, 1, len(functions)) + require.Contains(t, functions, *f1) + require.NotContains(t, functions, *f2) + }) + + t.Run("show function for SQL: no matches", func(t *testing.T) { + functions, err := client.Functions.Show(ctx, sdk.NewShowFunctionRequest().WithLike(sdk.Like{Pattern: sdk.String("non-existing-id-pattern")})) + require.NoError(t, err) + require.Equal(t, 0, len(functions)) + }) + + t.Run("describe function for SQL", func(t *testing.T) { + f := createFunctionForSQLHandle(t, true, true) + + details, err := client.Functions.Describe(ctx, f.ID()) + 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, "FLOAT", pairs["returns"]) + require.Equal(t, "3.141592654::FLOAT", pairs["body"]) + require.Equal(t, "(X FLOAT)", pairs["signature"]) + }) + + t.Run("describe function for SQL: no arguments", func(t *testing.T) { + f := createFunctionForSQLHandle(t, true, false) + + details, err := client.Functions.Describe(ctx, f.ID()) + 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, "FLOAT", pairs["returns"]) + require.Equal(t, "3.141592654::FLOAT", pairs["body"]) + require.Equal(t, "()", pairs["signature"]) + }) +} func TestInt_FunctionsShowByID(t *testing.T) { client := testClient(t) @@ -488,20 +490,14 @@ func TestInt_FunctionsShowByID(t *testing.T) { e1, err := client.Functions.ShowByID(ctx, id1) require.NoError(t, err) - e1Details, err := client.Functions.Describe(ctx, id1) - require.NoError(t, err) - - e1Id, err := e1.ID(e1Details) + e1Id := e1.ID() require.NoError(t, err) require.Equal(t, id1, e1Id) e2, err := client.Functions.ShowByID(ctx, id2) require.NoError(t, err) - e2Details, err := client.Functions.Describe(ctx, id2) - require.NoError(t, err) - - e2Id, err := e2.ID(e2Details) + e2Id := e2.ID() require.NoError(t, err) require.Equal(t, id2, e2Id) }) @@ -560,4 +556,60 @@ func TestInt_FunctionsShowByID(t *testing.T) { _, err = client.Functions.ShowByID(ctx, idWithArguments) require.NoError(t, err) }) + + t.Run("function returns non detailed data types of arguments", func(t *testing.T) { + // This test proves that every detailed data type (e.g. VARCHAR(20) and NUMBER(10, 0)) is generalized + // (to e.g. VARCHAR and NUMBER) and that sdk.ToDataType mapping function maps detailed types correctly to + // their generalized counterparts. + + id := testClientHelper().Ids.RandomSchemaObjectIdentifier() + args := []sdk.FunctionArgumentRequest{ + *sdk.NewFunctionArgumentRequest("A", "NUMBER(2, 0)"), + *sdk.NewFunctionArgumentRequest("B", "DECIMAL"), + *sdk.NewFunctionArgumentRequest("C", "INTEGER"), + *sdk.NewFunctionArgumentRequest("D", sdk.DataTypeFloat), + *sdk.NewFunctionArgumentRequest("E", "DOUBLE"), + *sdk.NewFunctionArgumentRequest("F", "VARCHAR(20)"), + *sdk.NewFunctionArgumentRequest("G", "CHAR"), + *sdk.NewFunctionArgumentRequest("H", sdk.DataTypeString), + *sdk.NewFunctionArgumentRequest("I", "TEXT"), + *sdk.NewFunctionArgumentRequest("J", sdk.DataTypeBinary), + *sdk.NewFunctionArgumentRequest("K", "VARBINARY"), + *sdk.NewFunctionArgumentRequest("L", sdk.DataTypeBoolean), + *sdk.NewFunctionArgumentRequest("M", sdk.DataTypeDate), + *sdk.NewFunctionArgumentRequest("N", "DATETIME"), + *sdk.NewFunctionArgumentRequest("O", sdk.DataTypeTime), + *sdk.NewFunctionArgumentRequest("P", sdk.DataTypeTimestamp), + *sdk.NewFunctionArgumentRequest("R", sdk.DataTypeTimestampLTZ), + *sdk.NewFunctionArgumentRequest("S", sdk.DataTypeTimestampNTZ), + *sdk.NewFunctionArgumentRequest("T", sdk.DataTypeTimestampTZ), + *sdk.NewFunctionArgumentRequest("U", sdk.DataTypeVariant), + *sdk.NewFunctionArgumentRequest("V", sdk.DataTypeObject), + *sdk.NewFunctionArgumentRequest("W", sdk.DataTypeArray), + *sdk.NewFunctionArgumentRequest("X", sdk.DataTypeGeography), + *sdk.NewFunctionArgumentRequest("Y", sdk.DataTypeGeometry), + *sdk.NewFunctionArgumentRequest("Z", "VECTOR(INT, 16)"), + } + err := client.Functions.CreateForPython(ctx, sdk.NewCreateForPythonFunctionRequest( + id, + *sdk.NewFunctionReturnsRequest().WithResultDataType(*sdk.NewFunctionReturnsResultDataTypeRequest(sdk.DataTypeVariant)), + "3.8", + "add", + ). + WithArguments(args). + WithFunctionDefinition("def add(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, R, S, T, U, V, W, X, Y, Z): A + A"), + ) + require.NoError(t, err) + + dataTypes := make([]sdk.DataType, len(args)) + for i, arg := range args { + dataTypes[i], err = sdk.ToDataType(string(arg.ArgDataType)) + require.NoError(t, err) + } + idWithArguments := sdk.NewSchemaObjectIdentifierWithArguments(id.DatabaseName(), id.SchemaName(), id.Name(), dataTypes...) + + function, err := client.Functions.ShowByID(ctx, idWithArguments) + require.NoError(t, err) + require.Equal(t, dataTypes, function.Arguments) + }) } From ac0f3896bc83e1053c073d8dc305826c700f4b17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Mon, 5 Aug 2024 16:02:02 +0200 Subject: [PATCH 08/13] wip --- pkg/provider/provider.go | 112 +- pkg/resources/external_function.go | 989 ++++++----- .../external_function_state_upgraders.go | 141 +- pkg/resources/function.go | 1 + pkg/resources/function_acceptance_test.go | 31 +- pkg/resources/procedure.go | 1569 +++++++++-------- pkg/resources/procedure_acceptance_test.go | 761 ++++---- pkg/resources/procedure_state_upgraders.go | 111 +- pkg/sdk/identifier_helpers.go | 56 +- 9 files changed, 1935 insertions(+), 1836 deletions(-) diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index f5c8f2d76a..edb760e412 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -436,62 +436,62 @@ func getResources() map[string]*schema.Resource { "snowflake_database_role": resources.DatabaseRole(), "snowflake_dynamic_table": resources.DynamicTable(), "snowflake_email_notification_integration": resources.EmailNotificationIntegration(), - //"snowflake_external_function": resources.ExternalFunction(), - "snowflake_external_oauth_integration": resources.ExternalOauthIntegration(), - "snowflake_external_table": resources.ExternalTable(), - "snowflake_failover_group": resources.FailoverGroup(), - "snowflake_file_format": resources.FileFormat(), - "snowflake_function": resources.Function(), - "snowflake_grant_account_role": resources.GrantAccountRole(), - "snowflake_grant_application_role": resources.GrantApplicationRole(), - "snowflake_grant_database_role": resources.GrantDatabaseRole(), - "snowflake_grant_ownership": resources.GrantOwnership(), - "snowflake_grant_privileges_to_account_role": resources.GrantPrivilegesToAccountRole(), - "snowflake_grant_privileges_to_database_role": resources.GrantPrivilegesToDatabaseRole(), - "snowflake_grant_privileges_to_share": resources.GrantPrivilegesToShare(), - "snowflake_managed_account": resources.ManagedAccount(), - "snowflake_masking_policy": resources.MaskingPolicy(), - "snowflake_materialized_view": resources.MaterializedView(), - "snowflake_network_policy": resources.NetworkPolicy(), - "snowflake_network_policy_attachment": resources.NetworkPolicyAttachment(), - "snowflake_network_rule": resources.NetworkRule(), - "snowflake_notification_integration": resources.NotificationIntegration(), - "snowflake_oauth_integration": resources.OAuthIntegration(), - "snowflake_oauth_integration_for_partner_applications": resources.OauthIntegrationForPartnerApplications(), - "snowflake_oauth_integration_for_custom_clients": resources.OauthIntegrationForCustomClients(), - "snowflake_object_parameter": resources.ObjectParameter(), - "snowflake_password_policy": resources.PasswordPolicy(), - "snowflake_pipe": resources.Pipe(), - //"snowflake_procedure": resources.Procedure(), - "snowflake_resource_monitor": resources.ResourceMonitor(), - "snowflake_role": resources.Role(), - "snowflake_row_access_policy": resources.RowAccessPolicy(), - "snowflake_saml_integration": resources.SAMLIntegration(), - "snowflake_saml2_integration": resources.SAML2Integration(), - "snowflake_schema": resources.Schema(), - "snowflake_scim_integration": resources.SCIMIntegration(), - "snowflake_secondary_database": resources.SecondaryDatabase(), - "snowflake_sequence": resources.Sequence(), - "snowflake_session_parameter": resources.SessionParameter(), - "snowflake_share": resources.Share(), - "snowflake_shared_database": resources.SharedDatabase(), - "snowflake_stage": resources.Stage(), - "snowflake_storage_integration": resources.StorageIntegration(), - "snowflake_stream": resources.Stream(), - "snowflake_streamlit": resources.Streamlit(), - "snowflake_table": resources.Table(), - "snowflake_table_column_masking_policy_application": resources.TableColumnMaskingPolicyApplication(), - "snowflake_table_constraint": resources.TableConstraint(), - "snowflake_tag": resources.Tag(), - "snowflake_tag_association": resources.TagAssociation(), - "snowflake_tag_masking_policy_association": resources.TagMaskingPolicyAssociation(), - "snowflake_task": resources.Task(), - "snowflake_unsafe_execute": resources.UnsafeExecute(), - "snowflake_user": resources.User(), - "snowflake_user_password_policy_attachment": resources.UserPasswordPolicyAttachment(), - "snowflake_user_public_keys": resources.UserPublicKeys(), - "snowflake_view": resources.View(), - "snowflake_warehouse": resources.Warehouse(), + "snowflake_external_function": resources.ExternalFunction(), + "snowflake_external_oauth_integration": resources.ExternalOauthIntegration(), + "snowflake_external_table": resources.ExternalTable(), + "snowflake_failover_group": resources.FailoverGroup(), + "snowflake_file_format": resources.FileFormat(), + "snowflake_function": resources.Function(), + "snowflake_grant_account_role": resources.GrantAccountRole(), + "snowflake_grant_application_role": resources.GrantApplicationRole(), + "snowflake_grant_database_role": resources.GrantDatabaseRole(), + "snowflake_grant_ownership": resources.GrantOwnership(), + "snowflake_grant_privileges_to_account_role": resources.GrantPrivilegesToAccountRole(), + "snowflake_grant_privileges_to_database_role": resources.GrantPrivilegesToDatabaseRole(), + "snowflake_grant_privileges_to_share": resources.GrantPrivilegesToShare(), + "snowflake_managed_account": resources.ManagedAccount(), + "snowflake_masking_policy": resources.MaskingPolicy(), + "snowflake_materialized_view": resources.MaterializedView(), + "snowflake_network_policy": resources.NetworkPolicy(), + "snowflake_network_policy_attachment": resources.NetworkPolicyAttachment(), + "snowflake_network_rule": resources.NetworkRule(), + "snowflake_notification_integration": resources.NotificationIntegration(), + "snowflake_oauth_integration": resources.OAuthIntegration(), + "snowflake_oauth_integration_for_partner_applications": resources.OauthIntegrationForPartnerApplications(), + "snowflake_oauth_integration_for_custom_clients": resources.OauthIntegrationForCustomClients(), + "snowflake_object_parameter": resources.ObjectParameter(), + "snowflake_password_policy": resources.PasswordPolicy(), + "snowflake_pipe": resources.Pipe(), + "snowflake_procedure": resources.Procedure(), + "snowflake_resource_monitor": resources.ResourceMonitor(), + "snowflake_role": resources.Role(), + "snowflake_row_access_policy": resources.RowAccessPolicy(), + "snowflake_saml_integration": resources.SAMLIntegration(), + "snowflake_saml2_integration": resources.SAML2Integration(), + "snowflake_schema": resources.Schema(), + "snowflake_scim_integration": resources.SCIMIntegration(), + "snowflake_secondary_database": resources.SecondaryDatabase(), + "snowflake_sequence": resources.Sequence(), + "snowflake_session_parameter": resources.SessionParameter(), + "snowflake_share": resources.Share(), + "snowflake_shared_database": resources.SharedDatabase(), + "snowflake_stage": resources.Stage(), + "snowflake_storage_integration": resources.StorageIntegration(), + "snowflake_stream": resources.Stream(), + "snowflake_streamlit": resources.Streamlit(), + "snowflake_table": resources.Table(), + "snowflake_table_column_masking_policy_application": resources.TableColumnMaskingPolicyApplication(), + "snowflake_table_constraint": resources.TableConstraint(), + "snowflake_tag": resources.Tag(), + "snowflake_tag_association": resources.TagAssociation(), + "snowflake_tag_masking_policy_association": resources.TagMaskingPolicyAssociation(), + "snowflake_task": resources.Task(), + "snowflake_unsafe_execute": resources.UnsafeExecute(), + "snowflake_user": resources.User(), + "snowflake_user_password_policy_attachment": resources.UserPasswordPolicyAttachment(), + "snowflake_user_public_keys": resources.UserPublicKeys(), + "snowflake_view": resources.View(), + "snowflake_warehouse": resources.Warehouse(), } } diff --git a/pkg/resources/external_function.go b/pkg/resources/external_function.go index a78e9c5d6b..7fb246ab33 100644 --- a/pkg/resources/external_function.go +++ b/pkg/resources/external_function.go @@ -1,489 +1,504 @@ package resources -//var externalFunctionSchema = map[string]*schema.Schema{ -// "name": { -// Type: schema.TypeString, -// Required: true, -// ForceNew: true, -// Description: "Specifies the identifier for the external function. The identifier can contain the schema name and database name, as well as the function name. The function's signature (name and argument data types) must be unique within the schema.", -// }, -// "schema": { -// Type: schema.TypeString, -// Required: true, -// ForceNew: true, -// Description: "The schema in which to create the external function.", -// }, -// "database": { -// Type: schema.TypeString, -// Required: true, -// ForceNew: true, -// Description: "The database in which to create the external function.", -// }, -// "arg": { -// Type: schema.TypeList, -// Optional: true, -// ForceNew: true, -// Description: "Specifies the arguments/inputs for the external function. These should correspond to the arguments that the remote service expects.", -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "name": { -// Type: schema.TypeString, -// Required: true, -// // Suppress the diff shown if the values are equal when both compared in lower case. -// DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { -// return strings.EqualFold(strings.ToLower(old), strings.ToLower(new)) -// }, -// Description: "Argument name", -// }, -// "type": { -// Type: schema.TypeString, -// Required: true, -// // Suppress the diff shown if the values are equal when both compared in lower case. -// DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { -// return strings.EqualFold(strings.ToLower(old), strings.ToLower(new)) -// }, -// Description: "Argument type, e.g. VARCHAR", -// }, -// }, -// }, -// }, -// "null_input_behavior": { -// Type: schema.TypeString, -// Optional: true, -// Default: "CALLED ON NULL INPUT", -// ForceNew: true, -// ValidateFunc: validation.StringInSlice([]string{"CALLED ON NULL INPUT", "RETURNS NULL ON NULL INPUT", "STRICT"}, false), -// Description: "Specifies the behavior of the external function when called with null inputs.", -// }, -// "return_type": { -// Type: schema.TypeString, -// Required: true, -// ForceNew: true, -// // Suppress the diff shown if the values are equal when both compared in lower case. -// DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { -// return strings.EqualFold(strings.ToLower(old), strings.ToLower(new)) -// }, -// Description: "Specifies the data type returned by the external function.", -// }, -// "return_null_allowed": { -// Type: schema.TypeBool, -// Optional: true, -// ForceNew: true, -// Description: "Indicates whether the function can return NULL values (true) or must return only NON-NULL values (false).", -// Default: true, -// }, -// "return_behavior": { -// Type: schema.TypeString, -// Required: true, -// ForceNew: true, -// ValidateFunc: validation.StringInSlice([]string{"VOLATILE", "IMMUTABLE"}, false), -// Description: "Specifies the behavior of the function when returning results", -// }, -// "api_integration": { -// Type: schema.TypeString, -// Required: true, -// ForceNew: true, -// Description: "The name of the API integration object that should be used to authenticate the call to the proxy service.", -// }, -// "header": { -// Type: schema.TypeSet, -// Optional: true, -// ForceNew: true, -// Description: "Allows users to specify key-value metadata that is sent with every request as HTTP headers.", -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "name": { -// Type: schema.TypeString, -// Required: true, -// ForceNew: true, -// Description: "Header name", -// }, -// "value": { -// Type: schema.TypeString, -// Required: true, -// ForceNew: true, -// Description: "Header value", -// }, -// }, -// }, -// }, -// "context_headers": { -// Type: schema.TypeList, -// Elem: &schema.Schema{Type: schema.TypeString}, -// Optional: true, -// ForceNew: true, -// // Suppress the diff shown if the values are equal when both compared in lower case. -// DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { -// return strings.EqualFold(strings.ToLower(old), strings.ToLower(new)) -// }, -// Description: "Binds Snowflake context function results to HTTP headers.", -// }, -// "max_batch_rows": { -// Type: schema.TypeInt, -// Optional: true, -// ForceNew: true, -// Description: "This specifies the maximum number of rows in each batch sent to the proxy service.", -// }, -// "compression": { -// Type: schema.TypeString, -// Optional: true, -// Default: "AUTO", -// ForceNew: true, -// ValidateFunc: validation.StringInSlice([]string{"NONE", "AUTO", "GZIP", "DEFLATE"}, false), -// Description: "If specified, the JSON payload is compressed when sent from Snowflake to the proxy service, and when sent back from the proxy service to Snowflake.", -// }, -// "request_translator": { -// Type: schema.TypeString, -// Optional: true, -// ForceNew: true, -// Description: "This specifies the name of the request translator function", -// }, -// "response_translator": { -// Type: schema.TypeString, -// Optional: true, -// ForceNew: true, -// Description: "This specifies the name of the response translator function.", -// }, -// "url_of_proxy_and_resource": { -// Type: schema.TypeString, -// Required: true, -// ForceNew: true, -// Description: "This is the invocation URL of the proxy service and resource through which Snowflake calls the remote service.", -// }, -// "comment": { -// Type: schema.TypeString, -// Optional: true, -// Default: "user-defined function", -// Description: "A description of the external function.", -// }, -// "created_on": { -// Type: schema.TypeString, -// Computed: true, -// Description: "Date and time when the external function was created.", -// }, -//} -// -//// ExternalFunction returns a pointer to the resource representing an external function. -//func ExternalFunction() *schema.Resource { -// return &schema.Resource{ -// SchemaVersion: 1, -// -// CreateContext: CreateContextExternalFunction, -// ReadContext: ReadContextExternalFunction, -// UpdateContext: UpdateContextExternalFunction, -// DeleteContext: DeleteContextExternalFunction, -// -// Schema: externalFunctionSchema, -// Importer: &schema.ResourceImporter{ -// StateContext: schema.ImportStatePassthroughContext, -// }, -// -// StateUpgraders: []schema.StateUpgrader{ -// { -// Version: 0, -// // setting type to cty.EmptyObject is a bit hacky here but following https://developer.hashicorp.com/terraform/plugin/framework/migrating/resources/state-upgrade#sdkv2-1 would require lots of repetitive code; this should work with cty.EmptyObject -// Type: cty.EmptyObject, -// Upgrade: v085ExternalFunctionStateUpgrader, -// }, -// }, -// } -//} -// -//func CreateContextExternalFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// client := meta.(*provider.Context).Client -// database := d.Get("database").(string) -// schemaName := d.Get("schema").(string) -// name := d.Get("name").(string) -// id := sdk.NewSchemaObjectIdentifier(database, schemaName, name) -// -// returnType := d.Get("return_type").(string) -// resultDataType, err := sdk.ToDataType(returnType) -// if err != nil { -// return diag.FromErr(err) -// } -// apiIntegration := sdk.NewAccountObjectIdentifier(d.Get("api_integration").(string)) -// urlOfProxyAndResource := d.Get("url_of_proxy_and_resource").(string) -// req := sdk.NewCreateExternalFunctionRequest(id, resultDataType, &apiIntegration, urlOfProxyAndResource) -// -// // Set optionals -// args := make([]sdk.ExternalFunctionArgumentRequest, 0) -// if v, ok := d.GetOk("arg"); ok { -// for _, arg := range v.([]interface{}) { -// argName := arg.(map[string]interface{})["name"].(string) -// argType := arg.(map[string]interface{})["type"].(string) -// argDataType, err := sdk.ToDataType(argType) -// if err != nil { -// return diag.FromErr(err) -// } -// args = append(args, sdk.ExternalFunctionArgumentRequest{ArgName: argName, ArgDataType: argDataType}) -// } -// } -// if len(args) > 0 { -// req.WithArguments(args) -// } -// -// if v, ok := d.GetOk("return_null_allowed"); ok { -// if v.(bool) { -// req.WithReturnNullValues(&sdk.ReturnNullValuesNull) -// } else { -// req.WithReturnNullValues(&sdk.ReturnNullValuesNotNull) -// } -// } -// -// if v, ok := d.GetOk("return_behavior"); ok { -// if v.(string) == "VOLATILE" { -// req.WithReturnResultsBehavior(&sdk.ReturnResultsBehaviorVolatile) -// } else { -// req.WithReturnResultsBehavior(&sdk.ReturnResultsBehaviorImmutable) -// } -// } -// -// if v, ok := d.GetOk("null_input_behavior"); ok { -// switch { -// case v.(string) == "CALLED ON NULL INPUT": -// req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehaviorCalledOnNullInput)) -// case v.(string) == "RETURNS NULL ON NULL INPUT": -// req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehaviorReturnNullInput)) -// default: -// req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehaviorStrict)) -// } -// } -// -// if v, ok := d.GetOk("comment"); ok { -// req.WithComment(sdk.String(v.(string))) -// } -// -// if _, ok := d.GetOk("header"); ok { -// headers := make([]sdk.ExternalFunctionHeaderRequest, 0) -// for _, header := range d.Get("header").(*schema.Set).List() { -// m := header.(map[string]interface{}) -// headerName := m["name"].(string) -// headerValue := m["value"].(string) -// headers = append(headers, sdk.ExternalFunctionHeaderRequest{ -// Name: headerName, -// Value: headerValue, -// }) -// } -// req.WithHeaders(headers) -// } -// -// if v, ok := d.GetOk("context_headers"); ok { -// contextHeadersList := expandStringList(v.([]interface{})) -// contextHeaders := make([]sdk.ExternalFunctionContextHeaderRequest, 0) -// for _, header := range contextHeadersList { -// contextHeaders = append(contextHeaders, sdk.ExternalFunctionContextHeaderRequest{ -// ContextFunction: header, -// }) -// } -// req.WithContextHeaders(contextHeaders) -// } -// -// if v, ok := d.GetOk("max_batch_rows"); ok { -// req.WithMaxBatchRows(sdk.Int(v.(int))) -// } -// -// if v, ok := d.GetOk("compression"); ok { -// req.WithCompression(sdk.String(v.(string))) -// } -// -// if v, ok := d.GetOk("request_translator"); ok { -// req.WithRequestTranslator(sdk.Pointer(sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(v.(string)))) -// } -// -// if v, ok := d.GetOk("response_translator"); ok { -// req.WithResponseTranslator(sdk.Pointer(sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(v.(string)))) -// } -// -// if err := client.ExternalFunctions.Create(ctx, req); err != nil { -// return diag.FromErr(err) -// } -// argTypes := make([]sdk.DataType, 0, len(args)) -// for _, item := range args { -// argTypes = append(argTypes, item.ArgDataType) -// } -// sid := sdk.NewSchemaObjectIdentifierWithArguments(database, schemaName, name, argTypes) -// d.SetId(sid.FullyQualifiedName()) -// return ReadContextExternalFunction(ctx, d, meta) -//} -// -//func ReadContextExternalFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// client := meta.(*provider.Context).Client -// -// id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) -// externalFunction, err := client.ExternalFunctions.ShowByID(ctx, id) -// if err != nil { -// d.SetId("") -// return nil -// } -// -// // Some properties can come from the SHOW EXTERNAL FUNCTION call -// if err := d.Set("name", externalFunction.Name); err != nil { -// return diag.FromErr(err) -// } -// -// if err := d.Set("schema", strings.Trim(externalFunction.SchemaName, "\"")); err != nil { -// return diag.FromErr(err) -// } -// -// if err := d.Set("database", strings.Trim(externalFunction.CatalogName, "\"")); err != nil { -// return diag.FromErr(err) -// } -// -// if err := d.Set("comment", externalFunction.Description); err != nil { -// return diag.FromErr(err) -// } -// -// if err := d.Set("created_on", externalFunction.CreatedOn); err != nil { -// return diag.FromErr(err) -// } -// -// // Some properties come from the DESCRIBE FUNCTION call -// externalFunctionPropertyRows, err := client.ExternalFunctions.Describe(ctx, sdk.NewDescribeExternalFunctionRequest(id.WithoutArguments(), id.Arguments())) -// if err != nil { -// d.SetId("") -// return nil -// } -// -// for _, row := range externalFunctionPropertyRows { -// switch row.Property { -// case "signature": -// // Format in Snowflake DB is: (argName argType, argName argType, ...) -// args := strings.ReplaceAll(strings.ReplaceAll(row.Value, "(", ""), ")", "") -// -// if args != "" { // Do nothing for functions without arguments -// argPairs := strings.Split(args, ", ") -// args := []interface{}{} -// -// for _, argPair := range argPairs { -// argItem := strings.Split(argPair, " ") -// -// arg := map[string]interface{}{} -// arg["name"] = argItem[0] -// arg["type"] = argItem[1] -// args = append(args, arg) -// } -// -// if err := d.Set("arg", args); err != nil { -// return diag.Errorf("error setting arg: %v", err) -// } -// } -// case "returns": -// returnType := row.Value -// // We first check for VARIANT or OBJECT -// if returnType == "VARIANT" || returnType == "OBJECT" { -// if err := d.Set("return_type", returnType); err != nil { -// return diag.Errorf("error setting return_type: %v", err) -// } -// break -// } -// -// // otherwise, format in Snowflake DB is returnType() -// re := regexp.MustCompile(`^(\w+)\([0-9]*\)$`) -// match := re.FindStringSubmatch(row.Value) -// if len(match) < 2 { -// return diag.Errorf("return_type %s not recognized", returnType) -// } -// if err := d.Set("return_type", match[1]); err != nil { -// return diag.Errorf("error setting return_type: %v", err) -// } -// -// case "null handling": -// if err := d.Set("null_input_behavior", row.Value); err != nil { -// return diag.Errorf("error setting null_input_behavior: %v", err) -// } -// case "volatility": -// if err := d.Set("return_behavior", row.Value); err != nil { -// return diag.Errorf("error setting return_behavior: %v", err) -// } -// case "headers": -// if row.Value != "" && row.Value != "null" { -// // Format in Snowflake DB is: {"head1":"val1","head2":"val2"} -// var jsonHeaders map[string]string -// err := json.Unmarshal([]byte(row.Value), &jsonHeaders) -// if err != nil { -// return diag.Errorf("error unmarshalling headers: %v", err) -// } -// -// headers := make([]any, 0, len(jsonHeaders)) -// for key, value := range jsonHeaders { -// headers = append(headers, map[string]any{ -// "name": key, -// "value": value, -// }) -// } -// -// if err := d.Set("header", headers); err != nil { -// return diag.Errorf("error setting return_behavior: %v", err) -// } -// } -// case "context_headers": -// if row.Value != "" && row.Value != "null" { -// // Format in Snowflake DB is: ["CONTEXT_FUNCTION_1","CONTEXT_FUNCTION_2"] -// contextHeaders := strings.Split(strings.Trim(row.Value, "[]"), ",") -// for i, v := range contextHeaders { -// contextHeaders[i] = strings.Trim(v, "\"") -// } -// if err := d.Set("context_headers", contextHeaders); err != nil { -// return diag.Errorf("error setting context_headers: %v", err) -// } -// } -// case "max_batch_rows": -// if row.Value != "not set" { -// maxBatchRows, err := strconv.ParseInt(row.Value, 10, 64) -// if err != nil { -// return diag.Errorf("error parsing max_batch_rows: %v", err) -// } -// if err := d.Set("max_batch_rows", maxBatchRows); err != nil { -// return diag.Errorf("error setting max_batch_rows: %v", err) -// } -// } -// case "compression": -// if err := d.Set("compression", row.Value); err != nil { -// return diag.Errorf("error setting compression: %v", err) -// } -// case "body": -// if err := d.Set("url_of_proxy_and_resource", row.Value); err != nil { -// return diag.Errorf("error setting url_of_proxy_and_resource: %v", err) -// } -// case "language": -// // To ignore -// default: -// log.Printf("[WARN] unexpected external function property %v returned from Snowflake", row.Property) -// } -// } -// -// return nil -//} -// -//func UpdateContextExternalFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// client := meta.(*provider.Context).Client -// -// id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) -// req := sdk.NewAlterFunctionRequest(id.WithoutArguments(), id.Arguments()) -// if d.HasChange("comment") { -// _, new := d.GetChange("comment") -// if new == "" { -// req.UnsetComment = sdk.Bool(true) -// } else { -// req.SetComment = sdk.String(new.(string)) -// } -// err := client.Functions.Alter(ctx, req) -// if err != nil { -// return diag.FromErr(err) -// } -// } -// return ReadContextExternalFunction(ctx, d, meta) -//} -// -//func DeleteContextExternalFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// client := meta.(*provider.Context).Client -// -// id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) -// req := sdk.NewDropFunctionRequest(id.WithoutArguments(), id.Arguments()) -// if err := client.Functions.Drop(ctx, req); err != nil { -// return diag.FromErr(err) -// } -// -// d.SetId("") -// return nil -//} +import ( + "context" + "encoding/json" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "log" + "regexp" + "strconv" + "strings" +) + +var externalFunctionSchema = map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Specifies the identifier for the external function. The identifier can contain the schema name and database name, as well as the function name. The function's signature (name and argument data types) must be unique within the schema.", + }, + "schema": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The schema in which to create the external function.", + }, + "database": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The database in which to create the external function.", + }, + "arg": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Description: "Specifies the arguments/inputs for the external function. These should correspond to the arguments that the remote service expects.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + // Suppress the diff shown if the values are equal when both compared in lower case. + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return strings.EqualFold(strings.ToLower(old), strings.ToLower(new)) + }, + Description: "Argument name", + }, + "type": { + Type: schema.TypeString, + Required: true, + // Suppress the diff shown if the values are equal when both compared in lower case. + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return strings.EqualFold(strings.ToLower(old), strings.ToLower(new)) + }, + Description: "Argument type, e.g. VARCHAR", + }, + }, + }, + }, + "null_input_behavior": { + Type: schema.TypeString, + Optional: true, + Default: "CALLED ON NULL INPUT", + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"CALLED ON NULL INPUT", "RETURNS NULL ON NULL INPUT", "STRICT"}, false), + Description: "Specifies the behavior of the external function when called with null inputs.", + }, + "return_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + // Suppress the diff shown if the values are equal when both compared in lower case. + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return strings.EqualFold(strings.ToLower(old), strings.ToLower(new)) + }, + Description: "Specifies the data type returned by the external function.", + }, + "return_null_allowed": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + Description: "Indicates whether the function can return NULL values (true) or must return only NON-NULL values (false).", + Default: true, + }, + "return_behavior": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"VOLATILE", "IMMUTABLE"}, false), + Description: "Specifies the behavior of the function when returning results", + }, + "api_integration": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The name of the API integration object that should be used to authenticate the call to the proxy service.", + }, + "header": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Description: "Allows users to specify key-value metadata that is sent with every request as HTTP headers.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Header name", + }, + "value": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Header value", + }, + }, + }, + }, + "context_headers": { + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + ForceNew: true, + // Suppress the diff shown if the values are equal when both compared in lower case. + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return strings.EqualFold(strings.ToLower(old), strings.ToLower(new)) + }, + Description: "Binds Snowflake context function results to HTTP headers.", + }, + "max_batch_rows": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + Description: "This specifies the maximum number of rows in each batch sent to the proxy service.", + }, + "compression": { + Type: schema.TypeString, + Optional: true, + Default: "AUTO", + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"NONE", "AUTO", "GZIP", "DEFLATE"}, false), + Description: "If specified, the JSON payload is compressed when sent from Snowflake to the proxy service, and when sent back from the proxy service to Snowflake.", + }, + "request_translator": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "This specifies the name of the request translator function", + }, + "response_translator": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "This specifies the name of the response translator function.", + }, + "url_of_proxy_and_resource": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "This is the invocation URL of the proxy service and resource through which Snowflake calls the remote service.", + }, + "comment": { + Type: schema.TypeString, + Optional: true, + Default: "user-defined function", + Description: "A description of the external function.", + }, + "created_on": { + Type: schema.TypeString, + Computed: true, + Description: "Date and time when the external function was created.", + }, +} + +// ExternalFunction returns a pointer to the resource representing an external function. +func ExternalFunction() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + CreateContext: CreateContextExternalFunction, + ReadContext: ReadContextExternalFunction, + UpdateContext: UpdateContextExternalFunction, + DeleteContext: DeleteContextExternalFunction, + + Schema: externalFunctionSchema, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + StateUpgraders: []schema.StateUpgrader{ + { + Version: 0, + // setting type to cty.EmptyObject is a bit hacky here but following https://developer.hashicorp.com/terraform/plugin/framework/migrating/resources/state-upgrade#sdkv2-1 would require lots of repetitive code; this should work with cty.EmptyObject + Type: cty.EmptyObject, + Upgrade: v085ExternalFunctionStateUpgrader, + }, + }, + } +} + +func CreateContextExternalFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + database := d.Get("database").(string) + schemaName := d.Get("schema").(string) + name := d.Get("name").(string) + id := sdk.NewSchemaObjectIdentifier(database, schemaName, name) + + returnType := d.Get("return_type").(string) + resultDataType, err := sdk.ToDataType(returnType) + if err != nil { + return diag.FromErr(err) + } + apiIntegration := sdk.NewAccountObjectIdentifier(d.Get("api_integration").(string)) + urlOfProxyAndResource := d.Get("url_of_proxy_and_resource").(string) + req := sdk.NewCreateExternalFunctionRequest(id, resultDataType, &apiIntegration, urlOfProxyAndResource) + + // Set optionals + args := make([]sdk.ExternalFunctionArgumentRequest, 0) + if v, ok := d.GetOk("arg"); ok { + for _, arg := range v.([]interface{}) { + argName := arg.(map[string]interface{})["name"].(string) + argType := arg.(map[string]interface{})["type"].(string) + argDataType, err := sdk.ToDataType(argType) + if err != nil { + return diag.FromErr(err) + } + args = append(args, sdk.ExternalFunctionArgumentRequest{ArgName: argName, ArgDataType: argDataType}) + } + } + if len(args) > 0 { + req.WithArguments(args) + } + + if v, ok := d.GetOk("return_null_allowed"); ok { + if v.(bool) { + req.WithReturnNullValues(&sdk.ReturnNullValuesNull) + } else { + req.WithReturnNullValues(&sdk.ReturnNullValuesNotNull) + } + } + + if v, ok := d.GetOk("return_behavior"); ok { + if v.(string) == "VOLATILE" { + req.WithReturnResultsBehavior(&sdk.ReturnResultsBehaviorVolatile) + } else { + req.WithReturnResultsBehavior(&sdk.ReturnResultsBehaviorImmutable) + } + } + + if v, ok := d.GetOk("null_input_behavior"); ok { + switch { + case v.(string) == "CALLED ON NULL INPUT": + req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehaviorCalledOnNullInput)) + case v.(string) == "RETURNS NULL ON NULL INPUT": + req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehaviorReturnNullInput)) + default: + req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehaviorStrict)) + } + } + + if v, ok := d.GetOk("comment"); ok { + req.WithComment(sdk.String(v.(string))) + } + + if _, ok := d.GetOk("header"); ok { + headers := make([]sdk.ExternalFunctionHeaderRequest, 0) + for _, header := range d.Get("header").(*schema.Set).List() { + m := header.(map[string]interface{}) + headerName := m["name"].(string) + headerValue := m["value"].(string) + headers = append(headers, sdk.ExternalFunctionHeaderRequest{ + Name: headerName, + Value: headerValue, + }) + } + req.WithHeaders(headers) + } + + if v, ok := d.GetOk("context_headers"); ok { + contextHeadersList := expandStringList(v.([]interface{})) + contextHeaders := make([]sdk.ExternalFunctionContextHeaderRequest, 0) + for _, header := range contextHeadersList { + contextHeaders = append(contextHeaders, sdk.ExternalFunctionContextHeaderRequest{ + ContextFunction: header, + }) + } + req.WithContextHeaders(contextHeaders) + } + + if v, ok := d.GetOk("max_batch_rows"); ok { + req.WithMaxBatchRows(sdk.Int(v.(int))) + } + + if v, ok := d.GetOk("compression"); ok { + req.WithCompression(sdk.String(v.(string))) + } + + if v, ok := d.GetOk("request_translator"); ok { + req.WithRequestTranslator(sdk.Pointer(sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(v.(string)))) + } + + if v, ok := d.GetOk("response_translator"); ok { + req.WithResponseTranslator(sdk.Pointer(sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(v.(string)))) + } + + if err := client.ExternalFunctions.Create(ctx, req); err != nil { + return diag.FromErr(err) + } + argTypes := make([]sdk.DataType, 0, len(args)) + for _, item := range args { + argTypes = append(argTypes, item.ArgDataType) + } + sid := sdk.NewSchemaObjectIdentifierWithArgumentsOld(database, schemaName, name, argTypes) + d.SetId(sid.FullyQualifiedName()) + return ReadContextExternalFunction(ctx, d, meta) +} + +func ReadContextExternalFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + + id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) + externalFunction, err := client.ExternalFunctions.ShowByID(ctx, id) + if err != nil { + d.SetId("") + return nil + } + + // Some properties can come from the SHOW EXTERNAL FUNCTION call + if err := d.Set("name", externalFunction.Name); err != nil { + return diag.FromErr(err) + } + + if err := d.Set("schema", strings.Trim(externalFunction.SchemaName, "\"")); err != nil { + return diag.FromErr(err) + } + + if err := d.Set("database", strings.Trim(externalFunction.CatalogName, "\"")); err != nil { + return diag.FromErr(err) + } + + if err := d.Set("comment", externalFunction.Description); err != nil { + return diag.FromErr(err) + } + + if err := d.Set("created_on", externalFunction.CreatedOn); err != nil { + return diag.FromErr(err) + } + + // Some properties come from the DESCRIBE FUNCTION call + externalFunctionPropertyRows, err := client.ExternalFunctions.Describe(ctx, sdk.NewDescribeExternalFunctionRequest(id.WithoutArguments(), id.Arguments())) + if err != nil { + d.SetId("") + return nil + } + + for _, row := range externalFunctionPropertyRows { + switch row.Property { + case "signature": + // Format in Snowflake DB is: (argName argType, argName argType, ...) + args := strings.ReplaceAll(strings.ReplaceAll(row.Value, "(", ""), ")", "") + + if args != "" { // Do nothing for functions without arguments + argPairs := strings.Split(args, ", ") + args := []interface{}{} + + for _, argPair := range argPairs { + argItem := strings.Split(argPair, " ") + + arg := map[string]interface{}{} + arg["name"] = argItem[0] + arg["type"] = argItem[1] + args = append(args, arg) + } + + if err := d.Set("arg", args); err != nil { + return diag.Errorf("error setting arg: %v", err) + } + } + case "returns": + returnType := row.Value + // We first check for VARIANT or OBJECT + if returnType == "VARIANT" || returnType == "OBJECT" { + if err := d.Set("return_type", returnType); err != nil { + return diag.Errorf("error setting return_type: %v", err) + } + break + } + + // otherwise, format in Snowflake DB is returnType() + re := regexp.MustCompile(`^(\w+)\([0-9]*\)$`) + match := re.FindStringSubmatch(row.Value) + if len(match) < 2 { + return diag.Errorf("return_type %s not recognized", returnType) + } + if err := d.Set("return_type", match[1]); err != nil { + return diag.Errorf("error setting return_type: %v", err) + } + + case "null handling": + if err := d.Set("null_input_behavior", row.Value); err != nil { + return diag.Errorf("error setting null_input_behavior: %v", err) + } + case "volatility": + if err := d.Set("return_behavior", row.Value); err != nil { + return diag.Errorf("error setting return_behavior: %v", err) + } + case "headers": + if row.Value != "" && row.Value != "null" { + // Format in Snowflake DB is: {"head1":"val1","head2":"val2"} + var jsonHeaders map[string]string + err := json.Unmarshal([]byte(row.Value), &jsonHeaders) + if err != nil { + return diag.Errorf("error unmarshalling headers: %v", err) + } + + headers := make([]any, 0, len(jsonHeaders)) + for key, value := range jsonHeaders { + headers = append(headers, map[string]any{ + "name": key, + "value": value, + }) + } + + if err := d.Set("header", headers); err != nil { + return diag.Errorf("error setting return_behavior: %v", err) + } + } + case "context_headers": + if row.Value != "" && row.Value != "null" { + // Format in Snowflake DB is: ["CONTEXT_FUNCTION_1","CONTEXT_FUNCTION_2"] + contextHeaders := strings.Split(strings.Trim(row.Value, "[]"), ",") + for i, v := range contextHeaders { + contextHeaders[i] = strings.Trim(v, "\"") + } + if err := d.Set("context_headers", contextHeaders); err != nil { + return diag.Errorf("error setting context_headers: %v", err) + } + } + case "max_batch_rows": + if row.Value != "not set" { + maxBatchRows, err := strconv.ParseInt(row.Value, 10, 64) + if err != nil { + return diag.Errorf("error parsing max_batch_rows: %v", err) + } + if err := d.Set("max_batch_rows", maxBatchRows); err != nil { + return diag.Errorf("error setting max_batch_rows: %v", err) + } + } + case "compression": + if err := d.Set("compression", row.Value); err != nil { + return diag.Errorf("error setting compression: %v", err) + } + case "body": + if err := d.Set("url_of_proxy_and_resource", row.Value); err != nil { + return diag.Errorf("error setting url_of_proxy_and_resource: %v", err) + } + case "language": + // To ignore + default: + log.Printf("[WARN] unexpected external function property %v returned from Snowflake", row.Property) + } + } + + return nil +} + +func UpdateContextExternalFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + + id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) + req := sdk.NewAlterFunctionRequest(sdk.NewSchemaObjectIdentifierWithArguments(id.DatabaseName(), id.SchemaName(), id.Name(), id.Arguments()...)) + if d.HasChange("comment") { + _, new := d.GetChange("comment") + if new == "" { + req.UnsetComment = sdk.Bool(true) + } else { + req.SetComment = sdk.String(new.(string)) + } + err := client.Functions.Alter(ctx, req) + if err != nil { + return diag.FromErr(err) + } + } + return ReadContextExternalFunction(ctx, d, meta) +} + +func DeleteContextExternalFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + + id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) + req := sdk.NewDropFunctionRequest(sdk.NewSchemaObjectIdentifierWithArguments(id.DatabaseName(), id.SchemaName(), id.Name(), id.Arguments()...)) + if err := client.Functions.Drop(ctx, req); err != nil { + return diag.FromErr(err) + } + + d.SetId("") + return nil +} diff --git a/pkg/resources/external_function_state_upgraders.go b/pkg/resources/external_function_state_upgraders.go index d27a732700..ebfd6fe2a4 100644 --- a/pkg/resources/external_function_state_upgraders.go +++ b/pkg/resources/external_function_state_upgraders.go @@ -1,69 +1,76 @@ package resources -//type v085ExternalFunctionId struct { -// DatabaseName string -// SchemaName string -// ExternalFunctionName string -// ExternalFunctionArgTypes string -//} -// -//func parseV085ExternalFunctionId(stringID string) (*v085ExternalFunctionId, error) { -// reader := csv.NewReader(strings.NewReader(stringID)) -// reader.Comma = '|' -// lines, err := reader.ReadAll() -// if err != nil { -// return nil, sdk.NewError("not CSV compatible") -// } -// -// if len(lines) != 1 { -// return nil, sdk.NewError("1 line at a time") -// } -// if len(lines[0]) != 4 { -// return nil, sdk.NewError("4 fields allowed") -// } -// -// return &v085ExternalFunctionId{ -// DatabaseName: lines[0][0], -// SchemaName: lines[0][1], -// ExternalFunctionName: lines[0][2], -// ExternalFunctionArgTypes: lines[0][3], -// }, nil -//} -// -//func v085ExternalFunctionStateUpgrader(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { -// if rawState == nil { -// return rawState, nil -// } -// -// oldId := rawState["id"].(string) -// parsedV085ExternalFunctionId, err := parseV085ExternalFunctionId(oldId) -// if err != nil { -// return nil, err -// } -// -// argDataTypes := make([]sdk.DataType, 0) -// if parsedV085ExternalFunctionId.ExternalFunctionArgTypes != "" { -// for _, argType := range strings.Split(parsedV085ExternalFunctionId.ExternalFunctionArgTypes, "-") { -// argDataType, err := sdk.ToDataType(argType) -// if err != nil { -// return nil, err -// } -// argDataTypes = append(argDataTypes, argDataType) -// } -// } -// -// schemaObjectIdentifierWithArguments := sdk.NewSchemaObjectIdentifierWithArguments(parsedV085ExternalFunctionId.DatabaseName, parsedV085ExternalFunctionId.SchemaName, parsedV085ExternalFunctionId.ExternalFunctionName, argDataTypes) -// rawState["id"] = schemaObjectIdentifierWithArguments.FullyQualifiedName() -// -// oldDatabase := rawState["database"].(string) -// oldSchema := rawState["schema"].(string) -// -// rawState["database"] = strings.Trim(oldDatabase, "\"") -// rawState["schema"] = strings.Trim(oldSchema, "\"") -// -// if old, isPresent := rawState["return_null_allowed"]; !isPresent || old == nil { -// rawState["return_null_allowed"] = "true" -// } -// -// return rawState, nil -//} +import ( + "context" + "encoding/csv" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "strings" +) + +type v085ExternalFunctionId struct { + DatabaseName string + SchemaName string + ExternalFunctionName string + ExternalFunctionArgTypes string +} + +func parseV085ExternalFunctionId(stringID string) (*v085ExternalFunctionId, error) { + reader := csv.NewReader(strings.NewReader(stringID)) + reader.Comma = '|' + lines, err := reader.ReadAll() + if err != nil { + return nil, sdk.NewError("not CSV compatible") + } + + if len(lines) != 1 { + return nil, sdk.NewError("1 line at a time") + } + if len(lines[0]) != 4 { + return nil, sdk.NewError("4 fields allowed") + } + + return &v085ExternalFunctionId{ + DatabaseName: lines[0][0], + SchemaName: lines[0][1], + ExternalFunctionName: lines[0][2], + ExternalFunctionArgTypes: lines[0][3], + }, nil +} + +func v085ExternalFunctionStateUpgrader(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { + if rawState == nil { + return rawState, nil + } + + oldId := rawState["id"].(string) + parsedV085ExternalFunctionId, err := parseV085ExternalFunctionId(oldId) + if err != nil { + return nil, err + } + + argDataTypes := make([]sdk.DataType, 0) + if parsedV085ExternalFunctionId.ExternalFunctionArgTypes != "" { + for _, argType := range strings.Split(parsedV085ExternalFunctionId.ExternalFunctionArgTypes, "-") { + argDataType, err := sdk.ToDataType(argType) + if err != nil { + return nil, err + } + argDataTypes = append(argDataTypes, argDataType) + } + } + + schemaObjectIdentifierWithArguments := sdk.NewSchemaObjectIdentifierWithArgumentsOld(parsedV085ExternalFunctionId.DatabaseName, parsedV085ExternalFunctionId.SchemaName, parsedV085ExternalFunctionId.ExternalFunctionName, argDataTypes) + rawState["id"] = schemaObjectIdentifierWithArguments.FullyQualifiedName() + + oldDatabase := rawState["database"].(string) + oldSchema := rawState["schema"].(string) + + rawState["database"] = strings.Trim(oldDatabase, "\"") + rawState["schema"] = strings.Trim(oldSchema, "\"") + + if old, isPresent := rawState["return_null_allowed"]; !isPresent || old == nil { + rawState["return_null_allowed"] = "true" + } + + return rawState, nil +} diff --git a/pkg/resources/function.go b/pkg/resources/function.go index 747429a270..4b8d187f47 100644 --- a/pkg/resources/function.go +++ b/pkg/resources/function.go @@ -48,6 +48,7 @@ var functionSchema = map[string]*schema.Schema{ }, Description: "The argument name", }, + // TODO(SNOW-1596962): Fully support VECTOR data type sdk.ParseFunctionArgumentsFromString could be a base for another function that takes argument names into consideration. "type": { Type: schema.TypeString, Required: true, diff --git a/pkg/resources/function_acceptance_test.go b/pkg/resources/function_acceptance_test.go index 2a41bff811..a05bdd1ff6 100644 --- a/pkg/resources/function_acceptance_test.go +++ b/pkg/resources/function_acceptance_test.go @@ -2,7 +2,6 @@ package resources_test import ( "fmt" - "regexp" "strings" "testing" @@ -237,7 +236,7 @@ func TestAcc_Function_migrateFromVersion085(t *testing.T) { }) } -func TestAcc_Function_Version0941_ResourceIdMigration(t *testing.T) { +func TestAcc_Function_Version0941_EnsureSmoothResourceIdMigration(t *testing.T) { name := acc.TestClient().Ids.RandomAccountObjectIdentifier().Name() resourceName := "snowflake_function.f" @@ -255,17 +254,16 @@ func TestAcc_Function_Version0941_ResourceIdMigration(t *testing.T) { Source: "Snowflake-Labs/snowflake", }, }, - Config: functionConfigWithVector(acc.TestDatabaseName, acc.TestSchemaName, name, ""), + Config: functionConfigWithMoreArguments(acc.TestDatabaseName, acc.TestSchemaName, name), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf(`"%s"."%s"."%s"(VARCHAR, VECTOR(INT, 20), FLOAT, NUMBER, VECTOR(FLOAT, 10))`, acc.TestDatabaseName, acc.TestSchemaName, name)), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf(`"%s"."%s"."%s"(VARCHAR, FLOAT, NUMBER)`, acc.TestDatabaseName, acc.TestSchemaName, name)), ), - ExpectError: regexp.MustCompile("Error: invalid data type: VECTOR\\(INT, 20\\)"), }, { ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, - Config: functionConfigWithVector(acc.TestDatabaseName, acc.TestSchemaName, name, ""), + Config: functionConfigWithMoreArguments(acc.TestDatabaseName, acc.TestSchemaName, name), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf(`"%s"."%s"."%s"(VARCHAR, VECTOR(INT, 20), FLOAT, NUMBER, VECTOR(FLOAT, 10))`, acc.TestDatabaseName, acc.TestSchemaName, name)), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf(`"%s"."%s"."%s"(VARCHAR, FLOAT, NUMBER)`, acc.TestDatabaseName, acc.TestSchemaName, name)), ), }, }, @@ -313,39 +311,30 @@ func TestAcc_Function_Rename(t *testing.T) { }) } -func functionConfigWithVector(database string, schema string, name string, comment string) string { +func functionConfigWithMoreArguments(database string, schema string, name string) string { return fmt.Sprintf(` resource "snowflake_function" "f" { database = "%[1]s" schema = "%[2]s" name = "%[3]s" - comment = "%[4]s" return_type = "VARCHAR" return_behavior = "IMMUTABLE" statement = "SELECT A" arguments { name = "A" - type = "VARCHAR(200)" + type = "VARCHAR" } arguments { name = "B" - type = "VECTOR(INT, 20)" - } - arguments { - name = "C" type = "FLOAT" } arguments { - name = "D" - type = "NUMBER(10, 2)" - } - arguments { - name = "E" - type = "VECTOR(FLOAT, 10)" + name = "C" + type = "NUMBER" } } -`, database, schema, name, comment) +`, database, schema, name) } func functionConfig(database string, schema string, name string, comment string) string { diff --git a/pkg/resources/procedure.go b/pkg/resources/procedure.go index 506b210021..fc6e918da8 100644 --- a/pkg/resources/procedure.go +++ b/pkg/resources/procedure.go @@ -1,779 +1,794 @@ package resources -//var procedureSchema = map[string]*schema.Schema{ -// "name": { -// Type: schema.TypeString, -// Required: true, -// Description: "Specifies the identifier for the procedure; does not have to be unique for the schema in which the procedure is created. Don't use the | character.", -// }, -// "database": { -// Type: schema.TypeString, -// Required: true, -// Description: "The database in which to create the procedure. Don't use the | character.", -// ForceNew: true, -// }, -// "schema": { -// Type: schema.TypeString, -// Required: true, -// Description: "The schema in which to create the procedure. Don't use the | character.", -// ForceNew: true, -// }, -// "secure": { -// Type: schema.TypeBool, -// Optional: true, -// Description: "Specifies that the procedure is secure. For more information about secure procedures, see Protecting Sensitive Information with Secure UDFs and Stored Procedures.", -// Default: false, -// }, -// "arguments": { -// Type: schema.TypeList, -// Elem: &schema.Resource{ -// Schema: map[string]*schema.Schema{ -// "name": { -// Type: schema.TypeString, -// Required: true, -// // Suppress the diff shown if the values are equal when both compared in lower case. -// DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { -// return strings.EqualFold(old, new) -// }, -// Description: "The argument name", -// }, -// "type": { -// Type: schema.TypeString, -// Required: true, -// ValidateFunc: dataTypeValidateFunc, -// DiffSuppressFunc: dataTypeDiffSuppressFunc, -// Description: "The argument type", -// }, -// }, -// }, -// Optional: true, -// Description: "List of the arguments for the procedure", -// ForceNew: true, -// }, -// "return_type": { -// Type: schema.TypeString, -// Description: "The return type of the procedure", -// // Suppress the diff shown if the values are equal when both compared in lower case. -// DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { -// if strings.EqualFold(old, new) { -// return true -// } -// -// varcharType := []string{"VARCHAR(16777216)", "VARCHAR", "text", "string", "NVARCHAR", "NVARCHAR2", "CHAR VARYING", "NCHAR VARYING"} -// if slices.Contains(varcharType, strings.ToUpper(old)) && slices.Contains(varcharType, strings.ToUpper(new)) { -// return true -// } -// -// // all these types are equivalent https://docs.snowflake.com/en/sql-reference/data-types-numeric.html#int-integer-bigint-smallint-tinyint-byteint -// integerTypes := []string{"INT", "INTEGER", "BIGINT", "SMALLINT", "TINYINT", "BYTEINT", "NUMBER(38,0)"} -// if slices.Contains(integerTypes, strings.ToUpper(old)) && slices.Contains(integerTypes, strings.ToUpper(new)) { -// return true -// } -// return false -// }, -// Required: true, -// ForceNew: true, -// }, -// "statement": { -// Type: schema.TypeString, -// Required: true, -// Description: "Specifies the code used to create the procedure.", -// ForceNew: true, -// DiffSuppressFunc: DiffSuppressStatement, -// }, -// "language": { -// Type: schema.TypeString, -// Optional: true, -// Default: "SQL", -// DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { -// return strings.EqualFold(old, new) -// }, -// ValidateFunc: validation.StringInSlice([]string{"javascript", "java", "scala", "SQL", "python"}, true), -// Description: "Specifies the language of the stored procedure code.", -// }, -// "execute_as": { -// Type: schema.TypeString, -// Optional: true, -// Default: "OWNER", -// DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { -// return strings.EqualFold(old, new) -// }, -// ValidateFunc: validation.StringInSlice([]string{"CALLER", "OWNER"}, true), -// Description: "Sets execution context. Allowed values are CALLER and OWNER (consult a proper section in the [docs](https://docs.snowflake.com/en/sql-reference/sql/create-procedure#id1)). For more information see [caller's rights and owner's rights](https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-rights).", -// }, -// "null_input_behavior": { -// Type: schema.TypeString, -// Optional: true, -// Default: "CALLED ON NULL INPUT", -// ForceNew: true, -// // We do not use STRICT, because Snowflake then in the Read phase returns RETURNS NULL ON NULL INPUT -// ValidateFunc: validation.StringInSlice([]string{"CALLED ON NULL INPUT", "RETURNS NULL ON NULL INPUT"}, false), -// Description: "Specifies the behavior of the procedure when called with null inputs.", -// }, -// "return_behavior": { -// Type: schema.TypeString, -// Optional: true, -// Default: "VOLATILE", -// ForceNew: true, -// ValidateFunc: validation.StringInSlice([]string{"VOLATILE", "IMMUTABLE"}, false), -// Description: "Specifies the behavior of the function when returning results", -// Deprecated: "These keywords are deprecated for stored procedures. These keywords are not intended to apply to stored procedures. In a future release, these keywords will be removed from the documentation.", -// }, -// "comment": { -// Type: schema.TypeString, -// Optional: true, -// Default: "user-defined procedure", -// Description: "Specifies a comment for the procedure.", -// }, -// "runtime_version": { -// Type: schema.TypeString, -// Optional: true, -// ForceNew: true, -// Description: "Required for Python procedures. Specifies Python runtime version.", -// }, -// "packages": { -// Type: schema.TypeList, -// Elem: &schema.Schema{ -// Type: schema.TypeString, -// }, -// Optional: true, -// ForceNew: true, -// Description: "List of package imports to use for Java / Python procedures. For Java, package imports should be of the form: package_name:version_number, where package_name is snowflake_domain:package. For Python use it should be: ('numpy','pandas','xgboost==1.5.0').", -// }, -// "imports": { -// Type: schema.TypeList, -// Elem: &schema.Schema{ -// Type: schema.TypeString, -// }, -// Optional: true, -// ForceNew: true, -// Description: "Imports for Java / Python procedures. For Java this a list of jar files, for Python this is a list of Python files.", -// }, -// "handler": { -// Type: schema.TypeString, -// Optional: true, -// ForceNew: true, -// Description: "The handler method for Java / Python procedures.", -// }, -//} -// -//// Procedure returns a pointer to the resource representing a stored procedure. -//func Procedure() *schema.Resource { -// return &schema.Resource{ -// SchemaVersion: 1, -// -// CreateContext: CreateContextProcedure, -// ReadContext: ReadContextProcedure, -// UpdateContext: UpdateContextProcedure, -// DeleteContext: DeleteContextProcedure, -// -// Schema: procedureSchema, -// Importer: &schema.ResourceImporter{ -// StateContext: schema.ImportStatePassthroughContext, -// }, -// -// StateUpgraders: []schema.StateUpgrader{ -// { -// Version: 0, -// // setting type to cty.EmptyObject is a bit hacky here but following https://developer.hashicorp.com/terraform/plugin/framework/migrating/resources/state-upgrade#sdkv2-1 would require lots of repetitive code; this should work with cty.EmptyObject -// Type: cty.EmptyObject, -// Upgrade: v085ProcedureStateUpgrader, -// }, -// }, -// } -//} -// -//func CreateContextProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// lang := strings.ToUpper(d.Get("language").(string)) -// switch lang { -// case "JAVA": -// return createJavaProcedure(ctx, d, meta) -// case "JAVASCRIPT": -// return createJavaScriptProcedure(ctx, d, meta) -// case "PYTHON": -// return createPythonProcedure(ctx, d, meta) -// case "SCALA": -// return createScalaProcedure(ctx, d, meta) -// case "SQL": -// return createSQLProcedure(ctx, d, meta) -// default: -// return diag.Diagnostics{ -// diag.Diagnostic{ -// Severity: diag.Error, -// Summary: "Invalid language", -// Detail: fmt.Sprintf("Language %s is not supported", lang), -// }, -// } -// } -//} -// -//func createJavaProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// client := meta.(*provider.Context).Client -// name := d.Get("name").(string) -// schema := d.Get("schema").(string) -// database := d.Get("database").(string) -// id := sdk.NewSchemaObjectIdentifier(database, schema, name) -// -// returns, diags := parseProcedureReturnsRequest(d.Get("return_type").(string)) -// if diags != nil { -// return diags -// } -// procedureDefinition := d.Get("statement").(string) -// runtimeVersion := d.Get("runtime_version").(string) -// packages := []sdk.ProcedurePackageRequest{} -// for _, item := range d.Get("packages").([]interface{}) { -// packages = append(packages, *sdk.NewProcedurePackageRequest(item.(string))) -// } -// handler := d.Get("handler").(string) -// req := sdk.NewCreateForJavaProcedureRequest(id, *returns, runtimeVersion, packages, handler) -// req.WithProcedureDefinition(sdk.String(procedureDefinition)) -// args, diags := getProcedureArguments(d) -// if diags != nil { -// return diags -// } -// if len(args) > 0 { -// req.WithArguments(args) -// } -// -// // read optional params -// if v, ok := d.GetOk("execute_as"); ok { -// if strings.ToUpper(v.(string)) == "OWNER" { -// req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsOwner)) -// } else if strings.ToUpper(v.(string)) == "CALLER" { -// req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsCaller)) -// } -// } -// if v, ok := d.GetOk("comment"); ok { -// req.WithComment(sdk.String(v.(string))) -// } -// if v, ok := d.GetOk("secure"); ok { -// req.WithSecure(sdk.Bool(v.(bool))) -// } -// if _, ok := d.GetOk("imports"); ok { -// imports := []sdk.ProcedureImportRequest{} -// for _, item := range d.Get("imports").([]interface{}) { -// imports = append(imports, *sdk.NewProcedureImportRequest(item.(string))) -// } -// req.WithImports(imports) -// } -// -// if err := client.Procedures.CreateForJava(ctx, req); err != nil { -// return diag.FromErr(err) -// } -// argTypes := make([]sdk.DataType, 0, len(args)) -// for _, item := range args { -// argTypes = append(argTypes, item.ArgDataType) -// } -// sid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argTypes) -// d.SetId(sid.FullyQualifiedName()) -// return ReadContextProcedure(ctx, d, meta) -//} -// -//func createJavaScriptProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// client := meta.(*provider.Context).Client -// name := d.Get("name").(string) -// schema := d.Get("schema").(string) -// database := d.Get("database").(string) -// id := sdk.NewSchemaObjectIdentifier(database, schema, name) -// -// returnType := d.Get("return_type").(string) -// returnDataType, diags := convertProcedureDataType(returnType) -// if diags != nil { -// return diags -// } -// procedureDefinition := d.Get("statement").(string) -// req := sdk.NewCreateForJavaScriptProcedureRequest(id, returnDataType, procedureDefinition) -// args, diags := getProcedureArguments(d) -// if diags != nil { -// return diags -// } -// if len(args) > 0 { -// req.WithArguments(args) -// } -// -// // read optional params -// if v, ok := d.GetOk("execute_as"); ok { -// if strings.ToUpper(v.(string)) == "OWNER" { -// req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsOwner)) -// } else if strings.ToUpper(v.(string)) == "CALLER" { -// req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsCaller)) -// } -// } -// if v, ok := d.GetOk("null_input_behavior"); ok { -// req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehavior(v.(string)))) -// } -// if v, ok := d.GetOk("comment"); ok { -// req.WithComment(sdk.String(v.(string))) -// } -// if v, ok := d.GetOk("secure"); ok { -// req.WithSecure(sdk.Bool(v.(bool))) -// } -// -// if err := client.Procedures.CreateForJavaScript(ctx, req); err != nil { -// return diag.FromErr(err) -// } -// argTypes := make([]sdk.DataType, 0, len(args)) -// for _, item := range args { -// argTypes = append(argTypes, item.ArgDataType) -// } -// sid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argTypes) -// d.SetId(sid.FullyQualifiedName()) -// return ReadContextProcedure(ctx, d, meta) -//} -// -//func createScalaProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// client := meta.(*provider.Context).Client -// name := d.Get("name").(string) -// schema := d.Get("schema").(string) -// database := d.Get("database").(string) -// id := sdk.NewSchemaObjectIdentifier(database, schema, name) -// -// returns, diags := parseProcedureReturnsRequest(d.Get("return_type").(string)) -// if diags != nil { -// return diags -// } -// procedureDefinition := d.Get("statement").(string) -// runtimeVersion := d.Get("runtime_version").(string) -// packages := []sdk.ProcedurePackageRequest{} -// for _, item := range d.Get("packages").([]interface{}) { -// packages = append(packages, *sdk.NewProcedurePackageRequest(item.(string))) -// } -// handler := d.Get("handler").(string) -// req := sdk.NewCreateForScalaProcedureRequest(id, *returns, runtimeVersion, packages, handler) -// req.WithProcedureDefinition(sdk.String(procedureDefinition)) -// args, diags := getProcedureArguments(d) -// if diags != nil { -// return diags -// } -// if len(args) > 0 { -// req.WithArguments(args) -// } -// -// // read optional params -// if v, ok := d.GetOk("execute_as"); ok { -// if strings.ToUpper(v.(string)) == "OWNER" { -// req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsOwner)) -// } else if strings.ToUpper(v.(string)) == "CALLER" { -// req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsCaller)) -// } -// } -// if v, ok := d.GetOk("comment"); ok { -// req.WithComment(sdk.String(v.(string))) -// } -// if v, ok := d.GetOk("secure"); ok { -// req.WithSecure(sdk.Bool(v.(bool))) -// } -// if _, ok := d.GetOk("imports"); ok { -// imports := []sdk.ProcedureImportRequest{} -// for _, item := range d.Get("imports").([]interface{}) { -// imports = append(imports, *sdk.NewProcedureImportRequest(item.(string))) -// } -// req.WithImports(imports) -// } -// -// if err := client.Procedures.CreateForScala(ctx, req); err != nil { -// return diag.FromErr(err) -// } -// argTypes := make([]sdk.DataType, 0, len(args)) -// for _, item := range args { -// argTypes = append(argTypes, item.ArgDataType) -// } -// sid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argTypes) -// d.SetId(sid.FullyQualifiedName()) -// return ReadContextProcedure(ctx, d, meta) -//} -// -//func createSQLProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// client := meta.(*provider.Context).Client -// name := d.Get("name").(string) -// schema := d.Get("schema").(string) -// database := d.Get("database").(string) -// id := sdk.NewSchemaObjectIdentifier(database, schema, name) -// -// returns, diags := parseProcedureSQLReturnsRequest(d.Get("return_type").(string)) -// if diags != nil { -// return diags -// } -// procedureDefinition := d.Get("statement").(string) -// req := sdk.NewCreateForSQLProcedureRequest(id, *returns, procedureDefinition) -// args, diags := getProcedureArguments(d) -// if diags != nil { -// return diags -// } -// if len(args) > 0 { -// req.WithArguments(args) -// } -// -// // read optional params -// if v, ok := d.GetOk("execute_as"); ok { -// if strings.ToUpper(v.(string)) == "OWNER" { -// req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsOwner)) -// } else if strings.ToUpper(v.(string)) == "CALLER" { -// req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsCaller)) -// } -// } -// if v, ok := d.GetOk("null_input_behavior"); ok { -// req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehavior(v.(string)))) -// } -// if v, ok := d.GetOk("comment"); ok { -// req.WithComment(sdk.String(v.(string))) -// } -// if v, ok := d.GetOk("secure"); ok { -// req.WithSecure(sdk.Bool(v.(bool))) -// } -// -// if err := client.Procedures.CreateForSQL(ctx, req); err != nil { -// return diag.FromErr(err) -// } -// argTypes := make([]sdk.DataType, 0, len(args)) -// for _, item := range args { -// argTypes = append(argTypes, item.ArgDataType) -// } -// sid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argTypes) -// d.SetId(sid.FullyQualifiedName()) -// return ReadContextProcedure(ctx, d, meta) -//} -// -//func createPythonProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// client := meta.(*provider.Context).Client -// name := d.Get("name").(string) -// schema := d.Get("schema").(string) -// database := d.Get("database").(string) -// id := sdk.NewSchemaObjectIdentifier(database, schema, name) -// -// returns, diags := parseProcedureReturnsRequest(d.Get("return_type").(string)) -// if diags != nil { -// return diags -// } -// procedureDefinition := d.Get("statement").(string) -// runtimeVersion := d.Get("runtime_version").(string) -// packages := []sdk.ProcedurePackageRequest{} -// for _, item := range d.Get("packages").([]interface{}) { -// packages = append(packages, *sdk.NewProcedurePackageRequest(item.(string))) -// } -// handler := d.Get("handler").(string) -// req := sdk.NewCreateForPythonProcedureRequest(id, *returns, runtimeVersion, packages, handler) -// req.WithProcedureDefinition(sdk.String(procedureDefinition)) -// args, diags := getProcedureArguments(d) -// if diags != nil { -// return diags -// } -// if len(args) > 0 { -// req.WithArguments(args) -// } -// -// // read optional params -// if v, ok := d.GetOk("execute_as"); ok { -// if strings.ToUpper(v.(string)) == "OWNER" { -// req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsOwner)) -// } else if strings.ToUpper(v.(string)) == "CALLER" { -// req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsCaller)) -// } -// } -// -// // [ { CALLED ON NULL INPUT | { RETURNS NULL ON NULL INPUT | STRICT } } ] does not work for java, scala or python -// // posted in docs-discuss channel, either docs need to be updated to reflect reality or this feature needs to be added -// // https://snowflake.slack.com/archives/C6380540P/p1707511734666249 -// // if v, ok := d.GetOk("null_input_behavior"); ok { -// // req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehavior(v.(string)))) -// // } -// -// if v, ok := d.GetOk("comment"); ok { -// req.WithComment(sdk.String(v.(string))) -// } -// if v, ok := d.GetOk("secure"); ok { -// req.WithSecure(sdk.Bool(v.(bool))) -// } -// if _, ok := d.GetOk("imports"); ok { -// imports := []sdk.ProcedureImportRequest{} -// for _, item := range d.Get("imports").([]interface{}) { -// imports = append(imports, *sdk.NewProcedureImportRequest(item.(string))) -// } -// req.WithImports(imports) -// } -// -// if err := client.Procedures.CreateForPython(ctx, req); err != nil { -// return diag.FromErr(err) -// } -// argTypes := make([]sdk.DataType, 0, len(args)) -// for _, item := range args { -// argTypes = append(argTypes, item.ArgDataType) -// } -// sid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argTypes) -// d.SetId(sid.FullyQualifiedName()) -// return ReadContextProcedure(ctx, d, meta) -//} -// -//func ReadContextProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// diags := diag.Diagnostics{} -// client := meta.(*provider.Context).Client -// -// id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) -// if err := d.Set("name", id.Name()); err != nil { -// return diag.FromErr(err) -// } -// if err := d.Set("database", id.DatabaseName()); err != nil { -// return diag.FromErr(err) -// } -// if err := d.Set("schema", id.SchemaName()); err != nil { -// return diag.FromErr(err) -// } -// args := d.Get("arguments").([]interface{}) -// argTypes := make([]string, len(args)) -// for i, arg := range args { -// argTypes[i] = arg.(map[string]interface{})["type"].(string) -// } -// procedureDetails, err := client.Procedures.Describe(ctx, sdk.NewDescribeProcedureRequest(id.WithoutArguments(), id.Arguments())) -// if err != nil { -// // if procedure is not found then mark resource to be removed from state file during apply or refresh -// d.SetId("") -// return diag.Diagnostics{ -// diag.Diagnostic{ -// Severity: diag.Warning, -// Summary: "Describe procedure failed.", -// Detail: fmt.Sprintf("Describe procedure failed: %v", err), -// }, -// } -// } -// for _, desc := range procedureDetails { -// switch desc.Property { -// case "signature": -// // Format in Snowflake DB is: (argName argType, argName argType, ...) -// args := strings.ReplaceAll(strings.ReplaceAll(desc.Value, "(", ""), ")", "") -// -// if args != "" { // Do nothing for functions without arguments -// argPairs := strings.Split(args, ", ") -// args := []interface{}{} -// -// for _, argPair := range argPairs { -// argItem := strings.Split(argPair, " ") -// -// arg := map[string]interface{}{} -// arg["name"] = argItem[0] -// arg["type"] = argItem[1] -// args = append(args, arg) -// } -// -// if err := d.Set("arguments", args); err != nil { -// return diag.FromErr(err) -// } -// } -// case "null handling": -// if err := d.Set("null_input_behavior", desc.Value); err != nil { -// return diag.FromErr(err) -// } -// case "body": -// if err := d.Set("statement", desc.Value); err != nil { -// return diag.FromErr(err) -// } -// case "execute as": -// if err := d.Set("execute_as", desc.Value); err != nil { -// return diag.FromErr(err) -// } -// case "returns": -// if err := d.Set("return_type", desc.Value); err != nil { -// return diag.FromErr(err) -// } -// case "language": -// if err := d.Set("language", desc.Value); err != nil { -// return diag.FromErr(err) -// } -// case "runtime_version": -// if err := d.Set("runtime_version", desc.Value); err != nil { -// return diag.FromErr(err) -// } -// case "packages": -// packagesString := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(desc.Value, "[", ""), "]", ""), "'", "") -// if packagesString != "" { // Do nothing for Java / Python functions without packages -// packages := strings.Split(packagesString, ",") -// if err := d.Set("packages", packages); err != nil { -// return diag.FromErr(err) -// } -// } -// case "imports": -// importsString := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(desc.Value, "[", ""), "]", ""), "'", ""), " ", "") -// if importsString != "" { // Do nothing for Java functions without imports -// imports := strings.Split(importsString, ",") -// if err := d.Set("imports", imports); err != nil { -// return diag.FromErr(err) -// } -// } -// case "handler": -// if err := d.Set("handler", desc.Value); err != nil { -// return diag.FromErr(err) -// } -// case "volatility": -// if err := d.Set("return_behavior", desc.Value); err != nil { -// return diag.FromErr(err) -// } -// default: -// log.Printf("[INFO] Unexpected procedure property %v returned from Snowflake with value %v", desc.Property, desc.Value) -// } -// } -// -// request := sdk.NewShowProcedureRequest().WithIn(&sdk.In{Schema: sdk.NewDatabaseObjectIdentifier(id.DatabaseName(), id.SchemaName())}).WithLike(&sdk.Like{Pattern: sdk.String(id.Name())}) -// -// procedures, err := client.Procedures.Show(ctx, request) -// if err != nil { -// return diag.FromErr(err) -// } -// // procedure names can be overloaded with different argument types so we iterate over and find the correct one -// // the ShowByID function should probably be updated to also require the list of arg types, like describe procedure -// for _, procedure := range procedures { -// argumentSignature := strings.Split(procedure.Arguments, " RETURN ")[0] -// argumentSignature = strings.ReplaceAll(argumentSignature, " ", "") -// if argumentSignature == id.ArgumentsSignature() { -// if err := d.Set("secure", procedure.IsSecure); err != nil { -// return diag.FromErr(err) -// } -// if err := d.Set("comment", procedure.Description); err != nil { -// return diag.FromErr(err) -// } -// } -// } -// -// return diags -//} -// -//func UpdateContextProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// client := meta.(*provider.Context).Client -// -// id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) -// if d.HasChange("name") { -// newId := sdk.NewSchemaObjectIdentifierWithArguments(id.DatabaseName(), id.SchemaName(), d.Get("name").(string), id.Arguments()) -// -// err := client.Procedures.Alter(ctx, sdk.NewAlterProcedureRequest(id.WithoutArguments(), id.Arguments()).WithRenameTo(sdk.Pointer(newId.WithoutArguments()))) -// if err != nil { -// return diag.FromErr(err) -// } -// -// d.SetId(newId.FullyQualifiedName()) -// id = newId -// } -// -// if d.HasChange("comment") { -// comment := d.Get("comment") -// if comment != "" { -// if err := client.Procedures.Alter(ctx, sdk.NewAlterProcedureRequest(id.WithoutArguments(), id.Arguments()).WithSetComment(sdk.String(comment.(string)))); err != nil { -// return diag.FromErr(err) -// } -// } else { -// if err := client.Procedures.Alter(ctx, sdk.NewAlterProcedureRequest(id.WithoutArguments(), id.Arguments()).WithUnsetComment(sdk.Bool(true))); err != nil { -// return diag.FromErr(err) -// } -// } -// } -// -// if d.HasChange("execute_as") { -// req := sdk.NewAlterProcedureRequest(id.WithoutArguments(), id.Arguments()) -// executeAs := d.Get("execute_as").(string) -// if strings.ToUpper(executeAs) == "OWNER" { -// req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsOwner)) -// } else if strings.ToUpper(executeAs) == "CALLER" { -// req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsCaller)) -// } -// if err := client.Procedures.Alter(ctx, req); err != nil { -// return diag.FromErr(err) -// } -// } -// -// return ReadContextProcedure(ctx, d, meta) -//} -// -//func DeleteContextProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// client := meta.(*provider.Context).Client -// -// id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) -// if err := client.Procedures.Drop(ctx, sdk.NewDropProcedureRequest(id.WithoutArguments(), id.Arguments())); err != nil { -// return diag.FromErr(err) -// } -// d.SetId("") -// return nil -//} -// -//func getProcedureArguments(d *schema.ResourceData) ([]sdk.ProcedureArgumentRequest, diag.Diagnostics) { -// args := make([]sdk.ProcedureArgumentRequest, 0) -// if v, ok := d.GetOk("arguments"); ok { -// for _, arg := range v.([]interface{}) { -// argName := arg.(map[string]interface{})["name"].(string) -// argType := arg.(map[string]interface{})["type"].(string) -// argDataType, diags := convertProcedureDataType(argType) -// if diags != nil { -// return nil, diags -// } -// args = append(args, sdk.ProcedureArgumentRequest{ArgName: argName, ArgDataType: argDataType}) -// } -// } -// return args, nil -//} -// -//func convertProcedureDataType(s string) (sdk.DataType, diag.Diagnostics) { -// dataType, err := sdk.ToDataType(s) -// if err != nil { -// return dataType, diag.FromErr(err) -// } -// return dataType, nil -//} -// -//func convertProcedureColumns(s string) ([]sdk.ProcedureColumn, diag.Diagnostics) { -// pattern := regexp.MustCompile(`(\w+)\s+(\w+)`) -// matches := pattern.FindAllStringSubmatch(s, -1) -// var columns []sdk.ProcedureColumn -// for _, match := range matches { -// if len(match) == 3 { -// dataType, err := sdk.ToDataType(match[2]) -// if err != nil { -// return nil, diag.FromErr(err) -// } -// columns = append(columns, sdk.ProcedureColumn{ -// ColumnName: match[1], -// ColumnDataType: dataType, -// }) -// } -// } -// return columns, nil -//} -// -//func parseProcedureReturnsRequest(s string) (*sdk.ProcedureReturnsRequest, diag.Diagnostics) { -// returns := sdk.NewProcedureReturnsRequest() -// if strings.HasPrefix(strings.ToLower(s), "table") { -// columns, diags := convertProcedureColumns(s) -// if diags != nil { -// return nil, diags -// } -// var cr []sdk.ProcedureColumnRequest -// for _, item := range columns { -// cr = append(cr, *sdk.NewProcedureColumnRequest(item.ColumnName, item.ColumnDataType)) -// } -// returns.WithTable(sdk.NewProcedureReturnsTableRequest().WithColumns(cr)) -// } else { -// returnDataType, diags := convertProcedureDataType(s) -// if diags != nil { -// return nil, diags -// } -// returns.WithResultDataType(sdk.NewProcedureReturnsResultDataTypeRequest(returnDataType)) -// } -// return returns, nil -//} -// -//func parseProcedureSQLReturnsRequest(s string) (*sdk.ProcedureSQLReturnsRequest, diag.Diagnostics) { -// returns := sdk.NewProcedureSQLReturnsRequest() -// if strings.HasPrefix(strings.ToLower(s), "table") { -// columns, diags := convertProcedureColumns(s) -// if diags != nil { -// return nil, diags -// } -// var cr []sdk.ProcedureColumnRequest -// for _, item := range columns { -// cr = append(cr, *sdk.NewProcedureColumnRequest(item.ColumnName, item.ColumnDataType)) -// } -// returns.WithTable(sdk.NewProcedureReturnsTableRequest().WithColumns(cr)) -// } else { -// returnDataType, diags := convertProcedureDataType(s) -// if diags != nil { -// return nil, diags -// } -// returns.WithResultDataType(sdk.NewProcedureReturnsResultDataTypeRequest(returnDataType)) -// } -// return returns, nil -//} +import ( + "context" + "fmt" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "log" + "regexp" + "slices" + "strings" +) + +var procedureSchema = map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "Specifies the identifier for the procedure; does not have to be unique for the schema in which the procedure is created. Don't use the | character.", + }, + "database": { + Type: schema.TypeString, + Required: true, + Description: "The database in which to create the procedure. Don't use the | character.", + ForceNew: true, + }, + "schema": { + Type: schema.TypeString, + Required: true, + Description: "The schema in which to create the procedure. Don't use the | character.", + ForceNew: true, + }, + "secure": { + Type: schema.TypeBool, + Optional: true, + Description: "Specifies that the procedure is secure. For more information about secure procedures, see Protecting Sensitive Information with Secure UDFs and Stored Procedures.", + Default: false, + }, + "arguments": { + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + // Suppress the diff shown if the values are equal when both compared in lower case. + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return strings.EqualFold(old, new) + }, + Description: "The argument name", + }, + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: dataTypeValidateFunc, + DiffSuppressFunc: dataTypeDiffSuppressFunc, + Description: "The argument type", + }, + }, + }, + Optional: true, + Description: "List of the arguments for the procedure", + ForceNew: true, + }, + "return_type": { + Type: schema.TypeString, + Description: "The return type of the procedure", + // Suppress the diff shown if the values are equal when both compared in lower case. + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if strings.EqualFold(old, new) { + return true + } + + varcharType := []string{"VARCHAR(16777216)", "VARCHAR", "text", "string", "NVARCHAR", "NVARCHAR2", "CHAR VARYING", "NCHAR VARYING"} + if slices.Contains(varcharType, strings.ToUpper(old)) && slices.Contains(varcharType, strings.ToUpper(new)) { + return true + } + + // all these types are equivalent https://docs.snowflake.com/en/sql-reference/data-types-numeric.html#int-integer-bigint-smallint-tinyint-byteint + integerTypes := []string{"INT", "INTEGER", "BIGINT", "SMALLINT", "TINYINT", "BYTEINT", "NUMBER(38,0)"} + if slices.Contains(integerTypes, strings.ToUpper(old)) && slices.Contains(integerTypes, strings.ToUpper(new)) { + return true + } + return false + }, + Required: true, + ForceNew: true, + }, + "statement": { + Type: schema.TypeString, + Required: true, + Description: "Specifies the code used to create the procedure.", + ForceNew: true, + DiffSuppressFunc: DiffSuppressStatement, + }, + "language": { + Type: schema.TypeString, + Optional: true, + Default: "SQL", + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return strings.EqualFold(old, new) + }, + ValidateFunc: validation.StringInSlice([]string{"javascript", "java", "scala", "SQL", "python"}, true), + Description: "Specifies the language of the stored procedure code.", + }, + "execute_as": { + Type: schema.TypeString, + Optional: true, + Default: "OWNER", + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return strings.EqualFold(old, new) + }, + ValidateFunc: validation.StringInSlice([]string{"CALLER", "OWNER"}, true), + Description: "Sets execution context. Allowed values are CALLER and OWNER (consult a proper section in the [docs](https://docs.snowflake.com/en/sql-reference/sql/create-procedure#id1)). For more information see [caller's rights and owner's rights](https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-rights).", + }, + "null_input_behavior": { + Type: schema.TypeString, + Optional: true, + Default: "CALLED ON NULL INPUT", + ForceNew: true, + // We do not use STRICT, because Snowflake then in the Read phase returns RETURNS NULL ON NULL INPUT + ValidateFunc: validation.StringInSlice([]string{"CALLED ON NULL INPUT", "RETURNS NULL ON NULL INPUT"}, false), + Description: "Specifies the behavior of the procedure when called with null inputs.", + }, + "return_behavior": { + Type: schema.TypeString, + Optional: true, + Default: "VOLATILE", + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"VOLATILE", "IMMUTABLE"}, false), + Description: "Specifies the behavior of the function when returning results", + Deprecated: "These keywords are deprecated for stored procedures. These keywords are not intended to apply to stored procedures. In a future release, these keywords will be removed from the documentation.", + }, + "comment": { + Type: schema.TypeString, + Optional: true, + Default: "user-defined procedure", + Description: "Specifies a comment for the procedure.", + }, + "runtime_version": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "Required for Python procedures. Specifies Python runtime version.", + }, + "packages": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + ForceNew: true, + Description: "List of package imports to use for Java / Python procedures. For Java, package imports should be of the form: package_name:version_number, where package_name is snowflake_domain:package. For Python use it should be: ('numpy','pandas','xgboost==1.5.0').", + }, + "imports": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + ForceNew: true, + Description: "Imports for Java / Python procedures. For Java this a list of jar files, for Python this is a list of Python files.", + }, + "handler": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The handler method for Java / Python procedures.", + }, +} + +// Procedure returns a pointer to the resource representing a stored procedure. +func Procedure() *schema.Resource { + return &schema.Resource{ + SchemaVersion: 1, + + CreateContext: CreateContextProcedure, + ReadContext: ReadContextProcedure, + UpdateContext: UpdateContextProcedure, + DeleteContext: DeleteContextProcedure, + + Schema: procedureSchema, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + StateUpgraders: []schema.StateUpgrader{ + { + Version: 0, + // setting type to cty.EmptyObject is a bit hacky here but following https://developer.hashicorp.com/terraform/plugin/framework/migrating/resources/state-upgrade#sdkv2-1 would require lots of repetitive code; this should work with cty.EmptyObject + Type: cty.EmptyObject, + Upgrade: v085ProcedureStateUpgrader, + }, + }, + } +} + +func CreateContextProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + lang := strings.ToUpper(d.Get("language").(string)) + switch lang { + case "JAVA": + return createJavaProcedure(ctx, d, meta) + case "JAVASCRIPT": + return createJavaScriptProcedure(ctx, d, meta) + case "PYTHON": + return createPythonProcedure(ctx, d, meta) + case "SCALA": + return createScalaProcedure(ctx, d, meta) + case "SQL": + return createSQLProcedure(ctx, d, meta) + default: + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Invalid language", + Detail: fmt.Sprintf("Language %s is not supported", lang), + }, + } + } +} + +func createJavaProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + name := d.Get("name").(string) + schema := d.Get("schema").(string) + database := d.Get("database").(string) + id := sdk.NewSchemaObjectIdentifier(database, schema, name) + + returns, diags := parseProcedureReturnsRequest(d.Get("return_type").(string)) + if diags != nil { + return diags + } + procedureDefinition := d.Get("statement").(string) + runtimeVersion := d.Get("runtime_version").(string) + packages := []sdk.ProcedurePackageRequest{} + for _, item := range d.Get("packages").([]interface{}) { + packages = append(packages, *sdk.NewProcedurePackageRequest(item.(string))) + } + handler := d.Get("handler").(string) + req := sdk.NewCreateForJavaProcedureRequest(id, *returns, runtimeVersion, packages, handler) + req.WithProcedureDefinition(sdk.String(procedureDefinition)) + args, diags := getProcedureArguments(d) + if diags != nil { + return diags + } + if len(args) > 0 { + req.WithArguments(args) + } + + // read optional params + if v, ok := d.GetOk("execute_as"); ok { + if strings.ToUpper(v.(string)) == "OWNER" { + req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsOwner)) + } else if strings.ToUpper(v.(string)) == "CALLER" { + req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsCaller)) + } + } + if v, ok := d.GetOk("comment"); ok { + req.WithComment(sdk.String(v.(string))) + } + if v, ok := d.GetOk("secure"); ok { + req.WithSecure(sdk.Bool(v.(bool))) + } + if _, ok := d.GetOk("imports"); ok { + imports := []sdk.ProcedureImportRequest{} + for _, item := range d.Get("imports").([]interface{}) { + imports = append(imports, *sdk.NewProcedureImportRequest(item.(string))) + } + req.WithImports(imports) + } + + if err := client.Procedures.CreateForJava(ctx, req); err != nil { + return diag.FromErr(err) + } + argTypes := make([]sdk.DataType, 0, len(args)) + for _, item := range args { + argTypes = append(argTypes, item.ArgDataType) + } + sid := sdk.NewSchemaObjectIdentifierWithArgumentsOld(database, schema, name, argTypes) + d.SetId(sid.FullyQualifiedName()) + return ReadContextProcedure(ctx, d, meta) +} + +func createJavaScriptProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + name := d.Get("name").(string) + schema := d.Get("schema").(string) + database := d.Get("database").(string) + id := sdk.NewSchemaObjectIdentifier(database, schema, name) + + returnType := d.Get("return_type").(string) + returnDataType, diags := convertProcedureDataType(returnType) + if diags != nil { + return diags + } + procedureDefinition := d.Get("statement").(string) + req := sdk.NewCreateForJavaScriptProcedureRequest(id, returnDataType, procedureDefinition) + args, diags := getProcedureArguments(d) + if diags != nil { + return diags + } + if len(args) > 0 { + req.WithArguments(args) + } + + // read optional params + if v, ok := d.GetOk("execute_as"); ok { + if strings.ToUpper(v.(string)) == "OWNER" { + req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsOwner)) + } else if strings.ToUpper(v.(string)) == "CALLER" { + req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsCaller)) + } + } + if v, ok := d.GetOk("null_input_behavior"); ok { + req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehavior(v.(string)))) + } + if v, ok := d.GetOk("comment"); ok { + req.WithComment(sdk.String(v.(string))) + } + if v, ok := d.GetOk("secure"); ok { + req.WithSecure(sdk.Bool(v.(bool))) + } + + if err := client.Procedures.CreateForJavaScript(ctx, req); err != nil { + return diag.FromErr(err) + } + argTypes := make([]sdk.DataType, 0, len(args)) + for _, item := range args { + argTypes = append(argTypes, item.ArgDataType) + } + sid := sdk.NewSchemaObjectIdentifierWithArgumentsOld(database, schema, name, argTypes) + d.SetId(sid.FullyQualifiedName()) + return ReadContextProcedure(ctx, d, meta) +} + +func createScalaProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + name := d.Get("name").(string) + schema := d.Get("schema").(string) + database := d.Get("database").(string) + id := sdk.NewSchemaObjectIdentifier(database, schema, name) + + returns, diags := parseProcedureReturnsRequest(d.Get("return_type").(string)) + if diags != nil { + return diags + } + procedureDefinition := d.Get("statement").(string) + runtimeVersion := d.Get("runtime_version").(string) + packages := []sdk.ProcedurePackageRequest{} + for _, item := range d.Get("packages").([]interface{}) { + packages = append(packages, *sdk.NewProcedurePackageRequest(item.(string))) + } + handler := d.Get("handler").(string) + req := sdk.NewCreateForScalaProcedureRequest(id, *returns, runtimeVersion, packages, handler) + req.WithProcedureDefinition(sdk.String(procedureDefinition)) + args, diags := getProcedureArguments(d) + if diags != nil { + return diags + } + if len(args) > 0 { + req.WithArguments(args) + } + + // read optional params + if v, ok := d.GetOk("execute_as"); ok { + if strings.ToUpper(v.(string)) == "OWNER" { + req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsOwner)) + } else if strings.ToUpper(v.(string)) == "CALLER" { + req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsCaller)) + } + } + if v, ok := d.GetOk("comment"); ok { + req.WithComment(sdk.String(v.(string))) + } + if v, ok := d.GetOk("secure"); ok { + req.WithSecure(sdk.Bool(v.(bool))) + } + if _, ok := d.GetOk("imports"); ok { + imports := []sdk.ProcedureImportRequest{} + for _, item := range d.Get("imports").([]interface{}) { + imports = append(imports, *sdk.NewProcedureImportRequest(item.(string))) + } + req.WithImports(imports) + } + + if err := client.Procedures.CreateForScala(ctx, req); err != nil { + return diag.FromErr(err) + } + argTypes := make([]sdk.DataType, 0, len(args)) + for _, item := range args { + argTypes = append(argTypes, item.ArgDataType) + } + sid := sdk.NewSchemaObjectIdentifierWithArgumentsOld(database, schema, name, argTypes) + d.SetId(sid.FullyQualifiedName()) + return ReadContextProcedure(ctx, d, meta) +} + +func createSQLProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + name := d.Get("name").(string) + schema := d.Get("schema").(string) + database := d.Get("database").(string) + id := sdk.NewSchemaObjectIdentifier(database, schema, name) + + returns, diags := parseProcedureSQLReturnsRequest(d.Get("return_type").(string)) + if diags != nil { + return diags + } + procedureDefinition := d.Get("statement").(string) + req := sdk.NewCreateForSQLProcedureRequest(id, *returns, procedureDefinition) + args, diags := getProcedureArguments(d) + if diags != nil { + return diags + } + if len(args) > 0 { + req.WithArguments(args) + } + + // read optional params + if v, ok := d.GetOk("execute_as"); ok { + if strings.ToUpper(v.(string)) == "OWNER" { + req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsOwner)) + } else if strings.ToUpper(v.(string)) == "CALLER" { + req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsCaller)) + } + } + if v, ok := d.GetOk("null_input_behavior"); ok { + req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehavior(v.(string)))) + } + if v, ok := d.GetOk("comment"); ok { + req.WithComment(sdk.String(v.(string))) + } + if v, ok := d.GetOk("secure"); ok { + req.WithSecure(sdk.Bool(v.(bool))) + } + + if err := client.Procedures.CreateForSQL(ctx, req); err != nil { + return diag.FromErr(err) + } + argTypes := make([]sdk.DataType, 0, len(args)) + for _, item := range args { + argTypes = append(argTypes, item.ArgDataType) + } + sid := sdk.NewSchemaObjectIdentifierWithArgumentsOld(database, schema, name, argTypes) + d.SetId(sid.FullyQualifiedName()) + return ReadContextProcedure(ctx, d, meta) +} + +func createPythonProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + name := d.Get("name").(string) + schema := d.Get("schema").(string) + database := d.Get("database").(string) + id := sdk.NewSchemaObjectIdentifier(database, schema, name) + + returns, diags := parseProcedureReturnsRequest(d.Get("return_type").(string)) + if diags != nil { + return diags + } + procedureDefinition := d.Get("statement").(string) + runtimeVersion := d.Get("runtime_version").(string) + packages := []sdk.ProcedurePackageRequest{} + for _, item := range d.Get("packages").([]interface{}) { + packages = append(packages, *sdk.NewProcedurePackageRequest(item.(string))) + } + handler := d.Get("handler").(string) + req := sdk.NewCreateForPythonProcedureRequest(id, *returns, runtimeVersion, packages, handler) + req.WithProcedureDefinition(sdk.String(procedureDefinition)) + args, diags := getProcedureArguments(d) + if diags != nil { + return diags + } + if len(args) > 0 { + req.WithArguments(args) + } + + // read optional params + if v, ok := d.GetOk("execute_as"); ok { + if strings.ToUpper(v.(string)) == "OWNER" { + req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsOwner)) + } else if strings.ToUpper(v.(string)) == "CALLER" { + req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsCaller)) + } + } + + // [ { CALLED ON NULL INPUT | { RETURNS NULL ON NULL INPUT | STRICT } } ] does not work for java, scala or python + // posted in docs-discuss channel, either docs need to be updated to reflect reality or this feature needs to be added + // https://snowflake.slack.com/archives/C6380540P/p1707511734666249 + // if v, ok := d.GetOk("null_input_behavior"); ok { + // req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehavior(v.(string)))) + // } + + if v, ok := d.GetOk("comment"); ok { + req.WithComment(sdk.String(v.(string))) + } + if v, ok := d.GetOk("secure"); ok { + req.WithSecure(sdk.Bool(v.(bool))) + } + if _, ok := d.GetOk("imports"); ok { + imports := []sdk.ProcedureImportRequest{} + for _, item := range d.Get("imports").([]interface{}) { + imports = append(imports, *sdk.NewProcedureImportRequest(item.(string))) + } + req.WithImports(imports) + } + + if err := client.Procedures.CreateForPython(ctx, req); err != nil { + return diag.FromErr(err) + } + argTypes := make([]sdk.DataType, 0, len(args)) + for _, item := range args { + argTypes = append(argTypes, item.ArgDataType) + } + sid := sdk.NewSchemaObjectIdentifierWithArgumentsOld(database, schema, name, argTypes) + d.SetId(sid.FullyQualifiedName()) + return ReadContextProcedure(ctx, d, meta) +} + +func ReadContextProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + diags := diag.Diagnostics{} + client := meta.(*provider.Context).Client + + id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) + if err := d.Set("name", id.Name()); err != nil { + return diag.FromErr(err) + } + if err := d.Set("database", id.DatabaseName()); err != nil { + return diag.FromErr(err) + } + if err := d.Set("schema", id.SchemaName()); err != nil { + return diag.FromErr(err) + } + args := d.Get("arguments").([]interface{}) + argTypes := make([]string, len(args)) + for i, arg := range args { + argTypes[i] = arg.(map[string]interface{})["type"].(string) + } + procedureDetails, err := client.Procedures.Describe(ctx, sdk.NewDescribeProcedureRequest(id.WithoutArguments(), id.Arguments())) + if err != nil { + // if procedure is not found then mark resource to be removed from state file during apply or refresh + d.SetId("") + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Describe procedure failed.", + Detail: fmt.Sprintf("Describe procedure failed: %v", err), + }, + } + } + for _, desc := range procedureDetails { + switch desc.Property { + case "signature": + // Format in Snowflake DB is: (argName argType, argName argType, ...) + args := strings.ReplaceAll(strings.ReplaceAll(desc.Value, "(", ""), ")", "") + + if args != "" { // Do nothing for functions without arguments + argPairs := strings.Split(args, ", ") + args := []interface{}{} + + for _, argPair := range argPairs { + argItem := strings.Split(argPair, " ") + + arg := map[string]interface{}{} + arg["name"] = argItem[0] + arg["type"] = argItem[1] + args = append(args, arg) + } + + if err := d.Set("arguments", args); err != nil { + return diag.FromErr(err) + } + } + case "null handling": + if err := d.Set("null_input_behavior", desc.Value); err != nil { + return diag.FromErr(err) + } + case "body": + if err := d.Set("statement", desc.Value); err != nil { + return diag.FromErr(err) + } + case "execute as": + if err := d.Set("execute_as", desc.Value); err != nil { + return diag.FromErr(err) + } + case "returns": + if err := d.Set("return_type", desc.Value); err != nil { + return diag.FromErr(err) + } + case "language": + if err := d.Set("language", desc.Value); err != nil { + return diag.FromErr(err) + } + case "runtime_version": + if err := d.Set("runtime_version", desc.Value); err != nil { + return diag.FromErr(err) + } + case "packages": + packagesString := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(desc.Value, "[", ""), "]", ""), "'", "") + if packagesString != "" { // Do nothing for Java / Python functions without packages + packages := strings.Split(packagesString, ",") + if err := d.Set("packages", packages); err != nil { + return diag.FromErr(err) + } + } + case "imports": + importsString := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(desc.Value, "[", ""), "]", ""), "'", ""), " ", "") + if importsString != "" { // Do nothing for Java functions without imports + imports := strings.Split(importsString, ",") + if err := d.Set("imports", imports); err != nil { + return diag.FromErr(err) + } + } + case "handler": + if err := d.Set("handler", desc.Value); err != nil { + return diag.FromErr(err) + } + case "volatility": + if err := d.Set("return_behavior", desc.Value); err != nil { + return diag.FromErr(err) + } + default: + log.Printf("[INFO] Unexpected procedure property %v returned from Snowflake with value %v", desc.Property, desc.Value) + } + } + + request := sdk.NewShowProcedureRequest().WithIn(&sdk.In{Schema: sdk.NewDatabaseObjectIdentifier(id.DatabaseName(), id.SchemaName())}).WithLike(&sdk.Like{Pattern: sdk.String(id.Name())}) + + procedures, err := client.Procedures.Show(ctx, request) + if err != nil { + return diag.FromErr(err) + } + // procedure names can be overloaded with different argument types so we iterate over and find the correct one + // the ShowByID function should probably be updated to also require the list of arg types, like describe procedure + for _, procedure := range procedures { + argumentSignature := strings.Split(procedure.Arguments, " RETURN ")[0] + argumentSignature = strings.ReplaceAll(argumentSignature, " ", "") + if argumentSignature == id.ArgumentsSignature() { + if err := d.Set("secure", procedure.IsSecure); err != nil { + return diag.FromErr(err) + } + if err := d.Set("comment", procedure.Description); err != nil { + return diag.FromErr(err) + } + } + } + + return diags +} + +func UpdateContextProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + + id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) + if d.HasChange("name") { + newId := sdk.NewSchemaObjectIdentifierWithArgumentsOld(id.DatabaseName(), id.SchemaName(), d.Get("name").(string), id.Arguments()) + + err := client.Procedures.Alter(ctx, sdk.NewAlterProcedureRequest(id.WithoutArguments(), id.Arguments()).WithRenameTo(sdk.Pointer(newId.WithoutArguments()))) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(newId.FullyQualifiedName()) + id = newId + } + + if d.HasChange("comment") { + comment := d.Get("comment") + if comment != "" { + if err := client.Procedures.Alter(ctx, sdk.NewAlterProcedureRequest(id.WithoutArguments(), id.Arguments()).WithSetComment(sdk.String(comment.(string)))); err != nil { + return diag.FromErr(err) + } + } else { + if err := client.Procedures.Alter(ctx, sdk.NewAlterProcedureRequest(id.WithoutArguments(), id.Arguments()).WithUnsetComment(sdk.Bool(true))); err != nil { + return diag.FromErr(err) + } + } + } + + if d.HasChange("execute_as") { + req := sdk.NewAlterProcedureRequest(id.WithoutArguments(), id.Arguments()) + executeAs := d.Get("execute_as").(string) + if strings.ToUpper(executeAs) == "OWNER" { + req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsOwner)) + } else if strings.ToUpper(executeAs) == "CALLER" { + req.WithExecuteAs(sdk.Pointer(sdk.ExecuteAsCaller)) + } + if err := client.Procedures.Alter(ctx, req); err != nil { + return diag.FromErr(err) + } + } + + return ReadContextProcedure(ctx, d, meta) +} + +func DeleteContextProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + + id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id()) + if err := client.Procedures.Drop(ctx, sdk.NewDropProcedureRequest(id.WithoutArguments(), id.Arguments())); err != nil { + return diag.FromErr(err) + } + d.SetId("") + return nil +} + +func getProcedureArguments(d *schema.ResourceData) ([]sdk.ProcedureArgumentRequest, diag.Diagnostics) { + args := make([]sdk.ProcedureArgumentRequest, 0) + if v, ok := d.GetOk("arguments"); ok { + for _, arg := range v.([]interface{}) { + argName := arg.(map[string]interface{})["name"].(string) + argType := arg.(map[string]interface{})["type"].(string) + argDataType, diags := convertProcedureDataType(argType) + if diags != nil { + return nil, diags + } + args = append(args, sdk.ProcedureArgumentRequest{ArgName: argName, ArgDataType: argDataType}) + } + } + return args, nil +} + +func convertProcedureDataType(s string) (sdk.DataType, diag.Diagnostics) { + dataType, err := sdk.ToDataType(s) + if err != nil { + return dataType, diag.FromErr(err) + } + return dataType, nil +} + +func convertProcedureColumns(s string) ([]sdk.ProcedureColumn, diag.Diagnostics) { + pattern := regexp.MustCompile(`(\w+)\s+(\w+)`) + matches := pattern.FindAllStringSubmatch(s, -1) + var columns []sdk.ProcedureColumn + for _, match := range matches { + if len(match) == 3 { + dataType, err := sdk.ToDataType(match[2]) + if err != nil { + return nil, diag.FromErr(err) + } + columns = append(columns, sdk.ProcedureColumn{ + ColumnName: match[1], + ColumnDataType: dataType, + }) + } + } + return columns, nil +} + +func parseProcedureReturnsRequest(s string) (*sdk.ProcedureReturnsRequest, diag.Diagnostics) { + returns := sdk.NewProcedureReturnsRequest() + if strings.HasPrefix(strings.ToLower(s), "table") { + columns, diags := convertProcedureColumns(s) + if diags != nil { + return nil, diags + } + var cr []sdk.ProcedureColumnRequest + for _, item := range columns { + cr = append(cr, *sdk.NewProcedureColumnRequest(item.ColumnName, item.ColumnDataType)) + } + returns.WithTable(sdk.NewProcedureReturnsTableRequest().WithColumns(cr)) + } else { + returnDataType, diags := convertProcedureDataType(s) + if diags != nil { + return nil, diags + } + returns.WithResultDataType(sdk.NewProcedureReturnsResultDataTypeRequest(returnDataType)) + } + return returns, nil +} + +func parseProcedureSQLReturnsRequest(s string) (*sdk.ProcedureSQLReturnsRequest, diag.Diagnostics) { + returns := sdk.NewProcedureSQLReturnsRequest() + if strings.HasPrefix(strings.ToLower(s), "table") { + columns, diags := convertProcedureColumns(s) + if diags != nil { + return nil, diags + } + var cr []sdk.ProcedureColumnRequest + for _, item := range columns { + cr = append(cr, *sdk.NewProcedureColumnRequest(item.ColumnName, item.ColumnDataType)) + } + returns.WithTable(sdk.NewProcedureReturnsTableRequest().WithColumns(cr)) + } else { + returnDataType, diags := convertProcedureDataType(s) + if diags != nil { + return nil, diags + } + returns.WithResultDataType(sdk.NewProcedureReturnsResultDataTypeRequest(returnDataType)) + } + return returns, nil +} diff --git a/pkg/resources/procedure_acceptance_test.go b/pkg/resources/procedure_acceptance_test.go index 1f13f783a2..fe5ca5283e 100644 --- a/pkg/resources/procedure_acceptance_test.go +++ b/pkg/resources/procedure_acceptance_test.go @@ -1,376 +1,389 @@ package resources_test -//func testAccProcedure(t *testing.T, configDirectory string) { -// t.Helper() -// -// name := acc.TestClient().Ids.Alpha() -// newName := acc.TestClient().Ids.Alpha() -// -// resourceName := "snowflake_procedure.p" -// m := func() map[string]config.Variable { -// return map[string]config.Variable{ -// "name": config.StringVariable(name), -// "database": config.StringVariable(acc.TestDatabaseName), -// "schema": config.StringVariable(acc.TestSchemaName), -// "comment": config.StringVariable("Terraform acceptance test"), -// "execute_as": config.StringVariable("CALLER"), -// } -// } -// variableSet2 := m() -// variableSet2["name"] = config.StringVariable(newName) -// variableSet2["comment"] = config.StringVariable("Terraform acceptance test - updated") -// variableSet2["execute_as"] = config.StringVariable("OWNER") -// -// ignoreDuringImport := []string{"null_input_behavior"} -// if strings.Contains(configDirectory, "/sql") { -// ignoreDuringImport = append(ignoreDuringImport, "return_behavior") -// } -// -// resource.Test(t, resource.TestCase{ -// ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, -// PreCheck: func() { acc.TestAccPreCheck(t) }, -// TerraformVersionChecks: []tfversion.TerraformVersionCheck{ -// tfversion.RequireAbove(tfversion.Version1_5_0), -// }, -// CheckDestroy: acc.CheckDestroy(t, resources.Procedure), -// Steps: []resource.TestStep{ -// { -// ConfigDirectory: acc.ConfigurationDirectory(configDirectory), -// ConfigVariables: m(), -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckResourceAttr(resourceName, "name", name), -// resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), -// resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), -// resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test"), -// resource.TestCheckResourceAttr(resourceName, "return_behavior", "VOLATILE"), -// resource.TestCheckResourceAttr(resourceName, "execute_as", "CALLER"), -// -// // computed attributes -// resource.TestCheckResourceAttrSet(resourceName, "return_type"), -// resource.TestCheckResourceAttrSet(resourceName, "statement"), -// resource.TestCheckResourceAttrSet(resourceName, "secure"), -// ), -// }, -// -// // test - rename + change comment and caller (proves https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2642) -// { -// ConfigDirectory: acc.ConfigurationDirectory(configDirectory), -// ConfigVariables: variableSet2, -// ConfigPlanChecks: resource.ConfigPlanChecks{ -// PreApply: []plancheck.PlanCheck{ -// plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate), -// }, -// }, -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckResourceAttr(resourceName, "name", newName), -// resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), -// resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), -// resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test - updated"), -// resource.TestCheckResourceAttr(resourceName, "execute_as", "OWNER"), -// ), -// }, -// -// // test - import -// { -// ConfigDirectory: acc.ConfigurationDirectory(configDirectory), -// ConfigVariables: variableSet2, -// ResourceName: resourceName, -// ImportState: true, -// ImportStateVerify: true, -// ImportStateVerifyIgnore: ignoreDuringImport, -// }, -// }, -// }) -//} -// -//func TestAcc_Procedure_SQL(t *testing.T) { -// testAccProcedure(t, "TestAcc_Procedure/sql") -//} -// -///* -//Error: 391531 (42601): SQL compilation error: An active warehouse is required for creating Python stored procedures. -//func TestAcc_Procedure_Python(t *testing.T) { -// testAccProcedure(t, "TestAcc_Procedure/python") -//} -//*/ -// -//func TestAcc_Procedure_Javascript(t *testing.T) { -// testAccProcedure(t, "TestAcc_Procedure/javascript") -//} -// -//func TestAcc_Procedure_Java(t *testing.T) { -// testAccProcedure(t, "TestAcc_Procedure/java") -//} -// -//func TestAcc_Procedure_Scala(t *testing.T) { -// testAccProcedure(t, "TestAcc_Procedure/scala") -//} -// -//func TestAcc_Procedure_complex(t *testing.T) { -// name := acc.TestClient().Ids.Alpha() -// resourceName := "snowflake_procedure.p" -// m := func() map[string]config.Variable { -// return map[string]config.Variable{ -// "name": config.StringVariable(name), -// "database": config.StringVariable(acc.TestDatabaseName), -// "schema": config.StringVariable(acc.TestSchemaName), -// "comment": config.StringVariable("Terraform acceptance test"), -// "execute_as": config.StringVariable("CALLER"), -// } -// } -// variableSet2 := m() -// variableSet2["comment"] = config.StringVariable("Terraform acceptance test - updated") -// -// statement := "var x = 1\nreturn x\n" -// resource.Test(t, resource.TestCase{ -// ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, -// PreCheck: func() { acc.TestAccPreCheck(t) }, -// TerraformVersionChecks: []tfversion.TerraformVersionCheck{ -// tfversion.RequireAbove(tfversion.Version1_5_0), -// }, -// CheckDestroy: acc.CheckDestroy(t, resources.Procedure), -// Steps: []resource.TestStep{ -// { -// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_Procedure/complex"), -// ConfigVariables: m(), -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckResourceAttr(resourceName, "name", name), -// resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), -// resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), -// resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test"), -// resource.TestCheckResourceAttr(resourceName, "statement", statement), -// resource.TestCheckResourceAttr(resourceName, "execute_as", "CALLER"), -// resource.TestCheckResourceAttr(resourceName, "arguments.#", "2"), -// resource.TestCheckResourceAttr(resourceName, "arguments.0.name", "ARG1"), -// resource.TestCheckResourceAttr(resourceName, "arguments.0.type", "VARCHAR"), -// resource.TestCheckResourceAttr(resourceName, "arguments.1.name", "ARG2"), -// resource.TestCheckResourceAttr(resourceName, "arguments.1.type", "DATE"), -// resource.TestCheckResourceAttr(resourceName, "null_input_behavior", "RETURNS NULL ON NULL INPUT"), -// -// // computed attributes -// resource.TestCheckResourceAttrSet(resourceName, "return_type"), -// resource.TestCheckResourceAttrSet(resourceName, "statement"), -// resource.TestCheckResourceAttrSet(resourceName, "execute_as"), -// resource.TestCheckResourceAttrSet(resourceName, "secure"), -// ), -// }, -// -// // test - change comment -// { -// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_Procedure/complex"), -// ConfigVariables: variableSet2, -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckResourceAttr(resourceName, "name", name), -// resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), -// resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), -// resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test - updated"), -// ), -// }, -// -// // test - import -// { -// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_Procedure/complex"), -// ConfigVariables: variableSet2, -// ResourceName: resourceName, -// ImportState: true, -// ImportStateVerify: true, -// ImportStateVerifyIgnore: []string{ -// "return_behavior", -// }, -// }, -// }, -// }) -//} -// -//func TestAcc_Procedure_migrateFromVersion085(t *testing.T) { -// id := acc.TestClient().Ids.RandomSchemaObjectIdentifier() -// name := id.Name() -// resourceName := "snowflake_procedure.p" -// -// resource.Test(t, resource.TestCase{ -// PreCheck: func() { acc.TestAccPreCheck(t) }, -// TerraformVersionChecks: []tfversion.TerraformVersionCheck{ -// tfversion.RequireAbove(tfversion.Version1_5_0), -// }, -// CheckDestroy: acc.CheckDestroy(t, resources.Procedure), -// -// Steps: []resource.TestStep{ -// { -// ExternalProviders: map[string]resource.ExternalProvider{ -// "snowflake": { -// VersionConstraint: "=0.85.0", -// Source: "Snowflake-Labs/snowflake", -// }, -// }, -// Config: procedureConfig(acc.TestDatabaseName, acc.TestSchemaName, name), -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|%s|%s|", acc.TestDatabaseName, acc.TestSchemaName, name)), -// resource.TestCheckResourceAttr(resourceName, "name", name), -// resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), -// resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), -// ), -// }, -// { -// ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, -// Config: procedureConfig(acc.TestDatabaseName, acc.TestSchemaName, name), -// ConfigPlanChecks: resource.ConfigPlanChecks{ -// PreApply: []plancheck.PlanCheck{plancheck.ExpectEmptyPlan()}, -// }, -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckResourceAttr(resourceName, "id", id.FullyQualifiedName()), -// resource.TestCheckResourceAttr(resourceName, "name", name), -// resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), -// resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), -// ), -// }, -// }, -// }) -//} -// -//func procedureConfig(database string, schema string, name string) string { -// return fmt.Sprintf(` -//resource "snowflake_procedure" "p" { -// database = "%[1]s" -// schema = "%[2]s" -// name = "%[3]s" -// language = "JAVASCRIPT" -// return_type = "VARCHAR" -// statement = < Date: Mon, 5 Aug 2024 16:19:28 +0200 Subject: [PATCH 09/13] wip --- pkg/acceptance/helpers/ids_generator.go | 8 ++--- pkg/datasources/functions.go | 1 + pkg/resources/function.go | 1 - pkg/resources/function_state_upgraders.go | 2 +- pkg/schemas/function_gen.go | 7 +++- pkg/schemas/replication_database_gen.go | 4 ++- pkg/sdk/functions_gen.go | 20 ----------- pkg/sdk/identifier_helpers.go | 2 +- pkg/sdk/identifier_helpers_test.go | 41 +++++++++++------------ 9 files changed, 36 insertions(+), 50 deletions(-) diff --git a/pkg/acceptance/helpers/ids_generator.go b/pkg/acceptance/helpers/ids_generator.go index 47be24644c..240d7669d2 100644 --- a/pkg/acceptance/helpers/ids_generator.go +++ b/pkg/acceptance/helpers/ids_generator.go @@ -85,14 +85,14 @@ func (c *IdsGenerator) NewSchemaObjectIdentifierWithArguments(name string, argum return sdk.NewSchemaObjectIdentifierWithArguments(c.SchemaId().DatabaseName(), c.SchemaId().Name(), name, arguments...) } -func (c *IdsGenerator) RandomSchemaObjectIdentifierWithArguments(arguments ...sdk.DataType) sdk.SchemaObjectIdentifierWithArguments { - return sdk.NewSchemaObjectIdentifierWithArguments(c.SchemaId().DatabaseName(), c.SchemaId().Name(), c.Alpha(), arguments...) -} - func (c *IdsGenerator) NewSchemaObjectIdentifierWithArgumentsInSchema(name string, schemaId sdk.DatabaseObjectIdentifier, argumentDataTypes ...sdk.DataType) sdk.SchemaObjectIdentifierWithArguments { return sdk.NewSchemaObjectIdentifierWithArgumentsInSchema(schemaId, name, argumentDataTypes...) } +func (c *IdsGenerator) RandomSchemaObjectIdentifierWithArguments(arguments ...sdk.DataType) sdk.SchemaObjectIdentifierWithArguments { + return sdk.NewSchemaObjectIdentifierWithArguments(c.SchemaId().DatabaseName(), c.SchemaId().Name(), c.Alpha(), arguments...) +} + func (c *IdsGenerator) Alpha() string { return c.AlphaN(6) } diff --git a/pkg/datasources/functions.go b/pkg/datasources/functions.go index 548f18ec5f..b54213442c 100644 --- a/pkg/datasources/functions.go +++ b/pkg/datasources/functions.go @@ -92,6 +92,7 @@ func ReadContextFunctions(ctx context.Context, d *schema.ResourceData, meta inte entities := []map[string]interface{}{} for _, item := range functions { + // TODO(SNOW-1596962): Create argument parsing function that takes argument names into consideration. signature, err := parseArguments(item.ArgumentsRaw) if err != nil { return diag.FromErr(err) diff --git a/pkg/resources/function.go b/pkg/resources/function.go index 4b8d187f47..f965c1f325 100644 --- a/pkg/resources/function.go +++ b/pkg/resources/function.go @@ -508,7 +508,6 @@ func createJavascriptFunction(ctx context.Context, d *schema.ResourceData, meta argumentTypes = append(argumentTypes, item.ArgDataType) } nid := sdk.NewSchemaObjectIdentifierWithArguments(database, schema, name, argumentTypes...) - // TODO: Create upgrader for id migration d.SetId(nid.FullyQualifiedName()) return ReadContextFunction(ctx, d, meta) } diff --git a/pkg/resources/function_state_upgraders.go b/pkg/resources/function_state_upgraders.go index ac7c1bd20a..1413876a37 100644 --- a/pkg/resources/function_state_upgraders.go +++ b/pkg/resources/function_state_upgraders.go @@ -54,7 +54,7 @@ func v085FunctionIdStateUpgrader(ctx context.Context, rawState map[string]interf argDataTypes[i] = argDataType } - schemaObjectIdentifierWithArguments := sdk.NewSchemaObjectIdentifierWithArguments(parsedV085FunctionId.DatabaseName, parsedV085FunctionId.SchemaName, parsedV085FunctionId.FunctionName, argDataTypes...) + schemaObjectIdentifierWithArguments := sdk.NewSchemaObjectIdentifierWithArgumentsOld(parsedV085FunctionId.DatabaseName, parsedV085FunctionId.SchemaName, parsedV085FunctionId.FunctionName, argDataTypes) rawState["id"] = schemaObjectIdentifierWithArguments.FullyQualifiedName() return rawState, nil diff --git a/pkg/schemas/function_gen.go b/pkg/schemas/function_gen.go index 0e841050db..ecd7d71f53 100644 --- a/pkg/schemas/function_gen.go +++ b/pkg/schemas/function_gen.go @@ -42,6 +42,10 @@ var ShowFunctionSchema = map[string]*schema.Schema{ Computed: true, }, "arguments": { + Type: schema.TypeInvalid, + Computed: true, + }, + "arguments_raw": { Type: schema.TypeString, Computed: true, }, @@ -91,7 +95,8 @@ func FunctionToSchema(function *sdk.Function) map[string]any { functionSchema["is_ansi"] = function.IsAnsi functionSchema["min_num_arguments"] = function.MinNumArguments functionSchema["max_num_arguments"] = function.MaxNumArguments - functionSchema["arguments"] = function.ArgumentsRaw + functionSchema["arguments"] = function.Arguments + functionSchema["arguments_raw"] = function.ArgumentsRaw functionSchema["description"] = function.Description functionSchema["catalog_name"] = function.CatalogName functionSchema["is_table_function"] = function.IsTableFunction diff --git a/pkg/schemas/replication_database_gen.go b/pkg/schemas/replication_database_gen.go index c121465956..73c182b03b 100644 --- a/pkg/schemas/replication_database_gen.go +++ b/pkg/schemas/replication_database_gen.go @@ -70,7 +70,9 @@ func ReplicationDatabaseToSchema(replicationDatabase *sdk.ReplicationDatabase) m replicationDatabaseSchema["name"] = replicationDatabase.Name replicationDatabaseSchema["comment"] = replicationDatabase.Comment replicationDatabaseSchema["is_primary"] = replicationDatabase.IsPrimary - replicationDatabaseSchema["primary_database"] = replicationDatabase.PrimaryDatabase + if replicationDatabase.PrimaryDatabase != nil { + replicationDatabaseSchema["primary_database"] = replicationDatabase.PrimaryDatabase.FullyQualifiedName() + } replicationDatabaseSchema["replication_allowed_to_accounts"] = replicationDatabase.ReplicationAllowedToAccounts replicationDatabaseSchema["failover_allowed_to_accounts"] = replicationDatabase.FailoverAllowedToAccounts replicationDatabaseSchema["organization_name"] = replicationDatabase.OrganizationName diff --git a/pkg/sdk/functions_gen.go b/pkg/sdk/functions_gen.go index 6ff4dc9632..7c3d55d76b 100644 --- a/pkg/sdk/functions_gen.go +++ b/pkg/sdk/functions_gen.go @@ -3,8 +3,6 @@ package sdk import ( "context" "database/sql" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" - "strings" ) type Functions interface { @@ -243,24 +241,6 @@ type Function struct { IsMemoizable bool } -// parseFunctionArgumentsFromDetails parses arguments from signature that is contained in function details -func parseFunctionArgumentsFromDetails(details []FunctionDetail) ([]DataType, error) { - signatureProperty, err := collections.FindOne(details, func(detail FunctionDetail) bool { return detail.Property == "signature" }) - if err != nil { - return nil, err - } - // signature has a structure of ( , ...); column names can contain commas and other special characters, - // and they're not escaped, meaning for names such as `"a,b.c|d e" NUMBER` the signature will contain `(a,b.c|d e NUMBER)`. - arguments := make([]DataType, 0) - // TODO(TODO - ticket number): Handle arguments with comma in the name (right now this could break for arguments containing dots) - for _, arg := range strings.Split(strings.Trim(signatureProperty.Value, "()"), ",") { - // single argument has a structure of - argumentSignatureParts := strings.Split(strings.TrimSpace(arg), " ") - arguments = append(arguments, DataType(argumentSignatureParts[len(argumentSignatureParts)-1])) - } - return arguments, nil -} - func (v *Function) ID() SchemaObjectIdentifierWithArguments { return NewSchemaObjectIdentifierWithArguments(v.CatalogName, v.SchemaName, v.Name, v.Arguments...) } diff --git a/pkg/sdk/identifier_helpers.go b/pkg/sdk/identifier_helpers.go index 5cb80415e7..b0e7b0b7e6 100644 --- a/pkg/sdk/identifier_helpers.go +++ b/pkg/sdk/identifier_helpers.go @@ -232,7 +232,7 @@ type SchemaObjectIdentifier struct { databaseName string schemaName string name string - // TODO(next prs): left right now for backward compatibility for procedures and externalFunctions + // TODO(next prs ???): left right now for backward compatibility for procedures and externalFunctions arguments []DataType } diff --git a/pkg/sdk/identifier_helpers_test.go b/pkg/sdk/identifier_helpers_test.go index 33800a1967..2f409cc84d 100644 --- a/pkg/sdk/identifier_helpers_test.go +++ b/pkg/sdk/identifier_helpers_test.go @@ -27,27 +27,26 @@ func TestNewAccountIdentifierFromFullyQualifiedName(t *testing.T) { } } -// TODO: TEST new identifier -//func TestNewSchemaObjectIdentifierFromFullyQualifiedName(t *testing.T) { -// type test struct { -// input string -// want SchemaObjectIdentifier -// } -// -// tests := []test{ -// {input: "\"MY_DB\".\"MY_SCHEMA\".\"multiply\"(number, number)", want: SchemaObjectIdentifier{databaseName: "MY_DB", schemaName: "MY_SCHEMA", name: "multiply", arguments: []DataType{DataTypeNumber, DataTypeNumber}}}, -// {input: "MY_DB.MY_SCHEMA.add(number, number)", want: SchemaObjectIdentifier{databaseName: "MY_DB", schemaName: "MY_SCHEMA", name: "add", arguments: []DataType{DataTypeNumber, DataTypeNumber}}}, -// {input: "\"MY_DB\".\"MY_SCHEMA\".\"MY_UDF\"()", want: SchemaObjectIdentifier{databaseName: "MY_DB", schemaName: "MY_SCHEMA", name: "MY_UDF", arguments: []DataType{}}}, -// {input: "\"MY_DB\".\"MY_SCHEMA\".\"MY_PIPE\"", want: SchemaObjectIdentifier{databaseName: "MY_DB", schemaName: "MY_SCHEMA", name: "MY_PIPE", arguments: nil}}, -// {input: "MY_DB.MY_SCHEMA.MY_STAGE", want: SchemaObjectIdentifier{databaseName: "MY_DB", schemaName: "MY_SCHEMA", name: "MY_STAGE", arguments: nil}}, -// } -// for _, tc := range tests { -// t.Run(tc.input, func(t *testing.T) { -// id := NewSchemaObjectIdentifierFromFullyQualifiedName(tc.input) -// require.Equal(t, tc.want, id) -// }) -// } -//} +func TestNewSchemaObjectIdentifierFromFullyQualifiedName(t *testing.T) { + type test struct { + input string + want SchemaObjectIdentifier + } + + tests := []test{ + {input: "\"MY_DB\".\"MY_SCHEMA\".\"multiply\"(number, number)", want: SchemaObjectIdentifier{databaseName: "MY_DB", schemaName: "MY_SCHEMA", name: "multiply", arguments: []DataType{DataTypeNumber, DataTypeNumber}}}, + {input: "MY_DB.MY_SCHEMA.add(number, number)", want: SchemaObjectIdentifier{databaseName: "MY_DB", schemaName: "MY_SCHEMA", name: "add", arguments: []DataType{DataTypeNumber, DataTypeNumber}}}, + {input: "\"MY_DB\".\"MY_SCHEMA\".\"MY_UDF\"()", want: SchemaObjectIdentifier{databaseName: "MY_DB", schemaName: "MY_SCHEMA", name: "MY_UDF", arguments: []DataType{}}}, + {input: "\"MY_DB\".\"MY_SCHEMA\".\"MY_PIPE\"", want: SchemaObjectIdentifier{databaseName: "MY_DB", schemaName: "MY_SCHEMA", name: "MY_PIPE", arguments: nil}}, + {input: "MY_DB.MY_SCHEMA.MY_STAGE", want: SchemaObjectIdentifier{databaseName: "MY_DB", schemaName: "MY_SCHEMA", name: "MY_STAGE", arguments: nil}}, + } + for _, tc := range tests { + t.Run(tc.input, func(t *testing.T) { + id := NewSchemaObjectIdentifierFromFullyQualifiedName(tc.input) + require.Equal(t, tc.want, id) + }) + } +} func TestDatabaseObjectIdentifier(t *testing.T) { t.Run("create from strings", func(t *testing.T) { From d81b847c4d03bc0fd36ecab57e66bc13ed2da320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Tue, 6 Aug 2024 11:16:02 +0200 Subject: [PATCH 10/13] wip --- pkg/acceptance/helpers/ids_generator.go | 4 + pkg/resources/external_function.go | 9 +- .../external_function_acceptance_test.go | 1104 ++++----- .../external_function_state_upgraders.go | 3 +- pkg/resources/function.go | 7 +- pkg/resources/function_state_upgraders.go | 3 +- pkg/resources/procedure.go | 9 +- pkg/resources/procedure_acceptance_test.go | 5 +- pkg/resources/procedure_state_upgraders.go | 3 +- pkg/schemas/gen/main/main.go | 3 +- pkg/sdk/external_functions_impl_gen.go | 77 +- pkg/sdk/functions_impl_gen.go | 6 +- pkg/sdk/grants.go | 1 + pkg/sdk/identifier_helpers.go | 67 +- .../external_functions_integration_test.go | 525 ++--- pkg/sdk/testint/functions_integration_test.go | 114 +- .../testint/procedures_integration_test.go | 2033 +++++++++-------- 17 files changed, 1958 insertions(+), 2015 deletions(-) diff --git a/pkg/acceptance/helpers/ids_generator.go b/pkg/acceptance/helpers/ids_generator.go index 240d7669d2..42e247e6d5 100644 --- a/pkg/acceptance/helpers/ids_generator.go +++ b/pkg/acceptance/helpers/ids_generator.go @@ -81,6 +81,10 @@ func (c *IdsGenerator) RandomSchemaObjectIdentifierInSchema(schemaId sdk.Databas return sdk.NewSchemaObjectIdentifierInSchema(schemaId, c.Alpha()) } +func (c *IdsGenerator) RandomSchemaObjectIdentifierWithArgumentsOld(arguments ...sdk.DataType) sdk.SchemaObjectIdentifier { + return sdk.NewSchemaObjectIdentifierWithArgumentsOld(c.SchemaId().DatabaseName(), c.SchemaId().Name(), c.Alpha(), arguments) +} + func (c *IdsGenerator) NewSchemaObjectIdentifierWithArguments(name string, arguments ...sdk.DataType) sdk.SchemaObjectIdentifierWithArguments { return sdk.NewSchemaObjectIdentifierWithArguments(c.SchemaId().DatabaseName(), c.SchemaId().Name(), name, arguments...) } diff --git a/pkg/resources/external_function.go b/pkg/resources/external_function.go index 7fb246ab33..e57e43ce51 100644 --- a/pkg/resources/external_function.go +++ b/pkg/resources/external_function.go @@ -3,16 +3,17 @@ package resources import ( "context" "encoding/json" + "log" + "regexp" + "strconv" + "strings" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "log" - "regexp" - "strconv" - "strings" ) var externalFunctionSchema = map[string]*schema.Schema{ diff --git a/pkg/resources/external_function_acceptance_test.go b/pkg/resources/external_function_acceptance_test.go index ecb8c43f11..2d31be5ccc 100644 --- a/pkg/resources/external_function_acceptance_test.go +++ b/pkg/resources/external_function_acceptance_test.go @@ -1,548 +1,560 @@ package resources_test -//func TestAcc_ExternalFunction_basic(t *testing.T) { -// accName := acc.TestClient().Ids.Alpha() -// -// m := func() map[string]config.Variable { -// return map[string]config.Variable{ -// "database": config.StringVariable(acc.TestDatabaseName), -// "schema": config.StringVariable(acc.TestSchemaName), -// "name": config.StringVariable(accName), -// "api_allowed_prefixes": config.ListVariable(config.StringVariable("https://123456.execute-api.us-west-2.amazonaws.com/prod/")), -// "url_of_proxy_and_resource": config.StringVariable("https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"), -// "comment": config.StringVariable("Terraform acceptance test"), -// } -// } -// -// resourceName := "snowflake_external_function.external_function" -// configVariables := m() -// configVariables2 := m() -// configVariables2["comment"] = config.StringVariable("Terraform acceptance test - updated") -// -// resource.Test(t, resource.TestCase{ -// ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, -// PreCheck: func() { acc.TestAccPreCheck(t) }, -// TerraformVersionChecks: []tfversion.TerraformVersionCheck{ -// tfversion.RequireAbove(tfversion.Version1_5_0), -// }, -// CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), -// Steps: []resource.TestStep{ -// { -// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/basic"), -// ConfigVariables: configVariables, -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckResourceAttr(resourceName, "name", accName), -// resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), -// resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), -// resource.TestCheckResourceAttr(resourceName, "arg.#", "2"), -// resource.TestCheckResourceAttr(resourceName, "arg.0.name", "ARG1"), -// resource.TestCheckResourceAttr(resourceName, "arg.0.type", "VARCHAR"), -// resource.TestCheckResourceAttr(resourceName, "arg.1.name", "ARG2"), -// resource.TestCheckResourceAttr(resourceName, "arg.1.type", "VARCHAR"), -// resource.TestCheckResourceAttr(resourceName, "null_input_behavior", "CALLED ON NULL INPUT"), -// resource.TestCheckResourceAttr(resourceName, "return_type", "VARIANT"), -// resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), -// resource.TestCheckResourceAttr(resourceName, "return_behavior", "IMMUTABLE"), -// resource.TestCheckResourceAttrSet(resourceName, "api_integration"), -// resource.TestCheckResourceAttr(resourceName, "compression", "AUTO"), -// resource.TestCheckResourceAttr(resourceName, "url_of_proxy_and_resource", "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"), -// resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test"), -// resource.TestCheckResourceAttrSet(resourceName, "created_on"), -// ), -// }, -// { -// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/basic"), -// ConfigVariables: configVariables2, -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test - updated"), -// ), -// }, -// // IMPORT -// { -// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/basic"), -// ConfigVariables: configVariables2, -// ResourceName: resourceName, -// ImportState: true, -// ImportStateVerify: true, -// // these two are not found in either the show or describe command -// ImportStateVerifyIgnore: []string{"return_null_allowed", "api_integration"}, -// }, -// }, -// }) -//} -// -//func TestAcc_ExternalFunction_no_arguments(t *testing.T) { -// accName := acc.TestClient().Ids.Alpha() -// -// m := func() map[string]config.Variable { -// return map[string]config.Variable{ -// "database": config.StringVariable(acc.TestDatabaseName), -// "schema": config.StringVariable(acc.TestSchemaName), -// "name": config.StringVariable(accName), -// "api_allowed_prefixes": config.ListVariable(config.StringVariable("https://123456.execute-api.us-west-2.amazonaws.com/prod/")), -// "url_of_proxy_and_resource": config.StringVariable("https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"), -// "comment": config.StringVariable("Terraform acceptance test"), -// } -// } -// -// resourceName := "snowflake_external_function.external_function" -// configVariables := m() -// configVariables2 := m() -// configVariables2["comment"] = config.StringVariable("Terraform acceptance test - updated") -// -// resource.Test(t, resource.TestCase{ -// ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, -// PreCheck: func() { acc.TestAccPreCheck(t) }, -// TerraformVersionChecks: []tfversion.TerraformVersionCheck{ -// tfversion.RequireAbove(tfversion.Version1_5_0), -// }, -// CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), -// Steps: []resource.TestStep{ -// { -// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/no_arguments"), -// ConfigVariables: configVariables, -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckResourceAttr(resourceName, "name", accName), -// resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), -// resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), -// resource.TestCheckResourceAttr(resourceName, "arg.#", "0"), -// resource.TestCheckResourceAttr(resourceName, "null_input_behavior", "CALLED ON NULL INPUT"), -// resource.TestCheckResourceAttr(resourceName, "return_type", "VARIANT"), -// resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), -// resource.TestCheckResourceAttr(resourceName, "return_behavior", "IMMUTABLE"), -// resource.TestCheckResourceAttrSet(resourceName, "api_integration"), -// resource.TestCheckResourceAttr(resourceName, "compression", "AUTO"), -// resource.TestCheckResourceAttr(resourceName, "url_of_proxy_and_resource", "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"), -// resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test"), -// resource.TestCheckResourceAttrSet(resourceName, "created_on"), -// ), -// }, -// { -// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/no_arguments"), -// ConfigVariables: configVariables2, -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test - updated"), -// ), -// }, -// // IMPORT -// { -// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/no_arguments"), -// ConfigVariables: configVariables2, -// ResourceName: resourceName, -// ImportState: true, -// ImportStateVerify: true, -// // these two are not found in either the show or describe command -// ImportStateVerifyIgnore: []string{"return_null_allowed", "api_integration"}, -// }, -// }, -// }) -//} -// -//func TestAcc_ExternalFunction_complete(t *testing.T) { -// accName := acc.TestClient().Ids.Alpha() -// -// m := func() map[string]config.Variable { -// return map[string]config.Variable{ -// "database": config.StringVariable(acc.TestDatabaseName), -// "schema": config.StringVariable(acc.TestSchemaName), -// "name": config.StringVariable(accName), -// "api_allowed_prefixes": config.ListVariable(config.StringVariable("https://123456.execute-api.us-west-2.amazonaws.com/prod/")), -// "url_of_proxy_and_resource": config.StringVariable("https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"), -// "comment": config.StringVariable("Terraform acceptance test"), -// } -// } -// -// resourceName := "snowflake_external_function.external_function" -// configVariables := m() -// configVariables2 := m() -// configVariables2["comment"] = config.StringVariable("Terraform acceptance test - updated") -// -// resource.Test(t, resource.TestCase{ -// ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, -// PreCheck: func() { acc.TestAccPreCheck(t) }, -// TerraformVersionChecks: []tfversion.TerraformVersionCheck{ -// tfversion.RequireAbove(tfversion.Version1_5_0), -// }, -// CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), -// Steps: []resource.TestStep{ -// { -// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/complete"), -// ConfigVariables: configVariables, -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckResourceAttr(resourceName, "name", accName), -// resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), -// resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), -// resource.TestCheckResourceAttr(resourceName, "arg.#", "0"), -// resource.TestCheckResourceAttr(resourceName, "null_input_behavior", "CALLED ON NULL INPUT"), -// resource.TestCheckResourceAttr(resourceName, "return_type", "VARIANT"), -// resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), -// resource.TestCheckResourceAttr(resourceName, "return_behavior", "IMMUTABLE"), -// resource.TestCheckResourceAttrSet(resourceName, "api_integration"), -// resource.TestCheckResourceAttr(resourceName, "compression", "AUTO"), -// resource.TestCheckResourceAttr(resourceName, "url_of_proxy_and_resource", "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"), -// resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test"), -// resource.TestCheckResourceAttrSet(resourceName, "created_on"), -// resource.TestCheckResourceAttr(resourceName, "header.#", "1"), -// resource.TestCheckResourceAttr(resourceName, "header.0.name", "x-custom-header"), -// resource.TestCheckResourceAttr(resourceName, "header.0.value", "snowflake"), -// resource.TestCheckResourceAttr(resourceName, "max_batch_rows", "500"), -// resource.TestCheckResourceAttr(resourceName, "request_translator", fmt.Sprintf("%s.%s.%s%s", acc.TestDatabaseName, acc.TestSchemaName, accName, "_request_translator")), -// resource.TestCheckResourceAttr(resourceName, "response_translator", fmt.Sprintf("%s.%s.%s%s", acc.TestDatabaseName, acc.TestSchemaName, accName, "_response_translator")), -// ), -// }, -// { -// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/complete"), -// ConfigVariables: configVariables2, -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test - updated"), -// ), -// }, -// // IMPORT -// { -// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/complete"), -// ConfigVariables: configVariables2, -// ResourceName: resourceName, -// ImportState: true, -// ImportStateVerify: true, -// // these four are not found in either the show or describe command -// ImportStateVerifyIgnore: []string{"return_null_allowed", "api_integration", "request_translator", "response_translator"}, -// }, -// }, -// }) -//} -// -//func TestAcc_ExternalFunction_migrateFromVersion085(t *testing.T) { -// id := acc.TestClient().Ids.RandomSchemaObjectIdentifierWithArguments([]sdk.DataType{sdk.DataTypeVARCHAR, sdk.DataTypeVARCHAR}) -// name := id.Name() -// resourceName := "snowflake_external_function.f" -// -// resource.Test(t, resource.TestCase{ -// PreCheck: func() { acc.TestAccPreCheck(t) }, -// TerraformVersionChecks: []tfversion.TerraformVersionCheck{ -// tfversion.RequireAbove(tfversion.Version1_5_0), -// }, -// CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), -// -// Steps: []resource.TestStep{ -// { -// ExternalProviders: map[string]resource.ExternalProvider{ -// "snowflake": { -// VersionConstraint: "=0.85.0", -// Source: "Snowflake-Labs/snowflake", -// }, -// }, -// Config: externalFunctionConfig(acc.TestDatabaseName, acc.TestSchemaName, name), -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|%s|%s|VARCHAR-VARCHAR", acc.TestDatabaseName, acc.TestSchemaName, name)), -// resource.TestCheckResourceAttr(resourceName, "name", name), -// resource.TestCheckResourceAttr(resourceName, "database", "\""+acc.TestDatabaseName+"\""), -// resource.TestCheckResourceAttr(resourceName, "schema", "\""+acc.TestSchemaName+"\""), -// resource.TestCheckNoResourceAttr(resourceName, "return_null_allowed"), -// ), -// ExpectNonEmptyPlan: true, -// }, -// { -// ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, -// Config: externalFunctionConfig(acc.TestDatabaseName, acc.TestSchemaName, name), -// ConfigPlanChecks: resource.ConfigPlanChecks{ -// PreApply: []plancheck.PlanCheck{plancheck.ExpectEmptyPlan()}, -// }, -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckResourceAttr(resourceName, "id", id.FullyQualifiedName()), -// resource.TestCheckResourceAttr(resourceName, "name", name), -// resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), -// resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), -// resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), -// ), -// }, -// }, -// }) -//} -// -//func TestAcc_ExternalFunction_migrateFromVersion085_issue2694_previousValuePresent(t *testing.T) { -// name := acc.TestClient().Ids.Alpha() -// resourceName := "snowflake_external_function.f" -// -// resource.Test(t, resource.TestCase{ -// PreCheck: func() { acc.TestAccPreCheck(t) }, -// TerraformVersionChecks: []tfversion.TerraformVersionCheck{ -// tfversion.RequireAbove(tfversion.Version1_5_0), -// }, -// CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), -// -// Steps: []resource.TestStep{ -// { -// ExternalProviders: map[string]resource.ExternalProvider{ -// "snowflake": { -// VersionConstraint: "=0.85.0", -// Source: "Snowflake-Labs/snowflake", -// }, -// }, -// Config: externalFunctionConfigWithReturnNullAllowed(acc.TestDatabaseName, acc.TestSchemaName, name, sdk.Bool(true)), -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), -// ), -// ExpectNonEmptyPlan: true, -// }, -// { -// ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, -// Config: externalFunctionConfig(acc.TestDatabaseName, acc.TestSchemaName, name), -// ConfigPlanChecks: resource.ConfigPlanChecks{ -// PreApply: []plancheck.PlanCheck{plancheck.ExpectEmptyPlan()}, -// }, -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), -// ), -// }, -// }, -// }) -//} -// -//func TestAcc_ExternalFunction_migrateFromVersion085_issue2694_previousValueRemoved(t *testing.T) { -// name := acc.TestClient().Ids.Alpha() -// resourceName := "snowflake_external_function.f" -// -// resource.Test(t, resource.TestCase{ -// PreCheck: func() { acc.TestAccPreCheck(t) }, -// TerraformVersionChecks: []tfversion.TerraformVersionCheck{ -// tfversion.RequireAbove(tfversion.Version1_5_0), -// }, -// CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), -// -// Steps: []resource.TestStep{ -// { -// ExternalProviders: map[string]resource.ExternalProvider{ -// "snowflake": { -// VersionConstraint: "=0.85.0", -// Source: "Snowflake-Labs/snowflake", -// }, -// }, -// Config: externalFunctionConfigWithReturnNullAllowed(acc.TestDatabaseName, acc.TestSchemaName, name, sdk.Bool(true)), -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), -// ), -// ExpectNonEmptyPlan: true, -// }, -// { -// ExternalProviders: map[string]resource.ExternalProvider{ -// "snowflake": { -// VersionConstraint: "=0.85.0", -// Source: "Snowflake-Labs/snowflake", -// }, -// }, -// Config: externalFunctionConfig(acc.TestDatabaseName, acc.TestSchemaName, name), -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckNoResourceAttr(resourceName, "return_null_allowed"), -// ), -// ExpectNonEmptyPlan: true, -// }, -// { -// ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, -// Config: externalFunctionConfig(acc.TestDatabaseName, acc.TestSchemaName, name), -// ConfigPlanChecks: resource.ConfigPlanChecks{ -// PreApply: []plancheck.PlanCheck{plancheck.ExpectEmptyPlan()}, -// }, -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), -// ), -// }, -// }, -// }) -//} -// -//// Proves issue https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2528. -//// The problem originated from ShowById without IN clause. There was no IN clause in the docs at the time. -//// It was raised with the appropriate team in Snowflake. -//func TestAcc_ExternalFunction_issue2528(t *testing.T) { -// accName := acc.TestClient().Ids.Alpha() -// secondSchema := acc.TestClient().Ids.Alpha() -// -// resourceName := "snowflake_external_function.f" -// -// resource.Test(t, resource.TestCase{ -// ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, -// PreCheck: func() { acc.TestAccPreCheck(t) }, -// TerraformVersionChecks: []tfversion.TerraformVersionCheck{ -// tfversion.RequireAbove(tfversion.Version1_5_0), -// }, -// CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), -// Steps: []resource.TestStep{ -// { -// Config: externalFunctionConfigIssue2528(acc.TestDatabaseName, acc.TestSchemaName, accName, secondSchema), -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckResourceAttr(resourceName, "name", accName), -// ), -// }, -// }, -// }) -//} -// -//// Proves that header parsing handles values wrapped in curly braces, e.g. `value = "{1}"` -//func TestAcc_ExternalFunction_HeaderParsing(t *testing.T) { -// id := acc.TestClient().Ids.RandomSchemaObjectIdentifier() -// -// resourceName := "snowflake_external_function.f" -// -// resource.Test(t, resource.TestCase{ -// PreCheck: func() { acc.TestAccPreCheck(t) }, -// TerraformVersionChecks: []tfversion.TerraformVersionCheck{ -// tfversion.RequireAbove(tfversion.Version1_5_0), -// }, -// CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), -// Steps: []resource.TestStep{ -// { -// ExternalProviders: map[string]resource.ExternalProvider{ -// "snowflake": { -// VersionConstraint: "=0.93.0", -// Source: "Snowflake-Labs/snowflake", -// }, -// }, -// Config: externalFunctionConfigIssueCurlyHeader(id), -// // Previous implementation produces a plan with the following changes -// // -// // - header { # forces replacement -// // - name = "name" -> null -// // - value = "0" -> null -// // } -// // -// // + header { # forces replacement -// // + name = "name" -// // + value = "{0}" -// // } -// ExpectNonEmptyPlan: true, -// }, -// { -// ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, -// Config: externalFunctionConfigIssueCurlyHeader(id), -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckResourceAttr(resourceName, "header.#", "1"), -// resource.TestCheckResourceAttr(resourceName, "header.0.name", "name"), -// resource.TestCheckResourceAttr(resourceName, "header.0.value", "{0}"), -// ), -// }, -// }, -// }) -//} -// -//func externalFunctionConfig(database string, schema string, name string) string { -// return externalFunctionConfigWithReturnNullAllowed(database, schema, name, nil) -//} -// -//func externalFunctionConfigWithReturnNullAllowed(database string, schema string, name string, returnNullAllowed *bool) string { -// returnNullAllowedText := "" -// if returnNullAllowed != nil { -// returnNullAllowedText = fmt.Sprintf("return_null_allowed = \"%t\"", *returnNullAllowed) -// } -// -// return fmt.Sprintf(` -//resource "snowflake_api_integration" "test_api_int" { -// name = "%[3]s" -// api_provider = "aws_api_gateway" -// api_aws_role_arn = "arn:aws:iam::000000000001:/role/test" -// api_allowed_prefixes = ["https://123456.execute-api.us-west-2.amazonaws.com/prod/"] -// enabled = true -//} -// -//resource "snowflake_external_function" "f" { -// name = "%[3]s" -// database = "%[1]s" -// schema = "%[2]s" -// arg { -// name = "ARG1" -// type = "VARCHAR" -// } -// arg { -// name = "ARG2" -// type = "VARCHAR" -// } -// return_type = "VARIANT" -// return_behavior = "IMMUTABLE" -// api_integration = snowflake_api_integration.test_api_int.name -// url_of_proxy_and_resource = "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func" -// %[4]s -//} -// -//`, database, schema, name, returnNullAllowedText) -//} -// -//func externalFunctionConfigIssue2528(database string, schema string, name string, schema2 string) string { -// return fmt.Sprintf(` -//resource "snowflake_api_integration" "test_api_int" { -// name = "%[3]s" -// api_provider = "aws_api_gateway" -// api_aws_role_arn = "arn:aws:iam::000000000001:/role/test" -// api_allowed_prefixes = ["https://123456.execute-api.us-west-2.amazonaws.com/prod/"] -// enabled = true -//} -// -//resource "snowflake_schema" "s2" { -// database = "%[1]s" -// name = "%[4]s" -//} -// -//resource "snowflake_external_function" "f" { -// name = "%[3]s" -// database = "%[1]s" -// schema = "%[2]s" -// arg { -// name = "SNS_NOTIF" -// type = "OBJECT" -// } -// return_type = "VARIANT" -// return_behavior = "VOLATILE" -// api_integration = snowflake_api_integration.test_api_int.name -// url_of_proxy_and_resource = "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func" -//} -// -//resource "snowflake_external_function" "f2" { -// depends_on = [snowflake_schema.s2] -// -// name = "%[3]s" -// database = "%[1]s" -// schema = "%[4]s" -// arg { -// name = "SNS_NOTIF" -// type = "OBJECT" -// } -// return_type = "VARIANT" -// return_behavior = "VOLATILE" -// api_integration = snowflake_api_integration.test_api_int.name -// url_of_proxy_and_resource = "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func" -//} -//`, database, schema, name, schema2) -//} -// -//func externalFunctionConfigIssueCurlyHeader(id sdk.SchemaObjectIdentifier) string { -// return fmt.Sprintf(` -//resource "snowflake_api_integration" "test_api_int" { -// name = "%[3]s" -// api_provider = "aws_api_gateway" -// api_aws_role_arn = "arn:aws:iam::000000000001:/role/test" -// api_allowed_prefixes = ["https://123456.execute-api.us-west-2.amazonaws.com/prod/"] -// enabled = true -//} -// -//resource "snowflake_external_function" "f" { -// name = "%[3]s" -// database = "%[1]s" -// schema = "%[2]s" -// arg { -// name = "ARG1" -// type = "VARCHAR" -// } -// arg { -// name = "ARG2" -// type = "VARCHAR" -// } -// header { -// name = "name" -// value = "{0}" -// } -// return_type = "VARIANT" -// return_behavior = "IMMUTABLE" -// api_integration = snowflake_api_integration.test_api_int.name -// url_of_proxy_and_resource = "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func" -//} -// -//`, id.DatabaseName(), id.SchemaName(), id.Name()) -//} +import ( + "fmt" + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/provider/resources" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + "testing" +) + +func TestAcc_ExternalFunction_basic(t *testing.T) { + accName := acc.TestClient().Ids.Alpha() + + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "database": config.StringVariable(acc.TestDatabaseName), + "schema": config.StringVariable(acc.TestSchemaName), + "name": config.StringVariable(accName), + "api_allowed_prefixes": config.ListVariable(config.StringVariable("https://123456.execute-api.us-west-2.amazonaws.com/prod/")), + "url_of_proxy_and_resource": config.StringVariable("https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"), + "comment": config.StringVariable("Terraform acceptance test"), + } + } + + resourceName := "snowflake_external_function.external_function" + configVariables := m() + configVariables2 := m() + configVariables2["comment"] = config.StringVariable("Terraform acceptance test - updated") + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/basic"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", accName), + resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), + resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), + resource.TestCheckResourceAttr(resourceName, "arg.#", "2"), + resource.TestCheckResourceAttr(resourceName, "arg.0.name", "ARG1"), + resource.TestCheckResourceAttr(resourceName, "arg.0.type", "VARCHAR"), + resource.TestCheckResourceAttr(resourceName, "arg.1.name", "ARG2"), + resource.TestCheckResourceAttr(resourceName, "arg.1.type", "VARCHAR"), + resource.TestCheckResourceAttr(resourceName, "null_input_behavior", "CALLED ON NULL INPUT"), + resource.TestCheckResourceAttr(resourceName, "return_type", "VARIANT"), + resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), + resource.TestCheckResourceAttr(resourceName, "return_behavior", "IMMUTABLE"), + resource.TestCheckResourceAttrSet(resourceName, "api_integration"), + resource.TestCheckResourceAttr(resourceName, "compression", "AUTO"), + resource.TestCheckResourceAttr(resourceName, "url_of_proxy_and_resource", "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"), + resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test"), + resource.TestCheckResourceAttrSet(resourceName, "created_on"), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/basic"), + ConfigVariables: configVariables2, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test - updated"), + ), + }, + // IMPORT + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/basic"), + ConfigVariables: configVariables2, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + // these two are not found in either the show or describe command + ImportStateVerifyIgnore: []string{"return_null_allowed", "api_integration"}, + }, + }, + }) +} + +func TestAcc_ExternalFunction_no_arguments(t *testing.T) { + accName := acc.TestClient().Ids.Alpha() + + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "database": config.StringVariable(acc.TestDatabaseName), + "schema": config.StringVariable(acc.TestSchemaName), + "name": config.StringVariable(accName), + "api_allowed_prefixes": config.ListVariable(config.StringVariable("https://123456.execute-api.us-west-2.amazonaws.com/prod/")), + "url_of_proxy_and_resource": config.StringVariable("https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"), + "comment": config.StringVariable("Terraform acceptance test"), + } + } + + resourceName := "snowflake_external_function.external_function" + configVariables := m() + configVariables2 := m() + configVariables2["comment"] = config.StringVariable("Terraform acceptance test - updated") + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/no_arguments"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", accName), + resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), + resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), + resource.TestCheckResourceAttr(resourceName, "arg.#", "0"), + resource.TestCheckResourceAttr(resourceName, "null_input_behavior", "CALLED ON NULL INPUT"), + resource.TestCheckResourceAttr(resourceName, "return_type", "VARIANT"), + resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), + resource.TestCheckResourceAttr(resourceName, "return_behavior", "IMMUTABLE"), + resource.TestCheckResourceAttrSet(resourceName, "api_integration"), + resource.TestCheckResourceAttr(resourceName, "compression", "AUTO"), + resource.TestCheckResourceAttr(resourceName, "url_of_proxy_and_resource", "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"), + resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test"), + resource.TestCheckResourceAttrSet(resourceName, "created_on"), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/no_arguments"), + ConfigVariables: configVariables2, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test - updated"), + ), + }, + // IMPORT + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/no_arguments"), + ConfigVariables: configVariables2, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + // these two are not found in either the show or describe command + ImportStateVerifyIgnore: []string{"return_null_allowed", "api_integration"}, + }, + }, + }) +} + +func TestAcc_ExternalFunction_complete(t *testing.T) { + accName := acc.TestClient().Ids.Alpha() + + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "database": config.StringVariable(acc.TestDatabaseName), + "schema": config.StringVariable(acc.TestSchemaName), + "name": config.StringVariable(accName), + "api_allowed_prefixes": config.ListVariable(config.StringVariable("https://123456.execute-api.us-west-2.amazonaws.com/prod/")), + "url_of_proxy_and_resource": config.StringVariable("https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"), + "comment": config.StringVariable("Terraform acceptance test"), + } + } + + resourceName := "snowflake_external_function.external_function" + configVariables := m() + configVariables2 := m() + configVariables2["comment"] = config.StringVariable("Terraform acceptance test - updated") + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/complete"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", accName), + resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), + resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), + resource.TestCheckResourceAttr(resourceName, "arg.#", "0"), + resource.TestCheckResourceAttr(resourceName, "null_input_behavior", "CALLED ON NULL INPUT"), + resource.TestCheckResourceAttr(resourceName, "return_type", "VARIANT"), + resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), + resource.TestCheckResourceAttr(resourceName, "return_behavior", "IMMUTABLE"), + resource.TestCheckResourceAttrSet(resourceName, "api_integration"), + resource.TestCheckResourceAttr(resourceName, "compression", "AUTO"), + resource.TestCheckResourceAttr(resourceName, "url_of_proxy_and_resource", "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"), + resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test"), + resource.TestCheckResourceAttrSet(resourceName, "created_on"), + resource.TestCheckResourceAttr(resourceName, "header.#", "1"), + resource.TestCheckResourceAttr(resourceName, "header.0.name", "x-custom-header"), + resource.TestCheckResourceAttr(resourceName, "header.0.value", "snowflake"), + resource.TestCheckResourceAttr(resourceName, "max_batch_rows", "500"), + resource.TestCheckResourceAttr(resourceName, "request_translator", fmt.Sprintf("%s.%s.%s%s", acc.TestDatabaseName, acc.TestSchemaName, accName, "_request_translator")), + resource.TestCheckResourceAttr(resourceName, "response_translator", fmt.Sprintf("%s.%s.%s%s", acc.TestDatabaseName, acc.TestSchemaName, accName, "_response_translator")), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/complete"), + ConfigVariables: configVariables2, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "comment", "Terraform acceptance test - updated"), + ), + }, + // IMPORT + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ExternalFunction/complete"), + ConfigVariables: configVariables2, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + // these four are not found in either the show or describe command + ImportStateVerifyIgnore: []string{"return_null_allowed", "api_integration", "request_translator", "response_translator"}, + }, + }, + }) +} + +func TestAcc_ExternalFunction_migrateFromVersion085(t *testing.T) { + id := acc.TestClient().Ids.RandomSchemaObjectIdentifierWithArgumentsOld(sdk.DataTypeVARCHAR, sdk.DataTypeVARCHAR) + name := id.Name() + resourceName := "snowflake_external_function.f" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), + + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "snowflake": { + VersionConstraint: "=0.85.0", + Source: "Snowflake-Labs/snowflake", + }, + }, + Config: externalFunctionConfig(acc.TestDatabaseName, acc.TestSchemaName, name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|%s|%s|VARCHAR-VARCHAR", acc.TestDatabaseName, acc.TestSchemaName, name)), + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "database", "\""+acc.TestDatabaseName+"\""), + resource.TestCheckResourceAttr(resourceName, "schema", "\""+acc.TestSchemaName+"\""), + resource.TestCheckNoResourceAttr(resourceName, "return_null_allowed"), + ), + ExpectNonEmptyPlan: true, + }, + { + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + Config: externalFunctionConfig(acc.TestDatabaseName, acc.TestSchemaName, name), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{plancheck.ExpectEmptyPlan()}, + }, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "id", id.FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "database", acc.TestDatabaseName), + resource.TestCheckResourceAttr(resourceName, "schema", acc.TestSchemaName), + resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), + ), + }, + }, + }) +} + +func TestAcc_ExternalFunction_migrateFromVersion085_issue2694_previousValuePresent(t *testing.T) { + name := acc.TestClient().Ids.Alpha() + resourceName := "snowflake_external_function.f" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), + + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "snowflake": { + VersionConstraint: "=0.85.0", + Source: "Snowflake-Labs/snowflake", + }, + }, + Config: externalFunctionConfigWithReturnNullAllowed(acc.TestDatabaseName, acc.TestSchemaName, name, sdk.Bool(true)), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), + ), + ExpectNonEmptyPlan: true, + }, + { + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + Config: externalFunctionConfig(acc.TestDatabaseName, acc.TestSchemaName, name), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{plancheck.ExpectEmptyPlan()}, + }, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), + ), + }, + }, + }) +} + +func TestAcc_ExternalFunction_migrateFromVersion085_issue2694_previousValueRemoved(t *testing.T) { + name := acc.TestClient().Ids.Alpha() + resourceName := "snowflake_external_function.f" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), + + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "snowflake": { + VersionConstraint: "=0.85.0", + Source: "Snowflake-Labs/snowflake", + }, + }, + Config: externalFunctionConfigWithReturnNullAllowed(acc.TestDatabaseName, acc.TestSchemaName, name, sdk.Bool(true)), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), + ), + ExpectNonEmptyPlan: true, + }, + { + ExternalProviders: map[string]resource.ExternalProvider{ + "snowflake": { + VersionConstraint: "=0.85.0", + Source: "Snowflake-Labs/snowflake", + }, + }, + Config: externalFunctionConfig(acc.TestDatabaseName, acc.TestSchemaName, name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckNoResourceAttr(resourceName, "return_null_allowed"), + ), + ExpectNonEmptyPlan: true, + }, + { + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + Config: externalFunctionConfig(acc.TestDatabaseName, acc.TestSchemaName, name), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{plancheck.ExpectEmptyPlan()}, + }, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "return_null_allowed", "true"), + ), + }, + }, + }) +} + +// Proves issue https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2528. +// The problem originated from ShowById without IN clause. There was no IN clause in the docs at the time. +// It was raised with the appropriate team in Snowflake. +func TestAcc_ExternalFunction_issue2528(t *testing.T) { + accName := acc.TestClient().Ids.Alpha() + secondSchema := acc.TestClient().Ids.Alpha() + + resourceName := "snowflake_external_function.f" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), + Steps: []resource.TestStep{ + { + Config: externalFunctionConfigIssue2528(acc.TestDatabaseName, acc.TestSchemaName, accName, secondSchema), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", accName), + ), + }, + }, + }) +} + +// Proves that header parsing handles values wrapped in curly braces, e.g. `value = "{1}"` +func TestAcc_ExternalFunction_HeaderParsing(t *testing.T) { + id := acc.TestClient().Ids.RandomSchemaObjectIdentifier() + + resourceName := "snowflake_external_function.f" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction), + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "snowflake": { + VersionConstraint: "=0.93.0", + Source: "Snowflake-Labs/snowflake", + }, + }, + Config: externalFunctionConfigIssueCurlyHeader(id), + // Previous implementation produces a plan with the following changes + // + // - header { # forces replacement + // - name = "name" -> null + // - value = "0" -> null + // } + // + // + header { # forces replacement + // + name = "name" + // + value = "{0}" + // } + ExpectNonEmptyPlan: true, + }, + { + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + Config: externalFunctionConfigIssueCurlyHeader(id), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "header.#", "1"), + resource.TestCheckResourceAttr(resourceName, "header.0.name", "name"), + resource.TestCheckResourceAttr(resourceName, "header.0.value", "{0}"), + ), + }, + }, + }) +} + +func externalFunctionConfig(database string, schema string, name string) string { + return externalFunctionConfigWithReturnNullAllowed(database, schema, name, nil) +} + +func externalFunctionConfigWithReturnNullAllowed(database string, schema string, name string, returnNullAllowed *bool) string { + returnNullAllowedText := "" + if returnNullAllowed != nil { + returnNullAllowedText = fmt.Sprintf("return_null_allowed = \"%t\"", *returnNullAllowed) + } + + return fmt.Sprintf(` +resource "snowflake_api_integration" "test_api_int" { + name = "%[3]s" + api_provider = "aws_api_gateway" + api_aws_role_arn = "arn:aws:iam::000000000001:/role/test" + api_allowed_prefixes = ["https://123456.execute-api.us-west-2.amazonaws.com/prod/"] + enabled = true +} + +resource "snowflake_external_function" "f" { + name = "%[3]s" + database = "%[1]s" + schema = "%[2]s" + arg { + name = "ARG1" + type = "VARCHAR" + } + arg { + name = "ARG2" + type = "VARCHAR" + } + return_type = "VARIANT" + return_behavior = "IMMUTABLE" + api_integration = snowflake_api_integration.test_api_int.name + url_of_proxy_and_resource = "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func" + %[4]s +} + +`, database, schema, name, returnNullAllowedText) +} + +func externalFunctionConfigIssue2528(database string, schema string, name string, schema2 string) string { + return fmt.Sprintf(` +resource "snowflake_api_integration" "test_api_int" { + name = "%[3]s" + api_provider = "aws_api_gateway" + api_aws_role_arn = "arn:aws:iam::000000000001:/role/test" + api_allowed_prefixes = ["https://123456.execute-api.us-west-2.amazonaws.com/prod/"] + enabled = true +} + +resource "snowflake_schema" "s2" { + database = "%[1]s" + name = "%[4]s" +} + +resource "snowflake_external_function" "f" { + name = "%[3]s" + database = "%[1]s" + schema = "%[2]s" + arg { + name = "SNS_NOTIF" + type = "OBJECT" + } + return_type = "VARIANT" + return_behavior = "VOLATILE" + api_integration = snowflake_api_integration.test_api_int.name + url_of_proxy_and_resource = "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func" +} + +resource "snowflake_external_function" "f2" { + depends_on = [snowflake_schema.s2] + + name = "%[3]s" + database = "%[1]s" + schema = "%[4]s" + arg { + name = "SNS_NOTIF" + type = "OBJECT" + } + return_type = "VARIANT" + return_behavior = "VOLATILE" + api_integration = snowflake_api_integration.test_api_int.name + url_of_proxy_and_resource = "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func" +} +`, database, schema, name, schema2) +} + +func externalFunctionConfigIssueCurlyHeader(id sdk.SchemaObjectIdentifier) string { + return fmt.Sprintf(` +resource "snowflake_api_integration" "test_api_int" { + name = "%[3]s" + api_provider = "aws_api_gateway" + api_aws_role_arn = "arn:aws:iam::000000000001:/role/test" + api_allowed_prefixes = ["https://123456.execute-api.us-west-2.amazonaws.com/prod/"] + enabled = true +} + +resource "snowflake_external_function" "f" { + name = "%[3]s" + database = "%[1]s" + schema = "%[2]s" + arg { + name = "ARG1" + type = "VARCHAR" + } + arg { + name = "ARG2" + type = "VARCHAR" + } + header { + name = "name" + value = "{0}" + } + return_type = "VARIANT" + return_behavior = "IMMUTABLE" + api_integration = snowflake_api_integration.test_api_int.name + url_of_proxy_and_resource = "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func" +} + +`, id.DatabaseName(), id.SchemaName(), id.Name()) +} diff --git a/pkg/resources/external_function_state_upgraders.go b/pkg/resources/external_function_state_upgraders.go index ebfd6fe2a4..aba74585aa 100644 --- a/pkg/resources/external_function_state_upgraders.go +++ b/pkg/resources/external_function_state_upgraders.go @@ -3,8 +3,9 @@ package resources import ( "context" "encoding/csv" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "strings" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" ) type v085ExternalFunctionId struct { diff --git a/pkg/resources/function.go b/pkg/resources/function.go index f965c1f325..77647ab1bc 100644 --- a/pkg/resources/function.go +++ b/pkg/resources/function.go @@ -3,6 +3,10 @@ package resources import ( "context" "fmt" + "log" + "regexp" + "strings" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/snowflake" @@ -10,9 +14,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "log" - "regexp" - "strings" ) var languages = []string{"javascript", "scala", "java", "sql", "python"} diff --git a/pkg/resources/function_state_upgraders.go b/pkg/resources/function_state_upgraders.go index 1413876a37..501e44f1dc 100644 --- a/pkg/resources/function_state_upgraders.go +++ b/pkg/resources/function_state_upgraders.go @@ -3,8 +3,9 @@ package resources import ( "context" "fmt" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "strings" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" ) type v085FunctionId struct { diff --git a/pkg/resources/procedure.go b/pkg/resources/procedure.go index fc6e918da8..118bcde253 100644 --- a/pkg/resources/procedure.go +++ b/pkg/resources/procedure.go @@ -3,16 +3,17 @@ package resources import ( "context" "fmt" + "log" + "regexp" + "slices" + "strings" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "log" - "regexp" - "slices" - "strings" ) var procedureSchema = map[string]*schema.Schema{ diff --git a/pkg/resources/procedure_acceptance_test.go b/pkg/resources/procedure_acceptance_test.go index fe5ca5283e..b601583a9a 100644 --- a/pkg/resources/procedure_acceptance_test.go +++ b/pkg/resources/procedure_acceptance_test.go @@ -2,6 +2,9 @@ package resources_test import ( "fmt" + "strings" + "testing" + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/provider/resources" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" @@ -9,8 +12,6 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/tfversion" - "strings" - "testing" ) func testAccProcedure(t *testing.T, configDirectory string) { diff --git a/pkg/resources/procedure_state_upgraders.go b/pkg/resources/procedure_state_upgraders.go index a01f256206..24e47d7d9f 100644 --- a/pkg/resources/procedure_state_upgraders.go +++ b/pkg/resources/procedure_state_upgraders.go @@ -3,8 +3,9 @@ package resources import ( "context" "fmt" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "strings" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" ) type v085ProcedureId struct { diff --git a/pkg/schemas/gen/main/main.go b/pkg/schemas/gen/main/main.go index 3e05f5d18c..7236d00304 100644 --- a/pkg/schemas/gen/main/main.go +++ b/pkg/schemas/gen/main/main.go @@ -4,11 +4,12 @@ package main import ( "fmt" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/genhelpers" "os" "slices" "strings" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/genhelpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/schemas/gen" "golang.org/x/exp/maps" ) diff --git a/pkg/sdk/external_functions_impl_gen.go b/pkg/sdk/external_functions_impl_gen.go index 54b282e8c6..fe81d14f56 100644 --- a/pkg/sdk/external_functions_impl_gen.go +++ b/pkg/sdk/external_functions_impl_gen.go @@ -2,6 +2,9 @@ package sdk import ( "context" + "strings" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" ) var _ ExternalFunctions = (*externalFunctions)(nil) @@ -31,32 +34,30 @@ func (v *externalFunctions) Show(ctx context.Context, request *ShowExternalFunct } func (v *externalFunctions) ShowByID(ctx context.Context, id SchemaObjectIdentifier) (*ExternalFunction, error) { - return nil, nil - // TODO - //arguments := id.Arguments() - //externalFunctions, err := v.Show(ctx, NewShowExternalFunctionRequest(). - // WithIn(&In{Schema: id.SchemaId()}). - // WithLike(&Like{Pattern: String(id.Name())})) - //if err != nil { - // return nil, err - //} - //return collections.FindOne(externalFunctions, func(r ExternalFunction) bool { - // database := strings.Trim(r.CatalogName, `"`) - // schema := strings.Trim(r.SchemaName, `"`) - // if r.Name != id.Name() || database != id.DatabaseName() || schema != id.SchemaName() { - // return false - // } - // var sb strings.Builder - // sb.WriteString("(") - // for i, argument := range arguments { - // sb.WriteString(string(argument)) - // if i < len(arguments)-1 { - // sb.WriteString(", ") - // } - // } - // sb.WriteString(")") - // return strings.Contains(r.Arguments, sb.String()) - //}) + arguments := id.Arguments() + externalFunctions, err := v.Show(ctx, NewShowExternalFunctionRequest(). + WithIn(&In{Schema: id.SchemaId()}). + WithLike(&Like{Pattern: String(id.Name())})) + if err != nil { + return nil, err + } + return collections.FindOne(externalFunctions, func(r ExternalFunction) bool { + database := strings.Trim(r.CatalogName, `"`) + schema := strings.Trim(r.SchemaName, `"`) + if r.Name != id.Name() || database != id.DatabaseName() || schema != id.SchemaName() { + return false + } + var sb strings.Builder + sb.WriteString("(") + for i, argument := range arguments { + sb.WriteString(string(argument)) + if i < len(arguments)-1 { + sb.WriteString(", ") + } + } + sb.WriteString(")") + return strings.Contains(r.Arguments, sb.String()) + }) } func (v *externalFunctions) Describe(ctx context.Context, request *DescribeExternalFunctionRequest) ([]ExternalFunctionProperty, error) { @@ -70,23 +71,20 @@ func (v *externalFunctions) Describe(ctx context.Context, request *DescribeExter func (r *CreateExternalFunctionRequest) toOpts() *CreateExternalFunctionOptions { opts := &CreateExternalFunctionOptions{ - OrReplace: r.OrReplace, - Secure: r.Secure, - // TODO: - // name: r.name.WithoutArguments(), - + OrReplace: r.OrReplace, + Secure: r.Secure, + name: r.name.WithoutArguments(), ResultDataType: r.ResultDataType, ReturnNullValues: r.ReturnNullValues, NullInputBehavior: r.NullInputBehavior, ReturnResultsBehavior: r.ReturnResultsBehavior, Comment: r.Comment, ApiIntegration: r.ApiIntegration, - - MaxBatchRows: r.MaxBatchRows, - Compression: r.Compression, - RequestTranslator: r.RequestTranslator, - ResponseTranslator: r.ResponseTranslator, - As: r.As, + MaxBatchRows: r.MaxBatchRows, + Compression: r.Compression, + RequestTranslator: r.RequestTranslator, + ResponseTranslator: r.ResponseTranslator, + As: r.As, } if r.Arguments != nil { s := make([]ExternalFunctionArgument, len(r.Arguments)) @@ -114,9 +112,8 @@ func (r *CreateExternalFunctionRequest) toOpts() *CreateExternalFunctionOptions func (r *AlterExternalFunctionRequest) toOpts() *AlterExternalFunctionOptions { opts := &AlterExternalFunctionOptions{ - IfExists: r.IfExists, - // TODO: - // name: r.name.WithoutArguments(), + IfExists: r.IfExists, + name: r.name.WithoutArguments(), ArgumentDataTypes: r.ArgumentDataTypes, } if r.Set != nil { diff --git a/pkg/sdk/functions_impl_gen.go b/pkg/sdk/functions_impl_gen.go index a082969111..f2123639e0 100644 --- a/pkg/sdk/functions_impl_gen.go +++ b/pkg/sdk/functions_impl_gen.go @@ -2,9 +2,10 @@ package sdk import ( "context" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" "log" "strings" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" ) var _ Functions = (*functions)(nil) @@ -64,8 +65,7 @@ func (v *functions) ShowByID(ctx context.Context, id SchemaObjectIdentifierWithA if err != nil { return nil, err } - // TODO: Should compare arguments - return collections.FindOne(functions, func(r Function) bool { return r.Name == id.Name() }) + return collections.FindOne(functions, func(r Function) bool { return r.ID().FullyQualifiedName() == id.FullyQualifiedName() }) } func (v *functions) Describe(ctx context.Context, id SchemaObjectIdentifierWithArguments) ([]FunctionDetail, error) { diff --git a/pkg/sdk/grants.go b/pkg/sdk/grants.go index 6a5bbef98c..1a28fe3e37 100644 --- a/pkg/sdk/grants.go +++ b/pkg/sdk/grants.go @@ -244,6 +244,7 @@ func (row grantRow) convert() *Grant { } else { granteeName = NewAccountObjectIdentifier(row.GranteeName) } + // TODO(SNOW-1058419): Add parser for function/procedure/external_function identifier representations returned by Snowflake (it's different from the standard identifier for those objects). var grantedOn ObjectType // true for current grants diff --git a/pkg/sdk/identifier_helpers.go b/pkg/sdk/identifier_helpers.go index b0e7b0b7e6..a7a643d167 100644 --- a/pkg/sdk/identifier_helpers.go +++ b/pkg/sdk/identifier_helpers.go @@ -232,7 +232,7 @@ type SchemaObjectIdentifier struct { databaseName string schemaName string name string - // TODO(next prs ???): left right now for backward compatibility for procedures and externalFunctions + // TODO(next prs): left right now for backward compatibility for procedures and externalFunctions arguments []DataType } @@ -334,16 +334,6 @@ func (i SchemaObjectIdentifier) ArgumentsSignature() string { return fmt.Sprintf("%v(%v)", i.Name(), strings.Join(arguments, ",")) } -// TODO: -// - Add parser -// - Add to IsValidIdentifier -// - Handle in the sql_builder -// - Use in function,procedure,external_function -// - Function (Test, Impl) -// - Fix after argumentDataTypes removed from SchemaObjectIdentifier -// - Look for todos on SNOW-999049 - -// TODO: Rename? type SchemaObjectIdentifierWithArguments struct { databaseName string schemaName string @@ -366,9 +356,7 @@ func NewSchemaObjectIdentifierWithArgumentsInSchema(schemaId DatabaseObjectIdent func NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(fullyQualifiedName string) (SchemaObjectIdentifierWithArguments, error) { splitIdIndex := strings.IndexRune(fullyQualifiedName, '(') - parts, err := parseIdentifierStringWithOpts(fullyQualifiedName[:splitIdIndex], func(r *csv.Reader) { - r.Comma = '.' - }) + parts, err := ParseIdentifierString(fullyQualifiedName[:splitIdIndex]) if err != nil { return SchemaObjectIdentifierWithArguments{}, err } @@ -384,47 +372,6 @@ func NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(fullyQualified ), nil } -// TODO: Remove this func -func parseIdentifierStringWithOpts(identifier string, opts func(*csv.Reader)) ([]string, error) { - reader := csv.NewReader(strings.NewReader(identifier)) - if opts != nil { - opts(reader) - } - lines, err := reader.ReadAll() - if err != nil { - return nil, fmt.Errorf("unable to read identifier: %s, err = %w", identifier, err) - } - if lines == nil { - return make([]string, 0), nil - } - if len(lines) != 1 { - return nil, fmt.Errorf("incompatible identifier: %s", identifier) - } - for _, part := range lines[0] { - // TODO(SNOW-1571674): Remove the validation - if strings.Contains(part, `"`) { - return nil, fmt.Errorf(`unable to parse identifier: %s, currently identifiers containing double quotes are not supported in the provider`, identifier) - } - if strings.ContainsAny(part, `()`) { - return nil, fmt.Errorf(`unable to parse identifier: %s, currently identifiers containing '(' or ')' parentheses are not supported in the provider`, identifier) - } - } - return lines[0], nil -} - -// TODO: Move to resource package (or use FullyQUalifiedName and NewFromFullyQualifiedName because it will be needed anyway for things like returned ids from SHOW GRANTS) -//func NewSchemaObjectIdentifierWithArgumentsFromResourceIdentifier(resourceId string) SchemaObjectIdentifierWithArguments { -// // TODO: use standard parsing method -// resourceIdParts := strings.Split(resourceId, "|") -// schemaObjectId := NewSchemaObjectIdentifierFromFullyQualifiedName(resourceIdParts[0]) -// argumentSlice := resourceIdParts[1:] -// arguments := make([]DataType, len(argumentSlice)) -// for i, argument := range argumentSlice { -// arguments[i] = DataType(argument) -// } -// return NewSchemaObjectIdentifierWithArguments(schemaObjectId.DatabaseName(), schemaObjectId.SchemaName(), schemaObjectId.Name(), arguments...) -//} - func (i SchemaObjectIdentifierWithArguments) DatabaseName() string { return i.databaseName } @@ -460,16 +407,6 @@ func (i SchemaObjectIdentifierWithArguments) FullyQualifiedName() string { return fmt.Sprintf(`"%v"."%v"."%v"(%v)`, i.databaseName, i.schemaName, i.name, strings.Join(AsStringList(i.argumentDataTypes), ",")) } -// TODO: Move to resource package -//func (i SchemaObjectIdentifierWithArguments) AsResourceIdentifier() string { -// // TODO: use standard encoding method -// resourceId := []string{ -// i.SchemaObjectId().FullyQualifiedName(), -// } -// resourceId = append(resourceId, AsStringList(i.ArgumentDataTypes())...) -// return strings.Join(resourceId, "|") -//} - type TableColumnIdentifier struct { databaseName string schemaName string diff --git a/pkg/sdk/testint/external_functions_integration_test.go b/pkg/sdk/testint/external_functions_integration_test.go index c901219faa..2901d5e3f7 100644 --- a/pkg/sdk/testint/external_functions_integration_test.go +++ b/pkg/sdk/testint/external_functions_integration_test.go @@ -1,260 +1,269 @@ package testint -//func TestInt_ExternalFunctions(t *testing.T) { -// client := testClient(t) -// ctx := context.Background() -// -// defaultDataTypes := []sdk.DataType{sdk.DataTypeVARCHAR} -// -// integration, integrationCleanup := testClientHelper().ApiIntegration.CreateApiIntegration(t) -// t.Cleanup(integrationCleanup) -// -// cleanupExternalFunctionHandle := func(id sdk.SchemaObjectIdentifier, dts []sdk.DataType) func() { -// return func() { -// err := client.Functions.Drop(ctx, sdk.NewDropFunctionRequest(id.WithoutArguments(), dts).WithIfExists(sdk.Bool(true))) -// require.NoError(t, err) -// } -// } -// -// // TODO [SNOW-999049]: id returned on purpose; address during identifiers rework -// createExternalFunction := func(t *testing.T) (*sdk.ExternalFunction, sdk.SchemaObjectIdentifier) { -// t.Helper() -// id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(defaultDataTypes) -// argument := sdk.NewExternalFunctionArgumentRequest("x", defaultDataTypes[0]) -// as := "https://xyz.execute-api.us-west-2.amazonaws.com/production/remote_echo" -// request := sdk.NewCreateExternalFunctionRequest(id, sdk.DataTypeVariant, sdk.Pointer(integration.ID()), as). -// WithOrReplace(sdk.Bool(true)). -// WithSecure(sdk.Bool(true)). -// WithArguments([]sdk.ExternalFunctionArgumentRequest{*argument}) -// err := client.ExternalFunctions.Create(ctx, request) -// require.NoError(t, err) -// t.Cleanup(cleanupExternalFunctionHandle(id.WithoutArguments(), []sdk.DataType{sdk.DataTypeVariant})) -// -// e, err := client.ExternalFunctions.ShowByID(ctx, id) -// require.NoError(t, err) -// return e, id -// } -// -// assertExternalFunction := func(t *testing.T, id sdk.SchemaObjectIdentifier, secure bool) { -// t.Helper() -// dts := id.Arguments() -// -// e, err := client.ExternalFunctions.ShowByID(ctx, id) -// require.NoError(t, err) -// -// require.NotEmpty(t, e.CreatedOn) -// require.Equal(t, id.Name(), e.Name) -// require.Equal(t, fmt.Sprintf(`"%v"`, id.SchemaName()), e.SchemaName) -// require.Equal(t, false, e.IsBuiltin) -// require.Equal(t, false, e.IsAggregate) -// require.Equal(t, false, e.IsAnsi) -// if len(dts) > 0 { -// require.Equal(t, 1, e.MinNumArguments) -// require.Equal(t, 1, e.MaxNumArguments) -// } else { -// require.Equal(t, 0, e.MinNumArguments) -// require.Equal(t, 0, e.MaxNumArguments) -// } -// require.NotEmpty(t, e.Arguments) -// require.NotEmpty(t, e.Description) -// require.NotEmpty(t, e.CatalogName) -// require.Equal(t, false, e.IsTableFunction) -// require.Equal(t, false, e.ValidForClustering) -// require.Equal(t, secure, e.IsSecure) -// require.Equal(t, true, e.IsExternalFunction) -// require.Equal(t, "EXTERNAL", e.Language) -// require.Equal(t, false, e.IsMemoizable) -// require.Equal(t, false, e.IsDataMetric) -// } -// -// t.Run("create external function", func(t *testing.T) { -// id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(defaultDataTypes) -// argument := sdk.NewExternalFunctionArgumentRequest("x", sdk.DataTypeVARCHAR) -// headers := []sdk.ExternalFunctionHeaderRequest{ -// { -// Name: "measure", -// Value: "kilometers", -// }, -// } -// ch := []sdk.ExternalFunctionContextHeaderRequest{ -// { -// ContextFunction: "CURRENT_DATE", -// }, -// { -// ContextFunction: "CURRENT_TIMESTAMP", -// }, -// } -// as := "https://xyz.execute-api.us-west-2.amazonaws.com/production/remote_echo" -// request := sdk.NewCreateExternalFunctionRequest(id, sdk.DataTypeVariant, sdk.Pointer(integration.ID()), as). -// WithOrReplace(sdk.Bool(true)). -// WithSecure(sdk.Bool(true)). -// WithArguments([]sdk.ExternalFunctionArgumentRequest{*argument}). -// WithNullInputBehavior(sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorCalledOnNullInput)). -// WithHeaders(headers). -// WithContextHeaders(ch). -// WithMaxBatchRows(sdk.Int(10)). -// WithCompression(sdk.String("GZIP")) -// err := client.ExternalFunctions.Create(ctx, request) -// require.NoError(t, err) -// t.Cleanup(cleanupExternalFunctionHandle(id.WithoutArguments(), []sdk.DataType{sdk.DataTypeVariant})) -// -// assertExternalFunction(t, id, true) -// }) -// -// t.Run("create external function without arguments", func(t *testing.T) { -// id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(nil) -// as := "https://xyz.execute-api.us-west-2.amazonaws.com/production/remote_echo" -// request := sdk.NewCreateExternalFunctionRequest(id, sdk.DataTypeVariant, sdk.Pointer(integration.ID()), as) -// err := client.ExternalFunctions.Create(ctx, request) -// require.NoError(t, err) -// t.Cleanup(cleanupExternalFunctionHandle(id, nil)) -// -// assertExternalFunction(t, id, false) -// }) -// -// t.Run("alter external function: set api integration", func(t *testing.T) { -// _, id := createExternalFunction(t) -// set := sdk.NewExternalFunctionSetRequest(). -// WithApiIntegration(sdk.Pointer(integration.ID())) -// request := sdk.NewAlterExternalFunctionRequest(id, defaultDataTypes).WithSet(set) -// err := client.ExternalFunctions.Alter(ctx, request) -// require.NoError(t, err) -// -// assertExternalFunction(t, id, true) -// }) -// -// t.Run("alter external function: set headers", func(t *testing.T) { -// _, id := createExternalFunction(t) -// -// headers := []sdk.ExternalFunctionHeaderRequest{ -// { -// Name: "measure", -// Value: "kilometers", -// }, -// } -// set := sdk.NewExternalFunctionSetRequest().WithHeaders(headers) -// request := sdk.NewAlterExternalFunctionRequest(id, defaultDataTypes).WithSet(set) -// err := client.ExternalFunctions.Alter(ctx, request) -// require.NoError(t, err) -// assertExternalFunction(t, id, true) -// }) -// -// t.Run("alter external function: set context headers", func(t *testing.T) { -// _, id := createExternalFunction(t) -// -// ch := []sdk.ExternalFunctionContextHeaderRequest{ -// { -// ContextFunction: "CURRENT_DATE", -// }, -// { -// ContextFunction: "CURRENT_TIMESTAMP", -// }, -// } -// set := sdk.NewExternalFunctionSetRequest().WithContextHeaders(ch) -// request := sdk.NewAlterExternalFunctionRequest(id, defaultDataTypes).WithSet(set) -// err := client.ExternalFunctions.Alter(ctx, request) -// require.NoError(t, err) -// assertExternalFunction(t, id, true) -// }) -// -// t.Run("alter external function: set compression", func(t *testing.T) { -// _, id := createExternalFunction(t) -// -// set := sdk.NewExternalFunctionSetRequest().WithCompression(sdk.String("AUTO")) -// request := sdk.NewAlterExternalFunctionRequest(id, defaultDataTypes).WithSet(set) -// err := client.ExternalFunctions.Alter(ctx, request) -// require.NoError(t, err) -// assertExternalFunction(t, id, true) -// }) -// -// t.Run("alter external function: set max batch rows", func(t *testing.T) { -// _, id := createExternalFunction(t) -// -// set := sdk.NewExternalFunctionSetRequest().WithMaxBatchRows(sdk.Int(20)) -// request := sdk.NewAlterExternalFunctionRequest(id, defaultDataTypes).WithSet(set) -// err := client.ExternalFunctions.Alter(ctx, request) -// require.NoError(t, err) -// assertExternalFunction(t, id, true) -// }) -// -// t.Run("alter external function: unset", func(t *testing.T) { -// _, id := createExternalFunction(t) -// -// unset := sdk.NewExternalFunctionUnsetRequest(). -// WithComment(sdk.Bool(true)). -// WithHeaders(sdk.Bool(true)) -// request := sdk.NewAlterExternalFunctionRequest(id, defaultDataTypes).WithUnset(unset) -// err := client.ExternalFunctions.Alter(ctx, request) -// require.NoError(t, err) -// -// assertExternalFunction(t, id, true) -// }) -// -// t.Run("show external function: with like", func(t *testing.T) { -// e1, _ := createExternalFunction(t) -// e2, _ := createExternalFunction(t) -// -// es, err := client.ExternalFunctions.Show(ctx, sdk.NewShowExternalFunctionRequest().WithLike(&sdk.Like{Pattern: sdk.String(e1.Name)})) -// require.NoError(t, err) -// -// require.Equal(t, 1, len(es)) -// require.Contains(t, es, *e1) -// require.NotContains(t, es, *e2) -// }) -// -// t.Run("show external function: with in", func(t *testing.T) { -// otherDb, otherDbCleanup := testClientHelper().Database.CreateDatabase(t) -// t.Cleanup(otherDbCleanup) -// -// e1, _ := createExternalFunction(t) -// -// es, err := client.ExternalFunctions.Show(ctx, sdk.NewShowExternalFunctionRequest().WithIn(&sdk.In{Schema: e1.ID().SchemaId()})) -// require.NoError(t, err) -// -// require.Contains(t, es, *e1) -// -// es, err = client.ExternalFunctions.Show(ctx, sdk.NewShowExternalFunctionRequest().WithIn(&sdk.In{Database: testClientHelper().Ids.DatabaseId()})) -// require.NoError(t, err) -// -// require.Contains(t, es, *e1) -// -// es, err = client.ExternalFunctions.Show(ctx, sdk.NewShowExternalFunctionRequest().WithIn(&sdk.In{Database: otherDb.ID()})) -// require.NoError(t, err) -// -// require.Empty(t, es) -// }) -// -// t.Run("show external function: no matches", func(t *testing.T) { -// es, err := client.ExternalFunctions.Show(ctx, sdk.NewShowExternalFunctionRequest().WithLike(&sdk.Like{Pattern: sdk.String("non-existing-id-pattern")})) -// require.NoError(t, err) -// require.Equal(t, 0, len(es)) -// }) -// -// t.Run("show external function by id", func(t *testing.T) { -// e, id := createExternalFunction(t) -// -// es, err := client.ExternalFunctions.ShowByID(ctx, id) -// require.NoError(t, err) -// require.Equal(t, *e, *es) -// -// _, err = client.ExternalFunctions.ShowByID(ctx, id.WithoutArguments()) -// require.Error(t, err, sdk.ErrObjectNotExistOrAuthorized) -// }) -// -// t.Run("describe external function", func(t *testing.T) { -// e, _ := createExternalFunction(t) -// -// request := sdk.NewDescribeExternalFunctionRequest(e.ID(), []sdk.DataType{sdk.DataTypeVARCHAR}) -// details, err := client.ExternalFunctions.Describe(ctx, request) -// require.NoError(t, err) -// pairs := make(map[string]string) -// for _, detail := range details { -// pairs[detail.Property] = detail.Value -// } -// require.Equal(t, "EXTERNAL", pairs["language"]) -// require.Equal(t, "VARIANT", pairs["returns"]) -// require.Equal(t, "VOLATILE", pairs["volatility"]) -// require.Equal(t, "AUTO", pairs["compression"]) -// require.Equal(t, "(X VARCHAR)", pairs["signature"]) -// }) -//} +import ( + "context" + "fmt" + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/stretchr/testify/require" +) + +func TestInt_ExternalFunctions(t *testing.T) { + client := testClient(t) + ctx := context.Background() + + defaultDataTypes := []sdk.DataType{sdk.DataTypeVARCHAR} + + integration, integrationCleanup := testClientHelper().ApiIntegration.CreateApiIntegration(t) + t.Cleanup(integrationCleanup) + + cleanupExternalFunctionHandle := func(id sdk.SchemaObjectIdentifier, dts []sdk.DataType) func() { + return func() { + err := client.Functions.Drop(ctx, sdk.NewDropFunctionRequest(sdk.NewSchemaObjectIdentifierWithArguments(id.DatabaseName(), id.SchemaName(), id.Name(), dts...)).WithIfExists(true)) + require.NoError(t, err) + } + } + + // TODO [SNOW-999049]: id returned on purpose; address during identifiers rework + createExternalFunction := func(t *testing.T) (*sdk.ExternalFunction, sdk.SchemaObjectIdentifier) { + t.Helper() + id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArgumentsOld(defaultDataTypes...) + argument := sdk.NewExternalFunctionArgumentRequest("x", defaultDataTypes[0]) + as := "https://xyz.execute-api.us-west-2.amazonaws.com/production/remote_echo" + request := sdk.NewCreateExternalFunctionRequest(id, sdk.DataTypeVariant, sdk.Pointer(integration.ID()), as). + WithOrReplace(sdk.Bool(true)). + WithSecure(sdk.Bool(true)). + WithArguments([]sdk.ExternalFunctionArgumentRequest{*argument}) + err := client.ExternalFunctions.Create(ctx, request) + require.NoError(t, err) + t.Cleanup(cleanupExternalFunctionHandle(id.WithoutArguments(), []sdk.DataType{sdk.DataTypeVariant})) + + e, err := client.ExternalFunctions.ShowByID(ctx, id) + require.NoError(t, err) + return e, id + } + + assertExternalFunction := func(t *testing.T, id sdk.SchemaObjectIdentifier, secure bool) { + t.Helper() + dts := id.Arguments() + + e, err := client.ExternalFunctions.ShowByID(ctx, id) + require.NoError(t, err) + + require.NotEmpty(t, e.CreatedOn) + require.Equal(t, id.Name(), e.Name) + require.Equal(t, fmt.Sprintf(`"%v"`, id.SchemaName()), e.SchemaName) + require.Equal(t, false, e.IsBuiltin) + require.Equal(t, false, e.IsAggregate) + require.Equal(t, false, e.IsAnsi) + if len(dts) > 0 { + require.Equal(t, 1, e.MinNumArguments) + require.Equal(t, 1, e.MaxNumArguments) + } else { + require.Equal(t, 0, e.MinNumArguments) + require.Equal(t, 0, e.MaxNumArguments) + } + require.NotEmpty(t, e.Arguments) + require.NotEmpty(t, e.Description) + require.NotEmpty(t, e.CatalogName) + require.Equal(t, false, e.IsTableFunction) + require.Equal(t, false, e.ValidForClustering) + require.Equal(t, secure, e.IsSecure) + require.Equal(t, true, e.IsExternalFunction) + require.Equal(t, "EXTERNAL", e.Language) + require.Equal(t, false, e.IsMemoizable) + require.Equal(t, false, e.IsDataMetric) + } + + t.Run("create external function", func(t *testing.T) { + id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArgumentsOld(defaultDataTypes...) + argument := sdk.NewExternalFunctionArgumentRequest("x", sdk.DataTypeVARCHAR) + headers := []sdk.ExternalFunctionHeaderRequest{ + { + Name: "measure", + Value: "kilometers", + }, + } + ch := []sdk.ExternalFunctionContextHeaderRequest{ + { + ContextFunction: "CURRENT_DATE", + }, + { + ContextFunction: "CURRENT_TIMESTAMP", + }, + } + as := "https://xyz.execute-api.us-west-2.amazonaws.com/production/remote_echo" + request := sdk.NewCreateExternalFunctionRequest(id, sdk.DataTypeVariant, sdk.Pointer(integration.ID()), as). + WithOrReplace(sdk.Bool(true)). + WithSecure(sdk.Bool(true)). + WithArguments([]sdk.ExternalFunctionArgumentRequest{*argument}). + WithNullInputBehavior(sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorCalledOnNullInput)). + WithHeaders(headers). + WithContextHeaders(ch). + WithMaxBatchRows(sdk.Int(10)). + WithCompression(sdk.String("GZIP")) + err := client.ExternalFunctions.Create(ctx, request) + require.NoError(t, err) + t.Cleanup(cleanupExternalFunctionHandle(id.WithoutArguments(), []sdk.DataType{sdk.DataTypeVariant})) + + assertExternalFunction(t, id, true) + }) + + t.Run("create external function without arguments", func(t *testing.T) { + id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArgumentsOld() + as := "https://xyz.execute-api.us-west-2.amazonaws.com/production/remote_echo" + request := sdk.NewCreateExternalFunctionRequest(id, sdk.DataTypeVariant, sdk.Pointer(integration.ID()), as) + err := client.ExternalFunctions.Create(ctx, request) + require.NoError(t, err) + t.Cleanup(cleanupExternalFunctionHandle(id, nil)) + + assertExternalFunction(t, id, false) + }) + + t.Run("alter external function: set api integration", func(t *testing.T) { + _, id := createExternalFunction(t) + set := sdk.NewExternalFunctionSetRequest(). + WithApiIntegration(sdk.Pointer(integration.ID())) + request := sdk.NewAlterExternalFunctionRequest(id, defaultDataTypes).WithSet(set) + err := client.ExternalFunctions.Alter(ctx, request) + require.NoError(t, err) + + assertExternalFunction(t, id, true) + }) + + t.Run("alter external function: set headers", func(t *testing.T) { + _, id := createExternalFunction(t) + + headers := []sdk.ExternalFunctionHeaderRequest{ + { + Name: "measure", + Value: "kilometers", + }, + } + set := sdk.NewExternalFunctionSetRequest().WithHeaders(headers) + request := sdk.NewAlterExternalFunctionRequest(id, defaultDataTypes).WithSet(set) + err := client.ExternalFunctions.Alter(ctx, request) + require.NoError(t, err) + assertExternalFunction(t, id, true) + }) + + t.Run("alter external function: set context headers", func(t *testing.T) { + _, id := createExternalFunction(t) + + ch := []sdk.ExternalFunctionContextHeaderRequest{ + { + ContextFunction: "CURRENT_DATE", + }, + { + ContextFunction: "CURRENT_TIMESTAMP", + }, + } + set := sdk.NewExternalFunctionSetRequest().WithContextHeaders(ch) + request := sdk.NewAlterExternalFunctionRequest(id, defaultDataTypes).WithSet(set) + err := client.ExternalFunctions.Alter(ctx, request) + require.NoError(t, err) + assertExternalFunction(t, id, true) + }) + + t.Run("alter external function: set compression", func(t *testing.T) { + _, id := createExternalFunction(t) + + set := sdk.NewExternalFunctionSetRequest().WithCompression(sdk.String("AUTO")) + request := sdk.NewAlterExternalFunctionRequest(id, defaultDataTypes).WithSet(set) + err := client.ExternalFunctions.Alter(ctx, request) + require.NoError(t, err) + assertExternalFunction(t, id, true) + }) + + t.Run("alter external function: set max batch rows", func(t *testing.T) { + _, id := createExternalFunction(t) + + set := sdk.NewExternalFunctionSetRequest().WithMaxBatchRows(sdk.Int(20)) + request := sdk.NewAlterExternalFunctionRequest(id, defaultDataTypes).WithSet(set) + err := client.ExternalFunctions.Alter(ctx, request) + require.NoError(t, err) + assertExternalFunction(t, id, true) + }) + + t.Run("alter external function: unset", func(t *testing.T) { + _, id := createExternalFunction(t) + + unset := sdk.NewExternalFunctionUnsetRequest(). + WithComment(sdk.Bool(true)). + WithHeaders(sdk.Bool(true)) + request := sdk.NewAlterExternalFunctionRequest(id, defaultDataTypes).WithUnset(unset) + err := client.ExternalFunctions.Alter(ctx, request) + require.NoError(t, err) + + assertExternalFunction(t, id, true) + }) + + t.Run("show external function: with like", func(t *testing.T) { + e1, _ := createExternalFunction(t) + e2, _ := createExternalFunction(t) + + es, err := client.ExternalFunctions.Show(ctx, sdk.NewShowExternalFunctionRequest().WithLike(&sdk.Like{Pattern: sdk.String(e1.Name)})) + require.NoError(t, err) + + require.Equal(t, 1, len(es)) + require.Contains(t, es, *e1) + require.NotContains(t, es, *e2) + }) + + t.Run("show external function: with in", func(t *testing.T) { + otherDb, otherDbCleanup := testClientHelper().Database.CreateDatabase(t) + t.Cleanup(otherDbCleanup) + + e1, _ := createExternalFunction(t) + + es, err := client.ExternalFunctions.Show(ctx, sdk.NewShowExternalFunctionRequest().WithIn(&sdk.In{Schema: e1.ID().SchemaId()})) + require.NoError(t, err) + + require.Contains(t, es, *e1) + + es, err = client.ExternalFunctions.Show(ctx, sdk.NewShowExternalFunctionRequest().WithIn(&sdk.In{Database: testClientHelper().Ids.DatabaseId()})) + require.NoError(t, err) + + require.Contains(t, es, *e1) + + es, err = client.ExternalFunctions.Show(ctx, sdk.NewShowExternalFunctionRequest().WithIn(&sdk.In{Database: otherDb.ID()})) + require.NoError(t, err) + + require.Empty(t, es) + }) + + t.Run("show external function: no matches", func(t *testing.T) { + es, err := client.ExternalFunctions.Show(ctx, sdk.NewShowExternalFunctionRequest().WithLike(&sdk.Like{Pattern: sdk.String("non-existing-id-pattern")})) + require.NoError(t, err) + require.Equal(t, 0, len(es)) + }) + + t.Run("show external function by id", func(t *testing.T) { + e, id := createExternalFunction(t) + + es, err := client.ExternalFunctions.ShowByID(ctx, id) + require.NoError(t, err) + require.Equal(t, *e, *es) + + _, err = client.ExternalFunctions.ShowByID(ctx, id.WithoutArguments()) + require.Error(t, err, sdk.ErrObjectNotExistOrAuthorized) + }) + + t.Run("describe external function", func(t *testing.T) { + e, _ := createExternalFunction(t) + + request := sdk.NewDescribeExternalFunctionRequest(e.ID(), []sdk.DataType{sdk.DataTypeVARCHAR}) + details, err := client.ExternalFunctions.Describe(ctx, request) + require.NoError(t, err) + pairs := make(map[string]string) + for _, detail := range details { + pairs[detail.Property] = detail.Value + } + require.Equal(t, "EXTERNAL", pairs["language"]) + require.Equal(t, "VARIANT", pairs["returns"]) + require.Equal(t, "VOLATILE", pairs["volatility"]) + require.Equal(t, "AUTO", pairs["compression"]) + require.Equal(t, "(X VARCHAR)", pairs["signature"]) + }) +} diff --git a/pkg/sdk/testint/functions_integration_test.go b/pkg/sdk/testint/functions_integration_test.go index 883a90c6bd..2f530e054a 100644 --- a/pkg/sdk/testint/functions_integration_test.go +++ b/pkg/sdk/testint/functions_integration_test.go @@ -4,11 +4,12 @@ import ( "context" "errors" "fmt" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" - "github.com/stretchr/testify/assert" "testing" "time" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" + "github.com/stretchr/testify/assert" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/stretchr/testify/require" ) @@ -261,16 +262,14 @@ func TestInt_OtherFunctions(t *testing.T) { return function } - defaultAlterRequest := func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { - return sdk.NewAlterFunctionRequest(id) - } - t.Run("alter function: rename", func(t *testing.T) { f := createFunctionForSQLHandle(t, false, true) id := f.ID() nid := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithRenameTo(nid.SchemaObjectId())) + err := client.Functions.Alter(ctx, func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { + return sdk.NewAlterFunctionRequest(id) + }(id).WithRenameTo(nid.SchemaObjectId())) if err != nil { t.Cleanup(cleanupFunctionHandle(id)) } else { @@ -290,7 +289,9 @@ func TestInt_OtherFunctions(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) id := f.ID() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetLogLevel(string(sdk.LogLevelDebug))) + err := client.Functions.Alter(ctx, func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { + return sdk.NewAlterFunctionRequest(id) + }(id).WithSetLogLevel(string(sdk.LogLevelDebug))) require.NoError(t, err) assertFunction(t, id, false, true) }) @@ -299,7 +300,9 @@ func TestInt_OtherFunctions(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) id := f.ID() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetLogLevel(true)) + err := client.Functions.Alter(ctx, func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { + return sdk.NewAlterFunctionRequest(id) + }(id).WithUnsetLogLevel(true)) require.NoError(t, err) assertFunction(t, id, false, true) }) @@ -308,7 +311,9 @@ func TestInt_OtherFunctions(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) id := f.ID() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetTraceLevel(string(sdk.TraceLevelAlways))) + err := client.Functions.Alter(ctx, func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { + return sdk.NewAlterFunctionRequest(id) + }(id).WithSetTraceLevel(string(sdk.TraceLevelAlways))) require.NoError(t, err) assertFunction(t, id, false, true) }) @@ -317,7 +322,9 @@ func TestInt_OtherFunctions(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) id := f.ID() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetTraceLevel(true)) + err := client.Functions.Alter(ctx, func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { + return sdk.NewAlterFunctionRequest(id) + }(id).WithUnsetTraceLevel(true)) require.NoError(t, err) assertFunction(t, id, false, true) }) @@ -326,7 +333,9 @@ func TestInt_OtherFunctions(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) id := f.ID() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetComment("test comment")) + err := client.Functions.Alter(ctx, func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { + return sdk.NewAlterFunctionRequest(id) + }(id).WithSetComment("test comment")) require.NoError(t, err) assertFunction(t, id, false, true) }) @@ -335,7 +344,9 @@ func TestInt_OtherFunctions(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) id := f.ID() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetComment(true)) + err := client.Functions.Alter(ctx, func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { + return sdk.NewAlterFunctionRequest(id) + }(id).WithUnsetComment(true)) require.NoError(t, err) assertFunction(t, id, false, true) }) @@ -344,7 +355,9 @@ func TestInt_OtherFunctions(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) id := f.ID() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetSecure(true)) + err := client.Functions.Alter(ctx, func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { + return sdk.NewAlterFunctionRequest(id) + }(id).WithSetSecure(true)) require.NoError(t, err) assertFunction(t, id, true, true) }) @@ -361,7 +374,9 @@ func TestInt_OtherFunctions(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) id := f.ID() - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetSecure(true)) + err := client.Functions.Alter(ctx, func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { + return sdk.NewAlterFunctionRequest(id) + }(id).WithUnsetSecure(true)) require.NoError(t, err) assertFunction(t, id, false, true) }) @@ -376,14 +391,18 @@ func TestInt_OtherFunctions(t *testing.T) { Value: "v1", }, } - err := client.Functions.Alter(ctx, defaultAlterRequest(id).WithSetTags(setTags)) + err := client.Functions.Alter(ctx, func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { + return sdk.NewAlterFunctionRequest(id) + }(id).WithSetTags(setTags)) require.NoError(t, err) assertFunction(t, id, false, true) unsetTags := []sdk.ObjectIdentifier{ tagTest.ID(), } - err = client.Functions.Alter(ctx, defaultAlterRequest(id).WithUnsetTags(unsetTags)) + err = client.Functions.Alter(ctx, func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { + return sdk.NewAlterFunctionRequest(id) + }(id).WithUnsetTags(unsetTags)) require.NoError(t, err) assertFunction(t, id, false, true) }) @@ -503,64 +522,9 @@ func TestInt_FunctionsShowByID(t *testing.T) { }) t.Run("function returns non detailed data types of arguments", func(t *testing.T) { - // This test proves that every detailed data type (e.g. VARCHAR(20) and NUMBER(10, 0)) is generalized - // (to e.g. VARCHAR and NUMBER) and that sdk.ToDataType mapping function maps detailed types correctly to - // their generalized counterparts. - - id := testClientHelper().Ids.RandomSchemaObjectIdentifier() - args := []sdk.FunctionArgumentRequest{ - *sdk.NewFunctionArgumentRequest("A", "NUMBER(2, 0)"), - *sdk.NewFunctionArgumentRequest("B", "DECIMAL"), - *sdk.NewFunctionArgumentRequest("C", "INTEGER"), - *sdk.NewFunctionArgumentRequest("D", sdk.DataTypeFloat), - *sdk.NewFunctionArgumentRequest("E", "DOUBLE"), - *sdk.NewFunctionArgumentRequest("F", "VARCHAR(20)"), - *sdk.NewFunctionArgumentRequest("G", "CHAR"), - *sdk.NewFunctionArgumentRequest("H", sdk.DataTypeString), - *sdk.NewFunctionArgumentRequest("I", "TEXT"), - *sdk.NewFunctionArgumentRequest("J", sdk.DataTypeBinary), - *sdk.NewFunctionArgumentRequest("K", "VARBINARY"), - *sdk.NewFunctionArgumentRequest("L", sdk.DataTypeBoolean), - *sdk.NewFunctionArgumentRequest("M", sdk.DataTypeDate), - *sdk.NewFunctionArgumentRequest("N", "DATETIME"), - *sdk.NewFunctionArgumentRequest("O", sdk.DataTypeTime), - *sdk.NewFunctionArgumentRequest("P", sdk.DataTypeTimestamp), - *sdk.NewFunctionArgumentRequest("R", sdk.DataTypeTimestampLTZ), - *sdk.NewFunctionArgumentRequest("S", sdk.DataTypeTimestampNTZ), - *sdk.NewFunctionArgumentRequest("T", sdk.DataTypeTimestampTZ), - *sdk.NewFunctionArgumentRequest("U", sdk.DataTypeVariant), - *sdk.NewFunctionArgumentRequest("V", sdk.DataTypeObject), - *sdk.NewFunctionArgumentRequest("W", sdk.DataTypeArray), - *sdk.NewFunctionArgumentRequest("X", sdk.DataTypeGeography), - *sdk.NewFunctionArgumentRequest("Y", sdk.DataTypeGeometry), - *sdk.NewFunctionArgumentRequest("Z", "VECTOR(INT, 16)"), - } - err := client.Functions.CreateForPython(ctx, sdk.NewCreateForPythonFunctionRequest( - id, - *sdk.NewFunctionReturnsRequest().WithResultDataType(*sdk.NewFunctionReturnsResultDataTypeRequest(sdk.DataTypeVariant)), - "3.8", - "add", - ). - WithArguments(args). - WithFunctionDefinition("def add(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, R, S, T, U, V, W, X, Y, Z): A + A"), - ) - require.NoError(t, err) - - dataTypes := make([]sdk.DataType, len(args)) - for i, arg := range args { - dataTypes[i], err = sdk.ToDataType(string(arg.ArgDataType)) - require.NoError(t, err) - } - idWithArguments := sdk.NewSchemaObjectIdentifierWithArguments(id.DatabaseName(), id.SchemaName(), id.Name(), dataTypes...) - - _, err = client.Functions.ShowByID(ctx, idWithArguments) - require.NoError(t, err) - }) - - t.Run("function returns non detailed data types of arguments", func(t *testing.T) { - // This test proves that every detailed data type (e.g. VARCHAR(20) and NUMBER(10, 0)) is generalized - // (to e.g. VARCHAR and NUMBER) and that sdk.ToDataType mapping function maps detailed types correctly to - // their generalized counterparts. + // This test proves that every detailed data types (e.g. VARCHAR(20) and NUMBER(10, 0)) are generalized + // on Snowflake side (to e.g. VARCHAR and NUMBER) and that sdk.ToDataType mapping function maps detailed types + // correctly to their generalized counterparts (same as in Snowflake). id := testClientHelper().Ids.RandomSchemaObjectIdentifier() args := []sdk.FunctionArgumentRequest{ diff --git a/pkg/sdk/testint/procedures_integration_test.go b/pkg/sdk/testint/procedures_integration_test.go index ae48927d08..ea4df83a82 100644 --- a/pkg/sdk/testint/procedures_integration_test.go +++ b/pkg/sdk/testint/procedures_integration_test.go @@ -1,1016 +1,1027 @@ package testint +import ( + "errors" + "fmt" + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" + "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) -// -// 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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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/developer-guide/stored-procedure/stored-procedures-javascript#basic-examples -// name := "stproc1" -// id := testClientHelper().Ids.NewSchemaObjectIdentifier(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/developer-guide/stored-procedure/stored-procedures-javascript#basic-examples -// name := "sp_pi" -// id := testClientHelper().Ids.NewSchemaObjectIdentifier(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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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{"INT"})) -// -// 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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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) -// 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("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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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)). -// // Suddenly this is erroring out, when it used to not have an problem. Must be an error with the Snowflake API. -// // Created issue in docs-discuss channel. https://snowflake.slack.com/archives/C6380540P/p1707511734666249 -// // Error: Received unexpected error: -// // 001003 (42000): SQL compilation error: -// // syntax error line 1 at position 210 unexpected 'NULL'. -// // syntax error line 1 at position 215 unexpected 'ON'. -// // WithNullInputBehavior(sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorReturnNullInput)). -// 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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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)). -// // SNOW-1051627 todo: uncomment once null input behavior working again -// // WithNullInputBehavior(sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorReturnNullInput)). -// 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) -// -// tagTest, tagCleanup := testClientHelper().Tag.CreateTag(t) -// 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 := testClientHelper().Ids.RandomSchemaObjectIdentifier() -// 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 := f.ID() -// nid := testClientHelper().Ids.RandomSchemaObjectIdentifier() -// 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 := f.ID() -// 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 := f.ID() -// 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 := f.ID() -// 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 := f.ID() -// 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 := f.ID() -// 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 := f.ID() -// 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("non-existing-id-pattern")})) -// 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 := f.ID() -// -// 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 := testClientHelper().Ids.RandomSchemaObjectIdentifier() -// 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) -// }) -//} -// -//func TestInt_CallProcedure(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) -// } -// } -// -// createTableHandle := func(t *testing.T, table sdk.SchemaObjectIdentifier) { -// t.Helper() -// -// _, err := client.ExecForTests(ctx, fmt.Sprintf(`CREATE OR REPLACE TABLE %s (id NUMBER, name VARCHAR, role VARCHAR)`, table.FullyQualifiedName())) -// require.NoError(t, err) -// _, err = client.ExecForTests(ctx, fmt.Sprintf(`INSERT INTO %s (id, name, role) VALUES (1, 'Alice', 'op'), (2, 'Bob', 'dev'), (3, 'Cindy', 'dev')`, table.FullyQualifiedName())) -// require.NoError(t, err) -// t.Cleanup(func() { -// _, err := client.ExecForTests(ctx, fmt.Sprintf(`DROP TABLE %s`, table.FullyQualifiedName())) -// require.NoError(t, err) -// }) -// } -// -// // create a employees table -// tid := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, "employees") -// createTableHandle(t, tid) -// -// createProcedureForSQLHandle := func(t *testing.T, cleanup bool) *sdk.Procedure { -// t.Helper() -// -// definition := ` -// BEGIN -// RETURN message; -// END;` -// id := testClientHelper().Ids.RandomSchemaObjectIdentifier() -// 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 -// } -// -// t.Run("call procedure for SQL: argument positions", func(t *testing.T) { -// f := createProcedureForSQLHandle(t, true) -// err := client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(f.ID()).WithCallArguments([]string{"'hi'"})) -// require.NoError(t, err) -// }) -// -// t.Run("call procedure for SQL: argument names", func(t *testing.T) { -// f := createProcedureForSQLHandle(t, true) -// err := client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(f.ID()).WithCallArguments([]string{"message => 'hi'"})) -// require.NoError(t, err) -// }) -// -// t.Run("call procedure for Java: returns table", func(t *testing.T) { -// // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-java#omitting-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 name, String role) { -// DataFrame table = session.table(name); -// 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("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})) -// -// ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} -// err = client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(id).WithCallArguments(ca)) -// require.NoError(t, err) -// }) -// -// t.Run("call procedure for Scala: returns table", func(t *testing.T) { -// // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-scala#omitting-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, name: String, role: String): DataFrame = { -// val table = session.table(name) -// val filteredRows = table.filter(col("role") === role) -// return filteredRows -// } -// }` -// returnsTable := sdk.NewProcedureReturnsTableRequest().WithColumns([]sdk.ProcedureColumnRequest{}) -// returns := sdk.NewProcedureReturnsRequest().WithTable(returnsTable) -// arg1 := sdk.NewProcedureArgumentRequest("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})) -// -// ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} -// err = client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(id).WithCallArguments(ca)) -// require.NoError(t, err) -// }) -// -// t.Run("call procedure for Javascript", func(t *testing.T) { -// // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-javascript#basic-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. -// }` -// arg := sdk.NewProcedureArgumentRequest("FLOAT_PARAM1", sdk.DataTypeFloat) -// request := sdk.NewCreateForJavaScriptProcedureRequest(id, sdk.DataTypeString, definition). -// WithOrReplace(sdk.Bool(true)). -// WithArguments([]sdk.ProcedureArgumentRequest{*arg}). -// WithNullInputBehavior(sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorStrict)). -// WithExecuteAs(sdk.ExecuteAsPointer(sdk.ExecuteAsOwner)) -// err := client.Procedures.CreateForJavaScript(ctx, request) -// require.NoError(t, err) -// t.Cleanup(cleanupProcedureHandle(id, []sdk.DataType{sdk.DataTypeFloat})) -// -// err = client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(id).WithCallArguments([]string{"5.14::FLOAT"})) -// require.NoError(t, err) -// }) -// -// t.Run("call procedure for Javascript: no arguments", func(t *testing.T) { -// // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-javascript#basic-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)) -// -// err = client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(id)) -// require.NoError(t, err) -// }) -// -// t.Run("call procedure for Python: returns table", func(t *testing.T) { -// // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-python#omitting-return-column-names-and-types -// id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, "filterByRole") -// -// definition := ` -//from snowflake.snowpark.functions import col -//def filter_by_role(session, name, role): -// df = session.table(name) -// return df.filter(col("role") == role)` -// returnsTable := sdk.NewProcedureReturnsTableRequest().WithColumns([]sdk.ProcedureColumnRequest{}) -// returns := sdk.NewProcedureReturnsRequest().WithTable(returnsTable) -// arg1 := sdk.NewProcedureArgumentRequest("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})) -// -// id = sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, "filterByRole") -// ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} -// err = client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(id).WithCallArguments(ca)) -// require.NoError(t, err) -// }) -//} -// -//func TestInt_CreateAndCallProcedures(t *testing.T) { -// client := testClient(t) -// ctx := testContext(t) -// -// databaseTest, schemaTest := testDb(t), testSchema(t) -// createTableHandle := func(t *testing.T, table sdk.SchemaObjectIdentifier) { -// t.Helper() -// -// _, err := client.ExecForTests(ctx, fmt.Sprintf(`CREATE OR REPLACE TABLE %s (id NUMBER, name VARCHAR, role VARCHAR)`, table.FullyQualifiedName())) -// require.NoError(t, err) -// _, err = client.ExecForTests(ctx, fmt.Sprintf(`INSERT INTO %s (id, name, role) VALUES (1, 'Alice', 'op'), (2, 'Bob', 'dev'), (3, 'Cindy', 'dev')`, table.FullyQualifiedName())) -// require.NoError(t, err) -// t.Cleanup(func() { -// _, err := client.ExecForTests(ctx, fmt.Sprintf(`DROP TABLE %s`, table.FullyQualifiedName())) -// require.NoError(t, err) -// }) -// } -// -// // create a employees table -// tid := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, "employees") -// createTableHandle(t, tid) -// -// t.Run("create and call procedure for Java: returns table", func(t *testing.T) { -// // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-java#omitting-return-column-names-and-types -// // TODO [SNOW-1348106]: make random with procedures rework -// name := sdk.NewAccountObjectIdentifier("filter_by_role") -// -// definition := ` -// import com.snowflake.snowpark_java.*; -// public class Filter { -// public DataFrame filterByRole(Session session, String name, String role) { -// DataFrame table = session.table(name); -// 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("name", sdk.DataTypeVARCHAR) -// arg2 := sdk.NewProcedureArgumentRequest("role", sdk.DataTypeVARCHAR) -// packages := []sdk.ProcedurePackageRequest{*sdk.NewProcedurePackageRequest("com.snowflake:snowpark:latest")} -// ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} -// request := sdk.NewCreateAndCallForJavaProcedureRequest(name, *returns, "11", packages, "Filter.filterByRole", name). -// WithArguments([]sdk.ProcedureArgumentRequest{*arg1, *arg2}). -// WithProcedureDefinition(sdk.String(definition)). -// WithCallArguments(ca) -// err := client.Procedures.CreateAndCallForJava(ctx, request) -// require.NoError(t, err) -// }) -// -// t.Run("create and call procedure for Scala: returns table", func(t *testing.T) { -// // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-scala#omitting-return-column-names-and-types -// // TODO [SNOW-1348106]: make random with procedures rework -// name := sdk.NewAccountObjectIdentifier("filter_by_role") -// -// definition := ` -// import com.snowflake.snowpark.functions._ -// import com.snowflake.snowpark._ -// -// object Filter { -// def filterByRole(session: Session, name: String, role: String): DataFrame = { -// val table = session.table(name) -// 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("name", sdk.DataTypeVARCHAR) -// arg2 := sdk.NewProcedureArgumentRequest("role", sdk.DataTypeVARCHAR) -// packages := []sdk.ProcedurePackageRequest{*sdk.NewProcedurePackageRequest("com.snowflake:snowpark:latest")} -// ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} -// request := sdk.NewCreateAndCallForScalaProcedureRequest(name, *returns, "2.12", packages, "Filter.filterByRole", name). -// WithArguments([]sdk.ProcedureArgumentRequest{*arg1, *arg2}). -// WithProcedureDefinition(sdk.String(definition)). -// WithCallArguments(ca) -// err := client.Procedures.CreateAndCallForScala(ctx, request) -// require.NoError(t, err) -// }) -// -// t.Run("create and call procedure for Javascript", func(t *testing.T) { -// // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-javascript#basic-examples -// // TODO [SNOW-1348106]: make random with procedures rework -// name := sdk.NewAccountObjectIdentifier("stproc1") -// -// 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. -// }` -// arg := sdk.NewProcedureArgumentRequest("FLOAT_PARAM1", sdk.DataTypeFloat) -// request := sdk.NewCreateAndCallForJavaScriptProcedureRequest(name, sdk.DataTypeString, definition, name). -// WithArguments([]sdk.ProcedureArgumentRequest{*arg}). -// WithNullInputBehavior(sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorStrict)). -// WithCallArguments([]string{"5.14::FLOAT"}) -// err := client.Procedures.CreateAndCallForJavaScript(ctx, request) -// require.NoError(t, err) -// }) -// -// t.Run("create and call procedure for Javascript: no arguments", func(t *testing.T) { -// // https://docs.snowflake.com/en/sql-reference/sql/create-procedure#examples -// // TODO [SNOW-1348106]: make random with procedures rework -// name := sdk.NewAccountObjectIdentifier("sp_pi") -// -// definition := `return 3.1415926;` -// request := sdk.NewCreateAndCallForJavaScriptProcedureRequest(name, sdk.DataTypeFloat, definition, name).WithNotNull(sdk.Bool(true)) -// err := client.Procedures.CreateAndCallForJavaScript(ctx, request) -// require.NoError(t, err) -// }) -// -// t.Run("create and call procedure for SQL: argument positions", func(t *testing.T) { -// definition := ` -// BEGIN -// RETURN message; -// END;` -// -// name := testClientHelper().Ids.RandomAccountObjectIdentifier() -// dt := sdk.NewProcedureReturnsResultDataTypeRequest(sdk.DataTypeVARCHAR) -// returns := sdk.NewProcedureReturnsRequest().WithResultDataType(dt) -// argument := sdk.NewProcedureArgumentRequest("message", sdk.DataTypeVARCHAR) -// request := sdk.NewCreateAndCallForSQLProcedureRequest(name, *returns, definition, name). -// WithArguments([]sdk.ProcedureArgumentRequest{*argument}). -// WithCallArguments([]string{"message => 'hi'"}) -// err := client.Procedures.CreateAndCallForSQL(ctx, request) -// require.NoError(t, err) -// }) -// -// t.Run("create and call procedure for Python: returns table", func(t *testing.T) { -// // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-python#omitting-return-column-names-and-types -// // TODO [SNOW-1348106]: make random with procedures rework -// name := sdk.NewAccountObjectIdentifier("filterByRole") -// definition := ` -//from snowflake.snowpark.functions import col -//def filter_by_role(session, name, role): -// df = session.table(name) -// return df.filter(col("role") == role)` -// returnsTable := sdk.NewProcedureReturnsTableRequest().WithColumns([]sdk.ProcedureColumnRequest{}) -// returns := sdk.NewProcedureReturnsRequest().WithTable(returnsTable) -// arg1 := sdk.NewProcedureArgumentRequest("name", sdk.DataTypeVARCHAR) -// arg2 := sdk.NewProcedureArgumentRequest("role", sdk.DataTypeVARCHAR) -// packages := []sdk.ProcedurePackageRequest{*sdk.NewProcedurePackageRequest("snowflake-snowpark-python")} -// ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} -// request := sdk.NewCreateAndCallForPythonProcedureRequest(name, *returns, "3.8", packages, "filter_by_role", name). -// WithArguments([]sdk.ProcedureArgumentRequest{*arg1, *arg2}). -// WithProcedureDefinition(sdk.String(definition)). -// WithCallArguments(ca) -// err := client.Procedures.CreateAndCallForPython(ctx, request) -// require.NoError(t, err) -// }) -// -// t.Run("create and call procedure for Java: returns table and with clause", func(t *testing.T) { -// // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-java#omitting-return-column-names-and-types -// // TODO [SNOW-1348106]: make random with procedures rework -// name := sdk.NewAccountObjectIdentifier("filter_by_role") -// definition := ` -// import com.snowflake.snowpark_java.*; -// public class Filter { -// public DataFrame filterByRole(Session session, String name, String role) { -// DataFrame table = session.table(name); -// 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("name", sdk.DataTypeVARCHAR) -// arg2 := sdk.NewProcedureArgumentRequest("role", sdk.DataTypeVARCHAR) -// packages := []sdk.ProcedurePackageRequest{*sdk.NewProcedurePackageRequest("com.snowflake:snowpark:latest")} -// -// ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} -// // TODO [SNOW-1348106]: make random with procedures rework -// cte := sdk.NewAccountObjectIdentifier("records") -// statement := fmt.Sprintf(`(SELECT name, role FROM %s WHERE name = 'Bob')`, tid.FullyQualifiedName()) -// clause := sdk.NewProcedureWithClauseRequest(cte, statement).WithCteColumns([]string{"name", "role"}) -// request := sdk.NewCreateAndCallForJavaProcedureRequest(name, *returns, "11", packages, "Filter.filterByRole", name). -// WithArguments([]sdk.ProcedureArgumentRequest{*arg1, *arg2}). -// WithProcedureDefinition(sdk.String(definition)). -// WithWithClause(clause). -// WithCallArguments(ca) -// err := client.Procedures.CreateAndCallForJava(ctx, request) -// require.NoError(t, err) -// }) -//} -// -//func TestInt_ProceduresShowByID(t *testing.T) { -// client := testClient(t) -// ctx := testContext(t) -// -// cleanupProcedureHandle := func(id sdk.SchemaObjectIdentifier, dts []sdk.DataType) func() { -// return func() { -// err := client.Procedures.Drop(ctx, sdk.NewDropProcedureRequest(id, dts)) -// if errors.Is(err, sdk.ErrObjectNotExistOrAuthorized) { -// return -// } -// require.NoError(t, err) -// } -// } -// -// createProcedureForSQLHandle := func(t *testing.T, id sdk.SchemaObjectIdentifier) { -// t.Helper() -// -// 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). -// WithArguments([]sdk.ProcedureArgumentRequest{*argument}). -// WithExecuteAs(sdk.ExecuteAsPointer(sdk.ExecuteAsCaller)) -// err := client.Procedures.CreateForSQL(ctx, request) -// require.NoError(t, err) -// t.Cleanup(cleanupProcedureHandle(id, []sdk.DataType{sdk.DataTypeVARCHAR})) -// } -// -// t.Run("show by id - same name in different schemas", func(t *testing.T) { -// schema, schemaCleanup := testClientHelper().Schema.CreateSchema(t) -// t.Cleanup(schemaCleanup) -// -// id1 := testClientHelper().Ids.RandomSchemaObjectIdentifier() -// id2 := testClientHelper().Ids.NewSchemaObjectIdentifierInSchema(id1.Name(), schema.ID()) -// -// createProcedureForSQLHandle(t, id1) -// createProcedureForSQLHandle(t, id2) -// -// e1, err := client.Procedures.ShowByID(ctx, id1) -// require.NoError(t, err) -// require.Equal(t, id1, e1.ID()) -// -// e2, err := client.Procedures.ShowByID(ctx, id2) -// require.NoError(t, err) -// require.Equal(t, id2, e2.ID()) -// }) -//} +func TestInt_CreateProcedures(t *testing.T) { + client := testClient(t) + ctx := testContext(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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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/developer-guide/stored-procedure/stored-procedures-javascript#basic-examples + name := "stproc1" + id := testClientHelper().Ids.NewSchemaObjectIdentifier(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/developer-guide/stored-procedure/stored-procedures-javascript#basic-examples + name := "sp_pi" + id := testClientHelper().Ids.NewSchemaObjectIdentifier(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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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{"INT"})) + + 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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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) + 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("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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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)). + // Suddenly this is erroring out, when it used to not have an problem. Must be an error with the Snowflake API. + // Created issue in docs-discuss channel. https://snowflake.slack.com/archives/C6380540P/p1707511734666249 + // Error: Received unexpected error: + // 001003 (42000): SQL compilation error: + // syntax error line 1 at position 210 unexpected 'NULL'. + // syntax error line 1 at position 215 unexpected 'ON'. + // WithNullInputBehavior(sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorReturnNullInput)). + 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 := testClientHelper().Ids.NewSchemaObjectIdentifier(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)). + // SNOW-1051627 todo: uncomment once null input behavior working again + // WithNullInputBehavior(sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorReturnNullInput)). + 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) + + tagTest, tagCleanup := testClientHelper().Tag.CreateTag(t) + 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 := testClientHelper().Ids.RandomSchemaObjectIdentifier() + 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 := f.ID() + nid := testClientHelper().Ids.RandomSchemaObjectIdentifier() + 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 := f.ID() + 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 := f.ID() + 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 := f.ID() + 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 := f.ID() + 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 := f.ID() + 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 := f.ID() + 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("non-existing-id-pattern")})) + 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 := f.ID() + + 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 := testClientHelper().Ids.RandomSchemaObjectIdentifier() + 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) + }) +} + +func TestInt_CallProcedure(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) + } + } + + createTableHandle := func(t *testing.T, table sdk.SchemaObjectIdentifier) { + t.Helper() + + _, err := client.ExecForTests(ctx, fmt.Sprintf(`CREATE OR REPLACE TABLE %s (id NUMBER, name VARCHAR, role VARCHAR)`, table.FullyQualifiedName())) + require.NoError(t, err) + _, err = client.ExecForTests(ctx, fmt.Sprintf(`INSERT INTO %s (id, name, role) VALUES (1, 'Alice', 'op'), (2, 'Bob', 'dev'), (3, 'Cindy', 'dev')`, table.FullyQualifiedName())) + require.NoError(t, err) + t.Cleanup(func() { + _, err := client.ExecForTests(ctx, fmt.Sprintf(`DROP TABLE %s`, table.FullyQualifiedName())) + require.NoError(t, err) + }) + } + + // create a employees table + tid := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, "employees") + createTableHandle(t, tid) + + createProcedureForSQLHandle := func(t *testing.T, cleanup bool) *sdk.Procedure { + t.Helper() + + definition := ` + BEGIN + RETURN message; + END;` + id := testClientHelper().Ids.RandomSchemaObjectIdentifier() + 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 + } + + t.Run("call procedure for SQL: argument positions", func(t *testing.T) { + f := createProcedureForSQLHandle(t, true) + err := client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(f.ID()).WithCallArguments([]string{"'hi'"})) + require.NoError(t, err) + }) + + t.Run("call procedure for SQL: argument names", func(t *testing.T) { + f := createProcedureForSQLHandle(t, true) + err := client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(f.ID()).WithCallArguments([]string{"message => 'hi'"})) + require.NoError(t, err) + }) + + t.Run("call procedure for Java: returns table", func(t *testing.T) { + // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-java#omitting-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 name, String role) { + DataFrame table = session.table(name); + 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("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})) + + ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} + err = client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(id).WithCallArguments(ca)) + require.NoError(t, err) + }) + + t.Run("call procedure for Scala: returns table", func(t *testing.T) { + // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-scala#omitting-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, name: String, role: String): DataFrame = { + val table = session.table(name) + val filteredRows = table.filter(col("role") === role) + return filteredRows + } + }` + returnsTable := sdk.NewProcedureReturnsTableRequest().WithColumns([]sdk.ProcedureColumnRequest{}) + returns := sdk.NewProcedureReturnsRequest().WithTable(returnsTable) + arg1 := sdk.NewProcedureArgumentRequest("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})) + + ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} + err = client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(id).WithCallArguments(ca)) + require.NoError(t, err) + }) + + t.Run("call procedure for Javascript", func(t *testing.T) { + // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-javascript#basic-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. + }` + arg := sdk.NewProcedureArgumentRequest("FLOAT_PARAM1", sdk.DataTypeFloat) + request := sdk.NewCreateForJavaScriptProcedureRequest(id, sdk.DataTypeString, definition). + WithOrReplace(sdk.Bool(true)). + WithArguments([]sdk.ProcedureArgumentRequest{*arg}). + WithNullInputBehavior(sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorStrict)). + WithExecuteAs(sdk.ExecuteAsPointer(sdk.ExecuteAsOwner)) + err := client.Procedures.CreateForJavaScript(ctx, request) + require.NoError(t, err) + t.Cleanup(cleanupProcedureHandle(id, []sdk.DataType{sdk.DataTypeFloat})) + + err = client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(id).WithCallArguments([]string{"5.14::FLOAT"})) + require.NoError(t, err) + }) + + t.Run("call procedure for Javascript: no arguments", func(t *testing.T) { + // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-javascript#basic-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)) + + err = client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(id)) + require.NoError(t, err) + }) + + t.Run("call procedure for Python: returns table", func(t *testing.T) { + // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-python#omitting-return-column-names-and-types + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, "filterByRole") + + definition := ` +from snowflake.snowpark.functions import col +def filter_by_role(session, name, role): + df = session.table(name) + return df.filter(col("role") == role)` + returnsTable := sdk.NewProcedureReturnsTableRequest().WithColumns([]sdk.ProcedureColumnRequest{}) + returns := sdk.NewProcedureReturnsRequest().WithTable(returnsTable) + arg1 := sdk.NewProcedureArgumentRequest("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})) + + id = sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, "filterByRole") + ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} + err = client.Procedures.Call(ctx, sdk.NewCallProcedureRequest(id).WithCallArguments(ca)) + require.NoError(t, err) + }) +} + +func TestInt_CreateAndCallProcedures(t *testing.T) { + client := testClient(t) + ctx := testContext(t) + + databaseTest, schemaTest := testDb(t), testSchema(t) + createTableHandle := func(t *testing.T, table sdk.SchemaObjectIdentifier) { + t.Helper() + + _, err := client.ExecForTests(ctx, fmt.Sprintf(`CREATE OR REPLACE TABLE %s (id NUMBER, name VARCHAR, role VARCHAR)`, table.FullyQualifiedName())) + require.NoError(t, err) + _, err = client.ExecForTests(ctx, fmt.Sprintf(`INSERT INTO %s (id, name, role) VALUES (1, 'Alice', 'op'), (2, 'Bob', 'dev'), (3, 'Cindy', 'dev')`, table.FullyQualifiedName())) + require.NoError(t, err) + t.Cleanup(func() { + _, err := client.ExecForTests(ctx, fmt.Sprintf(`DROP TABLE %s`, table.FullyQualifiedName())) + require.NoError(t, err) + }) + } + + // create a employees table + tid := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, "employees") + createTableHandle(t, tid) + + t.Run("create and call procedure for Java: returns table", func(t *testing.T) { + // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-java#omitting-return-column-names-and-types + // TODO [SNOW-1348106]: make random with procedures rework + name := sdk.NewAccountObjectIdentifier("filter_by_role") + + definition := ` + import com.snowflake.snowpark_java.*; + public class Filter { + public DataFrame filterByRole(Session session, String name, String role) { + DataFrame table = session.table(name); + 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("name", sdk.DataTypeVARCHAR) + arg2 := sdk.NewProcedureArgumentRequest("role", sdk.DataTypeVARCHAR) + packages := []sdk.ProcedurePackageRequest{*sdk.NewProcedurePackageRequest("com.snowflake:snowpark:latest")} + ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} + request := sdk.NewCreateAndCallForJavaProcedureRequest(name, *returns, "11", packages, "Filter.filterByRole", name). + WithArguments([]sdk.ProcedureArgumentRequest{*arg1, *arg2}). + WithProcedureDefinition(sdk.String(definition)). + WithCallArguments(ca) + err := client.Procedures.CreateAndCallForJava(ctx, request) + require.NoError(t, err) + }) + + t.Run("create and call procedure for Scala: returns table", func(t *testing.T) { + // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-scala#omitting-return-column-names-and-types + // TODO [SNOW-1348106]: make random with procedures rework + name := sdk.NewAccountObjectIdentifier("filter_by_role") + + definition := ` + import com.snowflake.snowpark.functions._ + import com.snowflake.snowpark._ + + object Filter { + def filterByRole(session: Session, name: String, role: String): DataFrame = { + val table = session.table(name) + 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("name", sdk.DataTypeVARCHAR) + arg2 := sdk.NewProcedureArgumentRequest("role", sdk.DataTypeVARCHAR) + packages := []sdk.ProcedurePackageRequest{*sdk.NewProcedurePackageRequest("com.snowflake:snowpark:latest")} + ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} + request := sdk.NewCreateAndCallForScalaProcedureRequest(name, *returns, "2.12", packages, "Filter.filterByRole", name). + WithArguments([]sdk.ProcedureArgumentRequest{*arg1, *arg2}). + WithProcedureDefinition(sdk.String(definition)). + WithCallArguments(ca) + err := client.Procedures.CreateAndCallForScala(ctx, request) + require.NoError(t, err) + }) + + t.Run("create and call procedure for Javascript", func(t *testing.T) { + // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-javascript#basic-examples + // TODO [SNOW-1348106]: make random with procedures rework + name := sdk.NewAccountObjectIdentifier("stproc1") + + 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. + }` + arg := sdk.NewProcedureArgumentRequest("FLOAT_PARAM1", sdk.DataTypeFloat) + request := sdk.NewCreateAndCallForJavaScriptProcedureRequest(name, sdk.DataTypeString, definition, name). + WithArguments([]sdk.ProcedureArgumentRequest{*arg}). + WithNullInputBehavior(sdk.NullInputBehaviorPointer(sdk.NullInputBehaviorStrict)). + WithCallArguments([]string{"5.14::FLOAT"}) + err := client.Procedures.CreateAndCallForJavaScript(ctx, request) + require.NoError(t, err) + }) + + t.Run("create and call procedure for Javascript: no arguments", func(t *testing.T) { + // https://docs.snowflake.com/en/sql-reference/sql/create-procedure#examples + // TODO [SNOW-1348106]: make random with procedures rework + name := sdk.NewAccountObjectIdentifier("sp_pi") + + definition := `return 3.1415926;` + request := sdk.NewCreateAndCallForJavaScriptProcedureRequest(name, sdk.DataTypeFloat, definition, name).WithNotNull(sdk.Bool(true)) + err := client.Procedures.CreateAndCallForJavaScript(ctx, request) + require.NoError(t, err) + }) + + t.Run("create and call procedure for SQL: argument positions", func(t *testing.T) { + definition := ` + BEGIN + RETURN message; + END;` + + name := testClientHelper().Ids.RandomAccountObjectIdentifier() + dt := sdk.NewProcedureReturnsResultDataTypeRequest(sdk.DataTypeVARCHAR) + returns := sdk.NewProcedureReturnsRequest().WithResultDataType(dt) + argument := sdk.NewProcedureArgumentRequest("message", sdk.DataTypeVARCHAR) + request := sdk.NewCreateAndCallForSQLProcedureRequest(name, *returns, definition, name). + WithArguments([]sdk.ProcedureArgumentRequest{*argument}). + WithCallArguments([]string{"message => 'hi'"}) + err := client.Procedures.CreateAndCallForSQL(ctx, request) + require.NoError(t, err) + }) + + t.Run("create and call procedure for Python: returns table", func(t *testing.T) { + // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-python#omitting-return-column-names-and-types + // TODO [SNOW-1348106]: make random with procedures rework + name := sdk.NewAccountObjectIdentifier("filterByRole") + definition := ` +from snowflake.snowpark.functions import col +def filter_by_role(session, name, role): + df = session.table(name) + return df.filter(col("role") == role)` + returnsTable := sdk.NewProcedureReturnsTableRequest().WithColumns([]sdk.ProcedureColumnRequest{}) + returns := sdk.NewProcedureReturnsRequest().WithTable(returnsTable) + arg1 := sdk.NewProcedureArgumentRequest("name", sdk.DataTypeVARCHAR) + arg2 := sdk.NewProcedureArgumentRequest("role", sdk.DataTypeVARCHAR) + packages := []sdk.ProcedurePackageRequest{*sdk.NewProcedurePackageRequest("snowflake-snowpark-python")} + ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} + request := sdk.NewCreateAndCallForPythonProcedureRequest(name, *returns, "3.8", packages, "filter_by_role", name). + WithArguments([]sdk.ProcedureArgumentRequest{*arg1, *arg2}). + WithProcedureDefinition(sdk.String(definition)). + WithCallArguments(ca) + err := client.Procedures.CreateAndCallForPython(ctx, request) + require.NoError(t, err) + }) + + t.Run("create and call procedure for Java: returns table and with clause", func(t *testing.T) { + // https://docs.snowflake.com/en/developer-guide/stored-procedure/stored-procedures-java#omitting-return-column-names-and-types + // TODO [SNOW-1348106]: make random with procedures rework + name := sdk.NewAccountObjectIdentifier("filter_by_role") + definition := ` + import com.snowflake.snowpark_java.*; + public class Filter { + public DataFrame filterByRole(Session session, String name, String role) { + DataFrame table = session.table(name); + 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("name", sdk.DataTypeVARCHAR) + arg2 := sdk.NewProcedureArgumentRequest("role", sdk.DataTypeVARCHAR) + packages := []sdk.ProcedurePackageRequest{*sdk.NewProcedurePackageRequest("com.snowflake:snowpark:latest")} + + ca := []string{fmt.Sprintf(`'%s'`, tid.FullyQualifiedName()), "'dev'"} + // TODO [SNOW-1348106]: make random with procedures rework + cte := sdk.NewAccountObjectIdentifier("records") + statement := fmt.Sprintf(`(SELECT name, role FROM %s WHERE name = 'Bob')`, tid.FullyQualifiedName()) + clause := sdk.NewProcedureWithClauseRequest(cte, statement).WithCteColumns([]string{"name", "role"}) + request := sdk.NewCreateAndCallForJavaProcedureRequest(name, *returns, "11", packages, "Filter.filterByRole", name). + WithArguments([]sdk.ProcedureArgumentRequest{*arg1, *arg2}). + WithProcedureDefinition(sdk.String(definition)). + WithWithClause(clause). + WithCallArguments(ca) + err := client.Procedures.CreateAndCallForJava(ctx, request) + require.NoError(t, err) + }) +} + +func TestInt_ProceduresShowByID(t *testing.T) { + client := testClient(t) + ctx := testContext(t) + + cleanupProcedureHandle := func(id sdk.SchemaObjectIdentifier, dts []sdk.DataType) func() { + return func() { + err := client.Procedures.Drop(ctx, sdk.NewDropProcedureRequest(id, dts)) + if errors.Is(err, sdk.ErrObjectNotExistOrAuthorized) { + return + } + require.NoError(t, err) + } + } + + createProcedureForSQLHandle := func(t *testing.T, id sdk.SchemaObjectIdentifier) { + t.Helper() + + 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). + WithArguments([]sdk.ProcedureArgumentRequest{*argument}). + WithExecuteAs(sdk.ExecuteAsPointer(sdk.ExecuteAsCaller)) + err := client.Procedures.CreateForSQL(ctx, request) + require.NoError(t, err) + t.Cleanup(cleanupProcedureHandle(id, []sdk.DataType{sdk.DataTypeVARCHAR})) + } + + t.Run("show by id - same name in different schemas", func(t *testing.T) { + schema, schemaCleanup := testClientHelper().Schema.CreateSchema(t) + t.Cleanup(schemaCleanup) + + id1 := testClientHelper().Ids.RandomSchemaObjectIdentifier() + id2 := testClientHelper().Ids.NewSchemaObjectIdentifierInSchema(id1.Name(), schema.ID()) + + createProcedureForSQLHandle(t, id1) + createProcedureForSQLHandle(t, id2) + + e1, err := client.Procedures.ShowByID(ctx, id1) + require.NoError(t, err) + require.Equal(t, id1, e1.ID()) + + e2, err := client.Procedures.ShowByID(ctx, id2) + require.NoError(t, err) + require.Equal(t, id2, e2.ID()) + }) +} From 8d283eebce94895d6d26e7de23c0cd510878dc34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Tue, 6 Aug 2024 15:36:08 +0200 Subject: [PATCH 11/13] changes after review --- pkg/resources/external_function_acceptance_test.go | 3 ++- pkg/resources/function_acceptance_test.go | 2 +- pkg/sdk/data_types_test.go | 4 ++++ pkg/sdk/identifier_helpers_test.go | 2 +- pkg/sdk/testint/functions_integration_test.go | 12 +++--------- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/pkg/resources/external_function_acceptance_test.go b/pkg/resources/external_function_acceptance_test.go index 2d31be5ccc..d147071fd2 100644 --- a/pkg/resources/external_function_acceptance_test.go +++ b/pkg/resources/external_function_acceptance_test.go @@ -2,6 +2,8 @@ package resources_test import ( "fmt" + "testing" + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/provider/resources" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" @@ -9,7 +11,6 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/tfversion" - "testing" ) func TestAcc_ExternalFunction_basic(t *testing.T) { diff --git a/pkg/resources/function_acceptance_test.go b/pkg/resources/function_acceptance_test.go index a05bdd1ff6..b4e99be301 100644 --- a/pkg/resources/function_acceptance_test.go +++ b/pkg/resources/function_acceptance_test.go @@ -236,7 +236,7 @@ func TestAcc_Function_migrateFromVersion085(t *testing.T) { }) } -func TestAcc_Function_Version0941_EnsureSmoothResourceIdMigration(t *testing.T) { +func TestAcc_Function_EnsureSmoothResourceIdMigrationToV0950(t *testing.T) { name := acc.TestClient().Ids.RandomAccountObjectIdentifier().Name() resourceName := "snowflake_function.f" diff --git a/pkg/sdk/data_types_test.go b/pkg/sdk/data_types_test.go index 7123fd4092..17abeca4c4 100644 --- a/pkg/sdk/data_types_test.go +++ b/pkg/sdk/data_types_test.go @@ -77,6 +77,10 @@ func TestToDataType(t *testing.T) { {input: "array", want: DataTypeArray}, {input: "geography", want: DataTypeGeography}, {input: "geometry", want: DataTypeGeometry}, + {input: "VECTOR(INT, 10)", want: "VECTOR(INT, 10)"}, + {input: "VECTOR(INT, 20)", want: "VECTOR(INT, 20)"}, + {input: "VECTOR(FLOAT, 10)", want: "VECTOR(FLOAT, 10)"}, + {input: "VECTOR(FLOAT, 20)", want: "VECTOR(FLOAT, 20)"}, } for _, tc := range tests { diff --git a/pkg/sdk/identifier_helpers_test.go b/pkg/sdk/identifier_helpers_test.go index 2f409cc84d..59a3534fff 100644 --- a/pkg/sdk/identifier_helpers_test.go +++ b/pkg/sdk/identifier_helpers_test.go @@ -95,7 +95,7 @@ func TestNewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(t *testing {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, "VECTOR(INT, 20)", DataTypeFloat)}, {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeFloat, "VECTOR(INT, 20)", "VECTOR(INT, 10)")}, {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeTime, "VECTOR(INT, 20)", "VECTOR(FLOAT, 10)", DataTypeFloat)}, - // TODO(): Won't work, because of the assumption that identifiers are not containing '(' and ')' parentheses + // TODO(SNOW-1571674): Won't work, because of the assumption that identifiers are not containing '(' and ')' parentheses {Input: NewSchemaObjectIdentifierWithArguments(`ab()c`, `def()`, `()ghi`, DataTypeTime, "VECTOR(INT, 20)", "VECTOR(FLOAT, 10)", DataTypeFloat), Error: `unable to read identifier: "ab`}, {Input: NewSchemaObjectIdentifierWithArguments(`ab(,)c`, `,def()`, `()ghi,`, DataTypeTime, "VECTOR(INT, 20)", "VECTOR(FLOAT, 10)", DataTypeFloat), Error: `unable to read identifier: "ab`}, {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`)}, diff --git a/pkg/sdk/testint/functions_integration_test.go b/pkg/sdk/testint/functions_integration_test.go index 2f530e054a..d70b2c0e9a 100644 --- a/pkg/sdk/testint/functions_integration_test.go +++ b/pkg/sdk/testint/functions_integration_test.go @@ -267,9 +267,7 @@ func TestInt_OtherFunctions(t *testing.T) { id := f.ID() nid := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments() - err := client.Functions.Alter(ctx, func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { - return sdk.NewAlterFunctionRequest(id) - }(id).WithRenameTo(nid.SchemaObjectId())) + err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithRenameTo(nid.SchemaObjectId())) if err != nil { t.Cleanup(cleanupFunctionHandle(id)) } else { @@ -289,9 +287,7 @@ func TestInt_OtherFunctions(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) id := f.ID() - err := client.Functions.Alter(ctx, func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { - return sdk.NewAlterFunctionRequest(id) - }(id).WithSetLogLevel(string(sdk.LogLevelDebug))) + err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithSetLogLevel(string(sdk.LogLevelDebug))) require.NoError(t, err) assertFunction(t, id, false, true) }) @@ -300,9 +296,7 @@ func TestInt_OtherFunctions(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) id := f.ID() - err := client.Functions.Alter(ctx, func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { - return sdk.NewAlterFunctionRequest(id) - }(id).WithUnsetLogLevel(true)) + err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithUnsetLogLevel(true)) require.NoError(t, err) assertFunction(t, id, false, true) }) From 0b04a7fb558bbb7faa7d178adfa68739bfc78431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Wed, 7 Aug 2024 14:38:16 +0200 Subject: [PATCH 12/13] changes after review --- pkg/acceptance/check_destroy.go | 2 +- pkg/resources/function.go | 6 +- pkg/sdk/data_types.go | 6 ++ pkg/sdk/identifier_helpers.go | 18 ----- pkg/sdk/identifier_helpers_test.go | 40 ----------- pkg/sdk/identifier_parsers.go | 51 +++++++++++++- pkg/sdk/identifier_parsers_test.go | 70 +++++++++++++++++++ pkg/sdk/testint/functions_integration_test.go | 32 +++------ 8 files changed, 138 insertions(+), 87 deletions(-) diff --git a/pkg/acceptance/check_destroy.go b/pkg/acceptance/check_destroy.go index e5ae067b60..617c90e5d2 100644 --- a/pkg/acceptance/check_destroy.go +++ b/pkg/acceptance/check_destroy.go @@ -53,7 +53,7 @@ func decodeSnowflakeId(rs *terraform.ResourceState, resource resources.Resource) case resources.ExternalFunction: return sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(rs.Primary.ID), nil case resources.Function: - return sdk.NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(rs.Primary.ID) + return sdk.ParseSchemaObjectIdentifierWithArguments(rs.Primary.ID) case resources.Procedure: return sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(rs.Primary.ID), nil default: diff --git a/pkg/resources/function.go b/pkg/resources/function.go index 77647ab1bc..f222c9b0a7 100644 --- a/pkg/resources/function.go +++ b/pkg/resources/function.go @@ -517,7 +517,7 @@ func ReadContextFunction(ctx context.Context, d *schema.ResourceData, meta inter diags := diag.Diagnostics{} client := meta.(*provider.Context).Client - id, err := sdk.NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(d.Id()) + id, err := sdk.ParseSchemaObjectIdentifierWithArguments(d.Id()) if err != nil { return diag.FromErr(err) } @@ -651,7 +651,7 @@ func ReadContextFunction(ctx context.Context, d *schema.ResourceData, meta inter func UpdateContextFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*provider.Context).Client - id, err := sdk.NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(d.Id()) + id, err := sdk.ParseSchemaObjectIdentifierWithArguments(d.Id()) if err != nil { return diag.FromErr(err) } @@ -699,7 +699,7 @@ func UpdateContextFunction(ctx context.Context, d *schema.ResourceData, meta int func DeleteContextFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*provider.Context).Client - id, err := sdk.NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(d.Id()) + id, err := sdk.ParseSchemaObjectIdentifierWithArguments(d.Id()) if err != nil { return diag.FromErr(err) } diff --git a/pkg/sdk/data_types.go b/pkg/sdk/data_types.go index 1bb428beea..9a8c46c07f 100644 --- a/pkg/sdk/data_types.go +++ b/pkg/sdk/data_types.go @@ -9,8 +9,14 @@ import ( // DataType is based on https://docs.snowflake.com/en/sql-reference/intro-summary-data-types. type DataType string +var allowedVectorInnerTypes = []DataType{ + DataTypeInt, + DataTypeFloat, +} + const ( DataTypeNumber DataType = "NUMBER" + DataTypeInt DataType = "INT" DataTypeFloat DataType = "FLOAT" DataTypeVARCHAR DataType = "VARCHAR" DataTypeString DataType = "STRING" diff --git a/pkg/sdk/identifier_helpers.go b/pkg/sdk/identifier_helpers.go index a7a643d167..8bc4a33852 100644 --- a/pkg/sdk/identifier_helpers.go +++ b/pkg/sdk/identifier_helpers.go @@ -354,24 +354,6 @@ func NewSchemaObjectIdentifierWithArgumentsInSchema(schemaId DatabaseObjectIdent return NewSchemaObjectIdentifierWithArguments(schemaId.DatabaseName(), schemaId.Name(), name, argumentDataTypes...) } -func NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(fullyQualifiedName string) (SchemaObjectIdentifierWithArguments, error) { - splitIdIndex := strings.IndexRune(fullyQualifiedName, '(') - parts, err := ParseIdentifierString(fullyQualifiedName[:splitIdIndex]) - if err != nil { - return SchemaObjectIdentifierWithArguments{}, err - } - dataTypes, err := ParseFunctionArgumentsFromString(fullyQualifiedName[splitIdIndex:]) - if err != nil { - return SchemaObjectIdentifierWithArguments{}, err - } - return NewSchemaObjectIdentifierWithArguments( - parts[0], - parts[1], - parts[2], - dataTypes..., - ), nil -} - func (i SchemaObjectIdentifierWithArguments) DatabaseName() string { return i.databaseName } diff --git a/pkg/sdk/identifier_helpers_test.go b/pkg/sdk/identifier_helpers_test.go index 59a3534fff..d3b0a98bd3 100644 --- a/pkg/sdk/identifier_helpers_test.go +++ b/pkg/sdk/identifier_helpers_test.go @@ -1,7 +1,6 @@ package sdk import ( - "fmt" "testing" "github.com/stretchr/testify/assert" @@ -83,42 +82,3 @@ func TestDatabaseObjectIdentifier(t *testing.T) { assert.Equal(t, `"aaa"."bbb"`, identifier.FullyQualifiedName()) }) } - -func TestNewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(t *testing.T) { - testCases := []struct { - RawInput string - Input SchemaObjectIdentifierWithArguments - Error string - }{ - {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeFloat, DataTypeNumber, DataTypeTimestampTZ)}, - {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeFloat, "VECTOR(INT, 20)")}, - {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, "VECTOR(INT, 20)", DataTypeFloat)}, - {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeFloat, "VECTOR(INT, 20)", "VECTOR(INT, 10)")}, - {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeTime, "VECTOR(INT, 20)", "VECTOR(FLOAT, 10)", DataTypeFloat)}, - // TODO(SNOW-1571674): Won't work, because of the assumption that identifiers are not containing '(' and ')' parentheses - {Input: NewSchemaObjectIdentifierWithArguments(`ab()c`, `def()`, `()ghi`, DataTypeTime, "VECTOR(INT, 20)", "VECTOR(FLOAT, 10)", DataTypeFloat), Error: `unable to read identifier: "ab`}, - {Input: NewSchemaObjectIdentifierWithArguments(`ab(,)c`, `,def()`, `()ghi,`, DataTypeTime, "VECTOR(INT, 20)", "VECTOR(FLOAT, 10)", DataTypeFloat), Error: `unable to read identifier: "ab`}, - {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`)}, - {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`), RawInput: `abc.def.ghi()`}, - {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeFloat, "VECTOR(INT, 20)"), RawInput: `abc.def.ghi(FLOAT, VECTOR(INT, 20))`}, - } - - for _, testCase := range testCases { - t.Run(fmt.Sprintf("processing %s", testCase.Input.FullyQualifiedName()), func(t *testing.T) { - var id SchemaObjectIdentifierWithArguments - var err error - if testCase.RawInput != "" { - id, err = NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(testCase.RawInput) - } else { - id, err = NewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(testCase.Input.FullyQualifiedName()) - } - - if testCase.Error != "" { - assert.ErrorContains(t, err, testCase.Error) - } else { - assert.NoError(t, err) - assert.Equal(t, testCase.Input.FullyQualifiedName(), id.FullyQualifiedName()) - } - }) - } -} diff --git a/pkg/sdk/identifier_parsers.go b/pkg/sdk/identifier_parsers.go index 3fc4904907..b92a215f5b 100644 --- a/pkg/sdk/identifier_parsers.go +++ b/pkg/sdk/identifier_parsers.go @@ -4,6 +4,8 @@ import ( "bytes" "encoding/csv" "fmt" + "slices" + "strconv" "strings" ) @@ -146,6 +148,24 @@ func ParseExternalObjectIdentifier(identifier string) (ExternalObjectIdentifier, ) } +func ParseSchemaObjectIdentifierWithArguments(fullyQualifiedName string) (SchemaObjectIdentifierWithArguments, error) { + splitIdIndex := strings.IndexRune(fullyQualifiedName, '(') + parts, err := ParseIdentifierString(fullyQualifiedName[:splitIdIndex]) + if err != nil { + return SchemaObjectIdentifierWithArguments{}, err + } + dataTypes, err := ParseFunctionArgumentsFromString(fullyQualifiedName[splitIdIndex:]) + if err != nil { + return SchemaObjectIdentifierWithArguments{}, err + } + return NewSchemaObjectIdentifierWithArguments( + parts[0], + parts[1], + parts[2], + dataTypes..., + ), nil +} + // ParseFunctionArgumentsFromString parses function argument from arguments string with optional argument names. // Varying types are not supported (e.g. VARCHAR(200)), because Snowflake outputs them in shortened version // (VARCHAR in this case). The only exception is newly added type VECTOR which has the following structure @@ -167,8 +187,37 @@ func ParseFunctionArgumentsFromString(arguments string) ([]DataType, error) { switch { // For now, only vectors need special parsing behavior case strings.HasPrefix(peekDataType, "VECTOR"): - vectorDataType, _ := stringBuffer.ReadString(')') + vectorDataType, err := stringBuffer.ReadString(')') + if err != nil { + return nil, fmt.Errorf("failed to parse vector type, couldn't find the closing bracket, err = %w", err) + } + vectorDataType = strings.TrimSpace(vectorDataType) + vectorTypeBuffer := bytes.NewBufferString(vectorDataType) + if _, err := vectorTypeBuffer.ReadString('('); err != nil { + return nil, fmt.Errorf("failed to parse vector type, couldn't find the opening bracket, err = %w", err) + } + + vectorInnerType, err := vectorTypeBuffer.ReadString(',') + if err != nil { + return nil, fmt.Errorf("failed to parse vector inner type: %w", err) + } + + vectorInnerType = vectorInnerType[:len(vectorInnerType)-1] + if !slices.Contains(allowedVectorInnerTypes, DataType(vectorInnerType)) { + return nil, fmt.Errorf("invalid vector inner type: %s, allowed vector types are: %v", vectorInnerType, allowedVectorInnerTypes) + } + + vectorSize, err := vectorTypeBuffer.ReadString(')') + if err != nil { + return nil, fmt.Errorf("failed to parse vector size: %w", err) + } + + vectorSize = strings.TrimSpace(vectorSize[:len(vectorSize)-1]) + _, err = strconv.ParseInt(vectorSize, 0, 8) + if err != nil { + return nil, fmt.Errorf("invalid vector size: %s (not a number): %w", vectorSize, err) + } if stringBuffer.Len() > 0 { commaByte, err := stringBuffer.ReadByte() diff --git a/pkg/sdk/identifier_parsers_test.go b/pkg/sdk/identifier_parsers_test.go index cecb87db90..078cbbc30d 100644 --- a/pkg/sdk/identifier_parsers_test.go +++ b/pkg/sdk/identifier_parsers_test.go @@ -285,6 +285,20 @@ func Test_ParseFunctionArgumentsFromString(t *testing.T) { {Arguments: `FLOAT, NUMBER, VECTOR(FLOAT, 20)`, Expected: []DataType{DataTypeFloat, DataTypeNumber, DataType("VECTOR(FLOAT, 20)")}}, {Arguments: `(VECTOR(FLOAT, 10), NUMBER, VECTOR(FLOAT, 20))`, Expected: []DataType{DataType("VECTOR(FLOAT, 10)"), DataTypeNumber, DataType("VECTOR(FLOAT, 20)")}}, {Arguments: `VECTOR(FLOAT, 10)| NUMBER, VECTOR(FLOAT, 20)`, Error: "expected a comma delimited string but found |"}, + {Arguments: `FLOAT, NUMBER, VECTORFLOAT, 20)`, Error: `failed to parse vector type, couldn't find the opening bracket, err = EOF`}, + {Arguments: `FLOAT, NUMBER, VECTORFLOAT, 20), VECTOR(INT, 10)`, Error: `failed to parse vector type, couldn't find the opening bracket, err = EOF`}, + {Arguments: `FLOAT, NUMBER, VECTOR(FLOAT, 20`, Error: `failed to parse vector type, couldn't find the closing bracket, err = EOF`}, + {Arguments: `FLOAT, NUMBER, VECTOR(FLOAT, 20, VECTOR(INT, 10)`, Error: `invalid vector size: 20, VECTOR(INT, 10 (not a number): strconv.ParseInt: parsing "20, VECTOR(INT, 10": invalid syntax`}, + {Arguments: `(FLOAT, VARCHAR(200), TIME)`, Expected: []DataType{DataTypeFloat, DataType("VARCHAR(200)"), DataTypeTime}}, + {Arguments: `(FLOAT, VARCHAR(200))`, Expected: []DataType{DataTypeFloat, DataType("VARCHAR(200)")}}, + {Arguments: `(VARCHAR(200), FLOAT)`, Expected: []DataType{DataType("VARCHAR(200)"), DataTypeFloat}}, + {Arguments: `(FLOAT, NUMBER, VECTOR(VARCHAR, 20))`, Error: `invalid vector inner type: VARCHAR, allowed vector types are`}, + {Arguments: `(FLOAT, NUMBER, VECTOR(INT, INT))`, Error: `invalid vector size: INT (not a number): strconv.ParseInt: parsing "INT": invalid syntax`}, + {Arguments: `FLOAT, NUMBER, VECTOR(20, FLOAT)`, Error: `invalid vector inner type: 20, allowed vector types are`}, + // As the function is only used for identifiers with arguments the following cases are not supported (because they represent concrete types which are not used as part of the identifiers). + {Arguments: `(FLOAT, NUMBER(10, 2), TIME)`, Expected: []DataType{DataTypeFloat, DataType("NUMBER(10"), DataType("2)"), DataTypeTime}}, + {Arguments: `(FLOAT, NUMBER(10, 2))`, Expected: []DataType{DataTypeFloat, DataType("NUMBER(10"), DataType("2)")}}, + {Arguments: `(NUMBER(10, 2), FLOAT)`, Expected: []DataType{DataType("NUMBER(10"), DataType("2)"), DataTypeFloat}}, } for _, testCase := range testCases { @@ -299,3 +313,59 @@ func Test_ParseFunctionArgumentsFromString(t *testing.T) { }) } } + +func TestNewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName(t *testing.T) { + testCases := []struct { + Input SchemaObjectIdentifierWithArguments + Error string + }{ + {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeFloat, DataTypeNumber, DataTypeTimestampTZ)}, + {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeFloat, "VECTOR(INT, 20)")}, + {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, "VECTOR(INT, 20)", DataTypeFloat)}, + {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeFloat, "VECTOR(INT, 20)", "VECTOR(INT, 10)")}, + {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeTime, "VECTOR(INT, 20)", "VECTOR(FLOAT, 10)", DataTypeFloat)}, + // TODO(SNOW-1571674): Won't work, because of the assumption that identifiers are not containing '(' and ')' parentheses (unfortunately, we're not able to produce meaningful errors for those cases) + {Input: NewSchemaObjectIdentifierWithArguments(`ab()c`, `def()`, `()ghi`, DataTypeTime, "VECTOR(INT, 20)", "VECTOR(FLOAT, 10)", DataTypeFloat), Error: `unable to read identifier: "ab`}, + {Input: NewSchemaObjectIdentifierWithArguments(`ab(,)c`, `,def()`, `()ghi,`, DataTypeTime, "VECTOR(INT, 20)", "VECTOR(FLOAT, 10)", DataTypeFloat), Error: `unable to read identifier: "ab`}, + {Input: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`)}, + } + + for _, testCase := range testCases { + t.Run(fmt.Sprintf("processing %s", testCase.Input.FullyQualifiedName()), func(t *testing.T) { + id, err := ParseSchemaObjectIdentifierWithArguments(testCase.Input.FullyQualifiedName()) + + if testCase.Error != "" { + assert.ErrorContains(t, err, testCase.Error) + } else { + assert.NoError(t, err) + assert.Equal(t, testCase.Input.FullyQualifiedName(), id.FullyQualifiedName()) + } + }) + } +} + +func TestNewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName_WithRawInput(t *testing.T) { + testCases := []struct { + RawInput string + ExpectedIdentifierStructure SchemaObjectIdentifierWithArguments + Error string + }{ + {RawInput: `abc.def.ghi()`, ExpectedIdentifierStructure: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`)}, + {RawInput: `abc.def.ghi(FLOAT, VECTOR(INT, 20))`, ExpectedIdentifierStructure: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeFloat, "VECTOR(INT, 20)")}, + // TODO(SNOW-1571674): Won't work, because of the assumption that identifiers are not containing '(' and ')' parentheses (unfortunately, we're not able to produce meaningful errors for those cases) + {RawInput: `abc."(ef".ghi(FLOAT, VECTOR(INT, 20))`, Error: `unable to read identifier: abc."`}, + } + + for _, testCase := range testCases { + t.Run(fmt.Sprintf("processing %s", testCase.ExpectedIdentifierStructure.FullyQualifiedName()), func(t *testing.T) { + id, err := ParseSchemaObjectIdentifierWithArguments(testCase.RawInput) + + if testCase.Error != "" { + assert.ErrorContains(t, err, testCase.Error) + } else { + assert.NoError(t, err) + assert.Equal(t, testCase.ExpectedIdentifierStructure.FullyQualifiedName(), id.FullyQualifiedName()) + } + }) + } +} diff --git a/pkg/sdk/testint/functions_integration_test.go b/pkg/sdk/testint/functions_integration_test.go index d70b2c0e9a..e3a945934d 100644 --- a/pkg/sdk/testint/functions_integration_test.go +++ b/pkg/sdk/testint/functions_integration_test.go @@ -305,9 +305,7 @@ func TestInt_OtherFunctions(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) id := f.ID() - err := client.Functions.Alter(ctx, func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { - return sdk.NewAlterFunctionRequest(id) - }(id).WithSetTraceLevel(string(sdk.TraceLevelAlways))) + err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithSetTraceLevel(string(sdk.TraceLevelAlways))) require.NoError(t, err) assertFunction(t, id, false, true) }) @@ -316,9 +314,7 @@ func TestInt_OtherFunctions(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) id := f.ID() - err := client.Functions.Alter(ctx, func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { - return sdk.NewAlterFunctionRequest(id) - }(id).WithUnsetTraceLevel(true)) + err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithUnsetTraceLevel(true)) require.NoError(t, err) assertFunction(t, id, false, true) }) @@ -327,9 +323,7 @@ func TestInt_OtherFunctions(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) id := f.ID() - err := client.Functions.Alter(ctx, func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { - return sdk.NewAlterFunctionRequest(id) - }(id).WithSetComment("test comment")) + err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithSetComment("test comment")) require.NoError(t, err) assertFunction(t, id, false, true) }) @@ -338,9 +332,7 @@ func TestInt_OtherFunctions(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) id := f.ID() - err := client.Functions.Alter(ctx, func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { - return sdk.NewAlterFunctionRequest(id) - }(id).WithUnsetComment(true)) + err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithUnsetComment(true)) require.NoError(t, err) assertFunction(t, id, false, true) }) @@ -349,9 +341,7 @@ func TestInt_OtherFunctions(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) id := f.ID() - err := client.Functions.Alter(ctx, func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { - return sdk.NewAlterFunctionRequest(id) - }(id).WithSetSecure(true)) + err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithSetSecure(true)) require.NoError(t, err) assertFunction(t, id, true, true) }) @@ -368,9 +358,7 @@ func TestInt_OtherFunctions(t *testing.T) { f := createFunctionForSQLHandle(t, true, true) id := f.ID() - err := client.Functions.Alter(ctx, func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { - return sdk.NewAlterFunctionRequest(id) - }(id).WithUnsetSecure(true)) + err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithUnsetSecure(true)) require.NoError(t, err) assertFunction(t, id, false, true) }) @@ -385,18 +373,14 @@ func TestInt_OtherFunctions(t *testing.T) { Value: "v1", }, } - err := client.Functions.Alter(ctx, func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { - return sdk.NewAlterFunctionRequest(id) - }(id).WithSetTags(setTags)) + err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithSetTags(setTags)) require.NoError(t, err) assertFunction(t, id, false, true) unsetTags := []sdk.ObjectIdentifier{ tagTest.ID(), } - err = client.Functions.Alter(ctx, func(id sdk.SchemaObjectIdentifierWithArguments) *sdk.AlterFunctionRequest { - return sdk.NewAlterFunctionRequest(id) - }(id).WithUnsetTags(unsetTags)) + err = client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithUnsetTags(unsetTags)) require.NoError(t, err) assertFunction(t, id, false, true) }) From 9e5bfac0a06b00aa43d6178ff39425c67e83f20a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Thu, 8 Aug 2024 11:11:40 +0200 Subject: [PATCH 13/13] Fix test --- pkg/resources/procedure_acceptance_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/resources/procedure_acceptance_test.go b/pkg/resources/procedure_acceptance_test.go index b601583a9a..29e89243c1 100644 --- a/pkg/resources/procedure_acceptance_test.go +++ b/pkg/resources/procedure_acceptance_test.go @@ -257,7 +257,7 @@ resource "snowflake_procedure" "p" { } func TestAcc_Procedure_proveArgsPermanentDiff(t *testing.T) { - id := acc.TestClient().Ids.RandomSchemaObjectIdentifierWithArguments(sdk.DataTypeVARCHAR, sdk.DataTypeNumber) + id := acc.TestClient().Ids.RandomSchemaObjectIdentifierWithArgumentsOld(sdk.DataTypeVARCHAR, sdk.DataTypeNumber) name := id.Name() resourceName := "snowflake_procedure.p"