Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add all other functions and procedures implementations #3275

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading