Skip to content

Commit

Permalink
feat: Add all other functions and procedures implementations (#3275)
Browse files Browse the repository at this point in the history
Mimic the implementation in all other functions and procedures

Next PRs:
- docs + migration guide
- import
- acceptance tests
- missing aggregate, return not null
- validations
  • Loading branch information
sfc-gh-asawicki authored Dec 12, 2024
1 parent 53e7a0a commit 7a6f68d
Show file tree
Hide file tree
Showing 13 changed files with 1,002 additions and 231 deletions.
1 change: 0 additions & 1 deletion docs/resources/function_sql.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ Resource used to manage sql function objects. For more information, check [funct
- `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`.
- `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).

Expand Down
110 changes: 108 additions & 2 deletions pkg/resources/function_commons.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import (
"errors"
"fmt"
"log"
"reflect"
"slices"

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers"
"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"
Expand Down Expand Up @@ -86,7 +88,6 @@ var (
"is_secure",
"arguments",
"return_type",
"null_input_behavior",
"return_results_behavior",
"comment",
"function_definition",
Expand All @@ -98,6 +99,7 @@ var (
javaFunctionSchemaDefinition = functionSchemaDef{
additionalArguments: []string{
"runtime_version",
"null_input_behavior",
"imports",
"packages",
"handler",
Expand All @@ -115,14 +117,17 @@ var (
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{},
additionalArguments: []string{
"null_input_behavior",
},
functionDefinitionDescription: functionDefinitionTemplate("JavaScript", "https://docs.snowflake.com/en/developer-guide/udf/javascript/udf-javascript-introduction"),
functionDefinitionRequired: true,
}
pythonFunctionSchemaDefinition = functionSchemaDef{
additionalArguments: []string{
"is_aggregate",
"runtime_version",
"null_input_behavior",
"imports",
"packages",
"handler",
Expand All @@ -139,6 +144,7 @@ var (
scalaFunctionSchemaDefinition = functionSchemaDef{
additionalArguments: []string{
"runtime_version",
"null_input_behavior",
"imports",
"packages",
"handler",
Expand Down Expand Up @@ -405,6 +411,106 @@ func DeleteFunction(ctx context.Context, d *schema.ResourceData, meta any) diag.
return nil
}

func UpdateFunction(language string, readFunc func(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics) func(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
return func(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 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
}

// Batch SET operations and UNSET operations
setRequest := sdk.NewFunctionSetRequest()
unsetRequest := sdk.NewFunctionUnsetRequest()

_ = stringAttributeUpdate(d, "comment", &setRequest.Comment, &unsetRequest.Comment)

switch language {
case "JAVA", "SCALA", "PYTHON":
err = errors.Join(
func() error {
if d.HasChange("secrets") {
return setSecretsInBuilder(d, func(references []sdk.SecretReference) *sdk.FunctionSetRequest {
return setRequest.WithSecretsList(sdk.SecretsListRequest{SecretsList: references})
})
}
return nil
}(),
func() error {
if d.HasChange("external_access_integrations") {
return setExternalAccessIntegrationsInBuilder(d, func(references []sdk.AccountObjectIdentifier) any {
if len(references) == 0 {
return unsetRequest.WithExternalAccessIntegrations(true)
} else {
return setRequest.WithExternalAccessIntegrations(references)
}
})
}
return nil
}(),
)
if err != nil {
return diag.FromErr(err)
}
}

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 {
d.Partial(true)
return diag.FromErr(err)
}
}
if !reflect.DeepEqual(*unsetRequest, *sdk.NewFunctionUnsetRequest()) {
err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithUnset(*unsetRequest))
if err != nil {
d.Partial(true)
return diag.FromErr(err)
}
}

// has to be handled separately
if d.HasChange("is_secure") {
if v := d.Get("is_secure").(string); v != BooleanDefault {
parsed, err := booleanStringToBool(v)
if err != nil {
return diag.FromErr(err)
}
err = client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithSetSecure(parsed))
if err != nil {
d.Partial(true)
return diag.FromErr(err)
}
} else {
err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithUnsetSecure(true))
if err != nil {
d.Partial(true)
return diag.FromErr(err)
}
}
}

return readFunc(ctx, d, meta)
}
}

// TODO [SNOW-1850370]: Make the rest of the functions in this file generic (for reuse with procedures)
func parseFunctionArgumentsCommon(d *schema.ResourceData) ([]sdk.FunctionArgumentRequest, error) {
args := make([]sdk.FunctionArgumentRequest, 0)
Expand Down
97 changes: 1 addition & 96 deletions pkg/resources/function_java.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package resources
import (
"context"
"errors"
"fmt"
"reflect"
"strings"

Expand All @@ -23,7 +22,7 @@ func FunctionJava() *schema.Resource {
return &schema.Resource{
CreateContext: TrackingCreateWrapper(resources.FunctionJava, CreateContextFunctionJava),
ReadContext: TrackingReadWrapper(resources.FunctionJava, ReadContextFunctionJava),
UpdateContext: TrackingUpdateWrapper(resources.FunctionJava, UpdateContextFunctionJava),
UpdateContext: TrackingUpdateWrapper(resources.FunctionJava, UpdateFunction("JAVA", ReadContextFunctionJava)),
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).",

Expand Down Expand Up @@ -147,97 +146,3 @@ func ReadContextFunctionJava(ctx context.Context, d *schema.ResourceData, meta a

return nil
}

func UpdateContextFunctionJava(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 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
}

// Batch SET operations and UNSET operations
setRequest := sdk.NewFunctionSetRequest()
unsetRequest := sdk.NewFunctionUnsetRequest()

err = errors.Join(
stringAttributeUpdate(d, "comment", &setRequest.Comment, &unsetRequest.Comment),
func() error {
if d.HasChange("secrets") {
return setSecretsInBuilder(d, func(references []sdk.SecretReference) *sdk.FunctionSetRequest {
return setRequest.WithSecretsList(sdk.SecretsListRequest{SecretsList: references})
})
}
return nil
}(),
func() error {
if d.HasChange("external_access_integrations") {
return setExternalAccessIntegrationsInBuilder(d, func(references []sdk.AccountObjectIdentifier) any {
if len(references) == 0 {
return unsetRequest.WithExternalAccessIntegrations(true)
} else {
return setRequest.WithExternalAccessIntegrations(references)
}
})
}
return nil
}(),
)
if err != nil {
return diag.FromErr(err)
}

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 {
d.Partial(true)
return diag.FromErr(err)
}
}
if !reflect.DeepEqual(*unsetRequest, *sdk.NewFunctionUnsetRequest()) {
err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithUnset(*unsetRequest))
if err != nil {
d.Partial(true)
return diag.FromErr(err)
}
}

// has to be handled separately
if d.HasChange("is_secure") {
if v := d.Get("is_secure").(string); v != BooleanDefault {
parsed, err := booleanStringToBool(v)
if err != nil {
return diag.FromErr(err)
}
err = client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithSetSecure(parsed))
if err != nil {
d.Partial(true)
return diag.FromErr(err)
}
} else {
err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithUnsetSecure(true))
if err != nil {
d.Partial(true)
return diag.FromErr(err)
}
}
}

return ReadContextFunctionJava(ctx, d, meta)
}
Loading

0 comments on commit 7a6f68d

Please sign in to comment.