From cd6cf4d4d4cb2af3b76c14c9544ee9706980ed39 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Tue, 10 Dec 2024 09:53:24 +0100 Subject: [PATCH 01/45] functions basic impl starts here From 8c53454228c78b86d68d8557e189193cad6fc179 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Tue, 10 Dec 2024 15:45:28 +0100 Subject: [PATCH 02/45] Add common delete for function resources --- pkg/resources/function.go | 16 +--------------- pkg/resources/function_commons.go | 20 ++++++++++++++++++++ pkg/resources/function_java.go | 6 +----- pkg/resources/function_javascript.go | 2 +- pkg/resources/function_python.go | 2 +- pkg/resources/function_scala.go | 2 +- pkg/resources/function_sql.go | 2 +- 7 files changed, 26 insertions(+), 24 deletions(-) diff --git a/pkg/resources/function.go b/pkg/resources/function.go index 38c6619a37..69e972f7d5 100644 --- a/pkg/resources/function.go +++ b/pkg/resources/function.go @@ -171,7 +171,7 @@ func Function() *schema.Resource { CreateContext: TrackingCreateWrapper(resources.Function, CreateContextFunction), ReadContext: TrackingReadWrapper(resources.Function, ReadContextFunction), UpdateContext: TrackingUpdateWrapper(resources.Function, UpdateContextFunction), - DeleteContext: TrackingDeleteWrapper(resources.Function, DeleteContextFunction), + DeleteContext: TrackingDeleteWrapper(resources.Function, DeleteFunction), CustomizeDiff: TrackingCustomDiffWrapper(resources.Function, customdiff.All( // TODO(SNOW-1348103): add `arguments` to ComputedIfAnyAttributeChanged. This can't be done now because this function compares values without diff suppress. @@ -722,20 +722,6 @@ func UpdateContextFunction(ctx context.Context, d *schema.ResourceData, meta int 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.ParseSchemaObjectIdentifierWithArguments(d.Id()) - if err != nil { - return diag.FromErr(err) - } - if err := client.Functions.Drop(ctx, sdk.NewDropFunctionRequest(id).WithIfExists(true)); 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 { diff --git a/pkg/resources/function_commons.go b/pkg/resources/function_commons.go index fd4d57913e..0be6fcfeec 100644 --- a/pkg/resources/function_commons.go +++ b/pkg/resources/function_commons.go @@ -1,11 +1,14 @@ package resources import ( + "context" "fmt" "slices" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/schemas" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -344,3 +347,20 @@ func functionBaseSchema() map[string]schema.Schema { FullyQualifiedNameAttributeName: *schemas.FullyQualifiedNameSchema, } } + +func DeleteFunction(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*provider.Context).Client + + id, err := sdk.ParseSchemaObjectIdentifierWithArguments(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + err = client.Functions.Drop(ctx, sdk.NewDropFunctionRequest(id).WithIfExists(true)) + if err != nil { + return diag.FromErr(err) + } + + d.SetId("") + return nil +} diff --git a/pkg/resources/function_java.go b/pkg/resources/function_java.go index 5e05d3007f..69d575660a 100644 --- a/pkg/resources/function_java.go +++ b/pkg/resources/function_java.go @@ -17,7 +17,7 @@ func FunctionJava() *schema.Resource { CreateContext: TrackingCreateWrapper(resources.FunctionJava, CreateContextFunctionJava), ReadContext: TrackingReadWrapper(resources.FunctionJava, ReadContextFunctionJava), UpdateContext: TrackingUpdateWrapper(resources.FunctionJava, UpdateContextFunctionJava), - DeleteContext: TrackingDeleteWrapper(resources.FunctionJava, DeleteContextFunctionJava), + DeleteContext: TrackingDeleteWrapper(resources.FunctionJava, DeleteFunction), Description: "Resource used to manage java function objects. For more information, check [function documentation](https://docs.snowflake.com/en/sql-reference/sql/create-function).", CustomizeDiff: TrackingCustomDiffWrapper(resources.FunctionJava, customdiff.All( @@ -46,7 +46,3 @@ func ReadContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta a func UpdateContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { return nil } - -func DeleteContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - return nil -} diff --git a/pkg/resources/function_javascript.go b/pkg/resources/function_javascript.go index f1b3e17e2a..0ba7e955b7 100644 --- a/pkg/resources/function_javascript.go +++ b/pkg/resources/function_javascript.go @@ -17,7 +17,7 @@ func FunctionJavascript() *schema.Resource { CreateContext: TrackingCreateWrapper(resources.FunctionJavascript, CreateContextFunctionJavascript), ReadContext: TrackingReadWrapper(resources.FunctionJavascript, ReadContextFunctionJavascript), UpdateContext: TrackingUpdateWrapper(resources.FunctionJavascript, UpdateContextFunctionJavascript), - DeleteContext: TrackingDeleteWrapper(resources.FunctionJavascript, DeleteContextFunctionJavascript), + DeleteContext: TrackingDeleteWrapper(resources.FunctionJavascript, DeleteFunction), Description: "Resource used to manage javascript function objects. For more information, check [function documentation](https://docs.snowflake.com/en/sql-reference/sql/create-function).", CustomizeDiff: TrackingCustomDiffWrapper(resources.FunctionJavascript, customdiff.All( diff --git a/pkg/resources/function_python.go b/pkg/resources/function_python.go index e270f80ef6..cc6c137aff 100644 --- a/pkg/resources/function_python.go +++ b/pkg/resources/function_python.go @@ -17,7 +17,7 @@ func FunctionPython() *schema.Resource { CreateContext: TrackingCreateWrapper(resources.FunctionPython, CreateContextFunctionPython), ReadContext: TrackingReadWrapper(resources.FunctionPython, ReadContextFunctionPython), UpdateContext: TrackingUpdateWrapper(resources.FunctionPython, UpdateContextFunctionPython), - DeleteContext: TrackingDeleteWrapper(resources.FunctionPython, DeleteContextFunctionPython), + DeleteContext: TrackingDeleteWrapper(resources.FunctionPython, DeleteFunction), Description: "Resource used to manage python function objects. For more information, check [function documentation](https://docs.snowflake.com/en/sql-reference/sql/create-function).", CustomizeDiff: TrackingCustomDiffWrapper(resources.FunctionPython, customdiff.All( diff --git a/pkg/resources/function_scala.go b/pkg/resources/function_scala.go index 2c3adf0bc3..ff2bded481 100644 --- a/pkg/resources/function_scala.go +++ b/pkg/resources/function_scala.go @@ -17,7 +17,7 @@ func FunctionScala() *schema.Resource { CreateContext: TrackingCreateWrapper(resources.FunctionScala, CreateContextFunctionScala), ReadContext: TrackingReadWrapper(resources.FunctionScala, ReadContextFunctionScala), UpdateContext: TrackingUpdateWrapper(resources.FunctionScala, UpdateContextFunctionScala), - DeleteContext: TrackingDeleteWrapper(resources.FunctionScala, DeleteContextFunctionScala), + DeleteContext: TrackingDeleteWrapper(resources.FunctionScala, DeleteFunction), Description: "Resource used to manage scala function objects. For more information, check [function documentation](https://docs.snowflake.com/en/sql-reference/sql/create-function).", CustomizeDiff: TrackingCustomDiffWrapper(resources.FunctionScala, customdiff.All( diff --git a/pkg/resources/function_sql.go b/pkg/resources/function_sql.go index 48ea385f71..cd8cb31dc8 100644 --- a/pkg/resources/function_sql.go +++ b/pkg/resources/function_sql.go @@ -17,7 +17,7 @@ func FunctionSql() *schema.Resource { CreateContext: TrackingCreateWrapper(resources.FunctionSql, CreateContextFunctionSql), ReadContext: TrackingReadWrapper(resources.FunctionSql, ReadContextFunctionSql), UpdateContext: TrackingUpdateWrapper(resources.FunctionSql, UpdateContextFunctionSql), - DeleteContext: TrackingDeleteWrapper(resources.FunctionSql, DeleteContextFunctionSql), + DeleteContext: TrackingDeleteWrapper(resources.FunctionSql, DeleteFunction), Description: "Resource used to manage sql function objects. For more information, check [function documentation](https://docs.snowflake.com/en/sql-reference/sql/create-function).", CustomizeDiff: TrackingCustomDiffWrapper(resources.FunctionSql, customdiff.All( From 3a8fd0551a3b91f7a824767e15dbdc8a76b6f3b3 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Tue, 10 Dec 2024 15:48:26 +0100 Subject: [PATCH 03/45] Add common delete for procedure resources --- pkg/resources/procedure.go | 16 +--------------- pkg/resources/procedure_commons.go | 19 +++++++++++++++++++ pkg/resources/procedure_java.go | 6 +----- pkg/resources/procedure_javascript.go | 6 +----- pkg/resources/procedure_python.go | 6 +----- pkg/resources/procedure_scala.go | 6 +----- pkg/resources/procedure_sql.go | 6 +----- 7 files changed, 25 insertions(+), 40 deletions(-) diff --git a/pkg/resources/procedure.go b/pkg/resources/procedure.go index 8665f71d09..fa986ae8f5 100644 --- a/pkg/resources/procedure.go +++ b/pkg/resources/procedure.go @@ -186,7 +186,7 @@ func Procedure() *schema.Resource { CreateContext: TrackingCreateWrapper(resources.Procedure, CreateContextProcedure), ReadContext: TrackingReadWrapper(resources.Procedure, ReadContextProcedure), UpdateContext: TrackingUpdateWrapper(resources.Procedure, UpdateContextProcedure), - DeleteContext: TrackingDeleteWrapper(resources.Procedure, DeleteContextProcedure), + DeleteContext: TrackingDeleteWrapper(resources.Procedure, DeleteProcedure), // TODO(SNOW-1348106): add `arguments` to ComputedIfAnyAttributeChanged for FullyQualifiedNameAttributeName. // This can't be done now because this function compares values without diff suppress. @@ -714,20 +714,6 @@ func UpdateContextProcedure(ctx context.Context, d *schema.ResourceData, meta in return ReadContextProcedure(ctx, d, meta) } -func DeleteContextProcedure(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*provider.Context).Client - - id, err := sdk.ParseSchemaObjectIdentifierWithArguments(d.Id()) - if err != nil { - return diag.FromErr(err) - } - if err := client.Procedures.Drop(ctx, sdk.NewDropProcedureRequest(id).WithIfExists(true)); 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 { diff --git a/pkg/resources/procedure_commons.go b/pkg/resources/procedure_commons.go index 88e815978b..e9bd878171 100644 --- a/pkg/resources/procedure_commons.go +++ b/pkg/resources/procedure_commons.go @@ -1,11 +1,14 @@ package resources import ( + "context" "fmt" "slices" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/schemas" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -341,3 +344,19 @@ func procedureBaseSchema() map[string]schema.Schema { FullyQualifiedNameAttributeName: *schemas.FullyQualifiedNameSchema, } } + +func DeleteProcedure(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*provider.Context).Client + + id, err := sdk.ParseSchemaObjectIdentifierWithArguments(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + if err := client.Procedures.Drop(ctx, sdk.NewDropProcedureRequest(id).WithIfExists(true)); err != nil { + return diag.FromErr(err) + } + + d.SetId("") + return nil +} diff --git a/pkg/resources/procedure_java.go b/pkg/resources/procedure_java.go index 8019e72689..1804780de9 100644 --- a/pkg/resources/procedure_java.go +++ b/pkg/resources/procedure_java.go @@ -17,7 +17,7 @@ func ProcedureJava() *schema.Resource { CreateContext: TrackingCreateWrapper(resources.ProcedureJava, CreateContextProcedureJava), ReadContext: TrackingReadWrapper(resources.ProcedureJava, ReadContextProcedureJava), UpdateContext: TrackingUpdateWrapper(resources.ProcedureJava, UpdateContextProcedureJava), - DeleteContext: TrackingDeleteWrapper(resources.ProcedureJava, DeleteContextProcedureJava), + DeleteContext: TrackingDeleteWrapper(resources.ProcedureJava, DeleteProcedure), Description: "Resource used to manage java procedure objects. For more information, check [procedure documentation](https://docs.snowflake.com/en/sql-reference/sql/create-procedure).", CustomizeDiff: TrackingCustomDiffWrapper(resources.ProcedureJava, customdiff.All( @@ -46,7 +46,3 @@ func ReadContextProcedureJava(ctx context.Context, d *schema.ResourceData, meta func UpdateContextProcedureJava(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { return nil } - -func DeleteContextProcedureJava(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - return nil -} diff --git a/pkg/resources/procedure_javascript.go b/pkg/resources/procedure_javascript.go index 8c3958b99e..5088b492f7 100644 --- a/pkg/resources/procedure_javascript.go +++ b/pkg/resources/procedure_javascript.go @@ -17,7 +17,7 @@ func ProcedureJavascript() *schema.Resource { CreateContext: TrackingCreateWrapper(resources.ProcedureJavascript, CreateContextProcedureJavascript), ReadContext: TrackingReadWrapper(resources.ProcedureJavascript, ReadContextProcedureJavascript), UpdateContext: TrackingUpdateWrapper(resources.ProcedureJavascript, UpdateContextProcedureJavascript), - DeleteContext: TrackingDeleteWrapper(resources.ProcedureJavascript, DeleteContextProcedureJavascript), + DeleteContext: TrackingDeleteWrapper(resources.ProcedureJavascript, DeleteProcedure), Description: "Resource used to manage javascript procedure objects. For more information, check [procedure documentation](https://docs.snowflake.com/en/sql-reference/sql/create-procedure).", CustomizeDiff: TrackingCustomDiffWrapper(resources.ProcedureJavascript, customdiff.All( @@ -46,7 +46,3 @@ func ReadContextProcedureJavascript(ctx context.Context, d *schema.ResourceData, func UpdateContextProcedureJavascript(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { return nil } - -func DeleteContextProcedureJavascript(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - return nil -} diff --git a/pkg/resources/procedure_python.go b/pkg/resources/procedure_python.go index 48d70329e7..717cee32fe 100644 --- a/pkg/resources/procedure_python.go +++ b/pkg/resources/procedure_python.go @@ -17,7 +17,7 @@ func ProcedurePython() *schema.Resource { CreateContext: TrackingCreateWrapper(resources.ProcedurePython, CreateContextProcedurePython), ReadContext: TrackingReadWrapper(resources.ProcedurePython, ReadContextProcedurePython), UpdateContext: TrackingUpdateWrapper(resources.ProcedurePython, UpdateContextProcedurePython), - DeleteContext: TrackingDeleteWrapper(resources.ProcedurePython, DeleteContextProcedurePython), + DeleteContext: TrackingDeleteWrapper(resources.ProcedurePython, DeleteProcedure), Description: "Resource used to manage python procedure objects. For more information, check [procedure documentation](https://docs.snowflake.com/en/sql-reference/sql/create-procedure).", CustomizeDiff: TrackingCustomDiffWrapper(resources.ProcedurePython, customdiff.All( @@ -46,7 +46,3 @@ func ReadContextProcedurePython(ctx context.Context, d *schema.ResourceData, met func UpdateContextProcedurePython(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { return nil } - -func DeleteContextProcedurePython(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - return nil -} diff --git a/pkg/resources/procedure_scala.go b/pkg/resources/procedure_scala.go index 3a7816b7d0..793663d0e1 100644 --- a/pkg/resources/procedure_scala.go +++ b/pkg/resources/procedure_scala.go @@ -17,7 +17,7 @@ func ProcedureScala() *schema.Resource { CreateContext: TrackingCreateWrapper(resources.ProcedureScala, CreateContextProcedureScala), ReadContext: TrackingReadWrapper(resources.ProcedureScala, ReadContextProcedureScala), UpdateContext: TrackingUpdateWrapper(resources.ProcedureScala, UpdateContextProcedureScala), - DeleteContext: TrackingDeleteWrapper(resources.ProcedureScala, DeleteContextProcedureScala), + DeleteContext: TrackingDeleteWrapper(resources.ProcedureScala, DeleteProcedure), Description: "Resource used to manage scala procedure objects. For more information, check [procedure documentation](https://docs.snowflake.com/en/sql-reference/sql/create-procedure).", CustomizeDiff: TrackingCustomDiffWrapper(resources.ProcedureScala, customdiff.All( @@ -46,7 +46,3 @@ func ReadContextProcedureScala(ctx context.Context, d *schema.ResourceData, meta func UpdateContextProcedureScala(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { return nil } - -func DeleteContextProcedureScala(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - return nil -} diff --git a/pkg/resources/procedure_sql.go b/pkg/resources/procedure_sql.go index 0488941f03..11fcd69413 100644 --- a/pkg/resources/procedure_sql.go +++ b/pkg/resources/procedure_sql.go @@ -17,7 +17,7 @@ func ProcedureSql() *schema.Resource { CreateContext: TrackingCreateWrapper(resources.ProcedureSql, CreateContextProcedureSql), ReadContext: TrackingReadWrapper(resources.ProcedureSql, ReadContextProcedureSql), UpdateContext: TrackingUpdateWrapper(resources.ProcedureSql, UpdateContextProcedureSql), - DeleteContext: TrackingDeleteWrapper(resources.ProcedureSql, DeleteContextProcedureSql), + DeleteContext: TrackingDeleteWrapper(resources.ProcedureSql, DeleteProcedure), Description: "Resource used to manage sql procedure objects. For more information, check [procedure documentation](https://docs.snowflake.com/en/sql-reference/sql/create-procedure).", CustomizeDiff: TrackingCustomDiffWrapper(resources.ProcedureSql, customdiff.All( @@ -46,7 +46,3 @@ func ReadContextProcedureSql(ctx context.Context, d *schema.ResourceData, meta a func UpdateContextProcedureSql(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { return nil } - -func DeleteContextProcedureSql(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - return nil -} From 9600673574928b76c3f555da7dedfd3d32f5e104 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Tue, 10 Dec 2024 18:14:30 +0100 Subject: [PATCH 04/45] Start basic java function implementation --- .../config/model/function_java_model_ext.go | 13 ++ pkg/acceptance/helpers/ids_generator.go | 12 ++ pkg/resources/function_commons.go | 38 ++++ pkg/resources/function_java.go | 69 +++++- .../function_java_acceptance_test.go | 202 ++++++++++++++++++ pkg/sdk/data_types_deprecated.go | 4 + pkg/sdk/datatypes/legacy.go | 3 + pkg/sdk/datatypes/table.go | 39 ++++ pkg/sdk/identifier_helpers.go | 10 + 9 files changed, 389 insertions(+), 1 deletion(-) create mode 100644 pkg/resources/function_java_acceptance_test.go create mode 100644 pkg/sdk/datatypes/table.go diff --git a/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go b/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go index 4bac27ada5..10efc74743 100644 --- a/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go +++ b/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go @@ -2,6 +2,9 @@ package model import ( "encoding/json" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/datatypes" ) func (f *FunctionJavaModel) MarshalJSON() ([]byte, error) { @@ -14,3 +17,13 @@ func (f *FunctionJavaModel) MarshalJSON() ([]byte, error) { DependsOn: f.DependsOn(), }) } + +func FunctionJavaWithId( + resourceName string, + id sdk.SchemaObjectIdentifierWithArguments, + returnType datatypes.DataType, + handler string, + functionDefinition string, +) *FunctionJavaModel { + return FunctionJava(resourceName, id.DatabaseName(), functionDefinition, handler, id.Name(), returnType.ToSql(), id.SchemaName()) +} diff --git a/pkg/acceptance/helpers/ids_generator.go b/pkg/acceptance/helpers/ids_generator.go index ade93d46bc..74fa08b7ff 100644 --- a/pkg/acceptance/helpers/ids_generator.go +++ b/pkg/acceptance/helpers/ids_generator.go @@ -4,7 +4,9 @@ import ( "strings" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers/random" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/datatypes" ) type IdsGenerator struct { @@ -97,6 +99,11 @@ func (c *IdsGenerator) NewSchemaObjectIdentifierWithArguments(name string, argum return sdk.NewSchemaObjectIdentifierWithArguments(c.SchemaId().DatabaseName(), c.SchemaId().Name(), name, arguments...) } +func (c *IdsGenerator) NewSchemaObjectIdentifierWithArgumentsNewDataTypes(name string, arguments ...datatypes.DataType) sdk.SchemaObjectIdentifierWithArguments { + legacyDataTypes := collections.Map(arguments, func(dt datatypes.DataType) sdk.DataType { return sdk.LegacyDataTypeFrom(dt) }) + return sdk.NewSchemaObjectIdentifierWithArguments(c.SchemaId().DatabaseName(), c.SchemaId().Name(), name, legacyDataTypes...) +} + func (c *IdsGenerator) NewSchemaObjectIdentifierWithArgumentsInSchema(name string, schemaId sdk.DatabaseObjectIdentifier, argumentDataTypes ...sdk.DataType) sdk.SchemaObjectIdentifierWithArguments { return sdk.NewSchemaObjectIdentifierWithArgumentsInSchema(schemaId, name, argumentDataTypes...) } @@ -105,6 +112,11 @@ func (c *IdsGenerator) RandomSchemaObjectIdentifierWithArguments(arguments ...sd return sdk.NewSchemaObjectIdentifierWithArguments(c.SchemaId().DatabaseName(), c.SchemaId().Name(), c.Alpha(), arguments...) } +func (c *IdsGenerator) RandomSchemaObjectIdentifierWithArgumentsNewDataTypes(arguments ...datatypes.DataType) sdk.SchemaObjectIdentifierWithArguments { + legacyDataTypes := collections.Map(arguments, func(dt datatypes.DataType) sdk.DataType { return sdk.LegacyDataTypeFrom(dt) }) + return sdk.NewSchemaObjectIdentifierWithArguments(c.SchemaId().DatabaseName(), c.SchemaId().Name(), c.Alpha(), legacyDataTypes...) +} + func (c *IdsGenerator) Alpha() string { return c.AlphaN(6) } diff --git a/pkg/resources/function_commons.go b/pkg/resources/function_commons.go index 0be6fcfeec..c283224b24 100644 --- a/pkg/resources/function_commons.go +++ b/pkg/resources/function_commons.go @@ -8,6 +8,7 @@ import ( "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/schemas" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/datatypes" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -364,3 +365,40 @@ func DeleteFunction(ctx context.Context, d *schema.ResourceData, meta any) diag. d.SetId("") return nil } + +// TODO [SNOW-1348103]: handle defaults +func parseFunctionArgumentsCommon(d *schema.ResourceData) ([]sdk.FunctionArgumentRequest, diag.Diagnostics) { + args := make([]sdk.FunctionArgumentRequest, 0) + if v, ok := d.GetOk("arguments"); ok { + for _, arg := range v.([]any) { + argName := arg.(map[string]interface{})["arg_name"].(string) + argDataType := arg.(map[string]interface{})["arg_data_type"].(string) + dataType, err := datatypes.ParseDataType(argDataType) + if err != nil { + return nil, diag.FromErr(err) + } + args = append(args, *sdk.NewFunctionArgumentRequest(argName, dataType)) + } + } + return args, nil +} + +func parseFunctionReturnsCommon(d *schema.ResourceData) (*sdk.FunctionReturnsRequest, diag.Diagnostics) { + returnTypeRaw := d.Get("return_type").(string) + dataType, err := datatypes.ParseDataType(returnTypeRaw) + if err != nil { + return nil, diag.FromErr(err) + } + returns := sdk.NewFunctionReturnsRequest() + switch v := dataType.(type) { + case *datatypes.TableDataType: + var cr []sdk.FunctionColumnRequest + for _, c := range v.Columns() { + cr = append(cr, *sdk.NewFunctionColumnRequest(c.ColumnName(), c.ColumnType())) + } + returns.WithTable(*sdk.NewFunctionReturnsTableRequest().WithColumns(cr)) + default: + returns.WithResultDataType(*sdk.NewFunctionReturnsResultDataTypeRequest(dataType)) + } + return returns, nil +} diff --git a/pkg/resources/function_java.go b/pkg/resources/function_java.go index 69d575660a..be18f660c6 100644 --- a/pkg/resources/function_java.go +++ b/pkg/resources/function_java.go @@ -5,8 +5,10 @@ import ( "strings" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/provider/resources" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/datatypes" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -36,7 +38,72 @@ func FunctionJava() *schema.Resource { } func CreateContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - return nil + client := meta.(*provider.Context).Client + database := d.Get("database").(string) + sc := d.Get("schema").(string) + name := d.Get("name").(string) + + argumentRequests, diags := parseFunctionArgumentsCommon(d) + if diags != nil { + return diags + } + returns, diags := parseFunctionReturnsCommon(d) + if diags != nil { + return diags + } + handler := d.Get("handler").(string) + + argumentDataTypes := collections.Map(argumentRequests, func(r sdk.FunctionArgumentRequest) datatypes.DataType { return r.ArgDataType }) + id := sdk.NewSchemaObjectIdentifierWithArgumentsNormalized(database, sc, name, argumentDataTypes...) + request := sdk.NewCreateForJavaFunctionRequest(id.SchemaObjectId(), *returns, handler). + WithArguments(argumentRequests) + + if v, ok := d.GetOk("statement"); ok { + request.WithFunctionDefinitionWrapped(v.(string)) + } + + if err := client.Functions.CreateForJava(ctx, request); err != nil { + return diag.FromErr(err) + } + + // TODO [this PR]: handle parameters + + d.SetId(id.FullyQualifiedName()) + return ReadContextFunctionJava(ctx, d, meta) + + // Set optionals + //if v, ok := d.GetOk("is_secure"); ok { + // request.WithSecure(v.(bool)) + //} + //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 { + // var 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 { + // var 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)) + //} } func ReadContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { diff --git a/pkg/resources/function_java_acceptance_test.go b/pkg/resources/function_java_acceptance_test.go new file mode 100644 index 0000000000..afea32252e --- /dev/null +++ b/pkg/resources/function_java_acceptance_test.go @@ -0,0 +1,202 @@ +package resources_test + +import ( + "fmt" + "testing" + + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert/resourceassert" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert/resourceshowoutputassert" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/config" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/config/model" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/testdatatypes" + "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/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestAcc_FunctionJava_BasicFlows(t *testing.T) { + className := "TestFunc" + funcName := "echoVarchar" + argName := "x" + dataType := testdatatypes.DataTypeVarchar_100 + // differentDataType := testdatatypes.DataTypeNumber_36_2 + + id := acc.TestClient().Ids.RandomSchemaObjectIdentifierWithArgumentsNewDataTypes(dataType) + idWithChangedNameButTheSameDataType := acc.TestClient().Ids.RandomSchemaObjectIdentifierWithArgumentsNewDataTypes(dataType) + // idWithSameNameButDifferentDataType := acc.TestClient().Ids.NewSchemaObjectIdentifierWithArgumentsNewDataTypes(idWithChangedNameButTheSameDataType.Name(), differentDataType) + + handler := fmt.Sprintf("%s.%s", className, funcName) + definition := acc.TestClient().Function.SampleJavaDefinition(t, className, funcName, argName) + + functionModelNoAttributes := model.FunctionJavaWithId("w", id, dataType, handler, definition) + functionModelNoAttributesRenamed := model.FunctionJavaWithId("w", idWithChangedNameButTheSameDataType, dataType, handler, definition) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + PreCheck: func() { acc.TestAccPreCheck(t) }, + CheckDestroy: acc.CheckDestroy(t, resources.User), + Steps: []resource.TestStep{ + // CREATE BASIC + { + Config: config.ResourceFromModel(t, functionModelNoAttributes), + Check: assert.AssertThat(t, + resourceassert.FunctionJavaResource(t, functionModelNoAttributes.ResourceReference()). + HasNameString(id.Name()). + HasCommentString(sdk.DefaultFunctionComment). + HasFullyQualifiedNameString(id.FullyQualifiedName()), + resourceshowoutputassert.FunctionShowOutput(t, functionModelNoAttributes.ResourceReference()). + HasIsSecure(false), + ), + }, + // RENAME + { + Config: config.ResourceFromModel(t, functionModelNoAttributesRenamed), + Check: assert.AssertThat(t, + resourceassert.FunctionJavaResource(t, functionModelNoAttributesRenamed.ResourceReference()). + HasNameString(idWithChangedNameButTheSameDataType.Name()). + HasFullyQualifiedNameString(idWithChangedNameButTheSameDataType.FullyQualifiedName()), + ), + }, + //// IMPORT + //{ + // ResourceName: userModelNoAttributesRenamed.ResourceReference(), + // ImportState: true, + // ImportStateVerify: true, + // ImportStateVerifyIgnore: []string{"password", "disable_mfa", "days_to_expiry", "mins_to_unlock", "mins_to_bypass_mfa", "login_name", "display_name", "disabled", "must_change_password"}, + // ImportStateCheck: assert.AssertThatImport(t, + // resourceassert.ImportedUserResource(t, id2.Name()). + // HasLoginNameString(strings.ToUpper(id.Name())). + // HasDisplayNameString(id.Name()). + // HasDisabled(false). + // HasMustChangePassword(false), + // ), + //}, + //// DESTROY + //{ + // Config: config.FromModel(t, userModelNoAttributes), + // Destroy: true, + //}, + //// CREATE WITH ALL ATTRIBUTES + //{ + // Config: config.FromModel(t, userModelAllAttributes), + // Check: assert.AssertThat(t, + // resourceassert.UserResource(t, userModelAllAttributes.ResourceReference()). + // HasNameString(id.Name()). + // HasPasswordString(pass). + // HasLoginNameString(fmt.Sprintf("%s_login", id.Name())). + // HasDisplayNameString("Display Name"). + // HasFirstNameString("Jan"). + // HasMiddleNameString("Jakub"). + // HasLastNameString("Testowski"). + // HasEmailString("fake@email.com"). + // HasMustChangePassword(true). + // HasDisabled(false). + // HasDaysToExpiryString("8"). + // HasMinsToUnlockString("9"). + // HasDefaultWarehouseString("some_warehouse"). + // HasDefaultNamespaceString("some.namespace"). + // HasDefaultRoleString("some_role"). + // HasDefaultSecondaryRolesOption(sdk.SecondaryRolesOptionAll). + // HasMinsToBypassMfaString("10"). + // HasRsaPublicKeyString(key1). + // HasRsaPublicKey2String(key2). + // HasCommentString(comment). + // HasDisableMfaString(r.BooleanTrue). + // HasFullyQualifiedNameString(id.FullyQualifiedName()), + // ), + //}, + //// CHANGE PROPERTIES + //{ + // Config: config.FromModel(t, userModelAllAttributesChanged(id.Name()+"_other_login")), + // Check: assert.AssertThat(t, + // resourceassert.UserResource(t, userModelAllAttributesChanged(id.Name()+"_other_login").ResourceReference()). + // HasNameString(id.Name()). + // HasPasswordString(newPass). + // HasLoginNameString(fmt.Sprintf("%s_other_login", id.Name())). + // HasDisplayNameString("New Display Name"). + // HasFirstNameString("Janek"). + // HasMiddleNameString("Kuba"). + // HasLastNameString("Terraformowski"). + // HasEmailString("fake@email.net"). + // HasMustChangePassword(false). + // HasDisabled(true). + // HasDaysToExpiryString("12"). + // HasMinsToUnlockString("13"). + // HasDefaultWarehouseString("other_warehouse"). + // HasDefaultNamespaceString("one_part_namespace"). + // HasDefaultRoleString("other_role"). + // HasDefaultSecondaryRolesOption(sdk.SecondaryRolesOptionAll). + // HasMinsToBypassMfaString("14"). + // HasRsaPublicKeyString(key2). + // HasRsaPublicKey2String(key1). + // HasCommentString(newComment). + // HasDisableMfaString(r.BooleanFalse). + // HasFullyQualifiedNameString(id.FullyQualifiedName()), + // ), + //}, + //// IMPORT + //{ + // ResourceName: userModelAllAttributesChanged(id.Name() + "_other_login").ResourceReference(), + // ImportState: true, + // ImportStateVerify: true, + // ImportStateVerifyIgnore: []string{"password", "disable_mfa", "days_to_expiry", "mins_to_unlock", "mins_to_bypass_mfa", "default_namespace", "login_name", "show_output.0.days_to_expiry"}, + // ImportStateCheck: assert.AssertThatImport(t, + // resourceassert.ImportedUserResource(t, id.Name()). + // HasDefaultNamespaceString("ONE_PART_NAMESPACE"). + // HasLoginNameString(fmt.Sprintf("%s_OTHER_LOGIN", id.Name())), + // ), + //}, + //// CHANGE PROP TO THE CURRENT SNOWFLAKE VALUE + //{ + // PreConfig: func() { + // acc.TestClient().User.SetLoginName(t, id, id.Name()+"_different_login") + // }, + // Config: config.FromModel(t, userModelAllAttributesChanged(id.Name()+"_different_login")), + // ConfigPlanChecks: resource.ConfigPlanChecks{ + // PostApplyPostRefresh: []plancheck.PlanCheck{ + // plancheck.ExpectEmptyPlan(), + // }, + // }, + //}, + //// UNSET ALL + //{ + // Config: config.FromModel(t, userModelNoAttributes), + // Check: assert.AssertThat(t, + // resourceassert.UserResource(t, userModelNoAttributes.ResourceReference()). + // HasNameString(id.Name()). + // HasPasswordString(""). + // HasLoginNameString(""). + // HasDisplayNameString(""). + // HasFirstNameString(""). + // HasMiddleNameString(""). + // HasLastNameString(""). + // HasEmailString(""). + // HasMustChangePasswordString(r.BooleanDefault). + // HasDisabledString(r.BooleanDefault). + // HasDaysToExpiryString("0"). + // HasMinsToUnlockString(r.IntDefaultString). + // HasDefaultWarehouseString(""). + // HasDefaultNamespaceString(""). + // HasDefaultRoleString(""). + // HasDefaultSecondaryRolesOption(sdk.SecondaryRolesOptionDefault). + // HasMinsToBypassMfaString(r.IntDefaultString). + // HasRsaPublicKeyString(""). + // HasRsaPublicKey2String(""). + // HasCommentString(""). + // HasDisableMfaString(r.BooleanDefault). + // HasFullyQualifiedNameString(id.FullyQualifiedName()), + // resourceshowoutputassert.UserShowOutput(t, userModelNoAttributes.ResourceReference()). + // HasLoginName(strings.ToUpper(id.Name())). + // HasDisplayName(""), + // ), + //}, + }, + }) +} diff --git a/pkg/sdk/data_types_deprecated.go b/pkg/sdk/data_types_deprecated.go index 0d0315ad5e..492fcd80f1 100644 --- a/pkg/sdk/data_types_deprecated.go +++ b/pkg/sdk/data_types_deprecated.go @@ -47,5 +47,9 @@ func IsStringType(_type string) bool { } func LegacyDataTypeFrom(newDataType datatypes.DataType) DataType { + // TODO [this PR]: remove this check? + if newDataType == nil { + return "" + } return DataType(newDataType.ToLegacyDataTypeSql()) } diff --git a/pkg/sdk/datatypes/legacy.go b/pkg/sdk/datatypes/legacy.go index 5a0e249cd7..c77f286f9c 100644 --- a/pkg/sdk/datatypes/legacy.go +++ b/pkg/sdk/datatypes/legacy.go @@ -16,4 +16,7 @@ const ( TimestampNtzLegacyDataType = "TIMESTAMP_NTZ" TimestampTzLegacyDataType = "TIMESTAMP_TZ" VariantLegacyDataType = "VARIANT" + + // TableLegacyDataType was not a value of legacy data type in the old implementation. Left for now for an easier implementation. + TableLegacyDataType = "TABLE" ) diff --git a/pkg/sdk/datatypes/table.go b/pkg/sdk/datatypes/table.go new file mode 100644 index 0000000000..d5e8bda489 --- /dev/null +++ b/pkg/sdk/datatypes/table.go @@ -0,0 +1,39 @@ +package datatypes + +// TableDataType is based on TODO [this PR] +// It does not have synonyms. +// It consists of a list of column name + column type; may be empty. +// TODO [this PR]: test and improve +type TableDataType struct { + columns []TableDataTypeColumn + underlyingType string +} + +type TableDataTypeColumn struct { + name string + dataType DataType +} + +func (c *TableDataTypeColumn) ColumnName() string { + return c.name +} + +func (c *TableDataTypeColumn) ColumnType() DataType { + return c.dataType +} + +func (t *TableDataType) ToSql() string { + return t.underlyingType +} + +func (t *TableDataType) ToLegacyDataTypeSql() string { + return TableLegacyDataType +} + +func (t *TableDataType) Canonical() string { + return TableLegacyDataType +} + +func (t *TableDataType) Columns() []TableDataTypeColumn { + return t.columns +} diff --git a/pkg/sdk/identifier_helpers.go b/pkg/sdk/identifier_helpers.go index 1609593d71..f94bc2ea4b 100644 --- a/pkg/sdk/identifier_helpers.go +++ b/pkg/sdk/identifier_helpers.go @@ -5,6 +5,7 @@ import ( "log" "strings" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/datatypes" ) @@ -343,6 +344,15 @@ func NewSchemaObjectIdentifierWithArguments(databaseName, schemaName, name strin } } +func NewSchemaObjectIdentifierWithArgumentsNormalized(databaseName, schemaName, name string, argumentDataTypes ...datatypes.DataType) SchemaObjectIdentifierWithArguments { + return SchemaObjectIdentifierWithArguments{ + databaseName: strings.Trim(databaseName, `"`), + schemaName: strings.Trim(schemaName, `"`), + name: strings.Trim(name, `"`), + argumentDataTypes: collections.Map(argumentDataTypes, func(dt datatypes.DataType) DataType { return LegacyDataTypeFrom(dt) }), + } +} + func NewSchemaObjectIdentifierWithArgumentsInSchema(schemaId DatabaseObjectIdentifier, name string, argumentDataTypes ...DataType) SchemaObjectIdentifierWithArguments { return NewSchemaObjectIdentifierWithArguments(schemaId.DatabaseName(), schemaId.Name(), name, argumentDataTypes...) } From 944f67f625745b8cc4e0b2aee193bcf9c99e17ba Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Tue, 10 Dec 2024 18:34:03 +0100 Subject: [PATCH 05/45] Prepare read function for java function --- pkg/resources/function_commons.go | 49 +++++++++++++++++++++++++++++++ pkg/resources/function_java.go | 29 ++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/pkg/resources/function_commons.go b/pkg/resources/function_commons.go index c283224b24..5d666deda7 100644 --- a/pkg/resources/function_commons.go +++ b/pkg/resources/function_commons.go @@ -2,7 +2,9 @@ package resources import ( "context" + "errors" "fmt" + "log" "slices" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" @@ -402,3 +404,50 @@ func parseFunctionReturnsCommon(d *schema.ResourceData) (*sdk.FunctionReturnsReq } return returns, nil } + +func queryAllFunctionsDetailsCommon(ctx context.Context, d *schema.ResourceData, client *sdk.Client, id sdk.SchemaObjectIdentifierWithArguments) (*allFunctionDetailsCommon, diag.Diagnostics) { + functionDetails, err := client.Functions.DescribeDetails(ctx, id) + if err != nil { + if errors.Is(err, sdk.ErrObjectNotExistOrAuthorized) { + log.Printf("[DEBUG] function (%s) not found or we are not authorized. Err: %s", d.Id(), err) + d.SetId("") + return nil, diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Failed to query function. Marking the resource as removed.", + Detail: fmt.Sprintf("Function: %s, Err: %s", id.FullyQualifiedName(), err), + }, + } + } + return nil, diag.FromErr(err) + } + function, err := client.Functions.ShowByID(ctx, id) + if err != nil { + if errors.Is(err, sdk.ErrObjectNotFound) { + d.SetId("") + return nil, diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Failed to query function. Marking the resource as removed.", + Detail: fmt.Sprintf("Function: %s, Err: %s", id.FullyQualifiedName(), err), + }, + } + } + return nil, diag.FromErr(err) + } + functionParameters, err := client.Functions.ShowParameters(ctx, id) + if err != nil { + return nil, diag.FromErr(err) + } + return &allFunctionDetailsCommon{ + function: function, + functionDetails: functionDetails, + functionParameters: functionParameters, + }, nil +} + +type allFunctionDetailsCommon struct { + function *sdk.Function + functionDetails *sdk.FunctionDetails + functionParameters []*sdk.Parameter +} diff --git a/pkg/resources/function_java.go b/pkg/resources/function_java.go index be18f660c6..6596def87d 100644 --- a/pkg/resources/function_java.go +++ b/pkg/resources/function_java.go @@ -2,11 +2,13 @@ package resources import ( "context" + "errors" "strings" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/provider/resources" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/schemas" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/datatypes" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -107,6 +109,33 @@ func CreateContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta } func ReadContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*provider.Context).Client + id, err := sdk.ParseSchemaObjectIdentifierWithArguments(d.Id()) + if err != nil { + return diag.FromErr(err) + } + allFunctionDetails, diags := queryAllFunctionsDetailsCommon(ctx, d, client, id) + if diags != nil { + return diags + } + + // TODO [this PR]: handle external changes marking + // TODO [this PR]: handle setting state to value from config + + errs := errors.Join( + // TODO [this PR]: set all proper fields + + d.Set("function_type", allFunctionDetails.functionDetails.Language), + + d.Set(FullyQualifiedNameAttributeName, id.FullyQualifiedName()), + handleFunctionParameterRead(d, allFunctionDetails.functionParameters), + d.Set(ShowOutputAttributeName, []map[string]any{schemas.FunctionToSchema(allFunctionDetails.function)}), + d.Set(ParametersAttributeName, []map[string]any{schemas.UserParametersToSchema(allFunctionDetails.functionParameters)}), + ) + if errs != nil { + return diag.FromErr(err) + } + return nil } From 923964807229b9192a4c6b2bed1aae41317c1a63 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Tue, 10 Dec 2024 18:38:29 +0100 Subject: [PATCH 06/45] Prepare update function for java function --- pkg/resources/function_java.go | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/pkg/resources/function_java.go b/pkg/resources/function_java.go index 6596def87d..4df2c25f42 100644 --- a/pkg/resources/function_java.go +++ b/pkg/resources/function_java.go @@ -3,8 +3,10 @@ package resources import ( "context" "errors" + "fmt" "strings" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/provider/resources" @@ -114,6 +116,7 @@ func ReadContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta a if err != nil { return diag.FromErr(err) } + allFunctionDetails, diags := queryAllFunctionsDetailsCommon(ctx, d, client, id) if diags != nil { return diags @@ -140,5 +143,25 @@ func ReadContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta a } func UpdateContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - return nil + client := meta.(*provider.Context).Client + id, err := sdk.ParseSchemaObjectIdentifierWithArguments(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + if d.HasChange("name") { + newId := sdk.NewSchemaObjectIdentifierWithArgumentsInSchema(id.SchemaId(), d.Get("name").(string), id.ArgumentDataTypes()...) + + err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithRenameTo(newId.SchemaObjectId())) + if err != nil { + return diag.FromErr(fmt.Errorf("error renaming function %v err = %w", d.Id(), err)) + } + + d.SetId(helpers.EncodeResourceIdentifier(newId)) + id = newId + } + + // TODO [this PR]: handle all updates + + return ReadContextFunctionJava(ctx, d, meta) } From 6a81447e8fc4c1ad0fb7c9c4e7b3817dbdcec6f4 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Tue, 10 Dec 2024 18:56:14 +0100 Subject: [PATCH 07/45] Pass first java function resource test --- .../config/model/function_java_model_ext.go | 12 +++++++ pkg/resources/function_commons.go | 2 +- pkg/resources/function_java.go | 6 ++-- .../function_java_acceptance_test.go | 13 ++++--- pkg/schemas/function_parameters.go | 35 +++++++++++++++++++ 5 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 pkg/schemas/function_parameters.go diff --git a/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go b/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go index 10efc74743..8c3301bce8 100644 --- a/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go +++ b/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go @@ -5,6 +5,7 @@ import ( "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/datatypes" + "github.com/hashicorp/terraform-plugin-testing/config" ) func (f *FunctionJavaModel) MarshalJSON() ([]byte, error) { @@ -27,3 +28,14 @@ func FunctionJavaWithId( ) *FunctionJavaModel { return FunctionJava(resourceName, id.DatabaseName(), functionDefinition, handler, id.Name(), returnType.ToSql(), id.SchemaName()) } + +func (f *FunctionJavaModel) WithArgument(argName string, argDataType datatypes.DataType) *FunctionJavaModel { + return f.WithArgumentsValue( + config.ObjectVariable( + map[string]config.Variable{ + "arg_name": config.StringVariable(argName), + "arg_data_type": config.StringVariable(argDataType.ToSql()), + }, + ), + ) +} diff --git a/pkg/resources/function_commons.go b/pkg/resources/function_commons.go index 5d666deda7..c06f04242e 100644 --- a/pkg/resources/function_commons.go +++ b/pkg/resources/function_commons.go @@ -344,7 +344,7 @@ func functionBaseSchema() map[string]schema.Schema { Computed: true, Description: "Outputs the result of `SHOW PARAMETERS IN FUNCTION` for the given function.", Elem: &schema.Resource{ - Schema: functionParametersSchema, + Schema: schemas.ShowFunctionParametersSchema, }, }, FullyQualifiedNameAttributeName: *schemas.FullyQualifiedNameSchema, diff --git a/pkg/resources/function_java.go b/pkg/resources/function_java.go index 4df2c25f42..7e88ae7a52 100644 --- a/pkg/resources/function_java.go +++ b/pkg/resources/function_java.go @@ -62,7 +62,7 @@ func CreateContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta request := sdk.NewCreateForJavaFunctionRequest(id.SchemaObjectId(), *returns, handler). WithArguments(argumentRequests) - if v, ok := d.GetOk("statement"); ok { + if v, ok := d.GetOk("function_definition"); ok { request.WithFunctionDefinitionWrapped(v.(string)) } @@ -128,12 +128,12 @@ func ReadContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta a errs := errors.Join( // TODO [this PR]: set all proper fields - d.Set("function_type", allFunctionDetails.functionDetails.Language), + d.Set("function_language", allFunctionDetails.functionDetails.Language), d.Set(FullyQualifiedNameAttributeName, id.FullyQualifiedName()), handleFunctionParameterRead(d, allFunctionDetails.functionParameters), d.Set(ShowOutputAttributeName, []map[string]any{schemas.FunctionToSchema(allFunctionDetails.function)}), - d.Set(ParametersAttributeName, []map[string]any{schemas.UserParametersToSchema(allFunctionDetails.functionParameters)}), + d.Set(ParametersAttributeName, []map[string]any{schemas.FunctionParametersToSchema(allFunctionDetails.functionParameters)}), ) if errs != nil { return diag.FromErr(err) diff --git a/pkg/resources/function_java_acceptance_test.go b/pkg/resources/function_java_acceptance_test.go index afea32252e..097ed80433 100644 --- a/pkg/resources/function_java_acceptance_test.go +++ b/pkg/resources/function_java_acceptance_test.go @@ -2,9 +2,11 @@ package resources_test import ( "fmt" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/provider/resources" "testing" acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + r "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/resources" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert/resourceassert" @@ -12,7 +14,6 @@ import ( "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/config" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/config/model" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/testdatatypes" - "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/helper/resource" "github.com/hashicorp/terraform-plugin-testing/tfversion" @@ -32,8 +33,10 @@ func TestAcc_FunctionJava_BasicFlows(t *testing.T) { handler := fmt.Sprintf("%s.%s", className, funcName) definition := acc.TestClient().Function.SampleJavaDefinition(t, className, funcName, argName) - functionModelNoAttributes := model.FunctionJavaWithId("w", id, dataType, handler, definition) - functionModelNoAttributesRenamed := model.FunctionJavaWithId("w", idWithChangedNameButTheSameDataType, dataType, handler, definition) + functionModelNoAttributes := model.FunctionJavaWithId("w", id, dataType, handler, definition). + WithArgument(argName, dataType) + functionModelNoAttributesRenamed := model.FunctionJavaWithId("w", idWithChangedNameButTheSameDataType, dataType, handler, definition). + WithArgument(argName, dataType) resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, @@ -41,7 +44,7 @@ func TestAcc_FunctionJava_BasicFlows(t *testing.T) { tfversion.RequireAbove(tfversion.Version1_5_0), }, PreCheck: func() { acc.TestAccPreCheck(t) }, - CheckDestroy: acc.CheckDestroy(t, resources.User), + CheckDestroy: acc.CheckDestroy(t, resources.Function), Steps: []resource.TestStep{ // CREATE BASIC { @@ -50,6 +53,8 @@ func TestAcc_FunctionJava_BasicFlows(t *testing.T) { resourceassert.FunctionJavaResource(t, functionModelNoAttributes.ResourceReference()). HasNameString(id.Name()). HasCommentString(sdk.DefaultFunctionComment). + HasFunctionLanguageString("JAVA"). + HasIsSecureString(r.BooleanDefault). HasFullyQualifiedNameString(id.FullyQualifiedName()), resourceshowoutputassert.FunctionShowOutput(t, functionModelNoAttributes.ResourceReference()). HasIsSecure(false), diff --git a/pkg/schemas/function_parameters.go b/pkg/schemas/function_parameters.go new file mode 100644 index 0000000000..af7752c394 --- /dev/null +++ b/pkg/schemas/function_parameters.go @@ -0,0 +1,35 @@ +package schemas + +import ( + "slices" + "strings" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var ( + ShowFunctionParametersSchema = make(map[string]*schema.Schema) + functionParameters = []sdk.FunctionParameter{ + sdk.FunctionParameterEnableConsoleOutput, + sdk.FunctionParameterLogLevel, + sdk.FunctionParameterMetricLevel, + sdk.FunctionParameterTraceLevel, + } +) + +func init() { + for _, param := range functionParameters { + ShowFunctionParametersSchema[strings.ToLower(string(param))] = ParameterListSchema + } +} + +func FunctionParametersToSchema(parameters []*sdk.Parameter) map[string]any { + functionParametersValue := make(map[string]any) + for _, param := range parameters { + if slices.Contains(functionParameters, sdk.FunctionParameter(param.Key)) { + functionParametersValue[strings.ToLower(param.Key)] = []map[string]any{ParameterToSchema(param)} + } + } + return functionParametersValue +} From 3d34f25206740331f5266bdde3f49616d1a9918e Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Tue, 10 Dec 2024 18:57:28 +0100 Subject: [PATCH 08/45] Add missing schema to procedures too --- pkg/resources/procedure_commons.go | 2 +- pkg/schemas/procedure_parameters.go | 35 +++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 pkg/schemas/procedure_parameters.go diff --git a/pkg/resources/procedure_commons.go b/pkg/resources/procedure_commons.go index e9bd878171..163a33da0f 100644 --- a/pkg/resources/procedure_commons.go +++ b/pkg/resources/procedure_commons.go @@ -338,7 +338,7 @@ func procedureBaseSchema() map[string]schema.Schema { Computed: true, Description: "Outputs the result of `SHOW PARAMETERS IN PROCEDURE` for the given procedure.", Elem: &schema.Resource{ - Schema: procedureParametersSchema, + Schema: schemas.ShowProcedureParametersSchema, }, }, FullyQualifiedNameAttributeName: *schemas.FullyQualifiedNameSchema, diff --git a/pkg/schemas/procedure_parameters.go b/pkg/schemas/procedure_parameters.go new file mode 100644 index 0000000000..7e9c5c1638 --- /dev/null +++ b/pkg/schemas/procedure_parameters.go @@ -0,0 +1,35 @@ +package schemas + +import ( + "slices" + "strings" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var ( + ShowProcedureParametersSchema = make(map[string]*schema.Schema) + ProcedureParameters = []sdk.ProcedureParameter{ + sdk.ProcedureParameterEnableConsoleOutput, + sdk.ProcedureParameterLogLevel, + sdk.ProcedureParameterMetricLevel, + sdk.ProcedureParameterTraceLevel, + } +) + +func init() { + for _, param := range ProcedureParameters { + ShowProcedureParametersSchema[strings.ToLower(string(param))] = ParameterListSchema + } +} + +func ProcedureParametersToSchema(parameters []*sdk.Parameter) map[string]any { + ProcedureParametersValue := make(map[string]any) + for _, param := range parameters { + if slices.Contains(ProcedureParameters, sdk.ProcedureParameter(param.Key)) { + ProcedureParametersValue[strings.ToLower(param.Key)] = []map[string]any{ParameterToSchema(param)} + } + } + return ProcedureParametersValue +} From ecbcd3d20608382391479363c12b825535b9a0cd Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Tue, 10 Dec 2024 19:02:55 +0100 Subject: [PATCH 09/45] Fix check destroy --- pkg/acceptance/check_destroy.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pkg/acceptance/check_destroy.go b/pkg/acceptance/check_destroy.go index 404ad98917..aec0d3661c 100644 --- a/pkg/acceptance/check_destroy.go +++ b/pkg/acceptance/check_destroy.go @@ -67,9 +67,19 @@ func decodeSnowflakeId(rs *terraform.ResourceState, resource resources.Resource) switch resource { case resources.ExternalFunction: return sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(rs.Primary.ID), nil - case resources.Function: + case resources.Function, + resources.FunctionJava, + resources.FunctionJavascript, + resources.FunctionPython, + resources.FunctionScala, + resources.FunctionSql: return sdk.ParseSchemaObjectIdentifierWithArguments(rs.Primary.ID) - case resources.Procedure: + case resources.Procedure, + resources.ProcedureJava, + resources.ProcedureJavascript, + resources.ProcedurePython, + resources.ProcedureScala, + resources.ProcedureSql: return sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(rs.Primary.ID), nil default: return helpers.DecodeSnowflakeID(rs.Primary.ID), nil From b7ecf3cd0ba6713f0a102a4adeae1c331f7022a3 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Tue, 10 Dec 2024 19:21:55 +0100 Subject: [PATCH 10/45] Prepare function parameters test (WIP) --- .../function_resource_parameters_ext.go | 13 +++ pkg/resources/function_java.go | 23 +++++ .../function_java_acceptance_test.go | 96 ++++++++++++++++++- pkg/resources/function_parameters.go | 12 +-- 4 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 pkg/acceptance/bettertestspoc/assert/resourceparametersassert/function_resource_parameters_ext.go diff --git a/pkg/acceptance/bettertestspoc/assert/resourceparametersassert/function_resource_parameters_ext.go b/pkg/acceptance/bettertestspoc/assert/resourceparametersassert/function_resource_parameters_ext.go new file mode 100644 index 0000000000..2bc66908df --- /dev/null +++ b/pkg/acceptance/bettertestspoc/assert/resourceparametersassert/function_resource_parameters_ext.go @@ -0,0 +1,13 @@ +package resourceparametersassert + +import ( + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" +) + +func (f *FunctionResourceParametersAssert) HasAllDefaults() *FunctionResourceParametersAssert { + return f. + HasEnableConsoleOutput(false). + HasLogLevel(sdk.LogLevelOff). + HasMetricLevel(sdk.MetricLevelNone). + HasTraceLevel(sdk.TraceLevelOff) +} diff --git a/pkg/resources/function_java.go b/pkg/resources/function_java.go index 7e88ae7a52..48469bc3e7 100644 --- a/pkg/resources/function_java.go +++ b/pkg/resources/function_java.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "reflect" "strings" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" @@ -161,7 +162,29 @@ func UpdateContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta id = newId } + // Batch SET operations and UNSET operations + setRequest := sdk.NewFunctionSetRequest() + unsetRequest := sdk.NewFunctionUnsetRequest() + // TODO [this PR]: handle all updates + if updateParamDiags := handleFunctionParametersUpdate(d, setRequest, unsetRequest); len(updateParamDiags) > 0 { + return updateParamDiags + } + + // Apply SET and UNSET changes + if !reflect.DeepEqual(*setRequest, sdk.NewFunctionSetRequest()) { + err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithSet(*setRequest)) + if err != nil { + return diag.FromErr(err) + } + } + if !reflect.DeepEqual(*unsetRequest, sdk.NewFunctionUnsetRequest()) { + err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithUnset(*unsetRequest)) + if err != nil { + return diag.FromErr(err) + } + } + return ReadContextFunctionJava(ctx, d, meta) } diff --git a/pkg/resources/function_java_acceptance_test.go b/pkg/resources/function_java_acceptance_test.go index 097ed80433..9384b48529 100644 --- a/pkg/resources/function_java_acceptance_test.go +++ b/pkg/resources/function_java_acceptance_test.go @@ -2,18 +2,20 @@ package resources_test import ( "fmt" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/provider/resources" "testing" acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" r "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/resources" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert/objectparametersassert" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert/resourceassert" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert/resourceparametersassert" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert/resourceshowoutputassert" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/config" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/config/model" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/testdatatypes" + "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/helper/resource" "github.com/hashicorp/terraform-plugin-testing/tfversion" @@ -44,7 +46,7 @@ func TestAcc_FunctionJava_BasicFlows(t *testing.T) { tfversion.RequireAbove(tfversion.Version1_5_0), }, PreCheck: func() { acc.TestAccPreCheck(t) }, - CheckDestroy: acc.CheckDestroy(t, resources.Function), + CheckDestroy: acc.CheckDestroy(t, resources.FunctionJava), Steps: []resource.TestStep{ // CREATE BASIC { @@ -205,3 +207,93 @@ func TestAcc_FunctionJava_BasicFlows(t *testing.T) { }, }) } + +func TestAcc_FunctionJava_AllParameters(t *testing.T) { + className := "TestFunc" + funcName := "echoVarchar" + argName := "x" + dataType := testdatatypes.DataTypeVarchar_100 + id := acc.TestClient().Ids.RandomSchemaObjectIdentifierWithArgumentsNewDataTypes(dataType) + + handler := fmt.Sprintf("%s.%s", className, funcName) + definition := acc.TestClient().Function.SampleJavaDefinition(t, className, funcName, argName) + + functionModel := model.FunctionJavaWithId("w", id, dataType, handler, definition). + WithArgument(argName, dataType) + functionModelWithAllParametersSet := model.FunctionJavaWithId("w", id, dataType, handler, definition). + WithArgument(argName, dataType). + WithEnableConsoleOutput(true). + WithLogLevel(string(sdk.LogLevelWarn)). + WithMetricLevel(string(sdk.MetricLevelAll)). + WithTraceLevel(string(sdk.TraceLevelAlways)) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + PreCheck: func() { acc.TestAccPreCheck(t) }, + CheckDestroy: acc.CheckDestroy(t, resources.FunctionJava), + Steps: []resource.TestStep{ + // create with default values for all the parameters + { + Config: config.ResourceFromModel(t, functionModel), + Check: assert.AssertThat(t, + objectparametersassert.FunctionParameters(t, id). + HasAllDefaults(). + HasAllDefaultsExplicit(), + resourceparametersassert.FunctionResourceParameters(t, functionModel.ResourceReference()). + HasAllDefaults(), + ), + }, + // import when no parameter set + { + ResourceName: functionModel.ResourceReference(), + ImportState: true, + ImportStateCheck: assert.AssertThatImport(t, + resourceparametersassert.ImportedFunctionResourceParameters(t, id.Name()). + HasAllDefaults(), + ), + }, + // set all parameters + { + Config: config.FromModel(t, functionModelWithAllParametersSet), + Check: assert.AssertThat(t, + objectparametersassert.FunctionParameters(t, id). + HasEnableConsoleOutput(true). + HasLogLevel(sdk.LogLevelWarn). + HasMetricLevel(sdk.MetricLevelAll). + HasTraceLevel(sdk.TraceLevelAlways), + resourceparametersassert.FunctionResourceParameters(t, functionModelWithAllParametersSet.ResourceReference()). + HasEnableConsoleOutput(true). + HasLogLevel(sdk.LogLevelWarn). + HasMetricLevel(sdk.MetricLevelAll). + HasTraceLevel(sdk.TraceLevelAlways), + ), + }, + // import when all parameters set + { + ResourceName: functionModelWithAllParametersSet.ResourceReference(), + ImportState: true, + ImportStateCheck: assert.AssertThatImport(t, + resourceparametersassert.ImportedFunctionResourceParameters(t, id.Name()). + HasEnableConsoleOutput(true). + HasLogLevel(sdk.LogLevelWarn). + HasMetricLevel(sdk.MetricLevelAll). + HasTraceLevel(sdk.TraceLevelAlways), + ), + }, + // unset all the parameters + { + Config: config.FromModel(t, functionModel), + Check: assert.AssertThat(t, + objectparametersassert.FunctionParameters(t, id). + HasAllDefaults(). + HasAllDefaultsExplicit(), + resourceparametersassert.UserResourceParameters(t, functionModel.ResourceReference()). + HasAllDefaults(), + ), + }, + }, + }) +} diff --git a/pkg/resources/function_parameters.go b/pkg/resources/function_parameters.go index bccbe0666a..3ff28095b5 100644 --- a/pkg/resources/function_parameters.go +++ b/pkg/resources/function_parameters.go @@ -80,16 +80,16 @@ func handleFunctionParameterRead(d *schema.ResourceData, functionParameters []*s } // They do not work in create, that's why are set in alter -func handleFunctionParametersCreate(d *schema.ResourceData, alterOpts *sdk.FunctionSet) diag.Diagnostics { +func handleFunctionParametersCreate(d *schema.ResourceData, set *sdk.FunctionSetRequest) diag.Diagnostics { return JoinDiags( - handleParameterCreate(d, sdk.FunctionParameterEnableConsoleOutput, &alterOpts.EnableConsoleOutput), - handleParameterCreateWithMapping(d, sdk.FunctionParameterLogLevel, &alterOpts.LogLevel, stringToStringEnumProvider(sdk.ToLogLevel)), - handleParameterCreateWithMapping(d, sdk.FunctionParameterMetricLevel, &alterOpts.MetricLevel, stringToStringEnumProvider(sdk.ToMetricLevel)), - handleParameterCreateWithMapping(d, sdk.FunctionParameterTraceLevel, &alterOpts.TraceLevel, stringToStringEnumProvider(sdk.ToTraceLevel)), + handleParameterCreate(d, sdk.FunctionParameterEnableConsoleOutput, &set.EnableConsoleOutput), + handleParameterCreateWithMapping(d, sdk.FunctionParameterLogLevel, &set.LogLevel, stringToStringEnumProvider(sdk.ToLogLevel)), + handleParameterCreateWithMapping(d, sdk.FunctionParameterMetricLevel, &set.MetricLevel, stringToStringEnumProvider(sdk.ToMetricLevel)), + handleParameterCreateWithMapping(d, sdk.FunctionParameterTraceLevel, &set.TraceLevel, stringToStringEnumProvider(sdk.ToTraceLevel)), ) } -func handleFunctionParametersUpdate(d *schema.ResourceData, set *sdk.FunctionSet, unset *sdk.FunctionUnset) diag.Diagnostics { +func handleFunctionParametersUpdate(d *schema.ResourceData, set *sdk.FunctionSetRequest, unset *sdk.FunctionUnsetRequest) diag.Diagnostics { return JoinDiags( handleParameterUpdate(d, sdk.FunctionParameterEnableConsoleOutput, &set.EnableConsoleOutput, &unset.EnableConsoleOutput), handleParameterUpdateWithMapping(d, sdk.FunctionParameterLogLevel, &set.LogLevel, &unset.LogLevel, stringToStringEnumProvider(sdk.ToLogLevel)), From 684389a48aa42951b8cd5f4b946de93b34a5b6e4 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 01:22:42 +0100 Subject: [PATCH 11/45] Fix check destroy --- pkg/acceptance/check_destroy.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/pkg/acceptance/check_destroy.go b/pkg/acceptance/check_destroy.go index aec0d3661c..5959720df0 100644 --- a/pkg/acceptance/check_destroy.go +++ b/pkg/acceptance/check_destroy.go @@ -155,6 +155,21 @@ var showByIdFunctions = map[resources.Resource]showByIdFunc{ resources.Function: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { return runShowById(ctx, id, client.Functions.ShowByID) }, + resources.FunctionJava: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { + return runShowById(ctx, id, client.Functions.ShowByID) + }, + resources.FunctionJavascript: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { + return runShowById(ctx, id, client.Functions.ShowByID) + }, + resources.FunctionPython: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { + return runShowById(ctx, id, client.Functions.ShowByID) + }, + resources.FunctionScala: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { + return runShowById(ctx, id, client.Functions.ShowByID) + }, + resources.FunctionSql: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { + return runShowById(ctx, id, client.Functions.ShowByID) + }, resources.LegacyServiceUser: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { return runShowById(ctx, id, client.Users.ShowByID) }, @@ -191,6 +206,21 @@ var showByIdFunctions = map[resources.Resource]showByIdFunc{ resources.Procedure: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { return runShowById(ctx, id, client.Procedures.ShowByID) }, + resources.ProcedureJava: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { + return runShowById(ctx, id, client.Procedures.ShowByID) + }, + resources.ProcedureJavascript: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { + return runShowById(ctx, id, client.Procedures.ShowByID) + }, + resources.ProcedurePython: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { + return runShowById(ctx, id, client.Procedures.ShowByID) + }, + resources.ProcedureScala: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { + return runShowById(ctx, id, client.Procedures.ShowByID) + }, + resources.ProcedureSql: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { + return runShowById(ctx, id, client.Procedures.ShowByID) + }, resources.ResourceMonitor: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error { return runShowById(ctx, id, client.ResourceMonitors.ShowByID) }, From add601033f52a8341554cef34883973f5a1f86d7 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 01:45:06 +0100 Subject: [PATCH 12/45] Pass parameters test for java function --- pkg/resources/function_java.go | 20 +++++++++--- .../function_java_acceptance_test.go | 32 ++++++++++++++++--- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/pkg/resources/function_java.go b/pkg/resources/function_java.go index 48469bc3e7..5b46b0068d 100644 --- a/pkg/resources/function_java.go +++ b/pkg/resources/function_java.go @@ -67,13 +67,25 @@ func CreateContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta request.WithFunctionDefinitionWrapped(v.(string)) } + // TODO [this PR]: handle all attributes + if err := client.Functions.CreateForJava(ctx, request); err != nil { return diag.FromErr(err) } + d.SetId(helpers.EncodeResourceIdentifier(id)) - // TODO [this PR]: handle parameters + // parameters do not work in create function (query does not fail but parameters stay unchanged) + setRequest := sdk.NewFunctionSetRequest() + if parametersCreateDiags := handleFunctionParametersCreate(d, setRequest); len(parametersCreateDiags) > 0 { + return parametersCreateDiags + } + if !reflect.DeepEqual(*setRequest, *sdk.NewFunctionSetRequest()) { + err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithSet(*setRequest)) + if err != nil { + return diag.FromErr(err) + } + } - d.SetId(id.FullyQualifiedName()) return ReadContextFunctionJava(ctx, d, meta) // Set optionals @@ -173,13 +185,13 @@ func UpdateContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta } // Apply SET and UNSET changes - if !reflect.DeepEqual(*setRequest, sdk.NewFunctionSetRequest()) { + if !reflect.DeepEqual(*setRequest, *sdk.NewFunctionSetRequest()) { err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithSet(*setRequest)) if err != nil { return diag.FromErr(err) } } - if !reflect.DeepEqual(*unsetRequest, sdk.NewFunctionUnsetRequest()) { + if !reflect.DeepEqual(*unsetRequest, *sdk.NewFunctionUnsetRequest()) { err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithUnset(*unsetRequest)) if err != nil { return diag.FromErr(err) diff --git a/pkg/resources/function_java_acceptance_test.go b/pkg/resources/function_java_acceptance_test.go index 9384b48529..c776ba94bd 100644 --- a/pkg/resources/function_java_acceptance_test.go +++ b/pkg/resources/function_java_acceptance_test.go @@ -15,6 +15,7 @@ import ( "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/config" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/config/model" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/testdatatypes" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" "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/helper/resource" @@ -251,13 +252,13 @@ func TestAcc_FunctionJava_AllParameters(t *testing.T) { ResourceName: functionModel.ResourceReference(), ImportState: true, ImportStateCheck: assert.AssertThatImport(t, - resourceparametersassert.ImportedFunctionResourceParameters(t, id.Name()). + resourceparametersassert.ImportedFunctionResourceParameters(t, helpers.EncodeResourceIdentifier(id)). HasAllDefaults(), ), }, // set all parameters { - Config: config.FromModel(t, functionModelWithAllParametersSet), + Config: config.ResourceFromModel(t, functionModelWithAllParametersSet), Check: assert.AssertThat(t, objectparametersassert.FunctionParameters(t, id). HasEnableConsoleOutput(true). @@ -276,7 +277,7 @@ func TestAcc_FunctionJava_AllParameters(t *testing.T) { ResourceName: functionModelWithAllParametersSet.ResourceReference(), ImportState: true, ImportStateCheck: assert.AssertThatImport(t, - resourceparametersassert.ImportedFunctionResourceParameters(t, id.Name()). + resourceparametersassert.ImportedFunctionResourceParameters(t, helpers.EncodeResourceIdentifier(id)). HasEnableConsoleOutput(true). HasLogLevel(sdk.LogLevelWarn). HasMetricLevel(sdk.MetricLevelAll). @@ -285,15 +286,36 @@ func TestAcc_FunctionJava_AllParameters(t *testing.T) { }, // unset all the parameters { - Config: config.FromModel(t, functionModel), + Config: config.ResourceFromModel(t, functionModel), Check: assert.AssertThat(t, objectparametersassert.FunctionParameters(t, id). HasAllDefaults(). HasAllDefaultsExplicit(), - resourceparametersassert.UserResourceParameters(t, functionModel.ResourceReference()). + resourceparametersassert.FunctionResourceParameters(t, functionModel.ResourceReference()). HasAllDefaults(), ), }, + // destroy + { + Config: config.ResourceFromModel(t, functionModel), + Destroy: true, + }, + // create with all parameters set + { + Config: config.ResourceFromModel(t, functionModelWithAllParametersSet), + Check: assert.AssertThat(t, + objectparametersassert.FunctionParameters(t, id). + HasEnableConsoleOutput(true). + HasLogLevel(sdk.LogLevelWarn). + HasMetricLevel(sdk.MetricLevelAll). + HasTraceLevel(sdk.TraceLevelAlways), + resourceparametersassert.FunctionResourceParameters(t, functionModelWithAllParametersSet.ResourceReference()). + HasEnableConsoleOutput(true). + HasLogLevel(sdk.LogLevelWarn). + HasMetricLevel(sdk.MetricLevelAll). + HasTraceLevel(sdk.TraceLevelAlways), + ), + }, }, }) } From 2fd37cef720349f17d3281d1190ad2b8152d61e0 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 02:17:21 +0100 Subject: [PATCH 13/45] Continue with create and read --- pkg/resources/function_commons.go | 8 ++-- pkg/resources/function_java.go | 50 +++++++++++++++--------- pkg/resources/resource_helpers_create.go | 29 ++++++++++++++ pkg/resources/resource_helpers_read.go | 9 +++++ 4 files changed, 73 insertions(+), 23 deletions(-) diff --git a/pkg/resources/function_commons.go b/pkg/resources/function_commons.go index c06f04242e..f4720746f4 100644 --- a/pkg/resources/function_commons.go +++ b/pkg/resources/function_commons.go @@ -81,7 +81,7 @@ var ( "arguments", "return_type", "null_input_behavior", - "return_behavior", + "return_results_behavior", "comment", "function_definition", "function_language", @@ -241,15 +241,15 @@ func functionBaseSchema() map[string]schema.Schema { Optional: true, ForceNew: true, ValidateDiagFunc: sdkValidation(sdk.ToNullInputBehavior), - DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToNullInputBehavior), IgnoreChangeToCurrentSnowflakeValueInShow("null_input_behavior")), + DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToNullInputBehavior)), // TODO [this PR]: IgnoreChangeToCurrentSnowflakeValueInShow("null_input_behavior") but not in show Description: fmt.Sprintf("Specifies the behavior of the function when called with null inputs. Valid values are (case-insensitive): %s.", possibleValuesListed(sdk.AllAllowedNullInputBehaviors)), }, - "return_behavior": { + "return_results_behavior": { Type: schema.TypeString, Optional: true, ForceNew: true, ValidateDiagFunc: sdkValidation(sdk.ToReturnResultsBehavior), - DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToReturnResultsBehavior), IgnoreChangeToCurrentSnowflakeValueInShow("return_behavior")), + DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToReturnResultsBehavior)), // TODO [this PR]: IgnoreChangeToCurrentSnowflakeValueInShow("return_results_behavior") but not in show Description: fmt.Sprintf("Specifies the behavior of the function when returning results. Valid values are (case-insensitive): %s.", possibleValuesListed(sdk.AllAllowedReturnResultsBehaviors)), }, "runtime_version": { diff --git a/pkg/resources/function_java.go b/pkg/resources/function_java.go index 5b46b0068d..d0f4079c98 100644 --- a/pkg/resources/function_java.go +++ b/pkg/resources/function_java.go @@ -63,12 +63,24 @@ func CreateContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta request := sdk.NewCreateForJavaFunctionRequest(id.SchemaObjectId(), *returns, handler). WithArguments(argumentRequests) - if v, ok := d.GetOk("function_definition"); ok { - request.WithFunctionDefinitionWrapped(v.(string)) + errs := errors.Join( + booleanStringAttributeCreateBuilder(d, "is_secure", request.WithSecure), + attributeMappedValueCreateBuilder[string](d, "null_input_behavior", request.WithNullInputBehavior, sdk.ToNullInputBehavior), + attributeMappedValueCreateBuilder[string](d, "return_results_behavior", request.WithReturnResultsBehavior, sdk.ToReturnResultsBehavior), + stringAttributeCreateBuilder(d, "runtime_version", request.WithRuntimeVersion), + // TODO [this PR]: handle all attributes + // comment + // imports + // packages + // external_access_integrations + // secrets + // target_path + stringAttributeCreateBuilder(d, "function_definition", request.WithFunctionDefinitionWrapped), + ) + if errs != nil { + return diag.FromErr(errs) } - // TODO [this PR]: handle all attributes - if err := client.Functions.CreateForJava(ctx, request); err != nil { return diag.FromErr(err) } @@ -88,19 +100,6 @@ func CreateContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta return ReadContextFunctionJava(ctx, d, meta) - // Set optionals - //if v, ok := d.GetOk("is_secure"); ok { - // request.WithSecure(v.(bool)) - //} - //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)) //} @@ -140,11 +139,24 @@ func ReadContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta a errs := errors.Join( // TODO [this PR]: set all proper fields - + // not reading is_secure on purpose (handled as external change to show output) + // arguments + // return_type + // not reading null_input_behavior on purpose (handled as external change to show output) + // not reading return_results_behavior on purpose (handled as external change to show output) + setOptionalFromStringPtr(d, "runtime_version", allFunctionDetails.functionDetails.RuntimeVersion), + // comment + // imports + // packages + // handler + // external_access_integrations + // secrets + // target_path + setOptionalFromStringPtr(d, "function_definition", allFunctionDetails.functionDetails.Body), d.Set("function_language", allFunctionDetails.functionDetails.Language), - d.Set(FullyQualifiedNameAttributeName, id.FullyQualifiedName()), handleFunctionParameterRead(d, allFunctionDetails.functionParameters), + d.Set(FullyQualifiedNameAttributeName, id.FullyQualifiedName()), d.Set(ShowOutputAttributeName, []map[string]any{schemas.FunctionToSchema(allFunctionDetails.function)}), d.Set(ParametersAttributeName, []map[string]any{schemas.FunctionParametersToSchema(allFunctionDetails.functionParameters)}), ) diff --git a/pkg/resources/resource_helpers_create.go b/pkg/resources/resource_helpers_create.go index 837fada163..42c34ca0c7 100644 --- a/pkg/resources/resource_helpers_create.go +++ b/pkg/resources/resource_helpers_create.go @@ -12,6 +12,13 @@ func stringAttributeCreate(d *schema.ResourceData, key string, createField **str return nil } +func stringAttributeCreateBuilder[T any](d *schema.ResourceData, key string, setValue func(string) T) error { + if v, ok := d.GetOk(key); ok { + setValue(v.(string)) + } + return nil +} + func intAttributeCreate(d *schema.ResourceData, key string, createField **int) error { if v, ok := d.GetOk(key); ok { *createField = sdk.Int(v.(int)) @@ -37,6 +44,17 @@ func booleanStringAttributeCreate(d *schema.ResourceData, key string, createFiel return nil } +func booleanStringAttributeCreateBuilder[T any](d *schema.ResourceData, key string, setValue func(bool) T) error { + if v := d.Get(key).(string); v != BooleanDefault { + parsed, err := booleanStringToBool(v) + if err != nil { + return err + } + setValue(parsed) + } + return nil +} + func accountObjectIdentifierAttributeCreate(d *schema.ResourceData, key string, createField **sdk.AccountObjectIdentifier) error { if v, ok := d.GetOk(key); ok { *createField = sdk.Pointer(sdk.NewAccountObjectIdentifier(v.(string))) @@ -73,6 +91,17 @@ func attributeMappedValueCreate[T any](d *schema.ResourceData, key string, creat return nil } +func attributeMappedValueCreateBuilder[InputType any, MappedType any, RequestBuilder any](d *schema.ResourceData, key string, setValue func(MappedType) RequestBuilder, mapper func(value InputType) (MappedType, error)) error { + if v, ok := d.GetOk(key); ok { + value, err := mapper(v.(InputType)) + if err != nil { + return err + } + setValue(value) + } + return nil +} + func copyGrantsAttributeCreate(d *schema.ResourceData, isOrReplace bool, orReplaceField, copyGrantsField **bool) error { if isOrReplace { *orReplaceField = sdk.Bool(true) diff --git a/pkg/resources/resource_helpers_read.go b/pkg/resources/resource_helpers_read.go index b3dcfcebf1..06c1f84e63 100644 --- a/pkg/resources/resource_helpers_read.go +++ b/pkg/resources/resource_helpers_read.go @@ -63,3 +63,12 @@ func attributeMappedValueReadOrDefault[T, R any](d *schema.ResourceData, key str } return d.Set(key, nil) } + +func setOptionalFromStringPtr(d *schema.ResourceData, key string, ptr *string) error { + if ptr != nil { + if err := d.Set(key, *ptr); err != nil { + return err + } + } + return nil +} From 7ea11ba6eb947ff1c214d362d19733390c065196 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 02:32:02 +0100 Subject: [PATCH 14/45] Add note about return type --- pkg/resources/function_java.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/resources/function_java.go b/pkg/resources/function_java.go index d0f4079c98..79866ec1aa 100644 --- a/pkg/resources/function_java.go +++ b/pkg/resources/function_java.go @@ -141,7 +141,7 @@ func ReadContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta a // TODO [this PR]: set all proper fields // not reading is_secure on purpose (handled as external change to show output) // arguments - // return_type + // return_type -> parse Returns from allFunctionDetails.functionDetails (NOT NULL can be added) // not reading null_input_behavior on purpose (handled as external change to show output) // not reading return_results_behavior on purpose (handled as external change to show output) setOptionalFromStringPtr(d, "runtime_version", allFunctionDetails.functionDetails.RuntimeVersion), From fbb6c0996d26f41aba2721702025278309af85e2 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 02:44:37 +0100 Subject: [PATCH 15/45] Add recreation --- pkg/resources/custom_diffs.go | 15 +++++++++++++++ pkg/resources/function_java.go | 6 +++--- pkg/resources/resource_helpers_read.go | 10 ++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/pkg/resources/custom_diffs.go b/pkg/resources/custom_diffs.go index eb33b246b7..de608bb24f 100644 --- a/pkg/resources/custom_diffs.go +++ b/pkg/resources/custom_diffs.go @@ -284,3 +284,18 @@ func RecreateWhenResourceBoolFieldChangedExternally(boolField string, wantValue return nil } } + +// RecreateWhenResourceFieldChangedExternally recreates a resource when wantValue is different from value in field. +// TODO [this PR]: replace above func and test +func RecreateWhenResourceFieldChangedExternally[T bool | string](field string, wantValue T) schema.CustomizeDiffFunc { + return func(_ context.Context, diff *schema.ResourceDiff, _ any) error { + if n := diff.Get(field); n != nil { + logging.DebugLogger.Printf("[DEBUG] new external value for %v: %v, recreating the resource...\n", field, n.(T)) + + if n.(T) != wantValue { + return errors.Join(diff.SetNew(field, wantValue), diff.ForceNew(field)) + } + } + return nil + } +} diff --git a/pkg/resources/function_java.go b/pkg/resources/function_java.go index 79866ec1aa..4c68cb9fab 100644 --- a/pkg/resources/function_java.go +++ b/pkg/resources/function_java.go @@ -28,11 +28,11 @@ func FunctionJava() *schema.Resource { Description: "Resource used to manage java function objects. For more information, check [function documentation](https://docs.snowflake.com/en/sql-reference/sql/create-function).", CustomizeDiff: TrackingCustomDiffWrapper(resources.FunctionJava, customdiff.All( - // TODO[SNOW-1348103]: ComputedIfAnyAttributeChanged(javaFunctionSchema, ShowOutputAttributeName, ...), + // TODO [SNOW-1348103]: ComputedIfAnyAttributeChanged(javaFunctionSchema, ShowOutputAttributeName, ...), ComputedIfAnyAttributeChanged(javaFunctionSchema, FullyQualifiedNameAttributeName, "name"), ComputedIfAnyAttributeChanged(functionParametersSchema, ParametersAttributeName, collections.Map(sdk.AsStringList(sdk.AllFunctionParameters), strings.ToLower)...), functionParametersCustomDiff, - // TODO[SNOW-1348103]: recreate when type changed externally + RecreateWhenResourceFieldChangedExternally("function_language", "JAVA"), )), Schema: collections.MergeMaps(javaFunctionSchema, functionParametersSchema), @@ -148,7 +148,7 @@ func ReadContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta a // comment // imports // packages - // handler + setRequiredFromStringPtr(d, "handler", allFunctionDetails.functionDetails.Handler), // external_access_integrations // secrets // target_path diff --git a/pkg/resources/resource_helpers_read.go b/pkg/resources/resource_helpers_read.go index 06c1f84e63..5394ca599c 100644 --- a/pkg/resources/resource_helpers_read.go +++ b/pkg/resources/resource_helpers_read.go @@ -72,3 +72,13 @@ func setOptionalFromStringPtr(d *schema.ResourceData, key string, ptr *string) e } return nil } + +// TODO [this PR]: return error if nil +func setRequiredFromStringPtr(d *schema.ResourceData, key string, ptr *string) error { + if ptr != nil { + if err := d.Set(key, *ptr); err != nil { + return err + } + } + return nil +} From e6c9dbfc4bd8d2a542483cccec2b010d20855b44 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 02:56:29 +0100 Subject: [PATCH 16/45] Prepare language change test --- .../function_java_acceptance_test.go | 69 +++++++++++++++++-- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/pkg/resources/function_java_acceptance_test.go b/pkg/resources/function_java_acceptance_test.go index c776ba94bd..b6706d9c56 100644 --- a/pkg/resources/function_java_acceptance_test.go +++ b/pkg/resources/function_java_acceptance_test.go @@ -8,6 +8,7 @@ import ( r "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/resources" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert/objectassert" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert/objectparametersassert" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert/resourceassert" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert/resourceparametersassert" @@ -19,6 +20,7 @@ import ( "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/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/tfversion" ) @@ -51,7 +53,7 @@ func TestAcc_FunctionJava_BasicFlows(t *testing.T) { Steps: []resource.TestStep{ // CREATE BASIC { - Config: config.ResourceFromModel(t, functionModelNoAttributes), + Config: config.FromModels(t, functionModelNoAttributes), Check: assert.AssertThat(t, resourceassert.FunctionJavaResource(t, functionModelNoAttributes.ResourceReference()). HasNameString(id.Name()). @@ -65,7 +67,7 @@ func TestAcc_FunctionJava_BasicFlows(t *testing.T) { }, // RENAME { - Config: config.ResourceFromModel(t, functionModelNoAttributesRenamed), + Config: config.FromModels(t, functionModelNoAttributesRenamed), Check: assert.AssertThat(t, resourceassert.FunctionJavaResource(t, functionModelNoAttributesRenamed.ResourceReference()). HasNameString(idWithChangedNameButTheSameDataType.Name()). @@ -238,7 +240,7 @@ func TestAcc_FunctionJava_AllParameters(t *testing.T) { Steps: []resource.TestStep{ // create with default values for all the parameters { - Config: config.ResourceFromModel(t, functionModel), + Config: config.FromModels(t, functionModel), Check: assert.AssertThat(t, objectparametersassert.FunctionParameters(t, id). HasAllDefaults(). @@ -258,7 +260,7 @@ func TestAcc_FunctionJava_AllParameters(t *testing.T) { }, // set all parameters { - Config: config.ResourceFromModel(t, functionModelWithAllParametersSet), + Config: config.FromModels(t, functionModelWithAllParametersSet), Check: assert.AssertThat(t, objectparametersassert.FunctionParameters(t, id). HasEnableConsoleOutput(true). @@ -286,7 +288,7 @@ func TestAcc_FunctionJava_AllParameters(t *testing.T) { }, // unset all the parameters { - Config: config.ResourceFromModel(t, functionModel), + Config: config.FromModels(t, functionModel), Check: assert.AssertThat(t, objectparametersassert.FunctionParameters(t, id). HasAllDefaults(). @@ -297,12 +299,12 @@ func TestAcc_FunctionJava_AllParameters(t *testing.T) { }, // destroy { - Config: config.ResourceFromModel(t, functionModel), + Config: config.FromModels(t, functionModel), Destroy: true, }, // create with all parameters set { - Config: config.ResourceFromModel(t, functionModelWithAllParametersSet), + Config: config.FromModels(t, functionModelWithAllParametersSet), Check: assert.AssertThat(t, objectparametersassert.FunctionParameters(t, id). HasEnableConsoleOutput(true). @@ -319,3 +321,56 @@ func TestAcc_FunctionJava_AllParameters(t *testing.T) { }, }) } + +// TODO [this PR]: Test with Java versus Scala staged (probably the only way to set up functions to have exactly the same config in both languages) +func TestAcc_FunctionJava_handleExternalLanguageChange(t *testing.T) { + className := "TestFunc" + funcName := "echoVarchar" + argName := "x" + dataType := testdatatypes.DataTypeVarchar_100 + id := acc.TestClient().Ids.RandomSchemaObjectIdentifierWithArgumentsNewDataTypes(dataType) + + handler := fmt.Sprintf("%s.%s", className, funcName) + definition := acc.TestClient().Function.SampleJavaDefinition(t, className, funcName, argName) + + functionModel := model.FunctionJavaWithId("w", id, dataType, handler, definition). + WithArgument(argName, dataType) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + PreCheck: func() { acc.TestAccPreCheck(t) }, + CheckDestroy: acc.CheckDestroy(t, resources.FunctionJava), + Steps: []resource.TestStep{ + { + Config: config.FromModels(t, functionModel), + Check: assert.AssertThat(t, + objectassert.Function(t, id).HasLanguage("JAVA"), + resourceassert.FunctionJavaResource(t, functionModel.ResourceReference()).HasNameString(id.Name()).HasFunctionLanguageString("JAVA"), + resourceshowoutputassert.FunctionShowOutput(t, functionModel.ResourceReference()).HasLanguage("JAVA"), + ), + }, + // change type externally by creating a new function with the exact same id but using different language + { + PreConfig: func() { + acc.TestClient().Function.DropFunctionFunc(t, id)() + acc.TestClient().Function.CreateSqlWithIdentifierAndArgument(t, id.SchemaObjectId(), dataType) + objectassert.Function(t, id).HasLanguage("SQL") + }, + Config: config.FromModel(t, functionModel), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(functionModel.ResourceReference(), plancheck.ResourceActionDestroyBeforeCreate), + }, + }, + Check: assert.AssertThat(t, + objectassert.Function(t, id).HasLanguage("JAVA"), + resourceassert.FunctionJavaResource(t, functionModel.ResourceReference()).HasNameString(id.Name()).HasFunctionLanguageString("JAVA"), + resourceshowoutputassert.FunctionShowOutput(t, functionModel.ResourceReference()).HasLanguage("JAVA"), + ), + }, + }, + }) +} From 1937a9e3f1a0f5b671869c52e1701e16043a5296 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 09:54:02 +0100 Subject: [PATCH 17/45] Prepare basic staged setup --- .../config/model/function_java_model_ext.go | 51 ++++++++++++++++--- pkg/resources/function_commons.go | 1 + .../function_java_acceptance_test.go | 29 ++++++----- 3 files changed, 62 insertions(+), 19 deletions(-) diff --git a/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go b/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go index 8c3301bce8..1a4dcb63fe 100644 --- a/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go +++ b/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go @@ -3,9 +3,12 @@ package model import ( "encoding/json" + tfconfig "github.com/hashicorp/terraform-plugin-testing/config" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/config" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/provider/resources" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/datatypes" - "github.com/hashicorp/terraform-plugin-testing/config" ) func (f *FunctionJavaModel) MarshalJSON() ([]byte, error) { @@ -19,23 +22,57 @@ func (f *FunctionJavaModel) MarshalJSON() ([]byte, error) { }) } -func FunctionJavaWithId( +func FunctionJavaBasicInline( resourceName string, id sdk.SchemaObjectIdentifierWithArguments, returnType datatypes.DataType, handler string, functionDefinition string, ) *FunctionJavaModel { - return FunctionJava(resourceName, id.DatabaseName(), functionDefinition, handler, id.Name(), returnType.ToSql(), id.SchemaName()) + return FunctionJavaf(resourceName, id.DatabaseName(), handler, id.Name(), returnType.ToSql(), id.SchemaName()).WithFunctionDefinition(functionDefinition) +} + +func FunctionJavaBasicStaged( + resourceName string, + id sdk.SchemaObjectIdentifierWithArguments, + returnType datatypes.DataType, + handler string, + importPath string, +) *FunctionJavaModel { + return FunctionJavaf(resourceName, id.DatabaseName(), handler, id.Name(), returnType.ToSql(), id.SchemaName()). + WithImport(importPath) +} + +func FunctionJavaf( + resourceName string, + database string, + handler string, + name string, + returnType string, + schema string, +) *FunctionJavaModel { + f := &FunctionJavaModel{ResourceModelMeta: config.Meta(resourceName, resources.FunctionJava)} + f.WithDatabase(database) + f.WithHandler(handler) + f.WithName(name) + f.WithReturnType(returnType) + f.WithSchema(schema) + return f } func (f *FunctionJavaModel) WithArgument(argName string, argDataType datatypes.DataType) *FunctionJavaModel { return f.WithArgumentsValue( - config.ObjectVariable( - map[string]config.Variable{ - "arg_name": config.StringVariable(argName), - "arg_data_type": config.StringVariable(argDataType.ToSql()), + tfconfig.ObjectVariable( + map[string]tfconfig.Variable{ + "arg_name": tfconfig.StringVariable(argName), + "arg_data_type": tfconfig.StringVariable(argDataType.ToSql()), }, ), ) } + +func (f *FunctionJavaModel) WithImport(importPath string) *FunctionJavaModel { + return f.WithArgumentsValue( + tfconfig.SetVariable(tfconfig.StringVariable(importPath)), + ) +} diff --git a/pkg/resources/function_commons.go b/pkg/resources/function_commons.go index f4720746f4..8141159557 100644 --- a/pkg/resources/function_commons.go +++ b/pkg/resources/function_commons.go @@ -320,6 +320,7 @@ func functionBaseSchema() map[string]schema.Schema { Optional: true, ForceNew: true, }, + // TODO [this PR]: required only for javascript and sql "function_definition": { Type: schema.TypeString, Required: true, diff --git a/pkg/resources/function_java_acceptance_test.go b/pkg/resources/function_java_acceptance_test.go index b6706d9c56..6a7a146ccb 100644 --- a/pkg/resources/function_java_acceptance_test.go +++ b/pkg/resources/function_java_acceptance_test.go @@ -16,6 +16,7 @@ import ( "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/config" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/config/model" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/testdatatypes" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/testenvs" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/provider/resources" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" @@ -38,9 +39,9 @@ func TestAcc_FunctionJava_BasicFlows(t *testing.T) { handler := fmt.Sprintf("%s.%s", className, funcName) definition := acc.TestClient().Function.SampleJavaDefinition(t, className, funcName, argName) - functionModelNoAttributes := model.FunctionJavaWithId("w", id, dataType, handler, definition). + functionModelNoAttributes := model.FunctionJavaBasicInline("w", id, dataType, handler, definition). WithArgument(argName, dataType) - functionModelNoAttributesRenamed := model.FunctionJavaWithId("w", idWithChangedNameButTheSameDataType, dataType, handler, definition). + functionModelNoAttributesRenamed := model.FunctionJavaBasicInline("w", idWithChangedNameButTheSameDataType, dataType, handler, definition). WithArgument(argName, dataType) resource.Test(t, resource.TestCase{ @@ -221,9 +222,9 @@ func TestAcc_FunctionJava_AllParameters(t *testing.T) { handler := fmt.Sprintf("%s.%s", className, funcName) definition := acc.TestClient().Function.SampleJavaDefinition(t, className, funcName, argName) - functionModel := model.FunctionJavaWithId("w", id, dataType, handler, definition). + functionModel := model.FunctionJavaBasicInline("w", id, dataType, handler, definition). WithArgument(argName, dataType) - functionModelWithAllParametersSet := model.FunctionJavaWithId("w", id, dataType, handler, definition). + functionModelWithAllParametersSet := model.FunctionJavaBasicInline("w", id, dataType, handler, definition). WithArgument(argName, dataType). WithEnableConsoleOutput(true). WithLogLevel(string(sdk.LogLevelWarn)). @@ -324,16 +325,19 @@ func TestAcc_FunctionJava_AllParameters(t *testing.T) { // TODO [this PR]: Test with Java versus Scala staged (probably the only way to set up functions to have exactly the same config in both languages) func TestAcc_FunctionJava_handleExternalLanguageChange(t *testing.T) { - className := "TestFunc" - funcName := "echoVarchar" - argName := "x" - dataType := testdatatypes.DataTypeVarchar_100 + _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance) + acc.TestAccPreCheck(t) + + tmpJavaFunction := acc.TestClient().CreateSampleJavaFunctionAndJar(t) + + dataType := tmpJavaFunction.ArgType id := acc.TestClient().Ids.RandomSchemaObjectIdentifierWithArgumentsNewDataTypes(dataType) - handler := fmt.Sprintf("%s.%s", className, funcName) - definition := acc.TestClient().Function.SampleJavaDefinition(t, className, funcName, argName) + argName := "x" + handler := tmpJavaFunction.JavaHandler() + importPath := tmpJavaFunction.JarLocation() - functionModel := model.FunctionJavaWithId("w", id, dataType, handler, definition). + functionModel := model.FunctionJavaBasicStaged("w", id, dataType, handler, importPath). WithArgument(argName, dataType) resource.Test(t, resource.TestCase{ @@ -356,8 +360,9 @@ func TestAcc_FunctionJava_handleExternalLanguageChange(t *testing.T) { { PreConfig: func() { acc.TestClient().Function.DropFunctionFunc(t, id)() + // TODO [this PR]: create scala staged (id, args, return, import, handler) acc.TestClient().Function.CreateSqlWithIdentifierAndArgument(t, id.SchemaObjectId(), dataType) - objectassert.Function(t, id).HasLanguage("SQL") + objectassert.Function(t, id).HasLanguage("SCALA") }, Config: config.FromModel(t, functionModel), ConfigPlanChecks: resource.ConfigPlanChecks{ From f57c04b3f7666da0a45bbf904a26795917cc78df Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 10:23:43 +0100 Subject: [PATCH 18/45] Check behavior for stage other than user's --- .../helpers/function_setup_helpers.go | 62 +++++++++------ pkg/acceptance/helpers/stage_client.go | 15 ++++ .../function_java_acceptance_test.go | 2 +- pkg/sdk/testint/functions_integration_test.go | 76 ++++++++++++++++++- 4 files changed, 130 insertions(+), 25 deletions(-) diff --git a/pkg/acceptance/helpers/function_setup_helpers.go b/pkg/acceptance/helpers/function_setup_helpers.go index 8f0447e443..6221a36b4b 100644 --- a/pkg/acceptance/helpers/function_setup_helpers.go +++ b/pkg/acceptance/helpers/function_setup_helpers.go @@ -15,8 +15,20 @@ import ( "github.com/stretchr/testify/require" ) +func (c *TestClient) CreateSampleJavaFunctionAndJarOnUserStage(t *testing.T) *TmpFunction { + t.Helper() + + return c.CreateSampleJavaFunctionAndJarInLocation(t, "@~") +} + +func (c *TestClient) CreateSampleJavaFunctionAndJarOnStage(t *testing.T, stage *sdk.Stage) *TmpFunction { + t.Helper() + + return c.CreateSampleJavaFunctionAndJarInLocation(t, stage.Location()) +} + // TODO [SNOW-1827324]: add TestClient ref to each specific client, so that we enhance specific client and not the base one -func (c *TestClient) CreateSampleJavaFunctionAndJar(t *testing.T) *TmpFunction { +func (c *TestClient) CreateSampleJavaFunctionAndJarInLocation(t *testing.T, stageLocation string) *TmpFunction { t.Helper() ctx := context.Background() @@ -32,7 +44,7 @@ func (c *TestClient) CreateSampleJavaFunctionAndJar(t *testing.T) *TmpFunction { handler := fmt.Sprintf("%s.%s", className, funcName) definition := c.Function.SampleJavaDefinition(t, className, funcName, argName) jarName := fmt.Sprintf("tf-%d-%s.jar", time.Now().Unix(), random.AlphaN(5)) - targetPath := fmt.Sprintf("@~/%s", jarName) + targetPath := fmt.Sprintf("%s/%s", stageLocation, jarName) request := sdk.NewCreateForJavaFunctionRequest(id.SchemaObjectId(), *returns, handler). WithArguments([]sdk.FunctionArgumentRequest{*argument}). @@ -42,18 +54,20 @@ func (c *TestClient) CreateSampleJavaFunctionAndJar(t *testing.T) *TmpFunction { err := c.context.client.Functions.CreateForJava(ctx, request) require.NoError(t, err) t.Cleanup(c.Function.DropFunctionFunc(t, id)) - t.Cleanup(c.Stage.RemoveFromUserStageFunc(t, jarName)) + t.Cleanup(c.Stage.RemoveFromStageFunc(t, stageLocation, jarName)) return &TmpFunction{ - FunctionId: id, - ClassName: className, - FuncName: funcName, - ArgName: argName, - ArgType: dataType, - JarName: jarName, + FunctionId: id, + ClassName: className, + FuncName: funcName, + ArgName: argName, + ArgType: dataType, + JarName: jarName, + StageLocation: stageLocation, } } +// TODO [this PR]: adjust to switching location too func (c *TestClient) CreateSampleJavaProcedureAndJar(t *testing.T) *TmpFunction { t.Helper() ctx := context.Background() @@ -121,30 +135,32 @@ func (c *TestClient) CreateSamplePythonFunctionAndModule(t *testing.T) *TmpFunct moduleFileName := filepath.Base(modulePath) return &TmpFunction{ - FunctionId: id, - ModuleName: strings.TrimSuffix(moduleFileName, ".py"), - FuncName: funcName, - ArgName: argName, - ArgType: dataType, + FunctionId: id, + ModuleName: strings.TrimSuffix(moduleFileName, ".py"), + FuncName: funcName, + ArgName: argName, + ArgType: dataType, + StageLocation: "@~", } } type TmpFunction struct { - FunctionId sdk.SchemaObjectIdentifierWithArguments - ClassName string - ModuleName string - FuncName string - ArgName string - ArgType datatypes.DataType - JarName string + FunctionId sdk.SchemaObjectIdentifierWithArguments + ClassName string + ModuleName string + FuncName string + ArgName string + ArgType datatypes.DataType + JarName string + StageLocation string } func (f *TmpFunction) JarLocation() string { - return fmt.Sprintf("@~/%s", f.JarName) + return fmt.Sprintf("%s/%s", f.StageLocation, f.JarName) } func (f *TmpFunction) PythonModuleLocation() string { - return fmt.Sprintf("@~/%s", f.PythonFileName()) + return fmt.Sprintf("%s/%s", f.StageLocation, f.PythonFileName()) } func (f *TmpFunction) PythonFileName() string { diff --git a/pkg/acceptance/helpers/stage_client.go b/pkg/acceptance/helpers/stage_client.go index 60bac47c90..5a1176b314 100644 --- a/pkg/acceptance/helpers/stage_client.go +++ b/pkg/acceptance/helpers/stage_client.go @@ -126,6 +126,21 @@ func (c *StageClient) RemoveFromUserStageFunc(t *testing.T, pathOnStage string) } } +func (c *StageClient) RemoveFromStage(t *testing.T, stageLocation string, pathOnStage string) { + t.Helper() + ctx := context.Background() + + _, err := c.context.client.ExecForTests(ctx, fmt.Sprintf(`REMOVE %s/%s`, stageLocation, pathOnStage)) + require.NoError(t, err) +} + +func (c *StageClient) RemoveFromStageFunc(t *testing.T, stageLocation string, pathOnStage string) func() { + t.Helper() + return func() { + c.RemoveFromStage(t, stageLocation, pathOnStage) + } +} + func (c *StageClient) PutOnStageWithContent(t *testing.T, id sdk.SchemaObjectIdentifier, filename string, content string) { t.Helper() ctx := context.Background() diff --git a/pkg/resources/function_java_acceptance_test.go b/pkg/resources/function_java_acceptance_test.go index 6a7a146ccb..0ab6e168d6 100644 --- a/pkg/resources/function_java_acceptance_test.go +++ b/pkg/resources/function_java_acceptance_test.go @@ -328,7 +328,7 @@ func TestAcc_FunctionJava_handleExternalLanguageChange(t *testing.T) { _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance) acc.TestAccPreCheck(t) - tmpJavaFunction := acc.TestClient().CreateSampleJavaFunctionAndJar(t) + tmpJavaFunction := acc.TestClient().CreateSampleJavaFunctionAndJarOnUserStage(t) dataType := tmpJavaFunction.ArgType id := acc.TestClient().Ids.RandomSchemaObjectIdentifierWithArgumentsNewDataTypes(dataType) diff --git a/pkg/sdk/testint/functions_integration_test.go b/pkg/sdk/testint/functions_integration_test.go index bb292cd627..9fb341c3a7 100644 --- a/pkg/sdk/testint/functions_integration_test.go +++ b/pkg/sdk/testint/functions_integration_test.go @@ -48,7 +48,11 @@ func TestInt_Functions(t *testing.T) { externalAccessIntegration, externalAccessIntegrationCleanup := testClientHelper().ExternalAccessIntegration.CreateExternalAccessIntegrationWithNetworkRuleAndSecret(t, networkRule.ID(), secret.ID()) t.Cleanup(externalAccessIntegrationCleanup) - tmpJavaFunction := testClientHelper().CreateSampleJavaFunctionAndJar(t) + stage, stageCleanup := testClientHelper().Stage.CreateStage(t) + t.Cleanup(stageCleanup) + + tmpJavaFunction := testClientHelper().CreateSampleJavaFunctionAndJarOnUserStage(t) + tmpJavaFunctionDifferentStage := testClientHelper().CreateSampleJavaFunctionAndJarOnStage(t, stage) tmpPythonFunction := testClientHelper().CreateSamplePythonFunctionAndModule(t) assertParametersSet := func(t *testing.T, functionParametersAssert *objectparametersassert.FunctionParametersAssert) { @@ -381,6 +385,76 @@ func TestInt_Functions(t *testing.T) { ) }) + t.Run("create function for Java - different stage", func(t *testing.T) { + dataType := tmpJavaFunctionDifferentStage.ArgType + id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArgumentsNewDataTypes(dataType) + + argName := "x" + argument := sdk.NewFunctionArgumentRequest(argName, dataType) + dt := sdk.NewFunctionReturnsResultDataTypeRequest(dataType) + returns := sdk.NewFunctionReturnsRequest().WithResultDataType(*dt) + handler := tmpJavaFunctionDifferentStage.JavaHandler() + importPath := tmpJavaFunctionDifferentStage.JarLocation() + + requestStaged := sdk.NewCreateForJavaFunctionRequest(id.SchemaObjectId(), *returns, handler). + WithArguments([]sdk.FunctionArgumentRequest{*argument}). + WithImports([]sdk.FunctionImportRequest{*sdk.NewFunctionImportRequest().WithImport(importPath)}) + + err := client.Functions.CreateForJava(ctx, requestStaged) + require.NoError(t, err) + t.Cleanup(testClientHelper().Function.DropFunctionFunc(t, id)) + + function, err := client.Functions.ShowByID(ctx, id) + require.NoError(t, err) + + assertions.AssertThatObject(t, objectassert.FunctionFromObject(t, function). + HasCreatedOnNotEmpty(). + HasName(id.Name()). + HasSchemaName(id.SchemaName()). + HasIsBuiltin(false). + HasIsAggregate(false). + HasIsAnsi(false). + HasMinNumArguments(1). + HasMaxNumArguments(1). + HasArgumentsOld([]sdk.DataType{sdk.LegacyDataTypeFrom(dataType)}). + HasArgumentsRaw(fmt.Sprintf(`%[1]s(%[2]s) RETURN %[2]s`, function.ID().Name(), dataType.ToLegacyDataTypeSql())). + HasDescription(sdk.DefaultFunctionComment). + HasCatalogName(id.DatabaseName()). + HasIsTableFunction(false). + HasValidForClustering(false). + HasIsSecure(false). + HasExternalAccessIntegrations(""). + HasSecrets(""). + HasIsExternalFunction(false). + HasLanguage("JAVA"). + HasIsMemoizable(false). + HasIsDataMetric(false), + ) + + assertions.AssertThatObject(t, objectassert.FunctionDetails(t, function.ID()). + HasSignature(fmt.Sprintf(`(%s %s)`, argName, dataType.ToLegacyDataTypeSql())). + HasReturns(dataType.ToSql()). + HasLanguage("JAVA"). + HasBodyNil(). + HasNullHandling(string(sdk.NullInputBehaviorCalledOnNullInput)). + HasVolatility(string(sdk.ReturnResultsBehaviorVolatile)). + HasExternalAccessIntegrationsNil(). + HasSecretsNil(). + HasImports(fmt.Sprintf(`[@"%s"."%s".%s/%s]`, stage.ID().DatabaseName(), stage.ID().SchemaName(), stage.ID().Name(), tmpJavaFunctionDifferentStage.JarName)). + HasHandler(handler). + HasRuntimeVersionNil(). + HasPackages(`[]`). + HasTargetPathNil(). + HasInstalledPackagesNil(). + HasIsAggregateNil(), + ) + + assertions.AssertThatObject(t, objectparametersassert.FunctionParameters(t, id). + HasAllDefaults(). + HasAllDefaultsExplicit(), + ) + }) + t.Run("create function for Javascript - inline minimal", func(t *testing.T) { dataType := testdatatypes.DataTypeFloat id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(sdk.LegacyDataTypeFrom(dataType)) From 62927bee036d4a73c7eb922633192b0b3efb41aa Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 11:26:26 +0100 Subject: [PATCH 19/45] Parse import location --- pkg/resources/doc_helpers.go | 3 +- pkg/sdk/functions_ext.go | 52 ++++++++++++++++++++++++++++ pkg/sdk/functions_ext_test.go | 64 +++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 pkg/sdk/functions_ext_test.go diff --git a/pkg/resources/doc_helpers.go b/pkg/resources/doc_helpers.go index eb437015f9..51142971c6 100644 --- a/pkg/resources/doc_helpers.go +++ b/pkg/resources/doc_helpers.go @@ -4,8 +4,9 @@ import ( "fmt" "strings" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider/docs" providerresources "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/provider/resources" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider/docs" ) func possibleValuesListed[T ~string | ~int](values []T) string { diff --git a/pkg/sdk/functions_ext.go b/pkg/sdk/functions_ext.go index 531ddfd9fa..596b017544 100644 --- a/pkg/sdk/functions_ext.go +++ b/pkg/sdk/functions_ext.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "strconv" + "strings" ) const DefaultFunctionComment = "user-defined function" @@ -30,6 +31,15 @@ type FunctionDetails struct { TargetPath *string // list present for scala and java (hidden when SECURE) InstalledPackages *string // list present for python (hidden when SECURE) IsAggregate *bool // present for python + + NormalizedImports []FunctionDetailsImport +} + +type FunctionDetailsImport struct { + // StageLocation is a normalized (fully-quoted id or `~`) stage location + StageLocation string + // PathOnStage is path to the file on stage without opening `/` + PathOnStage string } func functionDetailsFromRows(rows []FunctionDetail) (*FunctionDetails, error) { @@ -69,9 +79,51 @@ func functionDetailsFromRows(rows []FunctionDetail) (*FunctionDetails, error) { v.TargetPath = row.Value } } + if e := errors.Join(errs...); e != nil { + return nil, e + } + + if functionDetailsImports, err := parseFunctionDetailsImport(*v); err != nil { + errs = append(errs, err) + } else { + v.NormalizedImports = functionDetailsImports + } + return v, errors.Join(errs...) } +func parseFunctionDetailsImport(details FunctionDetails) ([]FunctionDetailsImport, error) { + functionDetailsImports := make([]FunctionDetailsImport, 0) + if details.Imports == nil || *details.Imports == "" || *details.Imports == "[]" { + return functionDetailsImports, nil + } + if !strings.HasPrefix(*details.Imports, "[") || !strings.HasSuffix(*details.Imports, "]") { + return functionDetailsImports, fmt.Errorf("could not parse imports from Snowflake: %s, brackets not find", *details.Imports) + } + raw := (*details.Imports)[1 : len(*details.Imports)-1] + imports := strings.Split(raw, ",") + for _, imp := range imports { + idx := strings.Index(imp, "/") + if idx < 0 { + return functionDetailsImports, fmt.Errorf("could not parse imports from Snowflake: %s, part %s cannot be split into stage and path", *details.Imports, imp) + } + stageRaw := strings.TrimPrefix(strings.TrimSpace(imp[:idx]), "@") + if stageRaw != "~" { + stageId, err := ParseSchemaObjectIdentifier(stageRaw) + if err != nil { + return functionDetailsImports, fmt.Errorf("could not parse imports from Snowflake: %s, part %s contains incorrect stage location: %w", *details.Imports, imp, err) + } + stageRaw = stageId.FullyQualifiedName() + } + pathRaw := strings.TrimPrefix(strings.TrimSpace(imp[idx:]), "/") + if pathRaw == "" { + return functionDetailsImports, fmt.Errorf("could not parse imports from Snowflake: %s, part %s contains empty path", *details.Imports, imp) + } + functionDetailsImports = append(functionDetailsImports, FunctionDetailsImport{stageRaw, pathRaw}) + } + return functionDetailsImports, nil +} + func (v *functions) DescribeDetails(ctx context.Context, id SchemaObjectIdentifierWithArguments) (*FunctionDetails, error) { rows, err := v.Describe(ctx, id) if err != nil { diff --git a/pkg/sdk/functions_ext_test.go b/pkg/sdk/functions_ext_test.go new file mode 100644 index 0000000000..6eb9276862 --- /dev/null +++ b/pkg/sdk/functions_ext_test.go @@ -0,0 +1,64 @@ +package sdk + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_parseFunctionDetailsImport(t *testing.T) { + + inputs := []struct { + rawInput string + expected []FunctionDetailsImport + }{ + {"", []FunctionDetailsImport{}}, + {`[]`, []FunctionDetailsImport{}}, + {`[@~/abc]`, []FunctionDetailsImport{{"~", "abc"}}}, + {`[@~/abc/def]`, []FunctionDetailsImport{{"~", "abc/def"}}}, + {`[@"db"."sc"."st"/abc/def]`, []FunctionDetailsImport{{`"db"."sc"."st"`, "abc/def"}}}, + {`[@db.sc.st/abc/def]`, []FunctionDetailsImport{{`"db"."sc"."st"`, "abc/def"}}}, + {`[db.sc.st/abc/def]`, []FunctionDetailsImport{{`"db"."sc"."st"`, "abc/def"}}}, + {`[@"db"."sc".st/abc/def]`, []FunctionDetailsImport{{`"db"."sc"."st"`, "abc/def"}}}, + {`[@"db"."sc".st/abc/def, db."sc".st/abc]`, []FunctionDetailsImport{{`"db"."sc"."st"`, "abc/def"}, {`"db"."sc"."st"`, "abc"}}}, + } + + badInputs := []struct { + rawInput string + expectedErrorPart string + }{ + {"[", "brackets not find"}, + {"]", "brackets not find"}, + {`[@~/]`, "contains empty path"}, + {`[@~]`, "cannot be split into stage and path"}, + {`[@"db"."sc"/abc]`, "contains incorrect stage location"}, + {`[@"db"/abc]`, "contains incorrect stage location"}, + {`[@"db"."sc"."st"."smth"/abc]`, "contains incorrect stage location"}, + {`[@"db/a"."sc"."st"/abc]`, "contains incorrect stage location"}, + {`[@"db"."sc"."st"/abc], @"db"."sc"/abc]`, "contains incorrect stage location"}, + } + + for _, tc := range inputs { + tc := tc + t.Run(fmt.Sprintf("Snowflake raw imports: %s", tc.rawInput), func(t *testing.T) { + details := FunctionDetails{Imports: &tc.rawInput} + + results, err := parseFunctionDetailsImport(details) + require.NoError(t, err) + require.Equal(t, tc.expected, results) + }) + } + + for _, tc := range badInputs { + tc := tc + t.Run(fmt.Sprintf("incorrect Snowflake input: %s, expecting error with: %s", tc.rawInput, tc.expectedErrorPart), func(t *testing.T) { + details := FunctionDetails{Imports: &tc.rawInput} + + _, err := parseFunctionDetailsImport(details) + require.Error(t, err) + require.ErrorContains(t, err, "could not parse imports from Snowflake") + require.ErrorContains(t, err, tc.expectedErrorPart) + }) + } +} From b53d053b2bbfe2ea6a995aa9d871a5bc80cebdde Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 11:37:58 +0100 Subject: [PATCH 20/45] Assert import location correct --- .../function_describe_snowflake_ext.go | 15 +++++++++++++++ pkg/sdk/testint/functions_integration_test.go | 5 ++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/pkg/acceptance/bettertestspoc/assert/objectassert/function_describe_snowflake_ext.go b/pkg/acceptance/bettertestspoc/assert/objectassert/function_describe_snowflake_ext.go index f540d487bd..8b885e91ab 100644 --- a/pkg/acceptance/bettertestspoc/assert/objectassert/function_describe_snowflake_ext.go +++ b/pkg/acceptance/bettertestspoc/assert/objectassert/function_describe_snowflake_ext.go @@ -6,6 +6,7 @@ import ( "testing" acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + assert2 "github.com/stretchr/testify/assert" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" @@ -405,3 +406,17 @@ func (f *FunctionDetailsAssert) HasExactlySecrets(expectedSecrets map[string]sdk }) return f } + +func (f *FunctionDetailsAssert) HasExactlyImportsNormalizedInAnyOrder(imports ...sdk.FunctionDetailsImport) *FunctionDetailsAssert { + f.AddAssertion(func(t *testing.T, o *sdk.FunctionDetails) error { + t.Helper() + if o.NormalizedImports == nil { + return fmt.Errorf("expected imports to have value; got: nil") + } + if !assert2.ElementsMatch(t, imports, o.NormalizedImports) { + return fmt.Errorf("expected %v imports in task relations, got %v", imports, o.NormalizedImports) + } + return nil + }) + return f +} diff --git a/pkg/sdk/testint/functions_integration_test.go b/pkg/sdk/testint/functions_integration_test.go index 9fb341c3a7..9dd3bf5b9c 100644 --- a/pkg/sdk/testint/functions_integration_test.go +++ b/pkg/sdk/testint/functions_integration_test.go @@ -20,7 +20,6 @@ import ( "github.com/stretchr/testify/require" ) -// TODO [SNOW-1348103]: schemaName and catalog name are quoted (because we use lowercase) // TODO [SNOW-1850370]: HasArgumentsRawFrom(functionId, arguments, return) // TODO [SNOW-1850370]: extract show assertions with commons fields // TODO [SNOW-1850370]: test confirming that runtime version is required for Scala function @@ -34,6 +33,7 @@ import ( // TODO [SNOW-1348103]: add a test documenting that we can't set parameters in create (and revert adding these parameters directly in object...) // TODO [SNOW-1850370]: active warehouse vs validations // TODO [SNOW-1348103]: add a test documenting STRICT behavior +// TODO [next PR]: add test with multiple imports func TestInt_Functions(t *testing.T) { client := testClient(t) ctx := context.Background() @@ -441,6 +441,9 @@ func TestInt_Functions(t *testing.T) { HasExternalAccessIntegrationsNil(). HasSecretsNil(). HasImports(fmt.Sprintf(`[@"%s"."%s".%s/%s]`, stage.ID().DatabaseName(), stage.ID().SchemaName(), stage.ID().Name(), tmpJavaFunctionDifferentStage.JarName)). + HasExactlyImportsNormalizedInAnyOrder(sdk.FunctionDetailsImport{ + StageLocation: stage.ID().FullyQualifiedName(), PathOnStage: tmpJavaFunctionDifferentStage.JarName, + }). HasHandler(handler). HasRuntimeVersionNil(). HasPackages(`[]`). From e3cc9fb3eb8b7d07a2fa5cc47118a1b7aa01fe04 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 12:02:57 +0100 Subject: [PATCH 21/45] Add import assertions to every function test --- pkg/sdk/testint/functions_integration_test.go | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/pkg/sdk/testint/functions_integration_test.go b/pkg/sdk/testint/functions_integration_test.go index 9dd3bf5b9c..a9f6b696af 100644 --- a/pkg/sdk/testint/functions_integration_test.go +++ b/pkg/sdk/testint/functions_integration_test.go @@ -123,6 +123,7 @@ func TestInt_Functions(t *testing.T) { HasExternalAccessIntegrationsNil(). HasSecretsNil(). HasImports(`[]`). + HasExactlyImportsNormalizedInAnyOrder(). HasHandler(handler). HasRuntimeVersionNil(). HasPackages(`[]`). @@ -219,6 +220,9 @@ func TestInt_Functions(t *testing.T) { // TODO [SNOW-1348103]: check multiple secrets (to know how to parse) HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). HasImports(fmt.Sprintf(`[%s]`, tmpJavaFunction.JarLocation())). + HasExactlyImportsNormalizedInAnyOrder(sdk.FunctionDetailsImport{ + StageLocation: "~", PathOnStage: tmpJavaFunction.JarName, + }). HasHandler(handler). HasRuntimeVersion("11"). HasPackages(`[com.snowflake:snowpark:1.14.0,com.snowflake:telemetry:0.1.0]`). @@ -289,6 +293,9 @@ func TestInt_Functions(t *testing.T) { HasExternalAccessIntegrationsNil(). HasSecretsNil(). HasImports(fmt.Sprintf(`[%s]`, importPath)). + HasExactlyImportsNormalizedInAnyOrder(sdk.FunctionDetailsImport{ + StageLocation: "~", PathOnStage: tmpJavaFunction.JarName, + }). HasHandler(handler). HasRuntimeVersionNil(). HasPackages(`[]`). @@ -371,6 +378,9 @@ func TestInt_Functions(t *testing.T) { HasExactlyExternalAccessIntegrations(externalAccessIntegration). HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). HasImports(fmt.Sprintf(`[%s]`, tmpJavaFunction.JarLocation())). + HasExactlyImportsNormalizedInAnyOrder(sdk.FunctionDetailsImport{ + StageLocation: "~", PathOnStage: tmpJavaFunction.JarName, + }). HasHandler(handler). HasRuntimeVersion("11"). HasPackages(`[com.snowflake:snowpark:1.14.0,com.snowflake:telemetry:0.1.0]`). @@ -512,6 +522,7 @@ func TestInt_Functions(t *testing.T) { HasExternalAccessIntegrationsNil(). HasSecretsNil(). HasImportsNil(). + HasExactlyImportsNormalizedInAnyOrder(). HasHandlerNil(). HasRuntimeVersionNil(). HasPackagesNil(). @@ -585,6 +596,7 @@ func TestInt_Functions(t *testing.T) { HasExternalAccessIntegrationsNil(). HasSecretsNil(). HasImportsNil(). + HasExactlyImportsNormalizedInAnyOrder(). HasHandlerNil(). HasRuntimeVersionNil(). HasPackagesNil(). @@ -654,6 +666,7 @@ func TestInt_Functions(t *testing.T) { HasExternalAccessIntegrationsNil(). HasSecretsNil(). HasImports(`[]`). + HasExactlyImportsNormalizedInAnyOrder(). HasHandler(funcName). HasRuntimeVersion("3.8"). HasPackages(`[]`). @@ -736,6 +749,9 @@ func TestInt_Functions(t *testing.T) { HasExactlyExternalAccessIntegrations(externalAccessIntegration). HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). HasImports(fmt.Sprintf(`[%s]`, tmpPythonFunction.PythonModuleLocation())). + HasExactlyImportsNormalizedInAnyOrder(sdk.FunctionDetailsImport{ + StageLocation: "~", PathOnStage: tmpPythonFunction.PythonFileName(), + }). HasHandler(funcName). HasRuntimeVersion("3.8"). HasPackages(`['absl-py==0.10.0','about-time==4.2.1']`). @@ -803,6 +819,9 @@ func TestInt_Functions(t *testing.T) { HasExternalAccessIntegrationsNil(). HasSecretsNil(). HasImports(fmt.Sprintf(`[%s]`, tmpPythonFunction.PythonModuleLocation())). + HasExactlyImportsNormalizedInAnyOrder(sdk.FunctionDetailsImport{ + StageLocation: "~", PathOnStage: tmpPythonFunction.PythonFileName(), + }). HasHandler(tmpPythonFunction.PythonHandler()). HasRuntimeVersion("3.8"). HasPackages(`[]`). @@ -882,6 +901,9 @@ func TestInt_Functions(t *testing.T) { HasExactlyExternalAccessIntegrations(externalAccessIntegration). HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). HasImports(fmt.Sprintf(`[%s]`, tmpPythonFunction.PythonModuleLocation())). + HasExactlyImportsNormalizedInAnyOrder(sdk.FunctionDetailsImport{ + StageLocation: "~", PathOnStage: tmpPythonFunction.PythonFileName(), + }). HasHandler(tmpPythonFunction.PythonHandler()). HasRuntimeVersion("3.8"). HasPackages(`['absl-py==0.10.0','about-time==4.2.1']`). @@ -952,6 +974,7 @@ func TestInt_Functions(t *testing.T) { HasExternalAccessIntegrationsNil(). HasSecretsNil(). HasImports(`[]`). + HasExactlyImportsNormalizedInAnyOrder(). HasHandler(handler). HasRuntimeVersion("2.12"). HasPackages(`[]`). @@ -1043,6 +1066,9 @@ func TestInt_Functions(t *testing.T) { HasExactlyExternalAccessIntegrations(externalAccessIntegration). HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). HasImports(fmt.Sprintf(`[%s]`, tmpJavaFunction.JarLocation())). + HasExactlyImportsNormalizedInAnyOrder(sdk.FunctionDetailsImport{ + StageLocation: "~", PathOnStage: tmpJavaFunction.JarName, + }). HasHandler(handler). HasRuntimeVersion("2.12"). HasPackages(`[com.snowflake:snowpark:1.14.0,com.snowflake:telemetry:0.1.0]`). @@ -1111,6 +1137,9 @@ func TestInt_Functions(t *testing.T) { HasExternalAccessIntegrationsNil(). HasSecretsNil(). HasImports(fmt.Sprintf(`[%s]`, importPath)). + HasExactlyImportsNormalizedInAnyOrder(sdk.FunctionDetailsImport{ + StageLocation: "~", PathOnStage: tmpJavaFunction.JarName, + }). HasHandler(handler). HasRuntimeVersion("2.12"). HasPackages(`[]`). @@ -1190,6 +1219,9 @@ func TestInt_Functions(t *testing.T) { HasExactlyExternalAccessIntegrations(externalAccessIntegration). HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). HasImports(fmt.Sprintf(`[%s]`, tmpJavaFunction.JarLocation())). + HasExactlyImportsNormalizedInAnyOrder(sdk.FunctionDetailsImport{ + StageLocation: "~", PathOnStage: tmpJavaFunction.JarName, + }). HasHandler(handler). HasRuntimeVersion("2.12"). HasPackages(`[com.snowflake:snowpark:1.14.0,com.snowflake:telemetry:0.1.0]`). @@ -1257,6 +1289,7 @@ func TestInt_Functions(t *testing.T) { HasExternalAccessIntegrationsNil(). HasSecretsNil(). HasImportsNil(). + HasExactlyImportsNormalizedInAnyOrder(). HasHandlerNil(). HasRuntimeVersionNil(). HasPackagesNil(). @@ -1332,6 +1365,7 @@ func TestInt_Functions(t *testing.T) { HasExternalAccessIntegrationsNil(). HasSecretsNil(). HasImportsNil(). + HasExactlyImportsNormalizedInAnyOrder(). HasHandlerNil(). HasRuntimeVersionNil(). HasPackagesNil(). @@ -1396,6 +1430,7 @@ func TestInt_Functions(t *testing.T) { HasExternalAccessIntegrationsNil(). HasSecretsNil(). HasImportsNil(). + HasExactlyImportsNormalizedInAnyOrder(). HasHandlerNil(). HasRuntimeVersionNil(). HasPackagesNil(). From d7a2602defc9553b117f6c934ad703f830ccb349 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 12:03:25 +0100 Subject: [PATCH 22/45] Change imports schema --- pkg/resources/function_commons.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/pkg/resources/function_commons.go b/pkg/resources/function_commons.go index 8141159557..56da575e55 100644 --- a/pkg/resources/function_commons.go +++ b/pkg/resources/function_commons.go @@ -264,12 +264,26 @@ func functionBaseSchema() map[string]schema.Schema { Default: "user-defined function", Description: "Specifies a comment for the function.", }, - // TODO [SNOW-1348103]: because of https://docs.snowflake.com/en/sql-reference/sql/create-function#id6, maybe it will be better to split into stage_name + target_path + // split into two because of https://docs.snowflake.com/en/sql-reference/sql/create-function#id6 + // TODO [next PR]: add validations preventing setting improper stage and path "imports": { Type: schema.TypeSet, - Elem: &schema.Schema{Type: schema.TypeString}, Optional: true, ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "stage_location": { + Type: schema.TypeString, + Required: true, + Description: "Stage location without leading `@`. To use your user's stage set this to `~`, otherwise pass fully qualified name of the stage (with every part contained in double quotes or use `snowflake_stage..fully_qualified_name` if you manage this stage through terraform).", + }, + "path_on_stage": { + Type: schema.TypeString, + Required: true, + Description: "Path for import on stage, without the leading `/`.", + }, + }, + }, }, // TODO [SNOW-1348103]: what do we do with the version "latest". "packages": { From 7613493e87a2c8fe4fdd356bba565d1d6f404c8c Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 12:37:27 +0100 Subject: [PATCH 23/45] Prepare import handling --- .../config/model/function_java_model_ext.go | 16 +++++-- pkg/acceptance/helpers/function_client.go | 20 ++++++++ pkg/resources/function_commons.go | 48 ++++++++++++++++--- pkg/resources/function_java.go | 20 ++++---- .../function_java_acceptance_test.go | 7 +-- 5 files changed, 87 insertions(+), 24 deletions(-) diff --git a/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go b/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go index 1a4dcb63fe..d7cc98c955 100644 --- a/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go +++ b/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go @@ -37,10 +37,11 @@ func FunctionJavaBasicStaged( id sdk.SchemaObjectIdentifierWithArguments, returnType datatypes.DataType, handler string, - importPath string, + stageLocation string, + pathOnStage string, ) *FunctionJavaModel { return FunctionJavaf(resourceName, id.DatabaseName(), handler, id.Name(), returnType.ToSql(), id.SchemaName()). - WithImport(importPath) + WithImport(stageLocation, pathOnStage) } func FunctionJavaf( @@ -71,8 +72,13 @@ func (f *FunctionJavaModel) WithArgument(argName string, argDataType datatypes.D ) } -func (f *FunctionJavaModel) WithImport(importPath string) *FunctionJavaModel { - return f.WithArgumentsValue( - tfconfig.SetVariable(tfconfig.StringVariable(importPath)), +func (f *FunctionJavaModel) WithImport(stageLocation string, pathOnStage string) *FunctionJavaModel { + return f.WithImportsValue( + tfconfig.ObjectVariable( + map[string]tfconfig.Variable{ + "stage_location": tfconfig.StringVariable(stageLocation), + "path_on_stage": tfconfig.StringVariable(pathOnStage), + }, + ), ) } diff --git a/pkg/acceptance/helpers/function_client.go b/pkg/acceptance/helpers/function_client.go index 4d9bf35aaa..5e054e3c23 100644 --- a/pkg/acceptance/helpers/function_client.go +++ b/pkg/acceptance/helpers/function_client.go @@ -138,6 +138,26 @@ func (c *FunctionClient) CreateJava(t *testing.T) (*sdk.Function, func()) { return function, c.DropFunctionFunc(t, id) } +func (c *FunctionClient) CreateScalaStaged(t *testing.T, id sdk.SchemaObjectIdentifierWithArguments, dataType datatypes.DataType, importPath string, handler string) (*sdk.Function, func()) { + t.Helper() + ctx := context.Background() + + argName := "x" + argument := sdk.NewFunctionArgumentRequest(argName, dataType) + + request := sdk.NewCreateForScalaFunctionRequest(id.SchemaObjectId(), dataType, handler, "2.12"). + WithArguments([]sdk.FunctionArgumentRequest{*argument}). + WithImports([]sdk.FunctionImportRequest{*sdk.NewFunctionImportRequest().WithImport(importPath)}) + + err := c.client().CreateForScala(ctx, request) + require.NoError(t, err) + + function, err := c.client().ShowByID(ctx, id) + require.NoError(t, err) + + return function, c.DropFunctionFunc(t, id) +} + func (c *FunctionClient) CreateWithRequest(t *testing.T, id sdk.SchemaObjectIdentifierWithArguments, req *sdk.CreateForSQLFunctionRequest) *sdk.Function { t.Helper() ctx := context.Background() diff --git a/pkg/resources/function_commons.go b/pkg/resources/function_commons.go index 56da575e55..5359ebabca 100644 --- a/pkg/resources/function_commons.go +++ b/pkg/resources/function_commons.go @@ -384,15 +384,15 @@ func DeleteFunction(ctx context.Context, d *schema.ResourceData, meta any) diag. } // TODO [SNOW-1348103]: handle defaults -func parseFunctionArgumentsCommon(d *schema.ResourceData) ([]sdk.FunctionArgumentRequest, diag.Diagnostics) { +func parseFunctionArgumentsCommon(d *schema.ResourceData) ([]sdk.FunctionArgumentRequest, error) { args := make([]sdk.FunctionArgumentRequest, 0) if v, ok := d.GetOk("arguments"); ok { for _, arg := range v.([]any) { - argName := arg.(map[string]interface{})["arg_name"].(string) - argDataType := arg.(map[string]interface{})["arg_data_type"].(string) + argName := arg.(map[string]any)["arg_name"].(string) + argDataType := arg.(map[string]any)["arg_data_type"].(string) dataType, err := datatypes.ParseDataType(argDataType) if err != nil { - return nil, diag.FromErr(err) + return nil, err } args = append(args, *sdk.NewFunctionArgumentRequest(argName, dataType)) } @@ -400,11 +400,23 @@ func parseFunctionArgumentsCommon(d *schema.ResourceData) ([]sdk.FunctionArgumen return args, nil } -func parseFunctionReturnsCommon(d *schema.ResourceData) (*sdk.FunctionReturnsRequest, diag.Diagnostics) { +func parseFunctionImportsCommon(d *schema.ResourceData) ([]sdk.FunctionImportRequest, error) { + imports := make([]sdk.FunctionImportRequest, 0) + if v, ok := d.GetOk("imports"); ok { + for _, imp := range v.([]any) { + stageLocation := imp.(map[string]any)["stage_location"].(string) + pathOnStage := imp.(map[string]any)["path_on_stage"].(string) + imports = append(imports, *sdk.NewFunctionImportRequest().WithImport(fmt.Sprintf("@%s/%s", stageLocation, pathOnStage))) + } + } + return imports, nil +} + +func parseFunctionReturnsCommon(d *schema.ResourceData) (*sdk.FunctionReturnsRequest, error) { returnTypeRaw := d.Get("return_type").(string) dataType, err := datatypes.ParseDataType(returnTypeRaw) if err != nil { - return nil, diag.FromErr(err) + return nil, err } returns := sdk.NewFunctionReturnsRequest() switch v := dataType.(type) { @@ -420,6 +432,15 @@ func parseFunctionReturnsCommon(d *schema.ResourceData) (*sdk.FunctionReturnsReq return returns, nil } +func setFunctionImportsInBuilder[T any](d *schema.ResourceData, setImports func([]sdk.FunctionImportRequest) T) error { + imports, err := parseFunctionImportsCommon(d) + if err != nil { + return err + } + setImports(imports) + return nil +} + func queryAllFunctionsDetailsCommon(ctx context.Context, d *schema.ResourceData, client *sdk.Client, id sdk.SchemaObjectIdentifierWithArguments) (*allFunctionDetailsCommon, diag.Diagnostics) { functionDetails, err := client.Functions.DescribeDetails(ctx, id) if err != nil { @@ -466,3 +487,18 @@ type allFunctionDetailsCommon struct { functionDetails *sdk.FunctionDetails functionParameters []*sdk.Parameter } + +func readFunctionImportsCommon(d *schema.ResourceData, imports []sdk.FunctionDetailsImport) error { + if len(imports) == 0 { + // don't do anything if imports not present + return nil + } + imps := make([]map[string]any, len(imports)) + for i, imp := range imports { + imps[i] = map[string]any{ + "stage_location": imp.StageLocation, + "path_on_stage": imp.PathOnStage, + } + } + return d.Set("imports", imps) +} diff --git a/pkg/resources/function_java.go b/pkg/resources/function_java.go index 4c68cb9fab..69ceb29d7c 100644 --- a/pkg/resources/function_java.go +++ b/pkg/resources/function_java.go @@ -32,6 +32,10 @@ func FunctionJava() *schema.Resource { ComputedIfAnyAttributeChanged(javaFunctionSchema, FullyQualifiedNameAttributeName, "name"), ComputedIfAnyAttributeChanged(functionParametersSchema, ParametersAttributeName, collections.Map(sdk.AsStringList(sdk.AllFunctionParameters), strings.ToLower)...), functionParametersCustomDiff, + // The language check is more for the future. + // Currently, almost all attributes are marked as forceNew. + // When language changes, these attributes also change, causing the object to recreate either way. + // The only potential option is java staged -> scala staged (however scala need runtime_version which may interfere). RecreateWhenResourceFieldChangedExternally("function_language", "JAVA"), )), @@ -48,13 +52,13 @@ func CreateContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta sc := d.Get("schema").(string) name := d.Get("name").(string) - argumentRequests, diags := parseFunctionArgumentsCommon(d) - if diags != nil { - return diags + argumentRequests, err := parseFunctionArgumentsCommon(d) + if err != nil { + return diag.FromErr(err) } - returns, diags := parseFunctionReturnsCommon(d) - if diags != nil { - return diags + returns, err := parseFunctionReturnsCommon(d) + if err != nil { + return diag.FromErr(err) } handler := d.Get("handler").(string) @@ -70,7 +74,7 @@ func CreateContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta stringAttributeCreateBuilder(d, "runtime_version", request.WithRuntimeVersion), // TODO [this PR]: handle all attributes // comment - // imports + setFunctionImportsInBuilder(d, request.WithImports), // packages // external_access_integrations // secrets @@ -146,7 +150,7 @@ func ReadContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta a // not reading return_results_behavior on purpose (handled as external change to show output) setOptionalFromStringPtr(d, "runtime_version", allFunctionDetails.functionDetails.RuntimeVersion), // comment - // imports + readFunctionImportsCommon(d, allFunctionDetails.functionDetails.NormalizedImports), // packages setRequiredFromStringPtr(d, "handler", allFunctionDetails.functionDetails.Handler), // external_access_integrations diff --git a/pkg/resources/function_java_acceptance_test.go b/pkg/resources/function_java_acceptance_test.go index 0ab6e168d6..a62cc54a4f 100644 --- a/pkg/resources/function_java_acceptance_test.go +++ b/pkg/resources/function_java_acceptance_test.go @@ -323,7 +323,6 @@ func TestAcc_FunctionJava_AllParameters(t *testing.T) { }) } -// TODO [this PR]: Test with Java versus Scala staged (probably the only way to set up functions to have exactly the same config in both languages) func TestAcc_FunctionJava_handleExternalLanguageChange(t *testing.T) { _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance) acc.TestAccPreCheck(t) @@ -335,9 +334,8 @@ func TestAcc_FunctionJava_handleExternalLanguageChange(t *testing.T) { argName := "x" handler := tmpJavaFunction.JavaHandler() - importPath := tmpJavaFunction.JarLocation() - functionModel := model.FunctionJavaBasicStaged("w", id, dataType, handler, importPath). + functionModel := model.FunctionJavaBasicStaged("w", id, dataType, handler, "~", tmpJavaFunction.JarName). WithArgument(argName, dataType) resource.Test(t, resource.TestCase{ @@ -360,8 +358,7 @@ func TestAcc_FunctionJava_handleExternalLanguageChange(t *testing.T) { { PreConfig: func() { acc.TestClient().Function.DropFunctionFunc(t, id)() - // TODO [this PR]: create scala staged (id, args, return, import, handler) - acc.TestClient().Function.CreateSqlWithIdentifierAndArgument(t, id.SchemaObjectId(), dataType) + acc.TestClient().Function.CreateScalaStaged(t, id, dataType, tmpJavaFunction.JarLocation(), handler) objectassert.Function(t, id).HasLanguage("SCALA") }, Config: config.FromModel(t, functionModel), From 369fc86322470ca4c56ff1eb19155e778868fa29 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 12:41:29 +0100 Subject: [PATCH 24/45] Run make pre-push --- docs/resources/function_java.md | 71 +++++++++++++++++-- docs/resources/function_javascript.md | 58 +++++++++++++-- docs/resources/function_python.md | 71 +++++++++++++++++-- docs/resources/function_scala.md | 71 +++++++++++++++++-- docs/resources/function_sql.md | 58 +++++++++++++-- docs/resources/procedure_java.md | 56 +++++++++++++-- docs/resources/procedure_javascript.md | 56 +++++++++++++-- docs/resources/procedure_python.md | 56 +++++++++++++-- docs/resources/procedure_scala.md | 56 +++++++++++++-- docs/resources/procedure_sql.md | 56 +++++++++++++-- pkg/resources/function_commons.go | 10 ++- .../function_java_acceptance_test.go | 2 +- pkg/sdk/functions_ext_test.go | 1 - 13 files changed, 567 insertions(+), 55 deletions(-) diff --git a/docs/resources/function_java.md b/docs/resources/function_java.md index 23ab3b5dc2..a17ac864ea 100644 --- a/docs/resources/function_java.md +++ b/docs/resources/function_java.md @@ -17,7 +17,6 @@ Resource used to manage java function objects. For more information, check [func ### Required - `database` (String) The database in which to create the function. Due to technical limitations (read more [here](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/docs/technical-documentation/identifiers_rework_design_decisions.md#known-limitations-and-identifier-recommendations)), avoid using the following characters: `|`, `.`, `"`. -- `function_definition` (String) Defines the handler code executed when the UDF is called. Wrapping `$$` signs are added by the provider automatically; do not include them. The `function_definition` value must be Java source code. For more information, see [Introduction to Java UDFs](https://docs.snowflake.com/en/developer-guide/udf/java/udf-java-introduction). To mitigate permadiff on this field, the provider replaces blank characters with a space. This can lead to false positives in cases where a change in case or run of whitespace is semantically significant. - `handler` (String) The name of the handler method or class. If the handler is for a scalar UDF, returning a non-tabular value, the HANDLER value should be a method name, as in the following form: `MyClass.myMethod`. If the handler is for a tabular UDF, the HANDLER value should be the name of a handler class. - `name` (String) The name of the function; the identifier does not need to be unique for the schema in which the function is created because UDFs are identified and resolved by the combination of the name and argument types. Check the [docs](https://docs.snowflake.com/en/sql-reference/sql/create-function#all-languages). Due to technical limitations (read more [here](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/docs/technical-documentation/identifiers_rework_design_decisions.md#known-limitations-and-identifier-recommendations)), avoid using the following characters: `|`, `.`, `"`. - `return_type` (String) Specifies the results returned by the UDF, which determines the UDF type. Use `` to create a scalar UDF that returns a single value with the specified data type. Use `TABLE (col_name col_data_type, ...)` to creates a table UDF that returns tabular results with the specified table column(s) and column type(s). For the details, consult the [docs](https://docs.snowflake.com/en/sql-reference/sql/create-function#all-languages). @@ -29,13 +28,14 @@ Resource used to manage java function objects. For more information, check [func - `comment` (String) Specifies a comment for the function. - `enable_console_output` (Boolean) Enable stdout/stderr fast path logging for anonyous stored procs. This is a public parameter (similar to LOG_LEVEL). For more information, check [ENABLE_CONSOLE_OUTPUT docs](https://docs.snowflake.com/en/sql-reference/parameters#enable-console-output). - `external_access_integrations` (Set of String) The names of [external access integrations](https://docs.snowflake.com/en/sql-reference/sql/create-external-access-integration) needed in order for this function’s handler code to access external networks. An external access integration specifies [network rules](https://docs.snowflake.com/en/sql-reference/sql/create-network-rule) and [secrets](https://docs.snowflake.com/en/sql-reference/sql/create-secret) that specify external locations and credentials (if any) allowed for use by handler code when making requests of an external network, such as an external REST API. -- `imports` (Set of String) The location (stage), path, and name of the file(s) to import. A file can be a JAR file or another type of file. If the file is a JAR file, it can contain one or more .class files and zero or more resource files. JNI (Java Native Interface) is not supported. Snowflake prohibits loading libraries that contain native code (as opposed to Java bytecode). Java UDFs can also read non-JAR files. For an example, see [Reading a file specified statically in IMPORTS](https://docs.snowflake.com/en/developer-guide/udf/java/udf-java-cookbook.html#label-reading-file-from-java-udf-imports). Consult the [docs](https://docs.snowflake.com/en/sql-reference/sql/create-function#java). +- `function_definition` (String) Defines the handler code executed when the UDF is called. Wrapping `$$` signs are added by the provider automatically; do not include them. The `function_definition` value must be Java source code. For more information, see [Introduction to Java UDFs](https://docs.snowflake.com/en/developer-guide/udf/java/udf-java-introduction). To mitigate permadiff on this field, the provider replaces blank characters with a space. This can lead to false positives in cases where a change in case or run of whitespace is semantically significant. +- `imports` (Block Set) The location (stage), path, and name of the file(s) to import. A file can be a JAR file or another type of file. If the file is a JAR file, it can contain one or more .class files and zero or more resource files. JNI (Java Native Interface) is not supported. Snowflake prohibits loading libraries that contain native code (as opposed to Java bytecode). Java UDFs can also read non-JAR files. For an example, see [Reading a file specified statically in IMPORTS](https://docs.snowflake.com/en/developer-guide/udf/java/udf-java-cookbook.html#label-reading-file-from-java-udf-imports). Consult the [docs](https://docs.snowflake.com/en/sql-reference/sql/create-function#java). (see [below for nested schema](#nestedblock--imports)) - `is_secure` (String) Specifies that the function is secure. By design, the Snowflake's `SHOW FUNCTIONS` command does not provide information about secure functions (consult [function docs](https://docs.snowflake.com/en/sql-reference/sql/create-function#id1) and [Protecting Sensitive Information with Secure UDFs and Stored Procedures](https://docs.snowflake.com/en/developer-guide/secure-udf-procedure)) which is essential to manage/import function with Terraform. Use the role owning the function while managing secure functions. Available options are: "true" or "false". When the value is not set in the configuration the provider will put "default" there which means to use the Snowflake default for this value. - `log_level` (String) LOG_LEVEL to use when filtering events For more information, check [LOG_LEVEL docs](https://docs.snowflake.com/en/sql-reference/parameters#log-level). - `metric_level` (String) METRIC_LEVEL value to control whether to emit metrics to Event Table For more information, check [METRIC_LEVEL docs](https://docs.snowflake.com/en/sql-reference/parameters#metric-level). - `null_input_behavior` (String) Specifies the behavior of the function when called with null inputs. Valid values are (case-insensitive): `CALLED ON NULL INPUT` | `RETURNS NULL ON NULL INPUT`. - `packages` (Set of String) The name and version number of Snowflake system packages required as dependencies. The value should be of the form `package_name:version_number`, where `package_name` is `snowflake_domain:package`. -- `return_behavior` (String) Specifies the behavior of the function when returning results. Valid values are (case-insensitive): `VOLATILE` | `IMMUTABLE`. +- `return_results_behavior` (String) Specifies the behavior of the function when returning results. Valid values are (case-insensitive): `VOLATILE` | `IMMUTABLE`. - `runtime_version` (String) Specifies the Java JDK runtime version to use. The supported versions of Java are 11.x and 17.x. If RUNTIME_VERSION is not set, Java JDK 11 is used. - `secrets` (Block Set) Assigns the names of [secrets](https://docs.snowflake.com/en/sql-reference/sql/create-secret) to variables so that you can use the variables to reference the secrets when retrieving information from secrets in handler code. Secrets you specify here must be allowed by the [external access integration](https://docs.snowflake.com/en/sql-reference/sql/create-external-access-integration) specified as a value of this CREATE FUNCTION command’s EXTERNAL_ACCESS_INTEGRATIONS parameter. (see [below for nested schema](#nestedblock--secrets)) - `target_path` (String) The name of the handler method or class. If the handler is for a scalar UDF, returning a non-tabular value, the HANDLER value should be a method name, as in the following form: `MyClass.myMethod`. If the handler is for a tabular UDF, the HANDLER value should be the name of a handler class. @@ -58,6 +58,15 @@ Required: - `arg_name` (String) The argument name. + +### Nested Schema for `imports` + +Required: + +- `path_on_stage` (String) Path for import on stage, without the leading `/`. +- `stage_location` (String) Stage location without leading `@`. To use your user's stage set this to `~`, otherwise pass fully qualified name of the stage (with every part contained in double quotes or use `snowflake_stage..fully_qualified_name` if you manage this stage through terraform). + + ### Nested Schema for `secrets` @@ -72,10 +81,58 @@ Required: Read-Only: -- `enable_console_output` (Boolean) -- `log_level` (String) -- `metric_level` (String) -- `trace_level` (String) +- `enable_console_output` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--enable_console_output)) +- `log_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--log_level)) +- `metric_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--metric_level)) +- `trace_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--trace_level)) + + +### Nested Schema for `parameters.enable_console_output` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.log_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.metric_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.trace_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + diff --git a/docs/resources/function_javascript.md b/docs/resources/function_javascript.md index 2680ff6653..e7ae472ed8 100644 --- a/docs/resources/function_javascript.md +++ b/docs/resources/function_javascript.md @@ -31,7 +31,7 @@ Resource used to manage javascript function objects. For more information, check - `log_level` (String) LOG_LEVEL to use when filtering events For more information, check [LOG_LEVEL docs](https://docs.snowflake.com/en/sql-reference/parameters#log-level). - `metric_level` (String) METRIC_LEVEL value to control whether to emit metrics to Event Table For more information, check [METRIC_LEVEL docs](https://docs.snowflake.com/en/sql-reference/parameters#metric-level). - `null_input_behavior` (String) Specifies the behavior of the function when called with null inputs. Valid values are (case-insensitive): `CALLED ON NULL INPUT` | `RETURNS NULL ON NULL INPUT`. -- `return_behavior` (String) Specifies the behavior of the function when returning results. Valid values are (case-insensitive): `VOLATILE` | `IMMUTABLE`. +- `return_results_behavior` (String) Specifies the behavior of the function when returning results. Valid values are (case-insensitive): `VOLATILE` | `IMMUTABLE`. - `trace_level` (String) Trace level value to use when generating/filtering trace events For more information, check [TRACE_LEVEL docs](https://docs.snowflake.com/en/sql-reference/parameters#trace-level). ### Read-Only @@ -56,10 +56,58 @@ Required: Read-Only: -- `enable_console_output` (Boolean) -- `log_level` (String) -- `metric_level` (String) -- `trace_level` (String) +- `enable_console_output` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--enable_console_output)) +- `log_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--log_level)) +- `metric_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--metric_level)) +- `trace_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--trace_level)) + + +### Nested Schema for `parameters.enable_console_output` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.log_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.metric_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.trace_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + diff --git a/docs/resources/function_python.md b/docs/resources/function_python.md index 5f68cfb014..ca0b1008df 100644 --- a/docs/resources/function_python.md +++ b/docs/resources/function_python.md @@ -17,7 +17,6 @@ Resource used to manage python function objects. For more information, check [fu ### Required - `database` (String) The database in which to create the function. Due to technical limitations (read more [here](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/docs/technical-documentation/identifiers_rework_design_decisions.md#known-limitations-and-identifier-recommendations)), avoid using the following characters: `|`, `.`, `"`. -- `function_definition` (String) Defines the handler code executed when the UDF is called. Wrapping `$$` signs are added by the provider automatically; do not include them. The `function_definition` value must be Python source code. For more information, see [Introduction to Python UDFs](https://docs.snowflake.com/en/developer-guide/udf/python/udf-python-introduction). To mitigate permadiff on this field, the provider replaces blank characters with a space. This can lead to false positives in cases where a change in case or run of whitespace is semantically significant. - `handler` (String) The name of the handler function or class. If the handler is for a scalar UDF, returning a non-tabular value, the HANDLER value should be a function name. If the handler code is in-line with the CREATE FUNCTION statement, you can use the function name alone. When the handler code is referenced at a stage, this value should be qualified with the module name, as in the following form: `my_module.my_function`. If the handler is for a tabular UDF, the HANDLER value should be the name of a handler class. - `name` (String) The name of the function; the identifier does not need to be unique for the schema in which the function is created because UDFs are identified and resolved by the combination of the name and argument types. Check the [docs](https://docs.snowflake.com/en/sql-reference/sql/create-function#all-languages). Due to technical limitations (read more [here](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/docs/technical-documentation/identifiers_rework_design_decisions.md#known-limitations-and-identifier-recommendations)), avoid using the following characters: `|`, `.`, `"`. - `return_type` (String) Specifies the results returned by the UDF, which determines the UDF type. Use `` to create a scalar UDF that returns a single value with the specified data type. Use `TABLE (col_name col_data_type, ...)` to creates a table UDF that returns tabular results with the specified table column(s) and column type(s). For the details, consult the [docs](https://docs.snowflake.com/en/sql-reference/sql/create-function#all-languages). @@ -30,14 +29,15 @@ Resource used to manage python function objects. For more information, check [fu - `comment` (String) Specifies a comment for the function. - `enable_console_output` (Boolean) Enable stdout/stderr fast path logging for anonyous stored procs. This is a public parameter (similar to LOG_LEVEL). For more information, check [ENABLE_CONSOLE_OUTPUT docs](https://docs.snowflake.com/en/sql-reference/parameters#enable-console-output). - `external_access_integrations` (Set of String) The names of [external access integrations](https://docs.snowflake.com/en/sql-reference/sql/create-external-access-integration) needed in order for this function’s handler code to access external networks. An external access integration specifies [network rules](https://docs.snowflake.com/en/sql-reference/sql/create-network-rule) and [secrets](https://docs.snowflake.com/en/sql-reference/sql/create-secret) that specify external locations and credentials (if any) allowed for use by handler code when making requests of an external network, such as an external REST API. -- `imports` (Set of String) The location (stage), path, and name of the file(s) to import. A file can be a `.py` file or another type of file. Python UDFs can also read non-Python files, such as text files. For an example, see [Reading a file](https://docs.snowflake.com/en/developer-guide/udf/python/udf-python-examples.html#label-udf-python-read-files). Consult the [docs](https://docs.snowflake.com/en/sql-reference/sql/create-function#python). +- `function_definition` (String) Defines the handler code executed when the UDF is called. Wrapping `$$` signs are added by the provider automatically; do not include them. The `function_definition` value must be Python source code. For more information, see [Introduction to Python UDFs](https://docs.snowflake.com/en/developer-guide/udf/python/udf-python-introduction). To mitigate permadiff on this field, the provider replaces blank characters with a space. This can lead to false positives in cases where a change in case or run of whitespace is semantically significant. +- `imports` (Block Set) The location (stage), path, and name of the file(s) to import. A file can be a `.py` file or another type of file. Python UDFs can also read non-Python files, such as text files. For an example, see [Reading a file](https://docs.snowflake.com/en/developer-guide/udf/python/udf-python-examples.html#label-udf-python-read-files). Consult the [docs](https://docs.snowflake.com/en/sql-reference/sql/create-function#python). (see [below for nested schema](#nestedblock--imports)) - `is_aggregate` (String) Specifies that the function is an aggregate function. For more information about user-defined aggregate functions, see [Python user-defined aggregate functions](https://docs.snowflake.com/en/developer-guide/udf/python/udf-python-aggregate-functions). Available options are: "true" or "false". When the value is not set in the configuration the provider will put "default" there which means to use the Snowflake default for this value. - `is_secure` (String) Specifies that the function is secure. By design, the Snowflake's `SHOW FUNCTIONS` command does not provide information about secure functions (consult [function docs](https://docs.snowflake.com/en/sql-reference/sql/create-function#id1) and [Protecting Sensitive Information with Secure UDFs and Stored Procedures](https://docs.snowflake.com/en/developer-guide/secure-udf-procedure)) which is essential to manage/import function with Terraform. Use the role owning the function while managing secure functions. Available options are: "true" or "false". When the value is not set in the configuration the provider will put "default" there which means to use the Snowflake default for this value. - `log_level` (String) LOG_LEVEL to use when filtering events For more information, check [LOG_LEVEL docs](https://docs.snowflake.com/en/sql-reference/parameters#log-level). - `metric_level` (String) METRIC_LEVEL value to control whether to emit metrics to Event Table For more information, check [METRIC_LEVEL docs](https://docs.snowflake.com/en/sql-reference/parameters#metric-level). - `null_input_behavior` (String) Specifies the behavior of the function when called with null inputs. Valid values are (case-insensitive): `CALLED ON NULL INPUT` | `RETURNS NULL ON NULL INPUT`. - `packages` (Set of String) The name and version number of packages required as dependencies. The value should be of the form `package_name==version_number`. -- `return_behavior` (String) Specifies the behavior of the function when returning results. Valid values are (case-insensitive): `VOLATILE` | `IMMUTABLE`. +- `return_results_behavior` (String) Specifies the behavior of the function when returning results. Valid values are (case-insensitive): `VOLATILE` | `IMMUTABLE`. - `secrets` (Block Set) Assigns the names of [secrets](https://docs.snowflake.com/en/sql-reference/sql/create-secret) to variables so that you can use the variables to reference the secrets when retrieving information from secrets in handler code. Secrets you specify here must be allowed by the [external access integration](https://docs.snowflake.com/en/sql-reference/sql/create-external-access-integration) specified as a value of this CREATE FUNCTION command’s EXTERNAL_ACCESS_INTEGRATIONS parameter. (see [below for nested schema](#nestedblock--secrets)) - `trace_level` (String) Trace level value to use when generating/filtering trace events For more information, check [TRACE_LEVEL docs](https://docs.snowflake.com/en/sql-reference/parameters#trace-level). @@ -58,6 +58,15 @@ Required: - `arg_name` (String) The argument name. + +### Nested Schema for `imports` + +Required: + +- `path_on_stage` (String) Path for import on stage, without the leading `/`. +- `stage_location` (String) Stage location without leading `@`. To use your user's stage set this to `~`, otherwise pass fully qualified name of the stage (with every part contained in double quotes or use `snowflake_stage..fully_qualified_name` if you manage this stage through terraform). + + ### Nested Schema for `secrets` @@ -72,10 +81,58 @@ Required: Read-Only: -- `enable_console_output` (Boolean) -- `log_level` (String) -- `metric_level` (String) -- `trace_level` (String) +- `enable_console_output` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--enable_console_output)) +- `log_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--log_level)) +- `metric_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--metric_level)) +- `trace_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--trace_level)) + + +### Nested Schema for `parameters.enable_console_output` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.log_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.metric_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.trace_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + diff --git a/docs/resources/function_scala.md b/docs/resources/function_scala.md index 9ec48d3866..5f3f53f4ed 100644 --- a/docs/resources/function_scala.md +++ b/docs/resources/function_scala.md @@ -17,7 +17,6 @@ Resource used to manage scala function objects. For more information, check [fun ### Required - `database` (String) The database in which to create the function. Due to technical limitations (read more [here](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/docs/technical-documentation/identifiers_rework_design_decisions.md#known-limitations-and-identifier-recommendations)), avoid using the following characters: `|`, `.`, `"`. -- `function_definition` (String) Defines the handler code executed when the UDF is called. Wrapping `$$` signs are added by the provider automatically; do not include them. The `function_definition` value must be Scala source code. For more information, see [Introduction to Scala UDFs](https://docs.snowflake.com/en/developer-guide/udf/scala/udf-scala-introduction). To mitigate permadiff on this field, the provider replaces blank characters with a space. This can lead to false positives in cases where a change in case or run of whitespace is semantically significant. - `handler` (String) The name of the handler method or class. If the handler is for a scalar UDF, returning a non-tabular value, the HANDLER value should be a method name, as in the following form: `MyClass.myMethod`. - `name` (String) The name of the function; the identifier does not need to be unique for the schema in which the function is created because UDFs are identified and resolved by the combination of the name and argument types. Check the [docs](https://docs.snowflake.com/en/sql-reference/sql/create-function#all-languages). Due to technical limitations (read more [here](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/docs/technical-documentation/identifiers_rework_design_decisions.md#known-limitations-and-identifier-recommendations)), avoid using the following characters: `|`, `.`, `"`. - `return_type` (String) Specifies the results returned by the UDF, which determines the UDF type. Use `` to create a scalar UDF that returns a single value with the specified data type. Use `TABLE (col_name col_data_type, ...)` to creates a table UDF that returns tabular results with the specified table column(s) and column type(s). For the details, consult the [docs](https://docs.snowflake.com/en/sql-reference/sql/create-function#all-languages). @@ -30,13 +29,14 @@ Resource used to manage scala function objects. For more information, check [fun - `comment` (String) Specifies a comment for the function. - `enable_console_output` (Boolean) Enable stdout/stderr fast path logging for anonyous stored procs. This is a public parameter (similar to LOG_LEVEL). For more information, check [ENABLE_CONSOLE_OUTPUT docs](https://docs.snowflake.com/en/sql-reference/parameters#enable-console-output). - `external_access_integrations` (Set of String) The names of [external access integrations](https://docs.snowflake.com/en/sql-reference/sql/create-external-access-integration) needed in order for this function’s handler code to access external networks. An external access integration specifies [network rules](https://docs.snowflake.com/en/sql-reference/sql/create-network-rule) and [secrets](https://docs.snowflake.com/en/sql-reference/sql/create-secret) that specify external locations and credentials (if any) allowed for use by handler code when making requests of an external network, such as an external REST API. -- `imports` (Set of String) The location (stage), path, and name of the file(s) to import, such as a JAR or other kind of file. The JAR file might contain handler dependency libraries. It can contain one or more .class files and zero or more resource files. JNI (Java Native Interface) is not supported. Snowflake prohibits loading libraries that contain native code (as opposed to Java bytecode). A non-JAR file might a file read by handler code. For an example, see [Reading a file specified statically in IMPORTS](https://docs.snowflake.com/en/developer-guide/udf/java/udf-java-cookbook.html#label-reading-file-from-java-udf-imports). Consult the [docs](https://docs.snowflake.com/en/sql-reference/sql/create-function#scala). +- `function_definition` (String) Defines the handler code executed when the UDF is called. Wrapping `$$` signs are added by the provider automatically; do not include them. The `function_definition` value must be Scala source code. For more information, see [Introduction to Scala UDFs](https://docs.snowflake.com/en/developer-guide/udf/scala/udf-scala-introduction). To mitigate permadiff on this field, the provider replaces blank characters with a space. This can lead to false positives in cases where a change in case or run of whitespace is semantically significant. +- `imports` (Block Set) The location (stage), path, and name of the file(s) to import, such as a JAR or other kind of file. The JAR file might contain handler dependency libraries. It can contain one or more .class files and zero or more resource files. JNI (Java Native Interface) is not supported. Snowflake prohibits loading libraries that contain native code (as opposed to Java bytecode). A non-JAR file might a file read by handler code. For an example, see [Reading a file specified statically in IMPORTS](https://docs.snowflake.com/en/developer-guide/udf/java/udf-java-cookbook.html#label-reading-file-from-java-udf-imports). Consult the [docs](https://docs.snowflake.com/en/sql-reference/sql/create-function#scala). (see [below for nested schema](#nestedblock--imports)) - `is_secure` (String) Specifies that the function is secure. By design, the Snowflake's `SHOW FUNCTIONS` command does not provide information about secure functions (consult [function docs](https://docs.snowflake.com/en/sql-reference/sql/create-function#id1) and [Protecting Sensitive Information with Secure UDFs and Stored Procedures](https://docs.snowflake.com/en/developer-guide/secure-udf-procedure)) which is essential to manage/import function with Terraform. Use the role owning the function while managing secure functions. Available options are: "true" or "false". When the value is not set in the configuration the provider will put "default" there which means to use the Snowflake default for this value. - `log_level` (String) LOG_LEVEL to use when filtering events For more information, check [LOG_LEVEL docs](https://docs.snowflake.com/en/sql-reference/parameters#log-level). - `metric_level` (String) METRIC_LEVEL value to control whether to emit metrics to Event Table For more information, check [METRIC_LEVEL docs](https://docs.snowflake.com/en/sql-reference/parameters#metric-level). - `null_input_behavior` (String) Specifies the behavior of the function when called with null inputs. Valid values are (case-insensitive): `CALLED ON NULL INPUT` | `RETURNS NULL ON NULL INPUT`. - `packages` (Set of String) The name and version number of Snowflake system packages required as dependencies. The value should be of the form `package_name:version_number`, where `package_name` is `snowflake_domain:package`. -- `return_behavior` (String) Specifies the behavior of the function when returning results. Valid values are (case-insensitive): `VOLATILE` | `IMMUTABLE`. +- `return_results_behavior` (String) Specifies the behavior of the function when returning results. Valid values are (case-insensitive): `VOLATILE` | `IMMUTABLE`. - `secrets` (Block Set) Assigns the names of [secrets](https://docs.snowflake.com/en/sql-reference/sql/create-secret) to variables so that you can use the variables to reference the secrets when retrieving information from secrets in handler code. Secrets you specify here must be allowed by the [external access integration](https://docs.snowflake.com/en/sql-reference/sql/create-external-access-integration) specified as a value of this CREATE FUNCTION command’s EXTERNAL_ACCESS_INTEGRATIONS parameter. (see [below for nested schema](#nestedblock--secrets)) - `target_path` (String) The name of the handler method or class. If the handler is for a scalar UDF, returning a non-tabular value, the HANDLER value should be a method name, as in the following form: `MyClass.myMethod`. - `trace_level` (String) Trace level value to use when generating/filtering trace events For more information, check [TRACE_LEVEL docs](https://docs.snowflake.com/en/sql-reference/parameters#trace-level). @@ -58,6 +58,15 @@ Required: - `arg_name` (String) The argument name. + +### Nested Schema for `imports` + +Required: + +- `path_on_stage` (String) Path for import on stage, without the leading `/`. +- `stage_location` (String) Stage location without leading `@`. To use your user's stage set this to `~`, otherwise pass fully qualified name of the stage (with every part contained in double quotes or use `snowflake_stage..fully_qualified_name` if you manage this stage through terraform). + + ### Nested Schema for `secrets` @@ -72,10 +81,58 @@ Required: Read-Only: -- `enable_console_output` (Boolean) -- `log_level` (String) -- `metric_level` (String) -- `trace_level` (String) +- `enable_console_output` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--enable_console_output)) +- `log_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--log_level)) +- `metric_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--metric_level)) +- `trace_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--trace_level)) + + +### Nested Schema for `parameters.enable_console_output` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.log_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.metric_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.trace_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + diff --git a/docs/resources/function_sql.md b/docs/resources/function_sql.md index 80d83727fb..ad371a20f5 100644 --- a/docs/resources/function_sql.md +++ b/docs/resources/function_sql.md @@ -31,7 +31,7 @@ Resource used to manage sql function objects. For more information, check [funct - `log_level` (String) LOG_LEVEL to use when filtering events For more information, check [LOG_LEVEL docs](https://docs.snowflake.com/en/sql-reference/parameters#log-level). - `metric_level` (String) METRIC_LEVEL value to control whether to emit metrics to Event Table For more information, check [METRIC_LEVEL docs](https://docs.snowflake.com/en/sql-reference/parameters#metric-level). - `null_input_behavior` (String) Specifies the behavior of the function when called with null inputs. Valid values are (case-insensitive): `CALLED ON NULL INPUT` | `RETURNS NULL ON NULL INPUT`. -- `return_behavior` (String) Specifies the behavior of the function when returning results. Valid values are (case-insensitive): `VOLATILE` | `IMMUTABLE`. +- `return_results_behavior` (String) Specifies the behavior of the function when returning results. Valid values are (case-insensitive): `VOLATILE` | `IMMUTABLE`. - `trace_level` (String) Trace level value to use when generating/filtering trace events For more information, check [TRACE_LEVEL docs](https://docs.snowflake.com/en/sql-reference/parameters#trace-level). ### Read-Only @@ -56,10 +56,58 @@ Required: Read-Only: -- `enable_console_output` (Boolean) -- `log_level` (String) -- `metric_level` (String) -- `trace_level` (String) +- `enable_console_output` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--enable_console_output)) +- `log_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--log_level)) +- `metric_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--metric_level)) +- `trace_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--trace_level)) + + +### Nested Schema for `parameters.enable_console_output` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.log_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.metric_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.trace_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + diff --git a/docs/resources/procedure_java.md b/docs/resources/procedure_java.md index 94490ed21e..dbb5f2eba3 100644 --- a/docs/resources/procedure_java.md +++ b/docs/resources/procedure_java.md @@ -73,10 +73,58 @@ Required: Read-Only: -- `enable_console_output` (Boolean) -- `log_level` (String) -- `metric_level` (String) -- `trace_level` (String) +- `enable_console_output` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--enable_console_output)) +- `log_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--log_level)) +- `metric_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--metric_level)) +- `trace_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--trace_level)) + + +### Nested Schema for `parameters.enable_console_output` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.log_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.metric_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.trace_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + diff --git a/docs/resources/procedure_javascript.md b/docs/resources/procedure_javascript.md index a562ad589d..a9364db4cf 100644 --- a/docs/resources/procedure_javascript.md +++ b/docs/resources/procedure_javascript.md @@ -56,10 +56,58 @@ Required: Read-Only: -- `enable_console_output` (Boolean) -- `log_level` (String) -- `metric_level` (String) -- `trace_level` (String) +- `enable_console_output` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--enable_console_output)) +- `log_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--log_level)) +- `metric_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--metric_level)) +- `trace_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--trace_level)) + + +### Nested Schema for `parameters.enable_console_output` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.log_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.metric_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.trace_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + diff --git a/docs/resources/procedure_python.md b/docs/resources/procedure_python.md index 7b6759ef75..a28cf0d0b5 100644 --- a/docs/resources/procedure_python.md +++ b/docs/resources/procedure_python.md @@ -72,10 +72,58 @@ Required: Read-Only: -- `enable_console_output` (Boolean) -- `log_level` (String) -- `metric_level` (String) -- `trace_level` (String) +- `enable_console_output` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--enable_console_output)) +- `log_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--log_level)) +- `metric_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--metric_level)) +- `trace_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--trace_level)) + + +### Nested Schema for `parameters.enable_console_output` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.log_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.metric_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.trace_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + diff --git a/docs/resources/procedure_scala.md b/docs/resources/procedure_scala.md index 1347bfb5cf..692fb569b1 100644 --- a/docs/resources/procedure_scala.md +++ b/docs/resources/procedure_scala.md @@ -73,10 +73,58 @@ Required: Read-Only: -- `enable_console_output` (Boolean) -- `log_level` (String) -- `metric_level` (String) -- `trace_level` (String) +- `enable_console_output` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--enable_console_output)) +- `log_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--log_level)) +- `metric_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--metric_level)) +- `trace_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--trace_level)) + + +### Nested Schema for `parameters.enable_console_output` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.log_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.metric_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.trace_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + diff --git a/docs/resources/procedure_sql.md b/docs/resources/procedure_sql.md index 3b078e3977..2533380779 100644 --- a/docs/resources/procedure_sql.md +++ b/docs/resources/procedure_sql.md @@ -56,10 +56,58 @@ Required: Read-Only: -- `enable_console_output` (Boolean) -- `log_level` (String) -- `metric_level` (String) -- `trace_level` (String) +- `enable_console_output` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--enable_console_output)) +- `log_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--log_level)) +- `metric_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--metric_level)) +- `trace_level` (List of Object) (see [below for nested schema](#nestedobjatt--parameters--trace_level)) + + +### Nested Schema for `parameters.enable_console_output` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.log_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.metric_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + + + +### Nested Schema for `parameters.trace_level` + +Read-Only: + +- `default` (String) +- `description` (String) +- `key` (String) +- `level` (String) +- `value` (String) + diff --git a/pkg/resources/function_commons.go b/pkg/resources/function_commons.go index 5359ebabca..e1524f91b2 100644 --- a/pkg/resources/function_commons.go +++ b/pkg/resources/function_commons.go @@ -26,6 +26,7 @@ func init() { type functionSchemaDef struct { additionalArguments []string functionDefinitionDescription string + functionDefinitionRequired bool runtimeVersionRequired bool runtimeVersionDescription string importsDescription string @@ -44,6 +45,11 @@ func setUpFunctionSchema(definition functionSchemaDef) map[string]*schema.Schema } if v, ok := currentSchema["function_definition"]; ok && v != nil { v.Description = diffSuppressStatementFieldDescription(definition.functionDefinitionDescription) + if definition.functionDefinitionRequired { + v.Required = true + } else { + v.Optional = true + } } if v, ok := currentSchema["runtime_version"]; ok && v != nil { if definition.runtimeVersionRequired { @@ -110,6 +116,7 @@ var ( javascriptFunctionSchemaDefinition = functionSchemaDef{ additionalArguments: []string{}, functionDefinitionDescription: functionDefinitionTemplate("JavaScript", "https://docs.snowflake.com/en/developer-guide/udf/javascript/udf-javascript-introduction"), + functionDefinitionRequired: true, } pythonFunctionSchemaDefinition = functionSchemaDef{ additionalArguments: []string{ @@ -149,6 +156,7 @@ var ( sqlFunctionSchemaDefinition = functionSchemaDef{ additionalArguments: []string{}, functionDefinitionDescription: functionDefinitionTemplate("SQL", "https://docs.snowflake.com/en/developer-guide/udf/sql/udf-sql-introduction"), + functionDefinitionRequired: true, } ) @@ -334,10 +342,8 @@ func functionBaseSchema() map[string]schema.Schema { Optional: true, ForceNew: true, }, - // TODO [this PR]: required only for javascript and sql "function_definition": { Type: schema.TypeString, - Required: true, ForceNew: true, DiffSuppressFunc: DiffSuppressStatement, }, diff --git a/pkg/resources/function_java_acceptance_test.go b/pkg/resources/function_java_acceptance_test.go index a62cc54a4f..6dacd8e0fa 100644 --- a/pkg/resources/function_java_acceptance_test.go +++ b/pkg/resources/function_java_acceptance_test.go @@ -361,7 +361,7 @@ func TestAcc_FunctionJava_handleExternalLanguageChange(t *testing.T) { acc.TestClient().Function.CreateScalaStaged(t, id, dataType, tmpJavaFunction.JarLocation(), handler) objectassert.Function(t, id).HasLanguage("SCALA") }, - Config: config.FromModel(t, functionModel), + Config: config.FromModels(t, functionModel), ConfigPlanChecks: resource.ConfigPlanChecks{ PreApply: []plancheck.PlanCheck{ plancheck.ExpectResourceAction(functionModel.ResourceReference(), plancheck.ResourceActionDestroyBeforeCreate), diff --git a/pkg/sdk/functions_ext_test.go b/pkg/sdk/functions_ext_test.go index 6eb9276862..a95441cc75 100644 --- a/pkg/sdk/functions_ext_test.go +++ b/pkg/sdk/functions_ext_test.go @@ -8,7 +8,6 @@ import ( ) func Test_parseFunctionDetailsImport(t *testing.T) { - inputs := []struct { rawInput string expected []FunctionDetailsImport From 6acfc14279b4caab341a3e181ab2a2aaae80ff21 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 12:56:53 +0100 Subject: [PATCH 25/45] Pass test with external type change --- pkg/resources/custom_diffs.go | 15 ++++++--------- pkg/resources/function_commons.go | 2 +- pkg/resources/function_java.go | 2 +- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/pkg/resources/custom_diffs.go b/pkg/resources/custom_diffs.go index de608bb24f..b5a9eae5da 100644 --- a/pkg/resources/custom_diffs.go +++ b/pkg/resources/custom_diffs.go @@ -285,16 +285,13 @@ func RecreateWhenResourceBoolFieldChangedExternally(boolField string, wantValue } } -// RecreateWhenResourceFieldChangedExternally recreates a resource when wantValue is different from value in field. -// TODO [this PR]: replace above func and test -func RecreateWhenResourceFieldChangedExternally[T bool | string](field string, wantValue T) schema.CustomizeDiffFunc { +// RecreateWhenResourceStringFieldChangedExternally recreates a resource when wantValue is different from value in field. +// TODO [next PR]: merge with above? test. +func RecreateWhenResourceStringFieldChangedExternally(field string, wantValue string) schema.CustomizeDiffFunc { return func(_ context.Context, diff *schema.ResourceDiff, _ any) error { - if n := diff.Get(field); n != nil { - logging.DebugLogger.Printf("[DEBUG] new external value for %v: %v, recreating the resource...\n", field, n.(T)) - - if n.(T) != wantValue { - return errors.Join(diff.SetNew(field, wantValue), diff.ForceNew(field)) - } + if o, n := diff.GetChange(field); n != nil && o != nil && o != "" && n.(string) != wantValue { + log.Printf("[DEBUG] new external value for %s: %s (want: %s), recreating the resource...\n", field, n.(string), wantValue) + return errors.Join(diff.SetNew(field, wantValue), diff.ForceNew(field)) } return nil } diff --git a/pkg/resources/function_commons.go b/pkg/resources/function_commons.go index e1524f91b2..3ae5e62094 100644 --- a/pkg/resources/function_commons.go +++ b/pkg/resources/function_commons.go @@ -409,7 +409,7 @@ func parseFunctionArgumentsCommon(d *schema.ResourceData) ([]sdk.FunctionArgumen func parseFunctionImportsCommon(d *schema.ResourceData) ([]sdk.FunctionImportRequest, error) { imports := make([]sdk.FunctionImportRequest, 0) if v, ok := d.GetOk("imports"); ok { - for _, imp := range v.([]any) { + for _, imp := range v.(*schema.Set).List() { stageLocation := imp.(map[string]any)["stage_location"].(string) pathOnStage := imp.(map[string]any)["path_on_stage"].(string) imports = append(imports, *sdk.NewFunctionImportRequest().WithImport(fmt.Sprintf("@%s/%s", stageLocation, pathOnStage))) diff --git a/pkg/resources/function_java.go b/pkg/resources/function_java.go index 69ceb29d7c..7f511d9305 100644 --- a/pkg/resources/function_java.go +++ b/pkg/resources/function_java.go @@ -36,7 +36,7 @@ func FunctionJava() *schema.Resource { // Currently, almost all attributes are marked as forceNew. // When language changes, these attributes also change, causing the object to recreate either way. // The only potential option is java staged -> scala staged (however scala need runtime_version which may interfere). - RecreateWhenResourceFieldChangedExternally("function_language", "JAVA"), + RecreateWhenResourceStringFieldChangedExternally("function_language", "JAVA"), )), Schema: collections.MergeMaps(javaFunctionSchema, functionParametersSchema), From 52fa10d05262aec9ea63e51d942dff98226f7997 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 13:00:36 +0100 Subject: [PATCH 26/45] Add basic test for staged --- .../function_java_acceptance_test.go | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/pkg/resources/function_java_acceptance_test.go b/pkg/resources/function_java_acceptance_test.go index 6dacd8e0fa..4f568bf130 100644 --- a/pkg/resources/function_java_acceptance_test.go +++ b/pkg/resources/function_java_acceptance_test.go @@ -25,7 +25,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/tfversion" ) -func TestAcc_FunctionJava_BasicFlows(t *testing.T) { +func TestAcc_FunctionJava_InlineBasic(t *testing.T) { className := "TestFunc" funcName := "echoVarchar" argName := "x" @@ -212,6 +212,47 @@ func TestAcc_FunctionJava_BasicFlows(t *testing.T) { }) } +func TestAcc_FunctionJava_StagedBasic(t *testing.T) { + _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance) + acc.TestAccPreCheck(t) + + tmpJavaFunction := acc.TestClient().CreateSampleJavaFunctionAndJarOnUserStage(t) + + dataType := tmpJavaFunction.ArgType + id := acc.TestClient().Ids.RandomSchemaObjectIdentifierWithArgumentsNewDataTypes(dataType) + + argName := "x" + handler := tmpJavaFunction.JavaHandler() + + functionModel := model.FunctionJavaBasicStaged("w", id, dataType, handler, "~", tmpJavaFunction.JarName). + WithArgument(argName, dataType) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + PreCheck: func() { acc.TestAccPreCheck(t) }, + CheckDestroy: acc.CheckDestroy(t, resources.FunctionJava), + Steps: []resource.TestStep{ + // CREATE BASIC + { + Config: config.FromModels(t, functionModel), + Check: assert.AssertThat(t, + resourceassert.FunctionJavaResource(t, functionModel.ResourceReference()). + HasNameString(id.Name()). + HasCommentString(sdk.DefaultFunctionComment). + HasFunctionLanguageString("JAVA"). + HasIsSecureString(r.BooleanDefault). + HasFullyQualifiedNameString(id.FullyQualifiedName()), + resourceshowoutputassert.FunctionShowOutput(t, functionModel.ResourceReference()). + HasIsSecure(false), + ), + }, + }, + }) +} + func TestAcc_FunctionJava_AllParameters(t *testing.T) { className := "TestFunc" funcName := "echoVarchar" From 92589beda0ac729e4b02077211435ab6381989ad Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 13:12:36 +0100 Subject: [PATCH 27/45] Assert imports --- .../function_java_resource_ext.go | 12 ++++++++++++ pkg/resources/function_java_acceptance_test.go | 17 +++++++++++++---- pkg/sdk/functions_ext.go | 2 ++ 3 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 pkg/acceptance/bettertestspoc/assert/resourceassert/function_java_resource_ext.go diff --git a/pkg/acceptance/bettertestspoc/assert/resourceassert/function_java_resource_ext.go b/pkg/acceptance/bettertestspoc/assert/resourceassert/function_java_resource_ext.go new file mode 100644 index 0000000000..6d25bd9732 --- /dev/null +++ b/pkg/acceptance/bettertestspoc/assert/resourceassert/function_java_resource_ext.go @@ -0,0 +1,12 @@ +package resourceassert + +import ( + "strconv" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert" +) + +func (f *FunctionJavaResourceAssert) HasImportsLength(len int) *FunctionJavaResourceAssert { + f.AddAssertion(assert.ValueSet("imports.#", strconv.FormatInt(int64(len), 10))) + return f +} diff --git a/pkg/resources/function_java_acceptance_test.go b/pkg/resources/function_java_acceptance_test.go index 4f568bf130..0426e1b727 100644 --- a/pkg/resources/function_java_acceptance_test.go +++ b/pkg/resources/function_java_acceptance_test.go @@ -58,9 +58,11 @@ func TestAcc_FunctionJava_InlineBasic(t *testing.T) { Check: assert.AssertThat(t, resourceassert.FunctionJavaResource(t, functionModelNoAttributes.ResourceReference()). HasNameString(id.Name()). + HasIsSecureString(r.BooleanDefault). HasCommentString(sdk.DefaultFunctionComment). + HasImportsLength(0). + HasFunctionDefinitionString(definition). HasFunctionLanguageString("JAVA"). - HasIsSecureString(r.BooleanDefault). HasFullyQualifiedNameString(id.FullyQualifiedName()), resourceshowoutputassert.FunctionShowOutput(t, functionModelNoAttributes.ResourceReference()). HasIsSecure(false), @@ -216,7 +218,10 @@ func TestAcc_FunctionJava_StagedBasic(t *testing.T) { _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance) acc.TestAccPreCheck(t) - tmpJavaFunction := acc.TestClient().CreateSampleJavaFunctionAndJarOnUserStage(t) + stage, stageCleanup := acc.TestClient().Stage.CreateStage(t) + t.Cleanup(stageCleanup) + + tmpJavaFunction := acc.TestClient().CreateSampleJavaFunctionAndJarOnStage(t, stage) dataType := tmpJavaFunction.ArgType id := acc.TestClient().Ids.RandomSchemaObjectIdentifierWithArgumentsNewDataTypes(dataType) @@ -224,7 +229,7 @@ func TestAcc_FunctionJava_StagedBasic(t *testing.T) { argName := "x" handler := tmpJavaFunction.JavaHandler() - functionModel := model.FunctionJavaBasicStaged("w", id, dataType, handler, "~", tmpJavaFunction.JarName). + functionModel := model.FunctionJavaBasicStaged("w", id, dataType, handler, stage.ID().FullyQualifiedName(), tmpJavaFunction.JarName). WithArgument(argName, dataType) resource.Test(t, resource.TestCase{ @@ -241,10 +246,14 @@ func TestAcc_FunctionJava_StagedBasic(t *testing.T) { Check: assert.AssertThat(t, resourceassert.FunctionJavaResource(t, functionModel.ResourceReference()). HasNameString(id.Name()). + HasIsSecureString(r.BooleanDefault). HasCommentString(sdk.DefaultFunctionComment). + HasImportsLength(1). + HasNoFunctionDefinition(). HasFunctionLanguageString("JAVA"). - HasIsSecureString(r.BooleanDefault). HasFullyQualifiedNameString(id.FullyQualifiedName()), + assert.Check(resource.TestCheckResourceAttr(functionModel.ResourceReference(), "imports.0.stage_location", stage.ID().FullyQualifiedName())), + assert.Check(resource.TestCheckResourceAttr(functionModel.ResourceReference(), "imports.0.path_on_stage", tmpJavaFunction.JarName)), resourceshowoutputassert.FunctionShowOutput(t, functionModel.ResourceReference()). HasIsSecure(false), ), diff --git a/pkg/sdk/functions_ext.go b/pkg/sdk/functions_ext.go index 596b017544..421906bbf7 100644 --- a/pkg/sdk/functions_ext.go +++ b/pkg/sdk/functions_ext.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "log" "strconv" "strings" ) @@ -103,6 +104,7 @@ func parseFunctionDetailsImport(details FunctionDetails) ([]FunctionDetailsImpor raw := (*details.Imports)[1 : len(*details.Imports)-1] imports := strings.Split(raw, ",") for _, imp := range imports { + log.Printf("[DEBUG] parsing imports part: %s", imp) idx := strings.Index(imp, "/") if idx < 0 { return functionDetailsImports, fmt.Errorf("could not parse imports from Snowflake: %s, part %s cannot be split into stage and path", *details.Imports, imp) From fdb64826a469fb8ef2f39da2bd7a22328692cef3 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 13:26:23 +0100 Subject: [PATCH 28/45] Prepare target path parsing --- .../function_describe_snowflake_ext.go | 2 +- pkg/resources/function_commons.go | 2 +- pkg/sdk/functions_ext.go | 57 ++++++++++++------- pkg/sdk/functions_ext_test.go | 20 +++---- pkg/sdk/testint/functions_integration_test.go | 20 +++---- 5 files changed, 58 insertions(+), 43 deletions(-) diff --git a/pkg/acceptance/bettertestspoc/assert/objectassert/function_describe_snowflake_ext.go b/pkg/acceptance/bettertestspoc/assert/objectassert/function_describe_snowflake_ext.go index 8b885e91ab..a46c94ae6b 100644 --- a/pkg/acceptance/bettertestspoc/assert/objectassert/function_describe_snowflake_ext.go +++ b/pkg/acceptance/bettertestspoc/assert/objectassert/function_describe_snowflake_ext.go @@ -407,7 +407,7 @@ func (f *FunctionDetailsAssert) HasExactlySecrets(expectedSecrets map[string]sdk return f } -func (f *FunctionDetailsAssert) HasExactlyImportsNormalizedInAnyOrder(imports ...sdk.FunctionDetailsImport) *FunctionDetailsAssert { +func (f *FunctionDetailsAssert) HasExactlyImportsNormalizedInAnyOrder(imports ...sdk.NormalizedPath) *FunctionDetailsAssert { f.AddAssertion(func(t *testing.T, o *sdk.FunctionDetails) error { t.Helper() if o.NormalizedImports == nil { diff --git a/pkg/resources/function_commons.go b/pkg/resources/function_commons.go index 3ae5e62094..8aea2e3861 100644 --- a/pkg/resources/function_commons.go +++ b/pkg/resources/function_commons.go @@ -494,7 +494,7 @@ type allFunctionDetailsCommon struct { functionParameters []*sdk.Parameter } -func readFunctionImportsCommon(d *schema.ResourceData, imports []sdk.FunctionDetailsImport) error { +func readFunctionImportsCommon(d *schema.ResourceData, imports []sdk.NormalizedPath) error { if len(imports) == 0 { // don't do anything if imports not present return nil diff --git a/pkg/sdk/functions_ext.go b/pkg/sdk/functions_ext.go index 421906bbf7..d5cb249d10 100644 --- a/pkg/sdk/functions_ext.go +++ b/pkg/sdk/functions_ext.go @@ -33,10 +33,11 @@ type FunctionDetails struct { InstalledPackages *string // list present for python (hidden when SECURE) IsAggregate *bool // present for python - NormalizedImports []FunctionDetailsImport + NormalizedImports []NormalizedPath + NormalizedTargetPath *NormalizedPath } -type FunctionDetailsImport struct { +type NormalizedPath struct { // StageLocation is a normalized (fully-quoted id or `~`) stage location StageLocation string // PathOnStage is path to the file on stage without opening `/` @@ -90,11 +91,17 @@ func functionDetailsFromRows(rows []FunctionDetail) (*FunctionDetails, error) { v.NormalizedImports = functionDetailsImports } + if p, err := parseStageLocationPath(*v.TargetPath); err != nil { + errs = append(errs, err) + } else { + v.NormalizedTargetPath = p + } + return v, errors.Join(errs...) } -func parseFunctionDetailsImport(details FunctionDetails) ([]FunctionDetailsImport, error) { - functionDetailsImports := make([]FunctionDetailsImport, 0) +func parseFunctionDetailsImport(details FunctionDetails) ([]NormalizedPath, error) { + functionDetailsImports := make([]NormalizedPath, 0) if details.Imports == nil || *details.Imports == "" || *details.Imports == "[]" { return functionDetailsImports, nil } @@ -104,28 +111,36 @@ func parseFunctionDetailsImport(details FunctionDetails) ([]FunctionDetailsImpor raw := (*details.Imports)[1 : len(*details.Imports)-1] imports := strings.Split(raw, ",") for _, imp := range imports { - log.Printf("[DEBUG] parsing imports part: %s", imp) - idx := strings.Index(imp, "/") - if idx < 0 { - return functionDetailsImports, fmt.Errorf("could not parse imports from Snowflake: %s, part %s cannot be split into stage and path", *details.Imports, imp) - } - stageRaw := strings.TrimPrefix(strings.TrimSpace(imp[:idx]), "@") - if stageRaw != "~" { - stageId, err := ParseSchemaObjectIdentifier(stageRaw) - if err != nil { - return functionDetailsImports, fmt.Errorf("could not parse imports from Snowflake: %s, part %s contains incorrect stage location: %w", *details.Imports, imp, err) - } - stageRaw = stageId.FullyQualifiedName() - } - pathRaw := strings.TrimPrefix(strings.TrimSpace(imp[idx:]), "/") - if pathRaw == "" { - return functionDetailsImports, fmt.Errorf("could not parse imports from Snowflake: %s, part %s contains empty path", *details.Imports, imp) + p, err := parseStageLocationPath(imp) + if err != nil { + return nil, fmt.Errorf("could not parse imports from Snowflake: %s, err: %w", *details.Imports, err) } - functionDetailsImports = append(functionDetailsImports, FunctionDetailsImport{stageRaw, pathRaw}) + functionDetailsImports = append(functionDetailsImports, *p) } return functionDetailsImports, nil } +func parseStageLocationPath(location string) (*NormalizedPath, error) { + log.Printf("[DEBUG] parsing stage location path part: %s", location) + idx := strings.Index(location, "/") + if idx < 0 { + return nil, fmt.Errorf("part %s cannot be split into stage and path", location) + } + stageRaw := strings.TrimPrefix(strings.TrimSpace(location[:idx]), "@") + if stageRaw != "~" { + stageId, err := ParseSchemaObjectIdentifier(stageRaw) + if err != nil { + return nil, fmt.Errorf("part %s contains incorrect stage location: %w", location, err) + } + stageRaw = stageId.FullyQualifiedName() + } + pathRaw := strings.TrimPrefix(strings.TrimSpace(location[idx:]), "/") + if pathRaw == "" { + return nil, fmt.Errorf("part %s contains empty path", location) + } + return &NormalizedPath{stageRaw, pathRaw}, nil +} + func (v *functions) DescribeDetails(ctx context.Context, id SchemaObjectIdentifierWithArguments) (*FunctionDetails, error) { rows, err := v.Describe(ctx, id) if err != nil { diff --git a/pkg/sdk/functions_ext_test.go b/pkg/sdk/functions_ext_test.go index a95441cc75..4c2c6788be 100644 --- a/pkg/sdk/functions_ext_test.go +++ b/pkg/sdk/functions_ext_test.go @@ -10,17 +10,17 @@ import ( func Test_parseFunctionDetailsImport(t *testing.T) { inputs := []struct { rawInput string - expected []FunctionDetailsImport + expected []NormalizedPath }{ - {"", []FunctionDetailsImport{}}, - {`[]`, []FunctionDetailsImport{}}, - {`[@~/abc]`, []FunctionDetailsImport{{"~", "abc"}}}, - {`[@~/abc/def]`, []FunctionDetailsImport{{"~", "abc/def"}}}, - {`[@"db"."sc"."st"/abc/def]`, []FunctionDetailsImport{{`"db"."sc"."st"`, "abc/def"}}}, - {`[@db.sc.st/abc/def]`, []FunctionDetailsImport{{`"db"."sc"."st"`, "abc/def"}}}, - {`[db.sc.st/abc/def]`, []FunctionDetailsImport{{`"db"."sc"."st"`, "abc/def"}}}, - {`[@"db"."sc".st/abc/def]`, []FunctionDetailsImport{{`"db"."sc"."st"`, "abc/def"}}}, - {`[@"db"."sc".st/abc/def, db."sc".st/abc]`, []FunctionDetailsImport{{`"db"."sc"."st"`, "abc/def"}, {`"db"."sc"."st"`, "abc"}}}, + {"", []NormalizedPath{}}, + {`[]`, []NormalizedPath{}}, + {`[@~/abc]`, []NormalizedPath{{"~", "abc"}}}, + {`[@~/abc/def]`, []NormalizedPath{{"~", "abc/def"}}}, + {`[@"db"."sc"."st"/abc/def]`, []NormalizedPath{{`"db"."sc"."st"`, "abc/def"}}}, + {`[@db.sc.st/abc/def]`, []NormalizedPath{{`"db"."sc"."st"`, "abc/def"}}}, + {`[db.sc.st/abc/def]`, []NormalizedPath{{`"db"."sc"."st"`, "abc/def"}}}, + {`[@"db"."sc".st/abc/def]`, []NormalizedPath{{`"db"."sc"."st"`, "abc/def"}}}, + {`[@"db"."sc".st/abc/def, db."sc".st/abc]`, []NormalizedPath{{`"db"."sc"."st"`, "abc/def"}, {`"db"."sc"."st"`, "abc"}}}, } badInputs := []struct { diff --git a/pkg/sdk/testint/functions_integration_test.go b/pkg/sdk/testint/functions_integration_test.go index a9f6b696af..fcbd947bf7 100644 --- a/pkg/sdk/testint/functions_integration_test.go +++ b/pkg/sdk/testint/functions_integration_test.go @@ -220,7 +220,7 @@ func TestInt_Functions(t *testing.T) { // TODO [SNOW-1348103]: check multiple secrets (to know how to parse) HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). HasImports(fmt.Sprintf(`[%s]`, tmpJavaFunction.JarLocation())). - HasExactlyImportsNormalizedInAnyOrder(sdk.FunctionDetailsImport{ + HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ StageLocation: "~", PathOnStage: tmpJavaFunction.JarName, }). HasHandler(handler). @@ -293,7 +293,7 @@ func TestInt_Functions(t *testing.T) { HasExternalAccessIntegrationsNil(). HasSecretsNil(). HasImports(fmt.Sprintf(`[%s]`, importPath)). - HasExactlyImportsNormalizedInAnyOrder(sdk.FunctionDetailsImport{ + HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ StageLocation: "~", PathOnStage: tmpJavaFunction.JarName, }). HasHandler(handler). @@ -378,7 +378,7 @@ func TestInt_Functions(t *testing.T) { HasExactlyExternalAccessIntegrations(externalAccessIntegration). HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). HasImports(fmt.Sprintf(`[%s]`, tmpJavaFunction.JarLocation())). - HasExactlyImportsNormalizedInAnyOrder(sdk.FunctionDetailsImport{ + HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ StageLocation: "~", PathOnStage: tmpJavaFunction.JarName, }). HasHandler(handler). @@ -451,7 +451,7 @@ func TestInt_Functions(t *testing.T) { HasExternalAccessIntegrationsNil(). HasSecretsNil(). HasImports(fmt.Sprintf(`[@"%s"."%s".%s/%s]`, stage.ID().DatabaseName(), stage.ID().SchemaName(), stage.ID().Name(), tmpJavaFunctionDifferentStage.JarName)). - HasExactlyImportsNormalizedInAnyOrder(sdk.FunctionDetailsImport{ + HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ StageLocation: stage.ID().FullyQualifiedName(), PathOnStage: tmpJavaFunctionDifferentStage.JarName, }). HasHandler(handler). @@ -749,7 +749,7 @@ func TestInt_Functions(t *testing.T) { HasExactlyExternalAccessIntegrations(externalAccessIntegration). HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). HasImports(fmt.Sprintf(`[%s]`, tmpPythonFunction.PythonModuleLocation())). - HasExactlyImportsNormalizedInAnyOrder(sdk.FunctionDetailsImport{ + HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ StageLocation: "~", PathOnStage: tmpPythonFunction.PythonFileName(), }). HasHandler(funcName). @@ -819,7 +819,7 @@ func TestInt_Functions(t *testing.T) { HasExternalAccessIntegrationsNil(). HasSecretsNil(). HasImports(fmt.Sprintf(`[%s]`, tmpPythonFunction.PythonModuleLocation())). - HasExactlyImportsNormalizedInAnyOrder(sdk.FunctionDetailsImport{ + HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ StageLocation: "~", PathOnStage: tmpPythonFunction.PythonFileName(), }). HasHandler(tmpPythonFunction.PythonHandler()). @@ -901,7 +901,7 @@ func TestInt_Functions(t *testing.T) { HasExactlyExternalAccessIntegrations(externalAccessIntegration). HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). HasImports(fmt.Sprintf(`[%s]`, tmpPythonFunction.PythonModuleLocation())). - HasExactlyImportsNormalizedInAnyOrder(sdk.FunctionDetailsImport{ + HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ StageLocation: "~", PathOnStage: tmpPythonFunction.PythonFileName(), }). HasHandler(tmpPythonFunction.PythonHandler()). @@ -1066,7 +1066,7 @@ func TestInt_Functions(t *testing.T) { HasExactlyExternalAccessIntegrations(externalAccessIntegration). HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). HasImports(fmt.Sprintf(`[%s]`, tmpJavaFunction.JarLocation())). - HasExactlyImportsNormalizedInAnyOrder(sdk.FunctionDetailsImport{ + HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ StageLocation: "~", PathOnStage: tmpJavaFunction.JarName, }). HasHandler(handler). @@ -1137,7 +1137,7 @@ func TestInt_Functions(t *testing.T) { HasExternalAccessIntegrationsNil(). HasSecretsNil(). HasImports(fmt.Sprintf(`[%s]`, importPath)). - HasExactlyImportsNormalizedInAnyOrder(sdk.FunctionDetailsImport{ + HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ StageLocation: "~", PathOnStage: tmpJavaFunction.JarName, }). HasHandler(handler). @@ -1219,7 +1219,7 @@ func TestInt_Functions(t *testing.T) { HasExactlyExternalAccessIntegrations(externalAccessIntegration). HasExactlySecrets(map[string]sdk.SchemaObjectIdentifier{"abc": secretId}). HasImports(fmt.Sprintf(`[%s]`, tmpJavaFunction.JarLocation())). - HasExactlyImportsNormalizedInAnyOrder(sdk.FunctionDetailsImport{ + HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ StageLocation: "~", PathOnStage: tmpJavaFunction.JarName, }). HasHandler(handler). From 7e53d4a9d4f60dd740d8c3f9d2a9a1c6db4ef43e Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 13:39:50 +0100 Subject: [PATCH 29/45] Add target path logic --- pkg/acceptance/helpers/ids_generator.go | 4 +- pkg/resources/function_commons.go | 52 ++++++++++++++++++++++++- pkg/resources/function_java.go | 4 +- pkg/sdk/identifier_helpers.go | 2 +- 4 files changed, 55 insertions(+), 7 deletions(-) diff --git a/pkg/acceptance/helpers/ids_generator.go b/pkg/acceptance/helpers/ids_generator.go index 74fa08b7ff..46b0e85d80 100644 --- a/pkg/acceptance/helpers/ids_generator.go +++ b/pkg/acceptance/helpers/ids_generator.go @@ -100,7 +100,7 @@ func (c *IdsGenerator) NewSchemaObjectIdentifierWithArguments(name string, argum } func (c *IdsGenerator) NewSchemaObjectIdentifierWithArgumentsNewDataTypes(name string, arguments ...datatypes.DataType) sdk.SchemaObjectIdentifierWithArguments { - legacyDataTypes := collections.Map(arguments, func(dt datatypes.DataType) sdk.DataType { return sdk.LegacyDataTypeFrom(dt) }) + legacyDataTypes := collections.Map(arguments, sdk.LegacyDataTypeFrom) return sdk.NewSchemaObjectIdentifierWithArguments(c.SchemaId().DatabaseName(), c.SchemaId().Name(), name, legacyDataTypes...) } @@ -113,7 +113,7 @@ func (c *IdsGenerator) RandomSchemaObjectIdentifierWithArguments(arguments ...sd } func (c *IdsGenerator) RandomSchemaObjectIdentifierWithArgumentsNewDataTypes(arguments ...datatypes.DataType) sdk.SchemaObjectIdentifierWithArguments { - legacyDataTypes := collections.Map(arguments, func(dt datatypes.DataType) sdk.DataType { return sdk.LegacyDataTypeFrom(dt) }) + legacyDataTypes := collections.Map(arguments, sdk.LegacyDataTypeFrom) return sdk.NewSchemaObjectIdentifierWithArguments(c.SchemaId().DatabaseName(), c.SchemaId().Name(), c.Alpha(), legacyDataTypes...) } diff --git a/pkg/resources/function_commons.go b/pkg/resources/function_commons.go index 8aea2e3861..29f9e42fdd 100644 --- a/pkg/resources/function_commons.go +++ b/pkg/resources/function_commons.go @@ -336,11 +336,25 @@ func functionBaseSchema() map[string]schema.Schema { }, Description: "Assigns the names of [secrets](https://docs.snowflake.com/en/sql-reference/sql/create-secret) to variables so that you can use the variables to reference the secrets when retrieving information from secrets in handler code. Secrets you specify here must be allowed by the [external access integration](https://docs.snowflake.com/en/sql-reference/sql/create-external-access-integration) specified as a value of this CREATE FUNCTION command’s EXTERNAL_ACCESS_INTEGRATIONS parameter.", }, - // TODO [SNOW-1348103]: because of https://docs.snowflake.com/en/sql-reference/sql/create-function#id6, maybe it will be better to split into stage + path "target_path": { - Type: schema.TypeString, + Type: schema.TypeSet, + MaxItems: 1, Optional: true, ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "stage_location": { + Type: schema.TypeString, + Required: true, + Description: "Stage location without leading `@`. To use your user's stage set this to `~`, otherwise pass fully qualified name of the stage (with every part contained in double quotes or use `snowflake_stage..fully_qualified_name` if you manage this stage through terraform).", + }, + "path_on_stage": { + Type: schema.TypeString, + Required: true, + Description: "Path for import on stage, without the leading `/`.", + }, + }, + }, }, "function_definition": { Type: schema.TypeString, @@ -418,6 +432,18 @@ func parseFunctionImportsCommon(d *schema.ResourceData) ([]sdk.FunctionImportReq return imports, nil } +func parseFunctionTargetPathCommon(d *schema.ResourceData) (string, error) { + var tp string + if v, ok := d.GetOk("target_path"); ok { + for _, tp := range v.(*schema.Set).List() { + stageLocation := tp.(map[string]any)["stage_location"].(string) + pathOnStage := tp.(map[string]any)["path_on_stage"].(string) + tp = fmt.Sprintf("@%s/%s", stageLocation, pathOnStage) + } + } + return tp, nil +} + func parseFunctionReturnsCommon(d *schema.ResourceData) (*sdk.FunctionReturnsRequest, error) { returnTypeRaw := d.Get("return_type").(string) dataType, err := datatypes.ParseDataType(returnTypeRaw) @@ -447,6 +473,15 @@ func setFunctionImportsInBuilder[T any](d *schema.ResourceData, setImports func( return nil } +func setFunctionTargetPathInBuilder[T any](d *schema.ResourceData, setTargetPath func(string) T) error { + tp, err := parseFunctionTargetPathCommon(d) + if err != nil { + return err + } + setTargetPath(tp) + return nil +} + func queryAllFunctionsDetailsCommon(ctx context.Context, d *schema.ResourceData, client *sdk.Client, id sdk.SchemaObjectIdentifierWithArguments) (*allFunctionDetailsCommon, diag.Diagnostics) { functionDetails, err := client.Functions.DescribeDetails(ctx, id) if err != nil { @@ -508,3 +543,16 @@ func readFunctionImportsCommon(d *schema.ResourceData, imports []sdk.NormalizedP } return d.Set("imports", imps) } + +func readFunctionTargetPathCommon(d *schema.ResourceData, normalizedPath *sdk.NormalizedPath) error { + if normalizedPath == nil { + // don't do anything if imports not present + return nil + } + tp := make([]map[string]any, 1) + tp[0] = map[string]any{ + "stage_location": normalizedPath.StageLocation, + "path_on_stage": normalizedPath.PathOnStage, + } + return d.Set("target_path", tp) +} diff --git a/pkg/resources/function_java.go b/pkg/resources/function_java.go index 7f511d9305..ba7ec0c1c1 100644 --- a/pkg/resources/function_java.go +++ b/pkg/resources/function_java.go @@ -78,7 +78,7 @@ func CreateContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta // packages // external_access_integrations // secrets - // target_path + setFunctionTargetPathInBuilder(d, request.WithTargetPath), stringAttributeCreateBuilder(d, "function_definition", request.WithFunctionDefinitionWrapped), ) if errs != nil { @@ -155,7 +155,7 @@ func ReadContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta a setRequiredFromStringPtr(d, "handler", allFunctionDetails.functionDetails.Handler), // external_access_integrations // secrets - // target_path + readFunctionTargetPathCommon(d, allFunctionDetails.functionDetails.NormalizedTargetPath), setOptionalFromStringPtr(d, "function_definition", allFunctionDetails.functionDetails.Body), d.Set("function_language", allFunctionDetails.functionDetails.Language), diff --git a/pkg/sdk/identifier_helpers.go b/pkg/sdk/identifier_helpers.go index f94bc2ea4b..fa244df341 100644 --- a/pkg/sdk/identifier_helpers.go +++ b/pkg/sdk/identifier_helpers.go @@ -349,7 +349,7 @@ func NewSchemaObjectIdentifierWithArgumentsNormalized(databaseName, schemaName, databaseName: strings.Trim(databaseName, `"`), schemaName: strings.Trim(schemaName, `"`), name: strings.Trim(name, `"`), - argumentDataTypes: collections.Map(argumentDataTypes, func(dt datatypes.DataType) DataType { return LegacyDataTypeFrom(dt) }), + argumentDataTypes: collections.Map(argumentDataTypes, LegacyDataTypeFrom), } } From 181536a75d13f994220d91999840e958df967d17 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 13:52:10 +0100 Subject: [PATCH 30/45] Test setting target path --- .../config/model/function_java_model_ext.go | 11 ++++ pkg/resources/function_commons.go | 6 +- .../function_java_acceptance_test.go | 55 +++++++++++++++++++ 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go b/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go index d7cc98c955..aaff798262 100644 --- a/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go +++ b/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go @@ -82,3 +82,14 @@ func (f *FunctionJavaModel) WithImport(stageLocation string, pathOnStage string) ), ) } + +func (f *FunctionJavaModel) WithTargetPathParts(stageLocation string, pathOnStage string) *FunctionJavaModel { + return f.WithTargetPathValue( + tfconfig.ObjectVariable( + map[string]tfconfig.Variable{ + "stage_location": tfconfig.StringVariable(stageLocation), + "path_on_stage": tfconfig.StringVariable(pathOnStage), + }, + ), + ) +} diff --git a/pkg/resources/function_commons.go b/pkg/resources/function_commons.go index 29f9e42fdd..dfa27e7b0d 100644 --- a/pkg/resources/function_commons.go +++ b/pkg/resources/function_commons.go @@ -435,9 +435,9 @@ func parseFunctionImportsCommon(d *schema.ResourceData) ([]sdk.FunctionImportReq func parseFunctionTargetPathCommon(d *schema.ResourceData) (string, error) { var tp string if v, ok := d.GetOk("target_path"); ok { - for _, tp := range v.(*schema.Set).List() { - stageLocation := tp.(map[string]any)["stage_location"].(string) - pathOnStage := tp.(map[string]any)["path_on_stage"].(string) + for _, p := range v.(*schema.Set).List() { + stageLocation := p.(map[string]any)["stage_location"].(string) + pathOnStage := p.(map[string]any)["path_on_stage"].(string) tp = fmt.Sprintf("@%s/%s", stageLocation, pathOnStage) } } diff --git a/pkg/resources/function_java_acceptance_test.go b/pkg/resources/function_java_acceptance_test.go index 0426e1b727..0d0ead64a5 100644 --- a/pkg/resources/function_java_acceptance_test.go +++ b/pkg/resources/function_java_acceptance_test.go @@ -3,6 +3,7 @@ package resources_test import ( "fmt" "testing" + "time" acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" r "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/resources" @@ -15,6 +16,7 @@ import ( "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert/resourceshowoutputassert" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/config" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/config/model" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers/random" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/testdatatypes" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/testenvs" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" @@ -214,6 +216,59 @@ func TestAcc_FunctionJava_InlineBasic(t *testing.T) { }) } +func TestAcc_FunctionJava_InlineFull(t *testing.T) { + _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance) + acc.TestAccPreCheck(t) + + stage, stageCleanup := acc.TestClient().Stage.CreateStage(t) + t.Cleanup(stageCleanup) + + className := "TestFunc" + funcName := "echoVarchar" + argName := "x" + dataType := testdatatypes.DataTypeVarchar_100 + + id := acc.TestClient().Ids.RandomSchemaObjectIdentifierWithArgumentsNewDataTypes(dataType) + + handler := fmt.Sprintf("%s.%s", className, funcName) + definition := acc.TestClient().Function.SampleJavaDefinition(t, className, funcName, argName) + // TODO [next PR]: extract to helper + jarName := fmt.Sprintf("tf-%d-%s.jar", time.Now().Unix(), random.AlphaN(5)) + + functionModel := model.FunctionJavaBasicInline("w", id, dataType, handler, definition). + WithArgument(argName, dataType). + WithTargetPathParts(stage.ID().FullyQualifiedName(), jarName) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + PreCheck: func() { acc.TestAccPreCheck(t) }, + CheckDestroy: acc.CheckDestroy(t, resources.FunctionJava), + Steps: []resource.TestStep{ + // CREATE BASIC + { + Config: config.FromModels(t, functionModel), + Check: assert.AssertThat(t, + resourceassert.FunctionJavaResource(t, functionModel.ResourceReference()). + HasNameString(id.Name()). + HasIsSecureString(r.BooleanDefault). + HasCommentString(sdk.DefaultFunctionComment). + HasImportsLength(0). + HasFunctionDefinitionString(definition). + HasFunctionLanguageString("JAVA"). + HasFullyQualifiedNameString(id.FullyQualifiedName()), + assert.Check(resource.TestCheckResourceAttr(functionModel.ResourceReference(), "target_path.0.stage_location", stage.ID().FullyQualifiedName())), + assert.Check(resource.TestCheckResourceAttr(functionModel.ResourceReference(), "target_path.0.path_on_stage", jarName)), + resourceshowoutputassert.FunctionShowOutput(t, functionModel.ResourceReference()). + HasIsSecure(false), + ), + }, + }, + }) +} + func TestAcc_FunctionJava_StagedBasic(t *testing.T) { _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance) acc.TestAccPreCheck(t) From bd1c97e301549900077939654b0d885f65a99914 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 14:02:00 +0100 Subject: [PATCH 31/45] Test setting runtime version --- .../resourceassert/function_java_resource_ext.go | 5 +++++ pkg/resources/function_commons.go | 4 +++- pkg/resources/function_java_acceptance_test.go | 6 +++++- pkg/sdk/functions_ext.go | 12 +++++++----- pkg/sdk/functions_ext_test.go | 1 + 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/pkg/acceptance/bettertestspoc/assert/resourceassert/function_java_resource_ext.go b/pkg/acceptance/bettertestspoc/assert/resourceassert/function_java_resource_ext.go index 6d25bd9732..9a3bb1fa15 100644 --- a/pkg/acceptance/bettertestspoc/assert/resourceassert/function_java_resource_ext.go +++ b/pkg/acceptance/bettertestspoc/assert/resourceassert/function_java_resource_ext.go @@ -10,3 +10,8 @@ func (f *FunctionJavaResourceAssert) HasImportsLength(len int) *FunctionJavaReso f.AddAssertion(assert.ValueSet("imports.#", strconv.FormatInt(int64(len), 10))) return f } + +func (f *FunctionJavaResourceAssert) HasTargetPathEmpty() *FunctionJavaResourceAssert { + f.AddAssertion(assert.ValueSet("target_path.#", "0")) + return f +} diff --git a/pkg/resources/function_commons.go b/pkg/resources/function_commons.go index dfa27e7b0d..731214d38e 100644 --- a/pkg/resources/function_commons.go +++ b/pkg/resources/function_commons.go @@ -478,7 +478,9 @@ func setFunctionTargetPathInBuilder[T any](d *schema.ResourceData, setTargetPath if err != nil { return err } - setTargetPath(tp) + if tp != "" { + setTargetPath(tp) + } return nil } diff --git a/pkg/resources/function_java_acceptance_test.go b/pkg/resources/function_java_acceptance_test.go index 0d0ead64a5..854028429f 100644 --- a/pkg/resources/function_java_acceptance_test.go +++ b/pkg/resources/function_java_acceptance_test.go @@ -63,6 +63,8 @@ func TestAcc_FunctionJava_InlineBasic(t *testing.T) { HasIsSecureString(r.BooleanDefault). HasCommentString(sdk.DefaultFunctionComment). HasImportsLength(0). + HasTargetPathEmpty(). + HasNoRuntimeVersion(). HasFunctionDefinitionString(definition). HasFunctionLanguageString("JAVA"). HasFullyQualifiedNameString(id.FullyQualifiedName()), @@ -237,7 +239,8 @@ func TestAcc_FunctionJava_InlineFull(t *testing.T) { functionModel := model.FunctionJavaBasicInline("w", id, dataType, handler, definition). WithArgument(argName, dataType). - WithTargetPathParts(stage.ID().FullyQualifiedName(), jarName) + WithTargetPathParts(stage.ID().FullyQualifiedName(), jarName). + WithRuntimeVersion("11") resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, @@ -256,6 +259,7 @@ func TestAcc_FunctionJava_InlineFull(t *testing.T) { HasIsSecureString(r.BooleanDefault). HasCommentString(sdk.DefaultFunctionComment). HasImportsLength(0). + HasRuntimeVersionString("11"). HasFunctionDefinitionString(definition). HasFunctionLanguageString("JAVA"). HasFullyQualifiedNameString(id.FullyQualifiedName()), diff --git a/pkg/sdk/functions_ext.go b/pkg/sdk/functions_ext.go index d5cb249d10..1dd4d4f049 100644 --- a/pkg/sdk/functions_ext.go +++ b/pkg/sdk/functions_ext.go @@ -29,7 +29,7 @@ type FunctionDetails struct { Handler *string // present for python, java, and scala (hidden when SECURE) RuntimeVersion *string // present for python, java, and scala (hidden when SECURE) Packages *string // list // present for python, java, and scala - TargetPath *string // list present for scala and java (hidden when SECURE) + TargetPath *string // present for scala and java (hidden when SECURE) InstalledPackages *string // list present for python (hidden when SECURE) IsAggregate *bool // present for python @@ -91,10 +91,12 @@ func functionDetailsFromRows(rows []FunctionDetail) (*FunctionDetails, error) { v.NormalizedImports = functionDetailsImports } - if p, err := parseStageLocationPath(*v.TargetPath); err != nil { - errs = append(errs, err) - } else { - v.NormalizedTargetPath = p + if v.TargetPath != nil { + if p, err := parseStageLocationPath(*v.TargetPath); err != nil { + errs = append(errs, err) + } else { + v.NormalizedTargetPath = p + } } return v, errors.Join(errs...) diff --git a/pkg/sdk/functions_ext_test.go b/pkg/sdk/functions_ext_test.go index 4c2c6788be..23c3ccc40d 100644 --- a/pkg/sdk/functions_ext_test.go +++ b/pkg/sdk/functions_ext_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/require" ) +// TODO [next PR]: test nil, test parsing single func Test_parseFunctionDetailsImport(t *testing.T) { inputs := []struct { rawInput string From 0d90e6728b531bc029b1022ce015eff2c62d51bc Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 14:22:13 +0100 Subject: [PATCH 32/45] Handle return type --- pkg/resources/function_commons.go | 15 ++++----- pkg/resources/function_java.go | 16 ++------- pkg/sdk/functions_ext.go | 21 ++++++++++++ pkg/sdk/functions_ext_test.go | 54 ++++++++++++++++++++++++++++++- 4 files changed, 84 insertions(+), 22 deletions(-) diff --git a/pkg/resources/function_commons.go b/pkg/resources/function_commons.go index 731214d38e..48f9f3e03a 100644 --- a/pkg/resources/function_commons.go +++ b/pkg/resources/function_commons.go @@ -106,12 +106,13 @@ var ( "target_path", }, functionDefinitionDescription: functionDefinitionTemplate("Java", "https://docs.snowflake.com/en/developer-guide/udf/java/udf-java-introduction"), - runtimeVersionRequired: false, - runtimeVersionDescription: "Specifies the Java JDK runtime version to use. The supported versions of Java are 11.x and 17.x. If RUNTIME_VERSION is not set, Java JDK 11 is used.", - importsDescription: "The location (stage), path, and name of the file(s) to import. A file can be a JAR file or another type of file. If the file is a JAR file, it can contain one or more .class files and zero or more resource files. JNI (Java Native Interface) is not supported. Snowflake prohibits loading libraries that contain native code (as opposed to Java bytecode). Java UDFs can also read non-JAR files. For an example, see [Reading a file specified statically in IMPORTS](https://docs.snowflake.com/en/developer-guide/udf/java/udf-java-cookbook.html#label-reading-file-from-java-udf-imports). Consult the [docs](https://docs.snowflake.com/en/sql-reference/sql/create-function#java).", - packagesDescription: "The name and version number of Snowflake system packages required as dependencies. The value should be of the form `package_name:version_number`, where `package_name` is `snowflake_domain:package`.", - handlerDescription: "The name of the handler method or class. If the handler is for a scalar UDF, returning a non-tabular value, the HANDLER value should be a method name, as in the following form: `MyClass.myMethod`. If the handler is for a tabular UDF, the HANDLER value should be the name of a handler class.", - targetPathDescription: "The TARGET_PATH clause specifies the location to which Snowflake should write the compiled code (JAR file) after compiling the source code specified in the `function_definition`. If this clause is included, the user should manually remove the JAR file when it is no longer needed (typically when the Java UDF is dropped). If this clause is omitted, Snowflake re-compiles the source code each time the code is needed. The JAR file is not stored permanently, and the user does not need to clean up the JAR file. Snowflake returns an error if the TARGET_PATH matches an existing file; you cannot use TARGET_PATH to overwrite an existing file.", + // May be optional for java because if it is not set, describe return empty version. + runtimeVersionRequired: false, + runtimeVersionDescription: "Specifies the Java JDK runtime version to use. The supported versions of Java are 11.x and 17.x. If RUNTIME_VERSION is not set, Java JDK 11 is used.", + importsDescription: "The location (stage), path, and name of the file(s) to import. A file can be a JAR file or another type of file. If the file is a JAR file, it can contain one or more .class files and zero or more resource files. JNI (Java Native Interface) is not supported. Snowflake prohibits loading libraries that contain native code (as opposed to Java bytecode). Java UDFs can also read non-JAR files. For an example, see [Reading a file specified statically in IMPORTS](https://docs.snowflake.com/en/developer-guide/udf/java/udf-java-cookbook.html#label-reading-file-from-java-udf-imports). Consult the [docs](https://docs.snowflake.com/en/sql-reference/sql/create-function#java).", + packagesDescription: "The name and version number of Snowflake system packages required as dependencies. The value should be of the form `package_name:version_number`, where `package_name` is `snowflake_domain:package`.", + handlerDescription: "The name of the handler method or class. If the handler is for a scalar UDF, returning a non-tabular value, the HANDLER value should be a method name, as in the following form: `MyClass.myMethod`. If the handler is for a tabular UDF, the HANDLER value should be the name of a handler class.", + targetPathDescription: "The TARGET_PATH clause specifies the location to which Snowflake should write the compiled code (JAR file) after compiling the source code specified in the `function_definition`. If this clause is included, the user should manually remove the JAR file when it is no longer needed (typically when the Java UDF is dropped). If this clause is omitted, Snowflake re-compiles the source code each time the code is needed. The JAR file is not stored permanently, and the user does not need to clean up the JAR file. Snowflake returns an error if the TARGET_PATH matches an existing file; you cannot use TARGET_PATH to overwrite an existing file.", } javascriptFunctionSchemaDefinition = functionSchemaDef{ additionalArguments: []string{}, @@ -242,7 +243,6 @@ func functionBaseSchema() map[string]schema.Schema { ValidateDiagFunc: IsDataTypeValid, DiffSuppressFunc: DiffSuppressDataTypes, Description: "Specifies the results returned by the UDF, which determines the UDF type. Use `` to create a scalar UDF that returns a single value with the specified data type. Use `TABLE (col_name col_data_type, ...)` to creates a table UDF that returns tabular results with the specified table column(s) and column type(s). For the details, consult the [docs](https://docs.snowflake.com/en/sql-reference/sql/create-function#all-languages).", - // TODO [SNOW-1348103]: adjust DiffSuppressFunc }, "null_input_behavior": { Type: schema.TypeString, @@ -263,7 +263,6 @@ func functionBaseSchema() map[string]schema.Schema { "runtime_version": { Type: schema.TypeString, ForceNew: true, - // TODO [SNOW-1348103]: may be optional for java without consequence because if it is not set, the describe is not returning any version. }, "comment": { Type: schema.TypeString, diff --git a/pkg/resources/function_java.go b/pkg/resources/function_java.go index ba7ec0c1c1..8db3a1102b 100644 --- a/pkg/resources/function_java.go +++ b/pkg/resources/function_java.go @@ -107,13 +107,6 @@ func CreateContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta //if v, ok := d.GetOk("comment"); ok { // request.WithComment(v.(string)) //} - //if _, ok := d.GetOk("imports"); ok { - // var 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 { // var packages []sdk.FunctionPackageRequest // for _, item := range d.Get("packages").([]interface{}) { @@ -121,9 +114,6 @@ func CreateContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta // } // request.WithPackages(packages) //} - //if v, ok := d.GetOk("target_path"); ok { - // request.WithTargetPath(v.(string)) - //} } func ReadContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { @@ -138,14 +128,14 @@ func ReadContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta a return diags } - // TODO [this PR]: handle external changes marking - // TODO [this PR]: handle setting state to value from config + // TODO [next PR]: handle external changes marking + // TODO [next PR]: handle setting state to value from config errs := errors.Join( // TODO [this PR]: set all proper fields // not reading is_secure on purpose (handled as external change to show output) // arguments - // return_type -> parse Returns from allFunctionDetails.functionDetails (NOT NULL can be added) + d.Set("return_type", allFunctionDetails.functionDetails.ReturnDataType.ToSql()), // not reading null_input_behavior on purpose (handled as external change to show output) // not reading return_results_behavior on purpose (handled as external change to show output) setOptionalFromStringPtr(d, "runtime_version", allFunctionDetails.functionDetails.RuntimeVersion), diff --git a/pkg/sdk/functions_ext.go b/pkg/sdk/functions_ext.go index 1dd4d4f049..0d53af2acc 100644 --- a/pkg/sdk/functions_ext.go +++ b/pkg/sdk/functions_ext.go @@ -7,6 +7,8 @@ import ( "log" "strconv" "strings" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/datatypes" ) const DefaultFunctionComment = "user-defined function" @@ -35,6 +37,7 @@ type FunctionDetails struct { NormalizedImports []NormalizedPath NormalizedTargetPath *NormalizedPath + ReturnDataType datatypes.DataType } type NormalizedPath struct { @@ -99,6 +102,13 @@ func functionDetailsFromRows(rows []FunctionDetail) (*FunctionDetails, error) { } } + if dt, returnNotNull, err := parseFunctionAndProcedureReturns(v.Returns); err != nil { + errs = append(errs, err) + } else { + v.ReturnDataType = dt + _ = returnNotNull // TODO [next PR]: used when adding return nullability to the resource + } + return v, errors.Join(errs...) } @@ -143,6 +153,17 @@ func parseStageLocationPath(location string) (*NormalizedPath, error) { return &NormalizedPath{stageRaw, pathRaw}, nil } +func parseFunctionAndProcedureReturns(returns string) (datatypes.DataType, bool, error) { + var returnNotNull bool + trimmed := strings.TrimSpace(returns) + if strings.HasSuffix(trimmed, " NOT NULL") { + returnNotNull = true + trimmed = strings.TrimSuffix(trimmed, " NOT NULL") + } + dt, err := datatypes.ParseDataType(trimmed) + return dt, returnNotNull, err +} + func (v *functions) DescribeDetails(ctx context.Context, id SchemaObjectIdentifierWithArguments) (*FunctionDetails, error) { rows, err := v.Describe(ctx, id) if err != nil { diff --git a/pkg/sdk/functions_ext_test.go b/pkg/sdk/functions_ext_test.go index 23c3ccc40d..377892403b 100644 --- a/pkg/sdk/functions_ext_test.go +++ b/pkg/sdk/functions_ext_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" ) -// TODO [next PR]: test nil, test parsing single +// TODO [next PR]: test parsing single func Test_parseFunctionDetailsImport(t *testing.T) { inputs := []struct { rawInput string @@ -61,4 +61,56 @@ func Test_parseFunctionDetailsImport(t *testing.T) { require.ErrorContains(t, err, tc.expectedErrorPart) }) } + + t.Run("Snowflake raw imports nil", func(t *testing.T) { + details := FunctionDetails{Imports: nil} + + results, err := parseFunctionDetailsImport(details) + require.NoError(t, err) + require.Equal(t, []NormalizedPath{}, results) + }) +} + +func Test_parseFunctionAndProcedureReturns(t *testing.T) { + inputs := []struct { + rawInput string + expectedRawDataType string + expectedReturnNotNull bool + }{ + {"CHAR", "CHAR(1)", false}, + {"CHAR(1)", "CHAR(1)", false}, + {"CHAR NOT NULL", "CHAR(1)", true}, + {" CHAR NOT NULL ", "CHAR(1)", true}, + {"OBJECT", "OBJECT", false}, + {"OBJECT NOT NULL", "OBJECT", true}, + } + + badInputs := []struct { + rawInput string + expectedErrorPart string + }{ + {"", "invalid data type"}, + {"NOT NULL", "invalid data type"}, + {"CHA NOT NULL", "invalid data type"}, + {"CHA NOT NULLS", "invalid data type"}, + } + + for _, tc := range inputs { + tc := tc + t.Run(fmt.Sprintf("return data type raw: %s", tc.rawInput), func(t *testing.T) { + dt, returnNotNull, err := parseFunctionAndProcedureReturns(tc.rawInput) + require.NoError(t, err) + require.Equal(t, tc.expectedRawDataType, dt.ToSql()) + require.Equal(t, tc.expectedReturnNotNull, returnNotNull) + }) + } + + for _, tc := range badInputs { + tc := tc + t.Run(fmt.Sprintf("incorrect return data type raw: %s, expecting error with: %s", tc.rawInput, tc.expectedErrorPart), func(t *testing.T) { + _, _, err := parseFunctionAndProcedureReturns(tc.rawInput) + require.Error(t, err) + require.ErrorContains(t, err, tc.expectedErrorPart) + }) + } } From a5fc8f5ce853af64000e84838b2ede531a919bdf Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 14:29:47 +0100 Subject: [PATCH 33/45] Update TODO comments --- pkg/resources/function_java.go | 4 ++++ pkg/sdk/testint/functions_integration_test.go | 1 + 2 files changed, 5 insertions(+) diff --git a/pkg/resources/function_java.go b/pkg/resources/function_java.go index 8db3a1102b..ddcaa11d23 100644 --- a/pkg/resources/function_java.go +++ b/pkg/resources/function_java.go @@ -185,6 +185,10 @@ func UpdateContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta unsetRequest := sdk.NewFunctionUnsetRequest() // TODO [this PR]: handle all updates + // secure + // external access integration + // secrets + // comment if updateParamDiags := handleFunctionParametersUpdate(d, setRequest, unsetRequest); len(updateParamDiags) > 0 { return updateParamDiags diff --git a/pkg/sdk/testint/functions_integration_test.go b/pkg/sdk/testint/functions_integration_test.go index fcbd947bf7..099be199a5 100644 --- a/pkg/sdk/testint/functions_integration_test.go +++ b/pkg/sdk/testint/functions_integration_test.go @@ -34,6 +34,7 @@ import ( // TODO [SNOW-1850370]: active warehouse vs validations // TODO [SNOW-1348103]: add a test documenting STRICT behavior // TODO [next PR]: add test with multiple imports +// TODO [this PR]: add assertion to each test for the normalized target path, returned data type, and return not null func TestInt_Functions(t *testing.T) { client := testClient(t) ctx := context.Background() From 6ce0e50a9183e8610ef6e683bcd46c49467195c9 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 15:10:32 +0100 Subject: [PATCH 34/45] Prepare signature parsing --- pkg/sdk/functions_ext.go | 73 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/pkg/sdk/functions_ext.go b/pkg/sdk/functions_ext.go index 0d53af2acc..d6e48b4b0d 100644 --- a/pkg/sdk/functions_ext.go +++ b/pkg/sdk/functions_ext.go @@ -38,6 +38,7 @@ type FunctionDetails struct { NormalizedImports []NormalizedPath NormalizedTargetPath *NormalizedPath ReturnDataType datatypes.DataType + NormalizedArguments []NormalizedArgument } type NormalizedPath struct { @@ -47,6 +48,12 @@ type NormalizedPath struct { PathOnStage string } +type NormalizedArgument struct { + name string + dataType datatypes.DataType + defaultValue string // TODO [next PR]: handle when adding default values +} + func functionDetailsFromRows(rows []FunctionDetail) (*FunctionDetails, error) { v := &FunctionDetails{} var errs []error @@ -109,6 +116,12 @@ func functionDetailsFromRows(rows []FunctionDetail) (*FunctionDetails, error) { _ = returnNotNull // TODO [next PR]: used when adding return nullability to the resource } + if args, err := parseFunctionAndProcedureSignature(v.Signature); err != nil { + errs = append(errs, err) + } else { + v.NormalizedArguments = args + } + return v, errors.Join(errs...) } @@ -118,7 +131,7 @@ func parseFunctionDetailsImport(details FunctionDetails) ([]NormalizedPath, erro return functionDetailsImports, nil } if !strings.HasPrefix(*details.Imports, "[") || !strings.HasSuffix(*details.Imports, "]") { - return functionDetailsImports, fmt.Errorf("could not parse imports from Snowflake: %s, brackets not find", *details.Imports) + return functionDetailsImports, fmt.Errorf("could not parse imports from Snowflake: %s, wrapping brackets not found", *details.Imports) } raw := (*details.Imports)[1 : len(*details.Imports)-1] imports := strings.Split(raw, ",") @@ -164,6 +177,64 @@ func parseFunctionAndProcedureReturns(returns string) (datatypes.DataType, bool, return dt, returnNotNull, err } +// Format in Snowflake DB is: (argName argType [DEFAULT defaultValue], argName argType [DEFAULT defaultValue], ...). +func parseFunctionAndProcedureSignature(signature string) ([]NormalizedArgument, error) { + normalizedArguments := make([]NormalizedArgument, 0) + trimmed := strings.TrimSpace(signature) + if trimmed == "" { + return normalizedArguments, fmt.Errorf("could not parse signature from Snowflake: %s, can't be empty", signature) + } + if trimmed == "()" { + return normalizedArguments, nil + } + if !strings.HasPrefix(trimmed, "(") || !strings.HasSuffix(trimmed, ")") { + return normalizedArguments, fmt.Errorf("could not parse signature from Snowflake: %s, wrapping parentheses not found", trimmed) + } + raw := (trimmed)[1 : len(trimmed)-1] + args := strings.Split(raw, ",") + + for _, arg := range args { + a, err := parseFunctionOrProcedureArgument(arg) + if err != nil { + return nil, fmt.Errorf("could not parse signature from Snowflake: %s, err: %w", trimmed, err) + } + normalizedArguments = append(normalizedArguments, *a) + } + return normalizedArguments, nil +} + +// TODO [next PR]: adjust after tests for strange arg names and defaults +func parseFunctionOrProcedureArgument(arg string) (*NormalizedArgument, error) { + log.Printf("[DEBUG] parsing argument: %s", arg) + trimmed := strings.TrimSpace(arg) + idx := strings.Index(trimmed, " ") + if idx < 0 { + return nil, fmt.Errorf("arg %s cannot be split into arg name, data type, and default", arg) + } + argName := trimmed[:idx] + rest := strings.TrimSpace(trimmed[idx:]) + split := strings.Split(rest, " DEFAULT ") + var dt datatypes.DataType + var defaultValue string + var err error + switch len(split) { + case 1: + dt, err = datatypes.ParseDataType(split[0]) + if err != nil { + return nil, fmt.Errorf("arg type %s cannot be parsed, err: %w", split[0], err) + } + case 2: + dt, err = datatypes.ParseDataType(split[0]) + if err != nil { + return nil, fmt.Errorf("arg type %s cannot be parsed, err: %w", split[0], err) + } + defaultValue = strings.TrimSpace(split[1]) + default: + return nil, fmt.Errorf("cannot parse arg %s, part: %s", arg, rest) + } + return &NormalizedArgument{argName, dt, defaultValue}, nil +} + func (v *functions) DescribeDetails(ctx context.Context, id SchemaObjectIdentifierWithArguments) (*FunctionDetails, error) { rows, err := v.Describe(ctx, id) if err != nil { From 632e2976f769184f13d496b287e9de87ccd80a15 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 15:28:47 +0100 Subject: [PATCH 35/45] Test default values --- pkg/sdk/testint/functions_integration_test.go | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/pkg/sdk/testint/functions_integration_test.go b/pkg/sdk/testint/functions_integration_test.go index 099be199a5..83516c2837 100644 --- a/pkg/sdk/testint/functions_integration_test.go +++ b/pkg/sdk/testint/functions_integration_test.go @@ -469,6 +469,40 @@ func TestInt_Functions(t *testing.T) { ) }) + // proves that we don't get default argument values from SHOW and DESCRIBE + t.Run("create function for Java - default argument value", func(t *testing.T) { + className := "TestFunc" + funcName := "echoVarchar" + argName := "x" + dataType := testdatatypes.DataTypeVarchar_100 + + id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(sdk.LegacyDataTypeFrom(dataType)) + argument := sdk.NewFunctionArgumentRequest(argName, dataType).WithDefaultValue(`'abc'`) + dt := sdk.NewFunctionReturnsResultDataTypeRequest(dataType) + returns := sdk.NewFunctionReturnsRequest().WithResultDataType(*dt) + handler := fmt.Sprintf("%s.%s", className, funcName) + definition := testClientHelper().Function.SampleJavaDefinition(t, className, funcName, argName) + + request := sdk.NewCreateForJavaFunctionRequest(id.SchemaObjectId(), *returns, handler). + WithArguments([]sdk.FunctionArgumentRequest{*argument}). + WithFunctionDefinitionWrapped(definition) + + err := client.Functions.CreateForJava(ctx, request) + require.NoError(t, err) + t.Cleanup(testClientHelper().Function.DropFunctionFunc(t, id)) + + function, err := client.Functions.ShowByID(ctx, id) + require.NoError(t, err) + + assertions.AssertThatObject(t, objectassert.FunctionFromObject(t, function). + HasArgumentsRaw(fmt.Sprintf(`%[1]s(DEFAULT %[2]s) RETURN %[2]s`, function.ID().Name(), dataType.ToLegacyDataTypeSql())), + ) + + assertions.AssertThatObject(t, objectassert.FunctionDetails(t, function.ID()). + HasSignature(fmt.Sprintf(`(%s %s)`, argName, dataType.ToLegacyDataTypeSql())), + ) + }) + t.Run("create function for Javascript - inline minimal", func(t *testing.T) { dataType := testdatatypes.DataTypeFloat id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(sdk.LegacyDataTypeFrom(dataType)) @@ -1305,6 +1339,35 @@ func TestInt_Functions(t *testing.T) { ) }) + // proves that we don't get default argument values from SHOW and DESCRIBE + t.Run("create function for SQL - default argument value", func(t *testing.T) { + argName := "x" + dataType := testdatatypes.DataTypeFloat + id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(sdk.LegacyDataTypeFrom(dataType)) + + definition := testClientHelper().Function.SampleSqlDefinition(t) + dt := sdk.NewFunctionReturnsResultDataTypeRequest(dataType) + returns := sdk.NewFunctionReturnsRequest().WithResultDataType(*dt) + argument := sdk.NewFunctionArgumentRequest(argName, dataType).WithDefaultValue("3.123") + request := sdk.NewCreateForSQLFunctionRequestDefinitionWrapped(id.SchemaObjectId(), *returns, definition). + WithArguments([]sdk.FunctionArgumentRequest{*argument}) + + err := client.Functions.CreateForSQL(ctx, request) + require.NoError(t, err) + t.Cleanup(testClientHelper().Function.DropFunctionFunc(t, id)) + + function, err := client.Functions.ShowByID(ctx, id) + require.NoError(t, err) + + assertions.AssertThatObject(t, objectassert.FunctionFromObject(t, function). + HasArgumentsRaw(fmt.Sprintf(`%[1]s(DEFAULT %[2]s) RETURN %[2]s`, function.ID().Name(), dataType.ToLegacyDataTypeSql())), + ) + + assertions.AssertThatObject(t, objectassert.FunctionDetails(t, function.ID()). + HasSignature(fmt.Sprintf(`(%s %s)`, argName, dataType.ToLegacyDataTypeSql())), + ) + }) + t.Run("create function for SQL - inline full", func(t *testing.T) { argName := "x" dataType := testdatatypes.DataTypeFloat From bbae0ee962baee1412e83eb313252944a6f56552 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 15:44:52 +0100 Subject: [PATCH 36/45] Set arguments in read --- pkg/resources/function_commons.go | 16 +++++++++ pkg/resources/function_java.go | 2 +- .../function_java_acceptance_test.go | 3 ++ pkg/sdk/functions_ext.go | 33 +++++-------------- 4 files changed, 29 insertions(+), 25 deletions(-) diff --git a/pkg/resources/function_commons.go b/pkg/resources/function_commons.go index 48f9f3e03a..f9ef999e5d 100644 --- a/pkg/resources/function_commons.go +++ b/pkg/resources/function_commons.go @@ -530,6 +530,22 @@ type allFunctionDetailsCommon struct { functionParameters []*sdk.Parameter } +func readFunctionArgumentsCommon(d *schema.ResourceData, args []sdk.NormalizedArgument) error { + if len(args) == 0 { + // TODO [next PR]: handle empty list + return nil + } + if currentArgs, ok := d.Get("arguments").([]map[string]any); !ok { + return fmt.Errorf("arguments must be a list") + } else { + for i, arg := range args { + currentArgs[i]["arg_name"] = arg.Name + currentArgs[i]["arg_data_type"] = arg.DataType.ToSql() + } + return d.Set("arguments", currentArgs) + } +} + func readFunctionImportsCommon(d *schema.ResourceData, imports []sdk.NormalizedPath) error { if len(imports) == 0 { // don't do anything if imports not present diff --git a/pkg/resources/function_java.go b/pkg/resources/function_java.go index ddcaa11d23..66ce727264 100644 --- a/pkg/resources/function_java.go +++ b/pkg/resources/function_java.go @@ -134,7 +134,7 @@ func ReadContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta a errs := errors.Join( // TODO [this PR]: set all proper fields // not reading is_secure on purpose (handled as external change to show output) - // arguments + readFunctionArgumentsCommon(d, allFunctionDetails.functionDetails.NormalizedArguments), d.Set("return_type", allFunctionDetails.functionDetails.ReturnDataType.ToSql()), // not reading null_input_behavior on purpose (handled as external change to show output) // not reading return_results_behavior on purpose (handled as external change to show output) diff --git a/pkg/resources/function_java_acceptance_test.go b/pkg/resources/function_java_acceptance_test.go index 854028429f..5fa88f06bf 100644 --- a/pkg/resources/function_java_acceptance_test.go +++ b/pkg/resources/function_java_acceptance_test.go @@ -27,6 +27,9 @@ import ( "github.com/hashicorp/terraform-plugin-testing/tfversion" ) +// TODO [this PR]: test empty args +// TODO [this PR]: test default args no change + func TestAcc_FunctionJava_InlineBasic(t *testing.T) { className := "TestFunc" funcName := "echoVarchar" diff --git a/pkg/sdk/functions_ext.go b/pkg/sdk/functions_ext.go index d6e48b4b0d..6f43c31842 100644 --- a/pkg/sdk/functions_ext.go +++ b/pkg/sdk/functions_ext.go @@ -48,10 +48,10 @@ type NormalizedPath struct { PathOnStage string } +// NormalizedArgument does not contain default value because it is not returned in the Signature (or any other field). type NormalizedArgument struct { - name string - dataType datatypes.DataType - defaultValue string // TODO [next PR]: handle when adding default values + Name string + DataType datatypes.DataType } func functionDetailsFromRows(rows []FunctionDetail) (*FunctionDetails, error) { @@ -177,7 +177,7 @@ func parseFunctionAndProcedureReturns(returns string) (datatypes.DataType, bool, return dt, returnNotNull, err } -// Format in Snowflake DB is: (argName argType [DEFAULT defaultValue], argName argType [DEFAULT defaultValue], ...). +// Format in Snowflake DB is: (argName argType, argName argType, ...). func parseFunctionAndProcedureSignature(signature string) ([]NormalizedArgument, error) { normalizedArguments := make([]NormalizedArgument, 0) trimmed := strings.TrimSpace(signature) @@ -203,7 +203,7 @@ func parseFunctionAndProcedureSignature(signature string) ([]NormalizedArgument, return normalizedArguments, nil } -// TODO [next PR]: adjust after tests for strange arg names and defaults +// TODO [next PR]: test with strange arg names (first integration test) func parseFunctionOrProcedureArgument(arg string) (*NormalizedArgument, error) { log.Printf("[DEBUG] parsing argument: %s", arg) trimmed := strings.TrimSpace(arg) @@ -213,26 +213,11 @@ func parseFunctionOrProcedureArgument(arg string) (*NormalizedArgument, error) { } argName := trimmed[:idx] rest := strings.TrimSpace(trimmed[idx:]) - split := strings.Split(rest, " DEFAULT ") - var dt datatypes.DataType - var defaultValue string - var err error - switch len(split) { - case 1: - dt, err = datatypes.ParseDataType(split[0]) - if err != nil { - return nil, fmt.Errorf("arg type %s cannot be parsed, err: %w", split[0], err) - } - case 2: - dt, err = datatypes.ParseDataType(split[0]) - if err != nil { - return nil, fmt.Errorf("arg type %s cannot be parsed, err: %w", split[0], err) - } - defaultValue = strings.TrimSpace(split[1]) - default: - return nil, fmt.Errorf("cannot parse arg %s, part: %s", arg, rest) + dt, err := datatypes.ParseDataType(rest) + if err != nil { + return nil, fmt.Errorf("arg type %s cannot be parsed, err: %w", rest, err) } - return &NormalizedArgument{argName, dt, defaultValue}, nil + return &NormalizedArgument{argName, dt}, nil } func (v *functions) DescribeDetails(ctx context.Context, id SchemaObjectIdentifierWithArguments) (*FunctionDetails, error) { From bdebe4c8168b966ebd2bfa841769265cdf600162 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 16:44:27 +0100 Subject: [PATCH 37/45] Test argument parsing --- pkg/sdk/functions_ext.go | 8 ++--- pkg/sdk/functions_ext_test.go | 62 +++++++++++++++++++++++++++++++++-- pkg/sdk/random_test.go | 12 ++++--- 3 files changed, 71 insertions(+), 11 deletions(-) diff --git a/pkg/sdk/functions_ext.go b/pkg/sdk/functions_ext.go index 6f43c31842..c728cd29e0 100644 --- a/pkg/sdk/functions_ext.go +++ b/pkg/sdk/functions_ext.go @@ -109,14 +109,14 @@ func functionDetailsFromRows(rows []FunctionDetail) (*FunctionDetails, error) { } } - if dt, returnNotNull, err := parseFunctionAndProcedureReturns(v.Returns); err != nil { + if dt, returnNotNull, err := parseFunctionOrProcedureReturns(v.Returns); err != nil { errs = append(errs, err) } else { v.ReturnDataType = dt _ = returnNotNull // TODO [next PR]: used when adding return nullability to the resource } - if args, err := parseFunctionAndProcedureSignature(v.Signature); err != nil { + if args, err := parseFunctionOrProcedureSignature(v.Signature); err != nil { errs = append(errs, err) } else { v.NormalizedArguments = args @@ -166,7 +166,7 @@ func parseStageLocationPath(location string) (*NormalizedPath, error) { return &NormalizedPath{stageRaw, pathRaw}, nil } -func parseFunctionAndProcedureReturns(returns string) (datatypes.DataType, bool, error) { +func parseFunctionOrProcedureReturns(returns string) (datatypes.DataType, bool, error) { var returnNotNull bool trimmed := strings.TrimSpace(returns) if strings.HasSuffix(trimmed, " NOT NULL") { @@ -178,7 +178,7 @@ func parseFunctionAndProcedureReturns(returns string) (datatypes.DataType, bool, } // Format in Snowflake DB is: (argName argType, argName argType, ...). -func parseFunctionAndProcedureSignature(signature string) ([]NormalizedArgument, error) { +func parseFunctionOrProcedureSignature(signature string) ([]NormalizedArgument, error) { normalizedArguments := make([]NormalizedArgument, 0) trimmed := strings.TrimSpace(signature) if trimmed == "" { diff --git a/pkg/sdk/functions_ext_test.go b/pkg/sdk/functions_ext_test.go index 377892403b..ab88552daf 100644 --- a/pkg/sdk/functions_ext_test.go +++ b/pkg/sdk/functions_ext_test.go @@ -71,7 +71,7 @@ func Test_parseFunctionDetailsImport(t *testing.T) { }) } -func Test_parseFunctionAndProcedureReturns(t *testing.T) { +func Test_parseFunctionOrProcedureReturns(t *testing.T) { inputs := []struct { rawInput string expectedRawDataType string @@ -98,7 +98,7 @@ func Test_parseFunctionAndProcedureReturns(t *testing.T) { for _, tc := range inputs { tc := tc t.Run(fmt.Sprintf("return data type raw: %s", tc.rawInput), func(t *testing.T) { - dt, returnNotNull, err := parseFunctionAndProcedureReturns(tc.rawInput) + dt, returnNotNull, err := parseFunctionOrProcedureReturns(tc.rawInput) require.NoError(t, err) require.Equal(t, tc.expectedRawDataType, dt.ToSql()) require.Equal(t, tc.expectedReturnNotNull, returnNotNull) @@ -108,9 +108,65 @@ func Test_parseFunctionAndProcedureReturns(t *testing.T) { for _, tc := range badInputs { tc := tc t.Run(fmt.Sprintf("incorrect return data type raw: %s, expecting error with: %s", tc.rawInput, tc.expectedErrorPart), func(t *testing.T) { - _, _, err := parseFunctionAndProcedureReturns(tc.rawInput) + _, _, err := parseFunctionOrProcedureReturns(tc.rawInput) require.Error(t, err) require.ErrorContains(t, err, tc.expectedErrorPart) }) } } + +func Test_parseFunctionOrProcedureSignature(t *testing.T) { + inputs := []struct { + rawInput string + expectedArgs []NormalizedArgument + }{ + {"()", []NormalizedArgument{}}, + {"(abc CHAR)", []NormalizedArgument{{"abc", dataTypeChar}}}, + {"(abc CHAR(1))", []NormalizedArgument{{"abc", dataTypeChar}}}, + {"(abc CHAR(100))", []NormalizedArgument{{"abc", dataTypeChar_100}}}, + {" ( abc CHAR(100 ) )", []NormalizedArgument{{"abc", dataTypeChar_100}}}, + {"( abc CHAR )", []NormalizedArgument{{"abc", dataTypeChar}}}, + {"(abc DOUBLE PRECISION)", []NormalizedArgument{{"abc", dataTypeDoublePrecision}}}, + {"(abc double precision)", []NormalizedArgument{{"abc", dataTypeDoublePrecision}}}, + {"(abc TIMESTAMP WITHOUT TIME ZONE(5))", []NormalizedArgument{{"abc", dataTypeTimestampWithoutTimeZone_5}}}, + } + + badInputs := []struct { + rawInput string + expectedErrorPart string + }{ + {"", "can't be empty"}, + {"(abc CHAR", "wrapping parentheses not found"}, + {"abc CHAR)", "wrapping parentheses not found"}, + {"(abc)", "cannot be split into arg name, data type, and default"}, + {"(CHAR)", "cannot be split into arg name, data type, and default"}, + {"(abc CHA)", "invalid data type"}, + {"(abc CHA(123))", "invalid data type"}, + {"(abc CHAR(1) DEFAULT)", "could not be parsed"}, + {"(abc CHAR(1) DEFAULT 'a')", "could not be parsed"}, + } + + for _, tc := range inputs { + tc := tc + t.Run(fmt.Sprintf("return data type raw: %s", tc.rawInput), func(t *testing.T) { + args, err := parseFunctionOrProcedureSignature(tc.rawInput) + + require.NoError(t, err) + require.Len(t, args, len(tc.expectedArgs)) + for i, arg := range args { + require.Equal(t, tc.expectedArgs[i].Name, arg.Name) + require.Equal(t, tc.expectedArgs[i].DataType.ToSql(), arg.DataType.ToSql()) + } + }) + } + + for _, tc := range badInputs { + tc := tc + t.Run(fmt.Sprintf("incorrect signature raw: %s, expecting error with: %s", tc.rawInput, tc.expectedErrorPart), func(t *testing.T) { + _, err := parseFunctionOrProcedureSignature(tc.rawInput) + require.Error(t, err) + require.ErrorContains(t, err, "could not parse signature from Snowflake") + require.ErrorContains(t, err, tc.expectedErrorPart) + }) + } +} diff --git a/pkg/sdk/random_test.go b/pkg/sdk/random_test.go index 83880167df..cedf8c3985 100644 --- a/pkg/sdk/random_test.go +++ b/pkg/sdk/random_test.go @@ -17,10 +17,14 @@ var ( emptySchemaObjectIdentifierWithArguments = NewSchemaObjectIdentifierWithArguments("", "", "") // TODO [SNOW-1843440]: create using constructors (when we add them)? - dataTypeNumber, _ = datatypes.ParseDataType("NUMBER(36, 2)") - dataTypeVarchar, _ = datatypes.ParseDataType("VARCHAR(100)") - dataTypeFloat, _ = datatypes.ParseDataType("FLOAT") - dataTypeVariant, _ = datatypes.ParseDataType("VARIANT") + dataTypeNumber, _ = datatypes.ParseDataType("NUMBER(36, 2)") + dataTypeVarchar, _ = datatypes.ParseDataType("VARCHAR(100)") + dataTypeFloat, _ = datatypes.ParseDataType("FLOAT") + dataTypeVariant, _ = datatypes.ParseDataType("VARIANT") + dataTypeChar, _ = datatypes.ParseDataType("CHAR") + dataTypeChar_100, _ = datatypes.ParseDataType("CHAR(100)") + dataTypeDoublePrecision, _ = datatypes.ParseDataType("DOUBLE PRECISION") + dataTypeTimestampWithoutTimeZone_5, _ = datatypes.ParseDataType("TIMESTAMP WITHOUT TIME ZONE(5)") ) func randomSchemaObjectIdentifierWithArguments(argumentDataTypes ...DataType) SchemaObjectIdentifierWithArguments { From ab8c8946070349486b604fbe96780615c3d58094 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 17:10:49 +0100 Subject: [PATCH 38/45] dd new assertions in integration tests --- .../function_describe_snowflake_ext.go | 54 ++++++++++++++ pkg/sdk/functions_ext.go | 3 +- pkg/sdk/functions_ext_test.go | 17 +++-- pkg/sdk/testint/functions_integration_test.go | 73 ++++++++++++++++--- 4 files changed, 131 insertions(+), 16 deletions(-) diff --git a/pkg/acceptance/bettertestspoc/assert/objectassert/function_describe_snowflake_ext.go b/pkg/acceptance/bettertestspoc/assert/objectassert/function_describe_snowflake_ext.go index a46c94ae6b..a4c256b172 100644 --- a/pkg/acceptance/bettertestspoc/assert/objectassert/function_describe_snowflake_ext.go +++ b/pkg/acceptance/bettertestspoc/assert/objectassert/function_describe_snowflake_ext.go @@ -11,6 +11,7 @@ import ( "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/datatypes" ) // TODO [SNOW-1501905]: this file should be fully regenerated when adding and option to assert the results of describe @@ -420,3 +421,56 @@ func (f *FunctionDetailsAssert) HasExactlyImportsNormalizedInAnyOrder(imports .. }) return f } + +func (f *FunctionDetailsAssert) HasNormalizedTargetPath(expectedStageLocation string, expectedPathOnStage string) *FunctionDetailsAssert { + f.AddAssertion(func(t *testing.T, o *sdk.FunctionDetails) error { + t.Helper() + if o.NormalizedTargetPath == nil { + return fmt.Errorf("expected normalized target path to have value; got: nil") + } + if o.NormalizedTargetPath.StageLocation != expectedStageLocation { + return fmt.Errorf("expected %s stage location for target path, got %v", expectedStageLocation, o.NormalizedTargetPath.StageLocation) + } + if o.NormalizedTargetPath.PathOnStage != expectedPathOnStage { + return fmt.Errorf("expected %s path on stage for target path, got %v", expectedPathOnStage, o.NormalizedTargetPath.PathOnStage) + } + return nil + }) + return f +} + +func (f *FunctionDetailsAssert) HasNormalizedTargetPathNil() *FunctionDetailsAssert { + f.AddAssertion(func(t *testing.T, o *sdk.FunctionDetails) error { + t.Helper() + if o.NormalizedTargetPath != nil { + return fmt.Errorf("expected normalized target path to be nil, got: %s", *o.NormalizedTargetPath) + } + return nil + }) + return f +} + +func (f *FunctionDetailsAssert) HasReturnDataType(expectedDataType datatypes.DataType) *FunctionDetailsAssert { + f.AddAssertion(func(t *testing.T, o *sdk.FunctionDetails) error { + t.Helper() + if o.ReturnDataType == nil { + return fmt.Errorf("expected return data type to have value; got: nil") + } + if !datatypes.AreTheSame(o.ReturnDataType, expectedDataType) { + return fmt.Errorf("expected %s return data type, got %v", expectedDataType, o.ReturnDataType.ToSql()) + } + return nil + }) + return f +} + +func (f *FunctionDetailsAssert) HasReturnNotNull(expected bool) *FunctionDetailsAssert { + f.AddAssertion(func(t *testing.T, o *sdk.FunctionDetails) error { + t.Helper() + if o.ReturnNotNull != expected { + return fmt.Errorf("expected return not null %t; got: %t", expected, o.ReturnNotNull) + } + return nil + }) + return f +} diff --git a/pkg/sdk/functions_ext.go b/pkg/sdk/functions_ext.go index c728cd29e0..4f5c55ab0e 100644 --- a/pkg/sdk/functions_ext.go +++ b/pkg/sdk/functions_ext.go @@ -38,6 +38,7 @@ type FunctionDetails struct { NormalizedImports []NormalizedPath NormalizedTargetPath *NormalizedPath ReturnDataType datatypes.DataType + ReturnNotNull bool NormalizedArguments []NormalizedArgument } @@ -113,7 +114,7 @@ func functionDetailsFromRows(rows []FunctionDetail) (*FunctionDetails, error) { errs = append(errs, err) } else { v.ReturnDataType = dt - _ = returnNotNull // TODO [next PR]: used when adding return nullability to the resource + v.ReturnNotNull = returnNotNull } if args, err := parseFunctionOrProcedureSignature(v.Signature); err != nil { diff --git a/pkg/sdk/functions_ext_test.go b/pkg/sdk/functions_ext_test.go index ab88552daf..42bf146895 100644 --- a/pkg/sdk/functions_ext_test.go +++ b/pkg/sdk/functions_ext_test.go @@ -4,6 +4,7 @@ import ( "fmt" "testing" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/datatypes" "github.com/stretchr/testify/require" ) @@ -28,8 +29,8 @@ func Test_parseFunctionDetailsImport(t *testing.T) { rawInput string expectedErrorPart string }{ - {"[", "brackets not find"}, - {"]", "brackets not find"}, + {"[", "wrapping brackets not found"}, + {"]", "wrapping brackets not found"}, {`[@~/]`, "contains empty path"}, {`[@~]`, "cannot be split into stage and path"}, {`[@"db"."sc"/abc]`, "contains incorrect stage location"}, @@ -79,6 +80,9 @@ func Test_parseFunctionOrProcedureReturns(t *testing.T) { }{ {"CHAR", "CHAR(1)", false}, {"CHAR(1)", "CHAR(1)", false}, + {"NUMBER(30, 2)", "NUMBER(30, 2)", false}, + {"NUMBER(30,2)", "NUMBER(30, 2)", false}, + {"NUMBER(30,2) NOT NULL", "NUMBER(30, 2)", true}, {"CHAR NOT NULL", "CHAR(1)", true}, {" CHAR NOT NULL ", "CHAR(1)", true}, {"OBJECT", "OBJECT", false}, @@ -142,8 +146,11 @@ func Test_parseFunctionOrProcedureSignature(t *testing.T) { {"(CHAR)", "cannot be split into arg name, data type, and default"}, {"(abc CHA)", "invalid data type"}, {"(abc CHA(123))", "invalid data type"}, - {"(abc CHAR(1) DEFAULT)", "could not be parsed"}, - {"(abc CHAR(1) DEFAULT 'a')", "could not be parsed"}, + {"(abc CHAR(1) DEFAULT)", "cannot be parsed"}, + {"(abc CHAR(1) DEFAULT 'a')", "cannot be parsed"}, + // TODO [SNOW-1850370]: Snowflake currently does not return concrete data types so we can fail on them currently but it should be improved in the future + {"(abc NUMBER(30,2))", "cannot be parsed"}, + {"(abc NUMBER(30, 2))", "cannot be parsed"}, } for _, tc := range inputs { @@ -155,7 +162,7 @@ func Test_parseFunctionOrProcedureSignature(t *testing.T) { require.Len(t, args, len(tc.expectedArgs)) for i, arg := range args { require.Equal(t, tc.expectedArgs[i].Name, arg.Name) - require.Equal(t, tc.expectedArgs[i].DataType.ToSql(), arg.DataType.ToSql()) + require.True(t, datatypes.AreTheSame(tc.expectedArgs[i].DataType, arg.DataType)) } }) } diff --git a/pkg/sdk/testint/functions_integration_test.go b/pkg/sdk/testint/functions_integration_test.go index 83516c2837..21277add90 100644 --- a/pkg/sdk/testint/functions_integration_test.go +++ b/pkg/sdk/testint/functions_integration_test.go @@ -23,18 +23,17 @@ import ( // TODO [SNOW-1850370]: HasArgumentsRawFrom(functionId, arguments, return) // TODO [SNOW-1850370]: extract show assertions with commons fields // TODO [SNOW-1850370]: test confirming that runtime version is required for Scala function -// TODO [SNOW-1348103 or SNOW-1850370]: test create or replace with name change, args change -// TODO [SNOW-1348103]: test rename more (arg stays, can't change arg, rename to different schema) -// TODO [SNOW-1348103]: test weird names for arg name - lower/upper if used with double quotes, to upper without quotes, dots, spaces, and both quotes not permitted +// TODO [SNOW-1850370]: test create or replace with name change, args change +// TODO [SNOW-1850370]: test rename more (arg stays, can't change arg, rename to different schema) // TODO [SNOW-1850370]: add test documenting that UNSET SECRETS does not work // TODO [SNOW-1850370]: add test documenting [JAVA]: 391516 (42601): SQL compilation error: Cannot specify TARGET_PATH without a function BODY. -// TODO [SNOW-1348103 or SNOW-1850370]: test secure -// TODO [SNOW-1348103]: python aggregate func (100357 (P0000): Could not find accumulate method in function CVVEMHIT_06547800_08D6_DBCA_1AC7_5E422AFF8B39 with handler dump) -// TODO [SNOW-1348103]: add a test documenting that we can't set parameters in create (and revert adding these parameters directly in object...) +// TODO [SNOW-1850370]: add a test documenting that we can't set parameters in create (and revert adding these parameters directly in object...) // TODO [SNOW-1850370]: active warehouse vs validations -// TODO [SNOW-1348103]: add a test documenting STRICT behavior +// TODO [SNOW-1850370]: add a test documenting STRICT behavior +// TODO [SNOW-1348103]: test weird names for arg name - lower/upper if used with double quotes, to upper without quotes, dots, spaces, and both quotes not permitted +// TODO [SNOW-1348103]: test secure +// TODO [SNOW-1348103]: python aggregate func (100357 (P0000): Could not find accumulate method in function CVVEMHIT_06547800_08D6_DBCA_1AC7_5E422AFF8B39 with handler dump) // TODO [next PR]: add test with multiple imports -// TODO [this PR]: add assertion to each test for the normalized target path, returned data type, and return not null func TestInt_Functions(t *testing.T) { client := testClient(t) ctx := context.Background() @@ -117,6 +116,8 @@ func TestInt_Functions(t *testing.T) { assertions.AssertThatObject(t, objectassert.FunctionDetails(t, function.ID()). HasSignature(fmt.Sprintf(`(%s %s)`, argName, dataType.ToLegacyDataTypeSql())). HasReturns(dataType.ToSql()). + HasReturnDataType(dataType). + HasReturnNotNull(false). HasLanguage("JAVA"). HasBody(definition). HasNullHandling(string(sdk.NullInputBehaviorCalledOnNullInput)). @@ -129,6 +130,7 @@ func TestInt_Functions(t *testing.T) { HasRuntimeVersionNil(). HasPackages(`[]`). HasTargetPathNil(). + HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). HasIsAggregateNil(), ) @@ -212,6 +214,8 @@ func TestInt_Functions(t *testing.T) { assertions.AssertThatObject(t, objectassert.FunctionDetails(t, function.ID()). HasSignature(fmt.Sprintf(`(%s %s)`, argName, dataType.ToLegacyDataTypeSql())). HasReturns(fmt.Sprintf(`%s NOT NULL`, dataType.ToSql())). + HasReturnDataType(dataType). + HasReturnNotNull(true). HasLanguage("JAVA"). HasBody(definition). HasNullHandling(string(sdk.NullInputBehaviorReturnsNullInput)). @@ -228,6 +232,7 @@ func TestInt_Functions(t *testing.T) { HasRuntimeVersion("11"). HasPackages(`[com.snowflake:snowpark:1.14.0,com.snowflake:telemetry:0.1.0]`). HasTargetPath(targetPath). + HasNormalizedTargetPath("~", jarName). HasInstalledPackagesNil(). HasIsAggregateNil(), ) @@ -287,6 +292,8 @@ func TestInt_Functions(t *testing.T) { assertions.AssertThatObject(t, objectassert.FunctionDetails(t, function.ID()). HasSignature(fmt.Sprintf(`(%s %s)`, argName, dataType.ToLegacyDataTypeSql())). HasReturns(dataType.ToSql()). + HasReturnDataType(dataType). + HasReturnNotNull(false). HasLanguage("JAVA"). HasBodyNil(). HasNullHandling(string(sdk.NullInputBehaviorCalledOnNullInput)). @@ -301,6 +308,7 @@ func TestInt_Functions(t *testing.T) { HasRuntimeVersionNil(). HasPackages(`[]`). HasTargetPathNil(). + HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). HasIsAggregateNil(), ) @@ -372,6 +380,8 @@ func TestInt_Functions(t *testing.T) { assertions.AssertThatObject(t, objectassert.FunctionDetails(t, function.ID()). HasSignature(fmt.Sprintf(`(%s %s)`, argName, dataType.ToLegacyDataTypeSql())). HasReturns(fmt.Sprintf(`%s NOT NULL`, dataType.ToSql())). + HasReturnDataType(dataType). + HasReturnNotNull(true). HasLanguage("JAVA"). HasBodyNil(). HasNullHandling(string(sdk.NullInputBehaviorReturnsNullInput)). @@ -386,6 +396,7 @@ func TestInt_Functions(t *testing.T) { HasRuntimeVersion("11"). HasPackages(`[com.snowflake:snowpark:1.14.0,com.snowflake:telemetry:0.1.0]`). HasTargetPathNil(). + HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). HasIsAggregateNil(), ) @@ -445,6 +456,8 @@ func TestInt_Functions(t *testing.T) { assertions.AssertThatObject(t, objectassert.FunctionDetails(t, function.ID()). HasSignature(fmt.Sprintf(`(%s %s)`, argName, dataType.ToLegacyDataTypeSql())). HasReturns(dataType.ToSql()). + HasReturnDataType(dataType). + HasReturnNotNull(false). HasLanguage("JAVA"). HasBodyNil(). HasNullHandling(string(sdk.NullInputBehaviorCalledOnNullInput)). @@ -459,6 +472,7 @@ func TestInt_Functions(t *testing.T) { HasRuntimeVersionNil(). HasPackages(`[]`). HasTargetPathNil(). + HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). HasIsAggregateNil(), ) @@ -550,6 +564,8 @@ func TestInt_Functions(t *testing.T) { assertions.AssertThatObject(t, objectassert.FunctionDetails(t, function.ID()). HasSignature(fmt.Sprintf(`(%s %s)`, argName, dataType.ToLegacyDataTypeSql())). HasReturns(dataType.ToSql()). + HasReturnDataType(dataType). + HasReturnNotNull(false). HasLanguage("JAVASCRIPT"). HasBody(definition). HasNullHandling(string(sdk.NullInputBehaviorCalledOnNullInput)). @@ -562,6 +578,7 @@ func TestInt_Functions(t *testing.T) { HasRuntimeVersionNil(). HasPackagesNil(). HasTargetPathNil(). + HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). HasIsAggregateNil(), ) @@ -624,6 +641,8 @@ func TestInt_Functions(t *testing.T) { assertions.AssertThatObject(t, objectassert.FunctionDetails(t, function.ID()). HasSignature(fmt.Sprintf(`(%s %s)`, argName, dataType.ToLegacyDataTypeSql())). HasReturns(fmt.Sprintf(`%s NOT NULL`, dataType.ToSql())). + HasReturnDataType(dataType). + HasReturnNotNull(true). HasLanguage("JAVASCRIPT"). HasBody(definition). HasNullHandling(string(sdk.NullInputBehaviorReturnsNullInput)). @@ -636,6 +655,7 @@ func TestInt_Functions(t *testing.T) { HasRuntimeVersionNil(). HasPackagesNil(). HasTargetPathNil(). + HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). HasIsAggregateNil(), ) @@ -693,7 +713,9 @@ func TestInt_Functions(t *testing.T) { assertions.AssertThatObject(t, objectassert.FunctionDetails(t, function.ID()). HasSignature(fmt.Sprintf(`(%s %s)`, argName, dataType.ToLegacyDataTypeSql())). - HasReturns(strings.ReplaceAll(dataType.ToSql(), " ", "")). // TODO [SNOW-1348103]: do we care about this whitespace? + HasReturns(strings.ReplaceAll(dataType.ToSql(), " ", "")). + HasReturnDataType(dataType). + HasReturnNotNull(false). HasLanguage("PYTHON"). HasBody(definition). HasNullHandling(string(sdk.NullInputBehaviorCalledOnNullInput)). @@ -706,6 +728,7 @@ func TestInt_Functions(t *testing.T) { HasRuntimeVersion("3.8"). HasPackages(`[]`). HasTargetPathNil(). + HasNormalizedTargetPathNil(). HasInstalledPackagesNotEmpty(). HasIsAggregate(false), ) @@ -776,7 +799,9 @@ func TestInt_Functions(t *testing.T) { assertions.AssertThatObject(t, objectassert.FunctionDetails(t, function.ID()). HasSignature(fmt.Sprintf(`(%s %s)`, argName, dataType.ToLegacyDataTypeSql())). - HasReturns(strings.ReplaceAll(dataType.ToSql(), " ", "")+" NOT NULL"). // TODO [SNOW-1348103]: do we care about this whitespace? + HasReturns(strings.ReplaceAll(dataType.ToSql(), " ", "")+" NOT NULL"). + HasReturnDataType(dataType). + HasReturnNotNull(true). HasLanguage("PYTHON"). HasBody(definition). HasNullHandling(string(sdk.NullInputBehaviorReturnsNullInput)). @@ -791,6 +816,7 @@ func TestInt_Functions(t *testing.T) { HasRuntimeVersion("3.8"). HasPackages(`['absl-py==0.10.0','about-time==4.2.1']`). HasTargetPathNil(). + HasNormalizedTargetPathNil(). HasInstalledPackagesNotEmpty(). HasIsAggregate(false), ) @@ -847,6 +873,8 @@ func TestInt_Functions(t *testing.T) { assertions.AssertThatObject(t, objectassert.FunctionDetails(t, function.ID()). HasSignature(fmt.Sprintf(`(%s %s)`, argName, dataType.ToLegacyDataTypeSql())). HasReturns(strings.ReplaceAll(dataType.ToSql(), " ", "")). + HasReturnDataType(dataType). + HasReturnNotNull(false). HasLanguage("PYTHON"). HasBodyNil(). HasNullHandling(string(sdk.NullInputBehaviorCalledOnNullInput)). @@ -861,6 +889,7 @@ func TestInt_Functions(t *testing.T) { HasRuntimeVersion("3.8"). HasPackages(`[]`). HasTargetPathNil(). + HasNormalizedTargetPathNil(). HasInstalledPackagesNotEmpty(). HasIsAggregate(false), ) @@ -929,6 +958,8 @@ func TestInt_Functions(t *testing.T) { assertions.AssertThatObject(t, objectassert.FunctionDetails(t, function.ID()). HasSignature(fmt.Sprintf(`(%s %s)`, argName, dataType.ToLegacyDataTypeSql())). HasReturns(strings.ReplaceAll(dataType.ToSql(), " ", "")+" NOT NULL"). + HasReturnDataType(dataType). + HasReturnNotNull(true). HasLanguage("PYTHON"). HasBodyNil(). HasNullHandling(string(sdk.NullInputBehaviorReturnsNullInput)). @@ -943,6 +974,7 @@ func TestInt_Functions(t *testing.T) { HasRuntimeVersion("3.8"). HasPackages(`['absl-py==0.10.0','about-time==4.2.1']`). HasTargetPathNil(). + HasNormalizedTargetPathNil(). HasInstalledPackagesNotEmpty(). HasIsAggregate(false), ) @@ -1002,6 +1034,8 @@ func TestInt_Functions(t *testing.T) { assertions.AssertThatObject(t, objectassert.FunctionDetails(t, function.ID()). HasSignature(fmt.Sprintf(`(%s %s)`, argName, dataType.ToLegacyDataTypeSql())). HasReturns(dataType.ToSql()). + HasReturnDataType(dataType). + HasReturnNotNull(false). HasLanguage("SCALA"). HasBody(definition). HasNullHandling(string(sdk.NullInputBehaviorCalledOnNullInput)). @@ -1014,6 +1048,7 @@ func TestInt_Functions(t *testing.T) { HasRuntimeVersion("2.12"). HasPackages(`[]`). HasTargetPathNil(). + HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). HasIsAggregateNil(), ) @@ -1094,6 +1129,8 @@ func TestInt_Functions(t *testing.T) { assertions.AssertThatObject(t, objectassert.FunctionDetails(t, function.ID()). HasSignature(fmt.Sprintf(`(%s %s)`, argName, dataType.ToLegacyDataTypeSql())). HasReturns(fmt.Sprintf(`%s NOT NULL`, dataType.ToSql())). + HasReturnDataType(dataType). + HasReturnNotNull(true). HasLanguage("SCALA"). HasBody(definition). HasNullHandling(string(sdk.NullInputBehaviorReturnsNullInput)). @@ -1108,6 +1145,7 @@ func TestInt_Functions(t *testing.T) { HasRuntimeVersion("2.12"). HasPackages(`[com.snowflake:snowpark:1.14.0,com.snowflake:telemetry:0.1.0]`). HasTargetPath(targetPath). + HasNormalizedTargetPath("~", jarName). HasInstalledPackagesNil(). HasIsAggregateNil(), ) @@ -1165,6 +1203,8 @@ func TestInt_Functions(t *testing.T) { assertions.AssertThatObject(t, objectassert.FunctionDetails(t, function.ID()). HasSignature(fmt.Sprintf(`(%s %s)`, argName, dataType.ToLegacyDataTypeSql())). HasReturns(dataType.ToSql()). + HasReturnDataType(dataType). + HasReturnNotNull(false). HasLanguage("SCALA"). HasBodyNil(). HasNullHandling(string(sdk.NullInputBehaviorCalledOnNullInput)). @@ -1179,6 +1219,7 @@ func TestInt_Functions(t *testing.T) { HasRuntimeVersion("2.12"). HasPackages(`[]`). HasTargetPathNil(). + HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). HasIsAggregateNil(), ) @@ -1247,6 +1288,8 @@ func TestInt_Functions(t *testing.T) { assertions.AssertThatObject(t, objectassert.FunctionDetails(t, function.ID()). HasSignature(fmt.Sprintf(`(%s %s)`, argName, dataType.ToLegacyDataTypeSql())). HasReturns(fmt.Sprintf(`%s NOT NULL`, dataType.ToSql())). + HasReturnDataType(dataType). + HasReturnNotNull(true). HasLanguage("SCALA"). HasBodyNil(). HasNullHandling(string(sdk.NullInputBehaviorReturnsNullInput)). @@ -1261,6 +1304,7 @@ func TestInt_Functions(t *testing.T) { HasRuntimeVersion("2.12"). HasPackages(`[com.snowflake:snowpark:1.14.0,com.snowflake:telemetry:0.1.0]`). HasTargetPathNil(). + HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). HasIsAggregateNil(), ) @@ -1317,6 +1361,8 @@ func TestInt_Functions(t *testing.T) { assertions.AssertThatObject(t, objectassert.FunctionDetails(t, function.ID()). HasSignature(fmt.Sprintf(`(%s %s)`, argName, dataType.ToLegacyDataTypeSql())). HasReturns(dataType.ToSql()). + HasReturnDataType(dataType). + HasReturnNotNull(false). HasLanguage("SQL"). HasBody(definition). HasNullHandlingNil(). @@ -1329,6 +1375,7 @@ func TestInt_Functions(t *testing.T) { HasRuntimeVersionNil(). HasPackagesNil(). HasTargetPathNil(). + HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). HasIsAggregateNil(), ) @@ -1420,6 +1467,8 @@ func TestInt_Functions(t *testing.T) { assertions.AssertThatObject(t, objectassert.FunctionDetails(t, function.ID()). HasSignature(fmt.Sprintf(`(%s %s)`, argName, dataType.ToLegacyDataTypeSql())). HasReturns(fmt.Sprintf(`%s NOT NULL`, dataType.ToSql())). + HasReturnDataType(dataType). + HasReturnNotNull(true). HasLanguage("SQL"). HasBody(definition). HasNullHandlingNil(). @@ -1434,6 +1483,7 @@ func TestInt_Functions(t *testing.T) { HasRuntimeVersionNil(). HasPackagesNil(). HasTargetPathNil(). + HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). HasIsAggregateNil(), ) @@ -1487,6 +1537,8 @@ func TestInt_Functions(t *testing.T) { assertions.AssertThatObject(t, objectassert.FunctionDetails(t, function.ID()). HasSignature("()"). HasReturns(dataType.ToSql()). + HasReturnDataType(dataType). + HasReturnNotNull(false). HasLanguage("SQL"). HasBody(definition). HasNullHandlingNil(). @@ -1499,6 +1551,7 @@ func TestInt_Functions(t *testing.T) { HasRuntimeVersionNil(). HasPackagesNil(). HasTargetPathNil(). + HasNormalizedTargetPathNil(). HasInstalledPackagesNil(). HasIsAggregateNil(), ) From 40ba6b36effaca15e61bc7ddea8e2eba2b2a9eee Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 17:14:28 +0100 Subject: [PATCH 39/45] Update TODOs --- pkg/resources/custom_diffs.go | 2 +- pkg/resources/function_commons.go | 4 ++-- pkg/resources/function_java.go | 4 ++-- pkg/resources/function_java_acceptance_test.go | 2 +- pkg/resources/user.go | 1 - pkg/sdk/functions_ext.go | 2 +- pkg/sdk/functions_ext_test.go | 2 +- pkg/sdk/identifier_helpers.go | 2 +- pkg/sdk/testint/functions_integration_test.go | 2 +- 9 files changed, 10 insertions(+), 11 deletions(-) diff --git a/pkg/resources/custom_diffs.go b/pkg/resources/custom_diffs.go index b5a9eae5da..1ea9025ac5 100644 --- a/pkg/resources/custom_diffs.go +++ b/pkg/resources/custom_diffs.go @@ -286,7 +286,7 @@ func RecreateWhenResourceBoolFieldChangedExternally(boolField string, wantValue } // RecreateWhenResourceStringFieldChangedExternally recreates a resource when wantValue is different from value in field. -// TODO [next PR]: merge with above? test. +// TODO [SNOW-1850370]: merge with above? test. func RecreateWhenResourceStringFieldChangedExternally(field string, wantValue string) schema.CustomizeDiffFunc { return func(_ context.Context, diff *schema.ResourceDiff, _ any) error { if o, n := diff.GetChange(field); n != nil && o != nil && o != "" && n.(string) != wantValue { diff --git a/pkg/resources/function_commons.go b/pkg/resources/function_commons.go index f9ef999e5d..f73fb61dc2 100644 --- a/pkg/resources/function_commons.go +++ b/pkg/resources/function_commons.go @@ -272,7 +272,7 @@ func functionBaseSchema() map[string]schema.Schema { Description: "Specifies a comment for the function.", }, // split into two because of https://docs.snowflake.com/en/sql-reference/sql/create-function#id6 - // TODO [next PR]: add validations preventing setting improper stage and path + // TODO [SNOW-1348103]: add validations preventing setting improper stage and path "imports": { Type: schema.TypeSet, Optional: true, @@ -532,7 +532,7 @@ type allFunctionDetailsCommon struct { func readFunctionArgumentsCommon(d *schema.ResourceData, args []sdk.NormalizedArgument) error { if len(args) == 0 { - // TODO [next PR]: handle empty list + // TODO [SNOW-1348103]: handle empty list return nil } if currentArgs, ok := d.Get("arguments").([]map[string]any); !ok { diff --git a/pkg/resources/function_java.go b/pkg/resources/function_java.go index 66ce727264..9f6763c4c5 100644 --- a/pkg/resources/function_java.go +++ b/pkg/resources/function_java.go @@ -128,8 +128,8 @@ func ReadContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta a return diags } - // TODO [next PR]: handle external changes marking - // TODO [next PR]: handle setting state to value from config + // TODO [SNOW-1348103]: handle external changes marking + // TODO [SNOW-1348103]: handle setting state to value from config errs := errors.Join( // TODO [this PR]: set all proper fields diff --git a/pkg/resources/function_java_acceptance_test.go b/pkg/resources/function_java_acceptance_test.go index 5fa88f06bf..bfa819f492 100644 --- a/pkg/resources/function_java_acceptance_test.go +++ b/pkg/resources/function_java_acceptance_test.go @@ -237,7 +237,7 @@ func TestAcc_FunctionJava_InlineFull(t *testing.T) { handler := fmt.Sprintf("%s.%s", className, funcName) definition := acc.TestClient().Function.SampleJavaDefinition(t, className, funcName, argName) - // TODO [next PR]: extract to helper + // TODO [SNOW-1850370]: extract to helper jarName := fmt.Sprintf("tf-%d-%s.jar", time.Now().Unix(), random.AlphaN(5)) functionModel := model.FunctionJavaBasicInline("w", id, dataType, handler, definition). diff --git a/pkg/resources/user.go b/pkg/resources/user.go index 1fb6f15127..c84040e642 100644 --- a/pkg/resources/user.go +++ b/pkg/resources/user.go @@ -199,7 +199,6 @@ func User() *schema.Resource { }, CustomizeDiff: TrackingCustomDiffWrapper(resources.User, customdiff.All( - // TODO [SNOW-1629468 - next pr]: test "default_role", "default_secondary_roles" ComputedIfAnyAttributeChanged(userSchema, ShowOutputAttributeName, userExternalChangesAttributes...), ComputedIfAnyAttributeChanged(userParametersSchema, ParametersAttributeName, collections.Map(sdk.AsStringList(sdk.AllUserParameters), strings.ToLower)...), ComputedIfAnyAttributeChanged(userSchema, FullyQualifiedNameAttributeName, "name"), diff --git a/pkg/sdk/functions_ext.go b/pkg/sdk/functions_ext.go index 4f5c55ab0e..a294b74c6d 100644 --- a/pkg/sdk/functions_ext.go +++ b/pkg/sdk/functions_ext.go @@ -204,7 +204,7 @@ func parseFunctionOrProcedureSignature(signature string) ([]NormalizedArgument, return normalizedArguments, nil } -// TODO [next PR]: test with strange arg names (first integration test) +// TODO [SNOW-1850370]: test with strange arg names (first integration test) func parseFunctionOrProcedureArgument(arg string) (*NormalizedArgument, error) { log.Printf("[DEBUG] parsing argument: %s", arg) trimmed := strings.TrimSpace(arg) diff --git a/pkg/sdk/functions_ext_test.go b/pkg/sdk/functions_ext_test.go index 42bf146895..a4f77431d0 100644 --- a/pkg/sdk/functions_ext_test.go +++ b/pkg/sdk/functions_ext_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" ) -// TODO [next PR]: test parsing single +// TODO [SNOW-1850370]: test parsing single func Test_parseFunctionDetailsImport(t *testing.T) { inputs := []struct { rawInput string diff --git a/pkg/sdk/identifier_helpers.go b/pkg/sdk/identifier_helpers.go index fa244df341..308535c4f8 100644 --- a/pkg/sdk/identifier_helpers.go +++ b/pkg/sdk/identifier_helpers.go @@ -212,7 +212,7 @@ type SchemaObjectIdentifier struct { databaseName string schemaName string name string - // TODO(next prs): left right now for backward compatibility for procedures and externalFunctions + // TODO [SNOW-1850370]: left right now for backward compatibility for procedures and externalFunctions arguments []DataType } diff --git a/pkg/sdk/testint/functions_integration_test.go b/pkg/sdk/testint/functions_integration_test.go index 21277add90..dae54dc656 100644 --- a/pkg/sdk/testint/functions_integration_test.go +++ b/pkg/sdk/testint/functions_integration_test.go @@ -33,7 +33,7 @@ import ( // TODO [SNOW-1348103]: test weird names for arg name - lower/upper if used with double quotes, to upper without quotes, dots, spaces, and both quotes not permitted // TODO [SNOW-1348103]: test secure // TODO [SNOW-1348103]: python aggregate func (100357 (P0000): Could not find accumulate method in function CVVEMHIT_06547800_08D6_DBCA_1AC7_5E422AFF8B39 with handler dump) -// TODO [next PR]: add test with multiple imports +// TODO [SNOW-1348103]: add test with multiple imports func TestInt_Functions(t *testing.T) { client := testClient(t) ctx := context.Background() From 15e0714dd7fd58b28ea7f3569e7286eed8675ec5 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 17:18:33 +0100 Subject: [PATCH 40/45] Update TODOs part 2 --- pkg/acceptance/helpers/function_setup_helpers.go | 2 +- pkg/resources/function_commons.go | 4 ++-- pkg/resources/function_java.go | 6 +++--- pkg/resources/resource_helpers_read.go | 2 +- pkg/sdk/data_types_deprecated.go | 2 +- pkg/sdk/datatypes/table.go | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/acceptance/helpers/function_setup_helpers.go b/pkg/acceptance/helpers/function_setup_helpers.go index 6221a36b4b..132bd34706 100644 --- a/pkg/acceptance/helpers/function_setup_helpers.go +++ b/pkg/acceptance/helpers/function_setup_helpers.go @@ -67,7 +67,7 @@ func (c *TestClient) CreateSampleJavaFunctionAndJarInLocation(t *testing.T, stag } } -// TODO [this PR]: adjust to switching location too +// TODO [SNOW-1348106]: adjust to switching location too func (c *TestClient) CreateSampleJavaProcedureAndJar(t *testing.T) *TmpFunction { t.Helper() ctx := context.Background() diff --git a/pkg/resources/function_commons.go b/pkg/resources/function_commons.go index f73fb61dc2..b5f9afad38 100644 --- a/pkg/resources/function_commons.go +++ b/pkg/resources/function_commons.go @@ -249,7 +249,7 @@ func functionBaseSchema() map[string]schema.Schema { Optional: true, ForceNew: true, ValidateDiagFunc: sdkValidation(sdk.ToNullInputBehavior), - DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToNullInputBehavior)), // TODO [this PR]: IgnoreChangeToCurrentSnowflakeValueInShow("null_input_behavior") but not in show + DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToNullInputBehavior)), // TODO [SNOW-1348103]: IgnoreChangeToCurrentSnowflakeValueInShow("null_input_behavior") but not in show Description: fmt.Sprintf("Specifies the behavior of the function when called with null inputs. Valid values are (case-insensitive): %s.", possibleValuesListed(sdk.AllAllowedNullInputBehaviors)), }, "return_results_behavior": { @@ -257,7 +257,7 @@ func functionBaseSchema() map[string]schema.Schema { Optional: true, ForceNew: true, ValidateDiagFunc: sdkValidation(sdk.ToReturnResultsBehavior), - DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToReturnResultsBehavior)), // TODO [this PR]: IgnoreChangeToCurrentSnowflakeValueInShow("return_results_behavior") but not in show + DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToReturnResultsBehavior)), // TODO [SNOW-1348103]: IgnoreChangeToCurrentSnowflakeValueInShow("return_results_behavior") but not in show Description: fmt.Sprintf("Specifies the behavior of the function when returning results. Valid values are (case-insensitive): %s.", possibleValuesListed(sdk.AllAllowedReturnResultsBehaviors)), }, "runtime_version": { diff --git a/pkg/resources/function_java.go b/pkg/resources/function_java.go index 9f6763c4c5..c22b862a0f 100644 --- a/pkg/resources/function_java.go +++ b/pkg/resources/function_java.go @@ -72,7 +72,7 @@ func CreateContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta attributeMappedValueCreateBuilder[string](d, "null_input_behavior", request.WithNullInputBehavior, sdk.ToNullInputBehavior), attributeMappedValueCreateBuilder[string](d, "return_results_behavior", request.WithReturnResultsBehavior, sdk.ToReturnResultsBehavior), stringAttributeCreateBuilder(d, "runtime_version", request.WithRuntimeVersion), - // TODO [this PR]: handle all attributes + // TODO [SNOW-1348103]: handle the rest of the attributes // comment setFunctionImportsInBuilder(d, request.WithImports), // packages @@ -132,7 +132,7 @@ func ReadContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta a // TODO [SNOW-1348103]: handle setting state to value from config errs := errors.Join( - // TODO [this PR]: set all proper fields + // TODO [SNOW-1348103]: set the rest of the fields // not reading is_secure on purpose (handled as external change to show output) readFunctionArgumentsCommon(d, allFunctionDetails.functionDetails.NormalizedArguments), d.Set("return_type", allFunctionDetails.functionDetails.ReturnDataType.ToSql()), @@ -184,7 +184,7 @@ func UpdateContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta setRequest := sdk.NewFunctionSetRequest() unsetRequest := sdk.NewFunctionUnsetRequest() - // TODO [this PR]: handle all updates + // TODO [SNOW-1348103]: handle all updates // secure // external access integration // secrets diff --git a/pkg/resources/resource_helpers_read.go b/pkg/resources/resource_helpers_read.go index 5394ca599c..20d1e69fc6 100644 --- a/pkg/resources/resource_helpers_read.go +++ b/pkg/resources/resource_helpers_read.go @@ -73,7 +73,7 @@ func setOptionalFromStringPtr(d *schema.ResourceData, key string, ptr *string) e return nil } -// TODO [this PR]: return error if nil +// TODO [SNOW-1348103]: return error if nil func setRequiredFromStringPtr(d *schema.ResourceData, key string, ptr *string) error { if ptr != nil { if err := d.Set(key, *ptr); err != nil { diff --git a/pkg/sdk/data_types_deprecated.go b/pkg/sdk/data_types_deprecated.go index 492fcd80f1..24149f8d9f 100644 --- a/pkg/sdk/data_types_deprecated.go +++ b/pkg/sdk/data_types_deprecated.go @@ -47,7 +47,7 @@ func IsStringType(_type string) bool { } func LegacyDataTypeFrom(newDataType datatypes.DataType) DataType { - // TODO [this PR]: remove this check? + // TODO [SNOW-1850370]: remove this check? if newDataType == nil { return "" } diff --git a/pkg/sdk/datatypes/table.go b/pkg/sdk/datatypes/table.go index d5e8bda489..e7c398ec6d 100644 --- a/pkg/sdk/datatypes/table.go +++ b/pkg/sdk/datatypes/table.go @@ -1,9 +1,9 @@ package datatypes -// TableDataType is based on TODO [this PR] +// TableDataType is based on TODO [SNOW-1348103] // It does not have synonyms. // It consists of a list of column name + column type; may be empty. -// TODO [this PR]: test and improve +// TODO [SNOW-1348103]: test and improve type TableDataType struct { columns []TableDataTypeColumn underlyingType string From 60d663cd702ddec631f147f2c90e7f8a125781fc Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 17:35:34 +0100 Subject: [PATCH 41/45] Test no arguments and default values --- .../config/model/function_java_model_ext.go | 12 + pkg/acceptance/helpers/function_client.go | 12 + pkg/resources/function_commons.go | 14 +- .../function_java_acceptance_test.go | 233 +++++++----------- 4 files changed, 125 insertions(+), 146 deletions(-) diff --git a/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go b/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go index aaff798262..b0c1414a42 100644 --- a/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go +++ b/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go @@ -72,6 +72,18 @@ func (f *FunctionJavaModel) WithArgument(argName string, argDataType datatypes.D ) } +func (f *FunctionJavaModel) WithArgumentWithDefaultValue(argName string, argDataType datatypes.DataType, value string) *FunctionJavaModel { + return f.WithArgumentsValue( + tfconfig.ObjectVariable( + map[string]tfconfig.Variable{ + "arg_name": tfconfig.StringVariable(argName), + "arg_data_type": tfconfig.StringVariable(argDataType.ToSql()), + "arg_default_value": tfconfig.StringVariable(value), + }, + ), + ) +} + func (f *FunctionJavaModel) WithImport(stageLocation string, pathOnStage string) *FunctionJavaModel { return f.WithImportsValue( tfconfig.ObjectVariable( diff --git a/pkg/acceptance/helpers/function_client.go b/pkg/acceptance/helpers/function_client.go index 5e054e3c23..36c5ffcfb4 100644 --- a/pkg/acceptance/helpers/function_client.go +++ b/pkg/acceptance/helpers/function_client.go @@ -212,6 +212,18 @@ func (c *FunctionClient) SampleJavaDefinition(t *testing.T, className string, fu `, className, funcName, argName) } +func (c *FunctionClient) SampleJavaDefinitionNoArgs(t *testing.T, className string, funcName string) string { + t.Helper() + + return fmt.Sprintf(` + class %[1]s { + public static String %[2]s() { + return "hello"; + } + } +`, className, funcName) +} + func (c *FunctionClient) SampleJavascriptDefinition(t *testing.T, argName string) string { t.Helper() diff --git a/pkg/resources/function_commons.go b/pkg/resources/function_commons.go index b5f9afad38..f9583cce5b 100644 --- a/pkg/resources/function_commons.go +++ b/pkg/resources/function_commons.go @@ -229,6 +229,11 @@ func functionBaseSchema() map[string]schema.Schema { DiffSuppressFunc: DiffSuppressDataTypes, Description: "The argument type.", }, + "arg_default_value": { + Type: schema.TypeString, + Optional: true, + Description: externalChangesNotDetectedFieldDescription("Optional default value for the argument. For text values use single quotes. Numeric values can be unquoted."), + }, }, }, Optional: true, @@ -402,7 +407,6 @@ func DeleteFunction(ctx context.Context, d *schema.ResourceData, meta any) diag. return nil } -// TODO [SNOW-1348103]: handle defaults func parseFunctionArgumentsCommon(d *schema.ResourceData) ([]sdk.FunctionArgumentRequest, error) { args := make([]sdk.FunctionArgumentRequest, 0) if v, ok := d.GetOk("arguments"); ok { @@ -413,7 +417,13 @@ func parseFunctionArgumentsCommon(d *schema.ResourceData) ([]sdk.FunctionArgumen if err != nil { return nil, err } - args = append(args, *sdk.NewFunctionArgumentRequest(argName, dataType)) + request := sdk.NewFunctionArgumentRequest(argName, dataType) + + if argDefaultValue, defaultValuePresent := arg.(map[string]any)["arg_default_value"]; defaultValuePresent && argDefaultValue.(string) != "" { + request.WithDefaultValue(argDefaultValue.(string)) + } + + args = append(args, *request) } } return args, nil diff --git a/pkg/resources/function_java_acceptance_test.go b/pkg/resources/function_java_acceptance_test.go index bfa819f492..b805187b69 100644 --- a/pkg/resources/function_java_acceptance_test.go +++ b/pkg/resources/function_java_acceptance_test.go @@ -27,26 +27,25 @@ import ( "github.com/hashicorp/terraform-plugin-testing/tfversion" ) -// TODO [this PR]: test empty args -// TODO [this PR]: test default args no change +// TODO [SNOW-1348103]: test import +// TODO [SNOW-1348103]: test external changes +// TODO [SNOW-1348103]: test changes of attributes separately func TestAcc_FunctionJava_InlineBasic(t *testing.T) { className := "TestFunc" funcName := "echoVarchar" argName := "x" dataType := testdatatypes.DataTypeVarchar_100 - // differentDataType := testdatatypes.DataTypeNumber_36_2 id := acc.TestClient().Ids.RandomSchemaObjectIdentifierWithArgumentsNewDataTypes(dataType) idWithChangedNameButTheSameDataType := acc.TestClient().Ids.RandomSchemaObjectIdentifierWithArgumentsNewDataTypes(dataType) - // idWithSameNameButDifferentDataType := acc.TestClient().Ids.NewSchemaObjectIdentifierWithArgumentsNewDataTypes(idWithChangedNameButTheSameDataType.Name(), differentDataType) handler := fmt.Sprintf("%s.%s", className, funcName) definition := acc.TestClient().Function.SampleJavaDefinition(t, className, funcName, argName) - functionModelNoAttributes := model.FunctionJavaBasicInline("w", id, dataType, handler, definition). + functionModel := model.FunctionJavaBasicInline("w", id, dataType, handler, definition). WithArgument(argName, dataType) - functionModelNoAttributesRenamed := model.FunctionJavaBasicInline("w", idWithChangedNameButTheSameDataType, dataType, handler, definition). + functionModelRenamed := model.FunctionJavaBasicInline("w", idWithChangedNameButTheSameDataType, dataType, handler, definition). WithArgument(argName, dataType) resource.Test(t, resource.TestCase{ @@ -59,9 +58,9 @@ func TestAcc_FunctionJava_InlineBasic(t *testing.T) { Steps: []resource.TestStep{ // CREATE BASIC { - Config: config.FromModels(t, functionModelNoAttributes), + Config: config.FromModels(t, functionModel), Check: assert.AssertThat(t, - resourceassert.FunctionJavaResource(t, functionModelNoAttributes.ResourceReference()). + resourceassert.FunctionJavaResource(t, functionModel.ResourceReference()). HasNameString(id.Name()). HasIsSecureString(r.BooleanDefault). HasCommentString(sdk.DefaultFunctionComment). @@ -71,152 +70,98 @@ func TestAcc_FunctionJava_InlineBasic(t *testing.T) { HasFunctionDefinitionString(definition). HasFunctionLanguageString("JAVA"). HasFullyQualifiedNameString(id.FullyQualifiedName()), - resourceshowoutputassert.FunctionShowOutput(t, functionModelNoAttributes.ResourceReference()). + resourceshowoutputassert.FunctionShowOutput(t, functionModel.ResourceReference()). HasIsSecure(false), + assert.Check(resource.TestCheckResourceAttr(functionModel.ResourceReference(), "arguments.0.arg_name", argName)), + assert.Check(resource.TestCheckResourceAttr(functionModel.ResourceReference(), "arguments.0.arg_data_type", dataType.ToSql())), + assert.Check(resource.TestCheckResourceAttr(functionModel.ResourceReference(), "arguments.0.arg_default_value", "")), ), }, // RENAME { - Config: config.FromModels(t, functionModelNoAttributesRenamed), + Config: config.FromModels(t, functionModelRenamed), Check: assert.AssertThat(t, - resourceassert.FunctionJavaResource(t, functionModelNoAttributesRenamed.ResourceReference()). + resourceassert.FunctionJavaResource(t, functionModelRenamed.ResourceReference()). HasNameString(idWithChangedNameButTheSameDataType.Name()). HasFullyQualifiedNameString(idWithChangedNameButTheSameDataType.FullyQualifiedName()), ), }, - //// IMPORT - //{ - // ResourceName: userModelNoAttributesRenamed.ResourceReference(), - // ImportState: true, - // ImportStateVerify: true, - // ImportStateVerifyIgnore: []string{"password", "disable_mfa", "days_to_expiry", "mins_to_unlock", "mins_to_bypass_mfa", "login_name", "display_name", "disabled", "must_change_password"}, - // ImportStateCheck: assert.AssertThatImport(t, - // resourceassert.ImportedUserResource(t, id2.Name()). - // HasLoginNameString(strings.ToUpper(id.Name())). - // HasDisplayNameString(id.Name()). - // HasDisabled(false). - // HasMustChangePassword(false), - // ), - //}, - //// DESTROY - //{ - // Config: config.FromModel(t, userModelNoAttributes), - // Destroy: true, - //}, - //// CREATE WITH ALL ATTRIBUTES - //{ - // Config: config.FromModel(t, userModelAllAttributes), - // Check: assert.AssertThat(t, - // resourceassert.UserResource(t, userModelAllAttributes.ResourceReference()). - // HasNameString(id.Name()). - // HasPasswordString(pass). - // HasLoginNameString(fmt.Sprintf("%s_login", id.Name())). - // HasDisplayNameString("Display Name"). - // HasFirstNameString("Jan"). - // HasMiddleNameString("Jakub"). - // HasLastNameString("Testowski"). - // HasEmailString("fake@email.com"). - // HasMustChangePassword(true). - // HasDisabled(false). - // HasDaysToExpiryString("8"). - // HasMinsToUnlockString("9"). - // HasDefaultWarehouseString("some_warehouse"). - // HasDefaultNamespaceString("some.namespace"). - // HasDefaultRoleString("some_role"). - // HasDefaultSecondaryRolesOption(sdk.SecondaryRolesOptionAll). - // HasMinsToBypassMfaString("10"). - // HasRsaPublicKeyString(key1). - // HasRsaPublicKey2String(key2). - // HasCommentString(comment). - // HasDisableMfaString(r.BooleanTrue). - // HasFullyQualifiedNameString(id.FullyQualifiedName()), - // ), - //}, - //// CHANGE PROPERTIES - //{ - // Config: config.FromModel(t, userModelAllAttributesChanged(id.Name()+"_other_login")), - // Check: assert.AssertThat(t, - // resourceassert.UserResource(t, userModelAllAttributesChanged(id.Name()+"_other_login").ResourceReference()). - // HasNameString(id.Name()). - // HasPasswordString(newPass). - // HasLoginNameString(fmt.Sprintf("%s_other_login", id.Name())). - // HasDisplayNameString("New Display Name"). - // HasFirstNameString("Janek"). - // HasMiddleNameString("Kuba"). - // HasLastNameString("Terraformowski"). - // HasEmailString("fake@email.net"). - // HasMustChangePassword(false). - // HasDisabled(true). - // HasDaysToExpiryString("12"). - // HasMinsToUnlockString("13"). - // HasDefaultWarehouseString("other_warehouse"). - // HasDefaultNamespaceString("one_part_namespace"). - // HasDefaultRoleString("other_role"). - // HasDefaultSecondaryRolesOption(sdk.SecondaryRolesOptionAll). - // HasMinsToBypassMfaString("14"). - // HasRsaPublicKeyString(key2). - // HasRsaPublicKey2String(key1). - // HasCommentString(newComment). - // HasDisableMfaString(r.BooleanFalse). - // HasFullyQualifiedNameString(id.FullyQualifiedName()), - // ), - //}, - //// IMPORT - //{ - // ResourceName: userModelAllAttributesChanged(id.Name() + "_other_login").ResourceReference(), - // ImportState: true, - // ImportStateVerify: true, - // ImportStateVerifyIgnore: []string{"password", "disable_mfa", "days_to_expiry", "mins_to_unlock", "mins_to_bypass_mfa", "default_namespace", "login_name", "show_output.0.days_to_expiry"}, - // ImportStateCheck: assert.AssertThatImport(t, - // resourceassert.ImportedUserResource(t, id.Name()). - // HasDefaultNamespaceString("ONE_PART_NAMESPACE"). - // HasLoginNameString(fmt.Sprintf("%s_OTHER_LOGIN", id.Name())), - // ), - //}, - //// CHANGE PROP TO THE CURRENT SNOWFLAKE VALUE - //{ - // PreConfig: func() { - // acc.TestClient().User.SetLoginName(t, id, id.Name()+"_different_login") - // }, - // Config: config.FromModel(t, userModelAllAttributesChanged(id.Name()+"_different_login")), - // ConfigPlanChecks: resource.ConfigPlanChecks{ - // PostApplyPostRefresh: []plancheck.PlanCheck{ - // plancheck.ExpectEmptyPlan(), - // }, - // }, - //}, - //// UNSET ALL - //{ - // Config: config.FromModel(t, userModelNoAttributes), - // Check: assert.AssertThat(t, - // resourceassert.UserResource(t, userModelNoAttributes.ResourceReference()). - // HasNameString(id.Name()). - // HasPasswordString(""). - // HasLoginNameString(""). - // HasDisplayNameString(""). - // HasFirstNameString(""). - // HasMiddleNameString(""). - // HasLastNameString(""). - // HasEmailString(""). - // HasMustChangePasswordString(r.BooleanDefault). - // HasDisabledString(r.BooleanDefault). - // HasDaysToExpiryString("0"). - // HasMinsToUnlockString(r.IntDefaultString). - // HasDefaultWarehouseString(""). - // HasDefaultNamespaceString(""). - // HasDefaultRoleString(""). - // HasDefaultSecondaryRolesOption(sdk.SecondaryRolesOptionDefault). - // HasMinsToBypassMfaString(r.IntDefaultString). - // HasRsaPublicKeyString(""). - // HasRsaPublicKey2String(""). - // HasCommentString(""). - // HasDisableMfaString(r.BooleanDefault). - // HasFullyQualifiedNameString(id.FullyQualifiedName()), - // resourceshowoutputassert.UserShowOutput(t, userModelNoAttributes.ResourceReference()). - // HasLoginName(strings.ToUpper(id.Name())). - // HasDisplayName(""), - // ), - //}, + }, + }) +} + +func TestAcc_FunctionJava_InlineEmptyArgs(t *testing.T) { + className := "TestFunc" + funcName := "echoVarchar" + returnDataType := testdatatypes.DataTypeVarchar_100 + + id := acc.TestClient().Ids.RandomSchemaObjectIdentifierWithArgumentsNewDataTypes() + + handler := fmt.Sprintf("%s.%s", className, funcName) + definition := acc.TestClient().Function.SampleJavaDefinitionNoArgs(t, className, funcName) + + functionModel := model.FunctionJavaBasicInline("w", id, returnDataType, handler, definition) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + PreCheck: func() { acc.TestAccPreCheck(t) }, + CheckDestroy: acc.CheckDestroy(t, resources.FunctionJava), + Steps: []resource.TestStep{ + // CREATE BASIC + { + Config: config.FromModels(t, functionModel), + Check: assert.AssertThat(t, + resourceassert.FunctionJavaResource(t, functionModel.ResourceReference()). + HasNameString(id.Name()). + HasFunctionDefinitionString(definition). + HasFunctionLanguageString("JAVA"). + HasFullyQualifiedNameString(id.FullyQualifiedName()), + ), + }, + }, + }) +} + +func TestAcc_FunctionJava_InlineBasicDefaultArg(t *testing.T) { + className := "TestFunc" + funcName := "echoVarchar" + argName := "x" + dataType := testdatatypes.DataTypeVarchar_100 + defaultValue := "'hello'" + + id := acc.TestClient().Ids.RandomSchemaObjectIdentifierWithArgumentsNewDataTypes(dataType) + + handler := fmt.Sprintf("%s.%s", className, funcName) + definition := acc.TestClient().Function.SampleJavaDefinition(t, className, funcName, argName) + + functionModel := model.FunctionJavaBasicInline("w", id, dataType, handler, definition). + WithArgumentWithDefaultValue(argName, dataType, defaultValue) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + PreCheck: func() { acc.TestAccPreCheck(t) }, + CheckDestroy: acc.CheckDestroy(t, resources.FunctionJava), + Steps: []resource.TestStep{ + // CREATE BASIC + { + Config: config.FromModels(t, functionModel), + Check: assert.AssertThat(t, + resourceassert.FunctionJavaResource(t, functionModel.ResourceReference()). + HasNameString(id.Name()). + HasFunctionDefinitionString(definition). + HasFunctionLanguageString("JAVA"). + HasFullyQualifiedNameString(id.FullyQualifiedName()), + assert.Check(resource.TestCheckResourceAttr(functionModel.ResourceReference(), "arguments.0.arg_name", argName)), + assert.Check(resource.TestCheckResourceAttr(functionModel.ResourceReference(), "arguments.0.arg_data_type", dataType.ToSql())), + assert.Check(resource.TestCheckResourceAttr(functionModel.ResourceReference(), "arguments.0.arg_default_value", defaultValue)), + ), + }, }, }) } From 391bb3a8a0165d0bec73162f7de7bffe3d549600 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 17:40:46 +0100 Subject: [PATCH 42/45] Regenerate model builders --- .../config/model/function_java_model_ext.go | 23 +---------- .../config/model/function_java_model_gen.go | 19 +++------ .../model/function_javascript_model_gen.go | 40 +++++++++---------- .../config/model/function_python_model_gen.go | 14 +++---- .../config/model/function_scala_model_gen.go | 19 +++------ .../config/model/function_sql_model_gen.go | 40 +++++++++---------- 6 files changed, 59 insertions(+), 96 deletions(-) diff --git a/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go b/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go index b0c1414a42..8579ea981a 100644 --- a/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go +++ b/pkg/acceptance/bettertestspoc/config/model/function_java_model_ext.go @@ -5,8 +5,6 @@ import ( tfconfig "github.com/hashicorp/terraform-plugin-testing/config" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/config" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/provider/resources" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/datatypes" ) @@ -29,7 +27,7 @@ func FunctionJavaBasicInline( handler string, functionDefinition string, ) *FunctionJavaModel { - return FunctionJavaf(resourceName, id.DatabaseName(), handler, id.Name(), returnType.ToSql(), id.SchemaName()).WithFunctionDefinition(functionDefinition) + return FunctionJava(resourceName, id.DatabaseName(), handler, id.Name(), returnType.ToSql(), id.SchemaName()).WithFunctionDefinition(functionDefinition) } func FunctionJavaBasicStaged( @@ -40,27 +38,10 @@ func FunctionJavaBasicStaged( stageLocation string, pathOnStage string, ) *FunctionJavaModel { - return FunctionJavaf(resourceName, id.DatabaseName(), handler, id.Name(), returnType.ToSql(), id.SchemaName()). + return FunctionJava(resourceName, id.DatabaseName(), handler, id.Name(), returnType.ToSql(), id.SchemaName()). WithImport(stageLocation, pathOnStage) } -func FunctionJavaf( - resourceName string, - database string, - handler string, - name string, - returnType string, - schema string, -) *FunctionJavaModel { - f := &FunctionJavaModel{ResourceModelMeta: config.Meta(resourceName, resources.FunctionJava)} - f.WithDatabase(database) - f.WithHandler(handler) - f.WithName(name) - f.WithReturnType(returnType) - f.WithSchema(schema) - return f -} - func (f *FunctionJavaModel) WithArgument(argName string, argDataType datatypes.DataType) *FunctionJavaModel { return f.WithArgumentsValue( tfconfig.ObjectVariable( diff --git a/pkg/acceptance/bettertestspoc/config/model/function_java_model_gen.go b/pkg/acceptance/bettertestspoc/config/model/function_java_model_gen.go index 704f6b2bcf..309a53b0a9 100644 --- a/pkg/acceptance/bettertestspoc/config/model/function_java_model_gen.go +++ b/pkg/acceptance/bettertestspoc/config/model/function_java_model_gen.go @@ -26,7 +26,7 @@ type FunctionJavaModel struct { Name tfconfig.Variable `json:"name,omitempty"` NullInputBehavior tfconfig.Variable `json:"null_input_behavior,omitempty"` Packages tfconfig.Variable `json:"packages,omitempty"` - ReturnBehavior tfconfig.Variable `json:"return_behavior,omitempty"` + ReturnResultsBehavior tfconfig.Variable `json:"return_results_behavior,omitempty"` ReturnType tfconfig.Variable `json:"return_type,omitempty"` RuntimeVersion tfconfig.Variable `json:"runtime_version,omitempty"` Schema tfconfig.Variable `json:"schema,omitempty"` @@ -44,7 +44,6 @@ type FunctionJavaModel struct { func FunctionJava( resourceName string, database string, - functionDefinition string, handler string, name string, returnType string, @@ -52,7 +51,6 @@ func FunctionJava( ) *FunctionJavaModel { f := &FunctionJavaModel{ResourceModelMeta: config.Meta(resourceName, resources.FunctionJava)} f.WithDatabase(database) - f.WithFunctionDefinition(functionDefinition) f.WithHandler(handler) f.WithName(name) f.WithReturnType(returnType) @@ -62,7 +60,6 @@ func FunctionJava( func FunctionJavaWithDefaultMeta( database string, - functionDefinition string, handler string, name string, returnType string, @@ -70,7 +67,6 @@ func FunctionJavaWithDefaultMeta( ) *FunctionJavaModel { f := &FunctionJavaModel{ResourceModelMeta: config.DefaultMeta(resources.FunctionJava)} f.WithDatabase(database) - f.WithFunctionDefinition(functionDefinition) f.WithHandler(handler) f.WithName(name) f.WithReturnType(returnType) @@ -150,8 +146,8 @@ func (f *FunctionJavaModel) WithNullInputBehavior(nullInputBehavior string) *Fun // packages attribute type is not yet supported, so WithPackages can't be generated -func (f *FunctionJavaModel) WithReturnBehavior(returnBehavior string) *FunctionJavaModel { - f.ReturnBehavior = tfconfig.StringVariable(returnBehavior) +func (f *FunctionJavaModel) WithReturnResultsBehavior(returnResultsBehavior string) *FunctionJavaModel { + f.ReturnResultsBehavior = tfconfig.StringVariable(returnResultsBehavior) return f } @@ -172,10 +168,7 @@ func (f *FunctionJavaModel) WithSchema(schema string) *FunctionJavaModel { // secrets attribute type is not yet supported, so WithSecrets can't be generated -func (f *FunctionJavaModel) WithTargetPath(targetPath string) *FunctionJavaModel { - f.TargetPath = tfconfig.StringVariable(targetPath) - return f -} +// target_path attribute type is not yet supported, so WithTargetPath can't be generated func (f *FunctionJavaModel) WithTraceLevel(traceLevel string) *FunctionJavaModel { f.TraceLevel = tfconfig.StringVariable(traceLevel) @@ -266,8 +259,8 @@ func (f *FunctionJavaModel) WithPackagesValue(value tfconfig.Variable) *Function return f } -func (f *FunctionJavaModel) WithReturnBehaviorValue(value tfconfig.Variable) *FunctionJavaModel { - f.ReturnBehavior = value +func (f *FunctionJavaModel) WithReturnResultsBehaviorValue(value tfconfig.Variable) *FunctionJavaModel { + f.ReturnResultsBehavior = value return f } diff --git a/pkg/acceptance/bettertestspoc/config/model/function_javascript_model_gen.go b/pkg/acceptance/bettertestspoc/config/model/function_javascript_model_gen.go index 5d8ad68aec..742dee099b 100644 --- a/pkg/acceptance/bettertestspoc/config/model/function_javascript_model_gen.go +++ b/pkg/acceptance/bettertestspoc/config/model/function_javascript_model_gen.go @@ -10,22 +10,22 @@ import ( ) type FunctionJavascriptModel struct { - Arguments tfconfig.Variable `json:"arguments,omitempty"` - Comment tfconfig.Variable `json:"comment,omitempty"` - Database tfconfig.Variable `json:"database,omitempty"` - EnableConsoleOutput tfconfig.Variable `json:"enable_console_output,omitempty"` - FullyQualifiedName tfconfig.Variable `json:"fully_qualified_name,omitempty"` - FunctionDefinition tfconfig.Variable `json:"function_definition,omitempty"` - FunctionLanguage tfconfig.Variable `json:"function_language,omitempty"` - IsSecure tfconfig.Variable `json:"is_secure,omitempty"` - LogLevel tfconfig.Variable `json:"log_level,omitempty"` - MetricLevel tfconfig.Variable `json:"metric_level,omitempty"` - Name tfconfig.Variable `json:"name,omitempty"` - NullInputBehavior tfconfig.Variable `json:"null_input_behavior,omitempty"` - ReturnBehavior tfconfig.Variable `json:"return_behavior,omitempty"` - ReturnType tfconfig.Variable `json:"return_type,omitempty"` - Schema tfconfig.Variable `json:"schema,omitempty"` - TraceLevel tfconfig.Variable `json:"trace_level,omitempty"` + Arguments tfconfig.Variable `json:"arguments,omitempty"` + Comment tfconfig.Variable `json:"comment,omitempty"` + Database tfconfig.Variable `json:"database,omitempty"` + EnableConsoleOutput tfconfig.Variable `json:"enable_console_output,omitempty"` + FullyQualifiedName tfconfig.Variable `json:"fully_qualified_name,omitempty"` + FunctionDefinition tfconfig.Variable `json:"function_definition,omitempty"` + FunctionLanguage tfconfig.Variable `json:"function_language,omitempty"` + IsSecure tfconfig.Variable `json:"is_secure,omitempty"` + LogLevel tfconfig.Variable `json:"log_level,omitempty"` + MetricLevel tfconfig.Variable `json:"metric_level,omitempty"` + Name tfconfig.Variable `json:"name,omitempty"` + NullInputBehavior tfconfig.Variable `json:"null_input_behavior,omitempty"` + ReturnResultsBehavior tfconfig.Variable `json:"return_results_behavior,omitempty"` + ReturnType tfconfig.Variable `json:"return_type,omitempty"` + Schema tfconfig.Variable `json:"schema,omitempty"` + TraceLevel tfconfig.Variable `json:"trace_level,omitempty"` *config.ResourceModelMeta } @@ -128,8 +128,8 @@ func (f *FunctionJavascriptModel) WithNullInputBehavior(nullInputBehavior string return f } -func (f *FunctionJavascriptModel) WithReturnBehavior(returnBehavior string) *FunctionJavascriptModel { - f.ReturnBehavior = tfconfig.StringVariable(returnBehavior) +func (f *FunctionJavascriptModel) WithReturnResultsBehavior(returnResultsBehavior string) *FunctionJavascriptModel { + f.ReturnResultsBehavior = tfconfig.StringVariable(returnResultsBehavior) return f } @@ -212,8 +212,8 @@ func (f *FunctionJavascriptModel) WithNullInputBehaviorValue(value tfconfig.Vari return f } -func (f *FunctionJavascriptModel) WithReturnBehaviorValue(value tfconfig.Variable) *FunctionJavascriptModel { - f.ReturnBehavior = value +func (f *FunctionJavascriptModel) WithReturnResultsBehaviorValue(value tfconfig.Variable) *FunctionJavascriptModel { + f.ReturnResultsBehavior = value return f } diff --git a/pkg/acceptance/bettertestspoc/config/model/function_python_model_gen.go b/pkg/acceptance/bettertestspoc/config/model/function_python_model_gen.go index 9d0ffbd348..17ae5eccaf 100644 --- a/pkg/acceptance/bettertestspoc/config/model/function_python_model_gen.go +++ b/pkg/acceptance/bettertestspoc/config/model/function_python_model_gen.go @@ -27,7 +27,7 @@ type FunctionPythonModel struct { Name tfconfig.Variable `json:"name,omitempty"` NullInputBehavior tfconfig.Variable `json:"null_input_behavior,omitempty"` Packages tfconfig.Variable `json:"packages,omitempty"` - ReturnBehavior tfconfig.Variable `json:"return_behavior,omitempty"` + ReturnResultsBehavior tfconfig.Variable `json:"return_results_behavior,omitempty"` ReturnType tfconfig.Variable `json:"return_type,omitempty"` RuntimeVersion tfconfig.Variable `json:"runtime_version,omitempty"` Schema tfconfig.Variable `json:"schema,omitempty"` @@ -44,7 +44,6 @@ type FunctionPythonModel struct { func FunctionPython( resourceName string, database string, - functionDefinition string, handler string, name string, returnType string, @@ -53,7 +52,6 @@ func FunctionPython( ) *FunctionPythonModel { f := &FunctionPythonModel{ResourceModelMeta: config.Meta(resourceName, resources.FunctionPython)} f.WithDatabase(database) - f.WithFunctionDefinition(functionDefinition) f.WithHandler(handler) f.WithName(name) f.WithReturnType(returnType) @@ -64,7 +62,6 @@ func FunctionPython( func FunctionPythonWithDefaultMeta( database string, - functionDefinition string, handler string, name string, returnType string, @@ -73,7 +70,6 @@ func FunctionPythonWithDefaultMeta( ) *FunctionPythonModel { f := &FunctionPythonModel{ResourceModelMeta: config.DefaultMeta(resources.FunctionPython)} f.WithDatabase(database) - f.WithFunctionDefinition(functionDefinition) f.WithHandler(handler) f.WithName(name) f.WithReturnType(returnType) @@ -159,8 +155,8 @@ func (f *FunctionPythonModel) WithNullInputBehavior(nullInputBehavior string) *F // packages attribute type is not yet supported, so WithPackages can't be generated -func (f *FunctionPythonModel) WithReturnBehavior(returnBehavior string) *FunctionPythonModel { - f.ReturnBehavior = tfconfig.StringVariable(returnBehavior) +func (f *FunctionPythonModel) WithReturnResultsBehavior(returnResultsBehavior string) *FunctionPythonModel { + f.ReturnResultsBehavior = tfconfig.StringVariable(returnResultsBehavior) return f } @@ -275,8 +271,8 @@ func (f *FunctionPythonModel) WithPackagesValue(value tfconfig.Variable) *Functi return f } -func (f *FunctionPythonModel) WithReturnBehaviorValue(value tfconfig.Variable) *FunctionPythonModel { - f.ReturnBehavior = value +func (f *FunctionPythonModel) WithReturnResultsBehaviorValue(value tfconfig.Variable) *FunctionPythonModel { + f.ReturnResultsBehavior = value return f } diff --git a/pkg/acceptance/bettertestspoc/config/model/function_scala_model_gen.go b/pkg/acceptance/bettertestspoc/config/model/function_scala_model_gen.go index 017c397af3..070933fd4e 100644 --- a/pkg/acceptance/bettertestspoc/config/model/function_scala_model_gen.go +++ b/pkg/acceptance/bettertestspoc/config/model/function_scala_model_gen.go @@ -26,7 +26,7 @@ type FunctionScalaModel struct { Name tfconfig.Variable `json:"name,omitempty"` NullInputBehavior tfconfig.Variable `json:"null_input_behavior,omitempty"` Packages tfconfig.Variable `json:"packages,omitempty"` - ReturnBehavior tfconfig.Variable `json:"return_behavior,omitempty"` + ReturnResultsBehavior tfconfig.Variable `json:"return_results_behavior,omitempty"` ReturnType tfconfig.Variable `json:"return_type,omitempty"` RuntimeVersion tfconfig.Variable `json:"runtime_version,omitempty"` Schema tfconfig.Variable `json:"schema,omitempty"` @@ -44,7 +44,6 @@ type FunctionScalaModel struct { func FunctionScala( resourceName string, database string, - functionDefinition string, handler string, name string, returnType string, @@ -53,7 +52,6 @@ func FunctionScala( ) *FunctionScalaModel { f := &FunctionScalaModel{ResourceModelMeta: config.Meta(resourceName, resources.FunctionScala)} f.WithDatabase(database) - f.WithFunctionDefinition(functionDefinition) f.WithHandler(handler) f.WithName(name) f.WithReturnType(returnType) @@ -64,7 +62,6 @@ func FunctionScala( func FunctionScalaWithDefaultMeta( database string, - functionDefinition string, handler string, name string, returnType string, @@ -73,7 +70,6 @@ func FunctionScalaWithDefaultMeta( ) *FunctionScalaModel { f := &FunctionScalaModel{ResourceModelMeta: config.DefaultMeta(resources.FunctionScala)} f.WithDatabase(database) - f.WithFunctionDefinition(functionDefinition) f.WithHandler(handler) f.WithName(name) f.WithReturnType(returnType) @@ -154,8 +150,8 @@ func (f *FunctionScalaModel) WithNullInputBehavior(nullInputBehavior string) *Fu // packages attribute type is not yet supported, so WithPackages can't be generated -func (f *FunctionScalaModel) WithReturnBehavior(returnBehavior string) *FunctionScalaModel { - f.ReturnBehavior = tfconfig.StringVariable(returnBehavior) +func (f *FunctionScalaModel) WithReturnResultsBehavior(returnResultsBehavior string) *FunctionScalaModel { + f.ReturnResultsBehavior = tfconfig.StringVariable(returnResultsBehavior) return f } @@ -176,10 +172,7 @@ func (f *FunctionScalaModel) WithSchema(schema string) *FunctionScalaModel { // secrets attribute type is not yet supported, so WithSecrets can't be generated -func (f *FunctionScalaModel) WithTargetPath(targetPath string) *FunctionScalaModel { - f.TargetPath = tfconfig.StringVariable(targetPath) - return f -} +// target_path attribute type is not yet supported, so WithTargetPath can't be generated func (f *FunctionScalaModel) WithTraceLevel(traceLevel string) *FunctionScalaModel { f.TraceLevel = tfconfig.StringVariable(traceLevel) @@ -270,8 +263,8 @@ func (f *FunctionScalaModel) WithPackagesValue(value tfconfig.Variable) *Functio return f } -func (f *FunctionScalaModel) WithReturnBehaviorValue(value tfconfig.Variable) *FunctionScalaModel { - f.ReturnBehavior = value +func (f *FunctionScalaModel) WithReturnResultsBehaviorValue(value tfconfig.Variable) *FunctionScalaModel { + f.ReturnResultsBehavior = value return f } diff --git a/pkg/acceptance/bettertestspoc/config/model/function_sql_model_gen.go b/pkg/acceptance/bettertestspoc/config/model/function_sql_model_gen.go index 14cbbe9136..0733c2add4 100644 --- a/pkg/acceptance/bettertestspoc/config/model/function_sql_model_gen.go +++ b/pkg/acceptance/bettertestspoc/config/model/function_sql_model_gen.go @@ -10,22 +10,22 @@ import ( ) type FunctionSqlModel struct { - Arguments tfconfig.Variable `json:"arguments,omitempty"` - Comment tfconfig.Variable `json:"comment,omitempty"` - Database tfconfig.Variable `json:"database,omitempty"` - EnableConsoleOutput tfconfig.Variable `json:"enable_console_output,omitempty"` - FullyQualifiedName tfconfig.Variable `json:"fully_qualified_name,omitempty"` - FunctionDefinition tfconfig.Variable `json:"function_definition,omitempty"` - FunctionLanguage tfconfig.Variable `json:"function_language,omitempty"` - IsSecure tfconfig.Variable `json:"is_secure,omitempty"` - LogLevel tfconfig.Variable `json:"log_level,omitempty"` - MetricLevel tfconfig.Variable `json:"metric_level,omitempty"` - Name tfconfig.Variable `json:"name,omitempty"` - NullInputBehavior tfconfig.Variable `json:"null_input_behavior,omitempty"` - ReturnBehavior tfconfig.Variable `json:"return_behavior,omitempty"` - ReturnType tfconfig.Variable `json:"return_type,omitempty"` - Schema tfconfig.Variable `json:"schema,omitempty"` - TraceLevel tfconfig.Variable `json:"trace_level,omitempty"` + Arguments tfconfig.Variable `json:"arguments,omitempty"` + Comment tfconfig.Variable `json:"comment,omitempty"` + Database tfconfig.Variable `json:"database,omitempty"` + EnableConsoleOutput tfconfig.Variable `json:"enable_console_output,omitempty"` + FullyQualifiedName tfconfig.Variable `json:"fully_qualified_name,omitempty"` + FunctionDefinition tfconfig.Variable `json:"function_definition,omitempty"` + FunctionLanguage tfconfig.Variable `json:"function_language,omitempty"` + IsSecure tfconfig.Variable `json:"is_secure,omitempty"` + LogLevel tfconfig.Variable `json:"log_level,omitempty"` + MetricLevel tfconfig.Variable `json:"metric_level,omitempty"` + Name tfconfig.Variable `json:"name,omitempty"` + NullInputBehavior tfconfig.Variable `json:"null_input_behavior,omitempty"` + ReturnResultsBehavior tfconfig.Variable `json:"return_results_behavior,omitempty"` + ReturnType tfconfig.Variable `json:"return_type,omitempty"` + Schema tfconfig.Variable `json:"schema,omitempty"` + TraceLevel tfconfig.Variable `json:"trace_level,omitempty"` *config.ResourceModelMeta } @@ -128,8 +128,8 @@ func (f *FunctionSqlModel) WithNullInputBehavior(nullInputBehavior string) *Func return f } -func (f *FunctionSqlModel) WithReturnBehavior(returnBehavior string) *FunctionSqlModel { - f.ReturnBehavior = tfconfig.StringVariable(returnBehavior) +func (f *FunctionSqlModel) WithReturnResultsBehavior(returnResultsBehavior string) *FunctionSqlModel { + f.ReturnResultsBehavior = tfconfig.StringVariable(returnResultsBehavior) return f } @@ -212,8 +212,8 @@ func (f *FunctionSqlModel) WithNullInputBehaviorValue(value tfconfig.Variable) * return f } -func (f *FunctionSqlModel) WithReturnBehaviorValue(value tfconfig.Variable) *FunctionSqlModel { - f.ReturnBehavior = value +func (f *FunctionSqlModel) WithReturnResultsBehaviorValue(value tfconfig.Variable) *FunctionSqlModel { + f.ReturnResultsBehavior = value return f } From 20601b190475aa6378d8db95935130919d84ea39 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 17:42:26 +0100 Subject: [PATCH 43/45] Run make pre-push --- docs/resources/function_java.md | 15 ++++++++++++++- docs/resources/function_javascript.md | 4 ++++ docs/resources/function_python.md | 4 ++++ docs/resources/function_scala.md | 15 ++++++++++++++- docs/resources/function_sql.md | 4 ++++ pkg/resources/function_java.go | 11 ----------- 6 files changed, 40 insertions(+), 13 deletions(-) diff --git a/docs/resources/function_java.md b/docs/resources/function_java.md index a17ac864ea..820bb4e63d 100644 --- a/docs/resources/function_java.md +++ b/docs/resources/function_java.md @@ -38,7 +38,7 @@ Resource used to manage java function objects. For more information, check [func - `return_results_behavior` (String) Specifies the behavior of the function when returning results. Valid values are (case-insensitive): `VOLATILE` | `IMMUTABLE`. - `runtime_version` (String) Specifies the Java JDK runtime version to use. The supported versions of Java are 11.x and 17.x. If RUNTIME_VERSION is not set, Java JDK 11 is used. - `secrets` (Block Set) Assigns the names of [secrets](https://docs.snowflake.com/en/sql-reference/sql/create-secret) to variables so that you can use the variables to reference the secrets when retrieving information from secrets in handler code. Secrets you specify here must be allowed by the [external access integration](https://docs.snowflake.com/en/sql-reference/sql/create-external-access-integration) specified as a value of this CREATE FUNCTION command’s EXTERNAL_ACCESS_INTEGRATIONS parameter. (see [below for nested schema](#nestedblock--secrets)) -- `target_path` (String) The name of the handler method or class. If the handler is for a scalar UDF, returning a non-tabular value, the HANDLER value should be a method name, as in the following form: `MyClass.myMethod`. If the handler is for a tabular UDF, the HANDLER value should be the name of a handler class. +- `target_path` (Block Set, Max: 1) The name of the handler method or class. If the handler is for a scalar UDF, returning a non-tabular value, the HANDLER value should be a method name, as in the following form: `MyClass.myMethod`. If the handler is for a tabular UDF, the HANDLER value should be the name of a handler class. (see [below for nested schema](#nestedblock--target_path)) - `trace_level` (String) Trace level value to use when generating/filtering trace events For more information, check [TRACE_LEVEL docs](https://docs.snowflake.com/en/sql-reference/parameters#trace-level). ### Read-Only @@ -57,6 +57,10 @@ Required: - `arg_data_type` (String) The argument type. - `arg_name` (String) The argument name. +Optional: + +- `arg_default_value` (String) Optional default value for the argument. For text values use single quotes. Numeric values can be unquoted. External changes for this field won't be detected. In case you want to apply external changes, you can re-create the resource manually using "terraform taint". + ### Nested Schema for `imports` @@ -76,6 +80,15 @@ Required: - `secret_variable_name` (String) The variable that will be used in handler code when retrieving information from the secret. + +### Nested Schema for `target_path` + +Required: + +- `path_on_stage` (String) Path for import on stage, without the leading `/`. +- `stage_location` (String) Stage location without leading `@`. To use your user's stage set this to `~`, otherwise pass fully qualified name of the stage (with every part contained in double quotes or use `snowflake_stage..fully_qualified_name` if you manage this stage through terraform). + + ### Nested Schema for `parameters` diff --git a/docs/resources/function_javascript.md b/docs/resources/function_javascript.md index e7ae472ed8..1619ab3a06 100644 --- a/docs/resources/function_javascript.md +++ b/docs/resources/function_javascript.md @@ -50,6 +50,10 @@ Required: - `arg_data_type` (String) The argument type. - `arg_name` (String) The argument name. +Optional: + +- `arg_default_value` (String) Optional default value for the argument. For text values use single quotes. Numeric values can be unquoted. External changes for this field won't be detected. In case you want to apply external changes, you can re-create the resource manually using "terraform taint". + ### Nested Schema for `parameters` diff --git a/docs/resources/function_python.md b/docs/resources/function_python.md index ca0b1008df..21e4244789 100644 --- a/docs/resources/function_python.md +++ b/docs/resources/function_python.md @@ -57,6 +57,10 @@ Required: - `arg_data_type` (String) The argument type. - `arg_name` (String) The argument name. +Optional: + +- `arg_default_value` (String) Optional default value for the argument. For text values use single quotes. Numeric values can be unquoted. External changes for this field won't be detected. In case you want to apply external changes, you can re-create the resource manually using "terraform taint". + ### Nested Schema for `imports` diff --git a/docs/resources/function_scala.md b/docs/resources/function_scala.md index 5f3f53f4ed..01226e5512 100644 --- a/docs/resources/function_scala.md +++ b/docs/resources/function_scala.md @@ -38,7 +38,7 @@ Resource used to manage scala function objects. For more information, check [fun - `packages` (Set of String) The name and version number of Snowflake system packages required as dependencies. The value should be of the form `package_name:version_number`, where `package_name` is `snowflake_domain:package`. - `return_results_behavior` (String) Specifies the behavior of the function when returning results. Valid values are (case-insensitive): `VOLATILE` | `IMMUTABLE`. - `secrets` (Block Set) Assigns the names of [secrets](https://docs.snowflake.com/en/sql-reference/sql/create-secret) to variables so that you can use the variables to reference the secrets when retrieving information from secrets in handler code. Secrets you specify here must be allowed by the [external access integration](https://docs.snowflake.com/en/sql-reference/sql/create-external-access-integration) specified as a value of this CREATE FUNCTION command’s EXTERNAL_ACCESS_INTEGRATIONS parameter. (see [below for nested schema](#nestedblock--secrets)) -- `target_path` (String) The name of the handler method or class. If the handler is for a scalar UDF, returning a non-tabular value, the HANDLER value should be a method name, as in the following form: `MyClass.myMethod`. +- `target_path` (Block Set, Max: 1) The name of the handler method or class. If the handler is for a scalar UDF, returning a non-tabular value, the HANDLER value should be a method name, as in the following form: `MyClass.myMethod`. (see [below for nested schema](#nestedblock--target_path)) - `trace_level` (String) Trace level value to use when generating/filtering trace events For more information, check [TRACE_LEVEL docs](https://docs.snowflake.com/en/sql-reference/parameters#trace-level). ### Read-Only @@ -57,6 +57,10 @@ Required: - `arg_data_type` (String) The argument type. - `arg_name` (String) The argument name. +Optional: + +- `arg_default_value` (String) Optional default value for the argument. For text values use single quotes. Numeric values can be unquoted. External changes for this field won't be detected. In case you want to apply external changes, you can re-create the resource manually using "terraform taint". + ### Nested Schema for `imports` @@ -76,6 +80,15 @@ Required: - `secret_variable_name` (String) The variable that will be used in handler code when retrieving information from the secret. + +### Nested Schema for `target_path` + +Required: + +- `path_on_stage` (String) Path for import on stage, without the leading `/`. +- `stage_location` (String) Stage location without leading `@`. To use your user's stage set this to `~`, otherwise pass fully qualified name of the stage (with every part contained in double quotes or use `snowflake_stage..fully_qualified_name` if you manage this stage through terraform). + + ### Nested Schema for `parameters` diff --git a/docs/resources/function_sql.md b/docs/resources/function_sql.md index ad371a20f5..4a48191740 100644 --- a/docs/resources/function_sql.md +++ b/docs/resources/function_sql.md @@ -50,6 +50,10 @@ Required: - `arg_data_type` (String) The argument type. - `arg_name` (String) The argument name. +Optional: + +- `arg_default_value` (String) Optional default value for the argument. For text values use single quotes. Numeric values can be unquoted. External changes for this field won't be detected. In case you want to apply external changes, you can re-create the resource manually using "terraform taint". + ### Nested Schema for `parameters` diff --git a/pkg/resources/function_java.go b/pkg/resources/function_java.go index c22b862a0f..b1e60da7cf 100644 --- a/pkg/resources/function_java.go +++ b/pkg/resources/function_java.go @@ -103,17 +103,6 @@ func CreateContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta } return ReadContextFunctionJava(ctx, d, meta) - - //if v, ok := d.GetOk("comment"); ok { - // request.WithComment(v.(string)) - //} - //if _, ok := d.GetOk("packages"); ok { - // var packages []sdk.FunctionPackageRequest - // for _, item := range d.Get("packages").([]interface{}) { - // packages = append(packages, *sdk.NewFunctionPackageRequest().WithPackage(item.(string))) - // } - // request.WithPackages(packages) - //} } func ReadContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { From 2eef7792c134bb91004f929f20a1f78a7d4e14cf Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 19:24:50 +0100 Subject: [PATCH 44/45] Fix procedures integration tests --- .../helpers/function_setup_helpers.go | 32 +++++++---- pkg/sdk/testint/functions_integration_test.go | 54 +++---------------- .../testint/procedures_integration_test.go | 39 +++++++++++++- 3 files changed, 66 insertions(+), 59 deletions(-) diff --git a/pkg/acceptance/helpers/function_setup_helpers.go b/pkg/acceptance/helpers/function_setup_helpers.go index 132bd34706..d0f34d6768 100644 --- a/pkg/acceptance/helpers/function_setup_helpers.go +++ b/pkg/acceptance/helpers/function_setup_helpers.go @@ -67,8 +67,19 @@ func (c *TestClient) CreateSampleJavaFunctionAndJarInLocation(t *testing.T, stag } } -// TODO [SNOW-1348106]: adjust to switching location too -func (c *TestClient) CreateSampleJavaProcedureAndJar(t *testing.T) *TmpFunction { +func (c *TestClient) CreateSampleJavaProcedureAndJarOnUserStage(t *testing.T) *TmpFunction { + t.Helper() + + return c.CreateSampleJavaProcedureAndJarInLocation(t, "@~") +} + +func (c *TestClient) CreateSampleJavaProcedureAndJarOnStage(t *testing.T, stage *sdk.Stage) *TmpFunction { + t.Helper() + + return c.CreateSampleJavaProcedureAndJarInLocation(t, stage.Location()) +} + +func (c *TestClient) CreateSampleJavaProcedureAndJarInLocation(t *testing.T, stageLocation string) *TmpFunction { t.Helper() ctx := context.Background() @@ -84,7 +95,7 @@ func (c *TestClient) CreateSampleJavaProcedureAndJar(t *testing.T) *TmpFunction handler := fmt.Sprintf("%s.%s", className, funcName) definition := c.Procedure.SampleJavaDefinition(t, className, funcName, argName) jarName := fmt.Sprintf("tf-%d-%s.jar", time.Now().Unix(), random.AlphaN(5)) - targetPath := fmt.Sprintf("@~/%s", jarName) + targetPath := fmt.Sprintf("%s/%s", stageLocation, jarName) packages := []sdk.ProcedurePackageRequest{*sdk.NewProcedurePackageRequest("com.snowflake:snowpark:1.14.0")} request := sdk.NewCreateForJavaProcedureRequest(id.SchemaObjectId(), *returns, "11", packages, handler). @@ -95,15 +106,16 @@ func (c *TestClient) CreateSampleJavaProcedureAndJar(t *testing.T) *TmpFunction err := c.context.client.Procedures.CreateForJava(ctx, request) require.NoError(t, err) t.Cleanup(c.Procedure.DropProcedureFunc(t, id)) - t.Cleanup(c.Stage.RemoveFromUserStageFunc(t, jarName)) + t.Cleanup(c.Stage.RemoveFromStageFunc(t, stageLocation, jarName)) return &TmpFunction{ - FunctionId: id, - ClassName: className, - FuncName: funcName, - ArgName: argName, - ArgType: dataType, - JarName: jarName, + FunctionId: id, + ClassName: className, + FuncName: funcName, + ArgName: argName, + ArgType: dataType, + JarName: jarName, + StageLocation: stageLocation, } } diff --git a/pkg/sdk/testint/functions_integration_test.go b/pkg/sdk/testint/functions_integration_test.go index dae54dc656..022ba7592a 100644 --- a/pkg/sdk/testint/functions_integration_test.go +++ b/pkg/sdk/testint/functions_integration_test.go @@ -48,11 +48,7 @@ func TestInt_Functions(t *testing.T) { externalAccessIntegration, externalAccessIntegrationCleanup := testClientHelper().ExternalAccessIntegration.CreateExternalAccessIntegrationWithNetworkRuleAndSecret(t, networkRule.ID(), secret.ID()) t.Cleanup(externalAccessIntegrationCleanup) - stage, stageCleanup := testClientHelper().Stage.CreateStage(t) - t.Cleanup(stageCleanup) - tmpJavaFunction := testClientHelper().CreateSampleJavaFunctionAndJarOnUserStage(t) - tmpJavaFunctionDifferentStage := testClientHelper().CreateSampleJavaFunctionAndJarOnStage(t, stage) tmpPythonFunction := testClientHelper().CreateSamplePythonFunctionAndModule(t) assertParametersSet := func(t *testing.T, functionParametersAssert *objectparametersassert.FunctionParametersAssert) { @@ -408,6 +404,11 @@ func TestInt_Functions(t *testing.T) { }) t.Run("create function for Java - different stage", func(t *testing.T) { + stage, stageCleanup := testClientHelper().Stage.CreateStage(t) + t.Cleanup(stageCleanup) + + tmpJavaFunctionDifferentStage := testClientHelper().CreateSampleJavaFunctionAndJarOnStage(t, stage) + dataType := tmpJavaFunctionDifferentStage.ArgType id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArgumentsNewDataTypes(dataType) @@ -429,57 +430,14 @@ func TestInt_Functions(t *testing.T) { function, err := client.Functions.ShowByID(ctx, id) require.NoError(t, err) - assertions.AssertThatObject(t, objectassert.FunctionFromObject(t, function). - HasCreatedOnNotEmpty(). - HasName(id.Name()). - HasSchemaName(id.SchemaName()). - HasIsBuiltin(false). - HasIsAggregate(false). - HasIsAnsi(false). - HasMinNumArguments(1). - HasMaxNumArguments(1). - HasArgumentsOld([]sdk.DataType{sdk.LegacyDataTypeFrom(dataType)}). - HasArgumentsRaw(fmt.Sprintf(`%[1]s(%[2]s) RETURN %[2]s`, function.ID().Name(), dataType.ToLegacyDataTypeSql())). - HasDescription(sdk.DefaultFunctionComment). - HasCatalogName(id.DatabaseName()). - HasIsTableFunction(false). - HasValidForClustering(false). - HasIsSecure(false). - HasExternalAccessIntegrations(""). - HasSecrets(""). - HasIsExternalFunction(false). - HasLanguage("JAVA"). - HasIsMemoizable(false). - HasIsDataMetric(false), - ) - assertions.AssertThatObject(t, objectassert.FunctionDetails(t, function.ID()). - HasSignature(fmt.Sprintf(`(%s %s)`, argName, dataType.ToLegacyDataTypeSql())). - HasReturns(dataType.ToSql()). - HasReturnDataType(dataType). - HasReturnNotNull(false). - HasLanguage("JAVA"). - HasBodyNil(). - HasNullHandling(string(sdk.NullInputBehaviorCalledOnNullInput)). - HasVolatility(string(sdk.ReturnResultsBehaviorVolatile)). - HasExternalAccessIntegrationsNil(). - HasSecretsNil(). HasImports(fmt.Sprintf(`[@"%s"."%s".%s/%s]`, stage.ID().DatabaseName(), stage.ID().SchemaName(), stage.ID().Name(), tmpJavaFunctionDifferentStage.JarName)). HasExactlyImportsNormalizedInAnyOrder(sdk.NormalizedPath{ StageLocation: stage.ID().FullyQualifiedName(), PathOnStage: tmpJavaFunctionDifferentStage.JarName, }). HasHandler(handler). - HasRuntimeVersionNil(). - HasPackages(`[]`). HasTargetPathNil(). - HasNormalizedTargetPathNil(). - HasInstalledPackagesNil(). - HasIsAggregateNil(), - ) - - assertions.AssertThatObject(t, objectparametersassert.FunctionParameters(t, id). - HasAllDefaults(). - HasAllDefaultsExplicit(), + HasNormalizedTargetPathNil(), ) }) diff --git a/pkg/sdk/testint/procedures_integration_test.go b/pkg/sdk/testint/procedures_integration_test.go index 2a69ef42c2..6e0298308e 100644 --- a/pkg/sdk/testint/procedures_integration_test.go +++ b/pkg/sdk/testint/procedures_integration_test.go @@ -37,7 +37,7 @@ func TestInt_Procedures(t *testing.T) { externalAccessIntegration, externalAccessIntegrationCleanup := testClientHelper().ExternalAccessIntegration.CreateExternalAccessIntegrationWithNetworkRuleAndSecret(t, networkRule.ID(), secret.ID()) t.Cleanup(externalAccessIntegrationCleanup) - tmpJavaProcedure := testClientHelper().CreateSampleJavaProcedureAndJar(t) + tmpJavaProcedure := testClientHelper().CreateSampleJavaProcedureAndJarOnUserStage(t) tmpPythonFunction := testClientHelper().CreateSamplePythonFunctionAndModule(t) assertParametersSet := func(t *testing.T, procedureParametersAssert *objectparametersassert.ProcedureParametersAssert) { @@ -354,6 +354,43 @@ func TestInt_Procedures(t *testing.T) { ) }) + t.Run("create procedure for Java - different stage", func(t *testing.T) { + stage, stageCleanup := testClientHelper().Stage.CreateStage(t) + t.Cleanup(stageCleanup) + + tmpJavaProcedureDifferentStage := testClientHelper().CreateSampleJavaProcedureAndJarOnStage(t, stage) + + dataType := tmpJavaProcedureDifferentStage.ArgType + id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArgumentsNewDataTypes(dataType) + + argName := "x" + argument := sdk.NewProcedureArgumentRequest(argName, dataType) + dt := sdk.NewProcedureReturnsResultDataTypeRequest(dataType) + returns := sdk.NewProcedureReturnsRequest().WithResultDataType(*dt) + handler := tmpJavaProcedureDifferentStage.JavaHandler() + importPath := tmpJavaProcedureDifferentStage.JarLocation() + packages := []sdk.ProcedurePackageRequest{ + *sdk.NewProcedurePackageRequest("com.snowflake:snowpark:1.14.0"), + } + + requestStaged := sdk.NewCreateForJavaProcedureRequest(id.SchemaObjectId(), *returns, "11", packages, handler). + WithArguments([]sdk.ProcedureArgumentRequest{*argument}). + WithImports([]sdk.ProcedureImportRequest{*sdk.NewProcedureImportRequest(importPath)}) + + err := client.Procedures.CreateForJava(ctx, requestStaged) + require.NoError(t, err) + t.Cleanup(testClientHelper().Procedure.DropProcedureFunc(t, id)) + + function, err := client.Procedures.ShowByID(ctx, id) + require.NoError(t, err) + + assertions.AssertThatObject(t, objectassert.ProcedureDetails(t, function.ID()). + HasImports(fmt.Sprintf(`[@"%s"."%s".%s/%s]`, stage.ID().DatabaseName(), stage.ID().SchemaName(), stage.ID().Name(), tmpJavaProcedureDifferentStage.JarName)). + HasHandler(handler). + HasTargetPathNil(), + ) + }) + t.Run("create procedure for Javascript - inline minimal", func(t *testing.T) { dataType := testdatatypes.DataTypeFloat id := testClientHelper().Ids.RandomSchemaObjectIdentifierWithArguments(sdk.LegacyDataTypeFrom(dataType)) From afaba5a68d6a21b0db8ea520de1d1a7e90803dd2 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 11 Dec 2024 21:07:06 +0100 Subject: [PATCH 45/45] Fix after review --- pkg/resources/function_commons.go | 10 ++++++---- pkg/sdk/functions_ext.go | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pkg/resources/function_commons.go b/pkg/resources/function_commons.go index f9583cce5b..ea005da2c2 100644 --- a/pkg/resources/function_commons.go +++ b/pkg/resources/function_commons.go @@ -7,6 +7,7 @@ import ( "log" "slices" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/schemas" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" @@ -545,6 +546,8 @@ func readFunctionArgumentsCommon(d *schema.ResourceData, args []sdk.NormalizedAr // TODO [SNOW-1348103]: handle empty list return nil } + // We do it the unusual way because the default values are not returned by SF. + // We update what we have - leaving the defaults unchanged. if currentArgs, ok := d.Get("arguments").([]map[string]any); !ok { return fmt.Errorf("arguments must be a list") } else { @@ -561,13 +564,12 @@ func readFunctionImportsCommon(d *schema.ResourceData, imports []sdk.NormalizedP // don't do anything if imports not present return nil } - imps := make([]map[string]any, len(imports)) - for i, imp := range imports { - imps[i] = map[string]any{ + imps := collections.Map(imports, func(imp sdk.NormalizedPath) map[string]any { + return map[string]any{ "stage_location": imp.StageLocation, "path_on_stage": imp.PathOnStage, } - } + }) return d.Set("imports", imps) } diff --git a/pkg/sdk/functions_ext.go b/pkg/sdk/functions_ext.go index a294b74c6d..2a87c2a458 100644 --- a/pkg/sdk/functions_ext.go +++ b/pkg/sdk/functions_ext.go @@ -126,6 +126,7 @@ func functionDetailsFromRows(rows []FunctionDetail) (*FunctionDetails, error) { return v, errors.Join(errs...) } +// TODO [SNOW-1850370]: use ParseCommaSeparatedStringArray + collections.MapErr combo here and in other methods? func parseFunctionDetailsImport(details FunctionDetails) ([]NormalizedPath, error) { functionDetailsImports := make([]NormalizedPath, 0) if details.Imports == nil || *details.Imports == "" || *details.Imports == "[]" {