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: Identifier with arguments for procedure and external function #2987

6 changes: 3 additions & 3 deletions pkg/datasources/procedures.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ func ReadContextProcedures(ctx context.Context, d *schema.ResourceData, meta int

req := sdk.NewShowProcedureRequest()
if databaseName != "" {
req.WithIn(&sdk.In{Database: sdk.NewAccountObjectIdentifier(databaseName)})
req.WithIn(sdk.In{Database: sdk.NewAccountObjectIdentifier(databaseName)})
}
if schemaName != "" {
req.WithIn(&sdk.In{Schema: sdk.NewDatabaseObjectIdentifier(databaseName, schemaName)})
req.WithIn(sdk.In{Schema: sdk.NewDatabaseObjectIdentifier(databaseName, schemaName)})
}
procedures, err := client.Procedures.Show(ctx, req)
if err != nil {
Expand All @@ -103,7 +103,7 @@ func ReadContextProcedures(ctx context.Context, d *schema.ResourceData, meta int
procedureMap["database"] = procedure.CatalogName
procedureMap["schema"] = procedure.SchemaName
procedureMap["comment"] = procedure.Description
procedureSignatureMap, err := parseArguments(procedure.Arguments)
procedureSignatureMap, err := parseArguments(procedure.ArgumentsRaw)
if err != nil {
return diag.FromErr(err)
}
Expand Down
94 changes: 56 additions & 38 deletions pkg/resources/external_function.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,9 @@ var externalFunctionSchema = map[string]*schema.Schema{
},
}

// ExternalFunction returns a pointer to the resource representing an external function.
func ExternalFunction() *schema.Resource {
return &schema.Resource{
SchemaVersion: 1,
SchemaVersion: 2,

CreateContext: CreateContextExternalFunction,
ReadContext: ReadContextExternalFunction,
Expand All @@ -201,6 +200,12 @@ func ExternalFunction() *schema.Resource {
Type: cty.EmptyObject,
Upgrade: v085ExternalFunctionStateUpgrader,
},
{
Version: 1,
// setting type to cty.EmptyObject is a bit hacky here but following https://developer.hashicorp.com/terraform/plugin/framework/migrating/resources/state-upgrade#sdkv2-1 would require lots of repetitive code; this should work with cty.EmptyObject
Type: cty.EmptyObject,
Upgrade: v0941ResourceIdentifierWithArguments,
},
},
}
}
Expand All @@ -210,18 +215,6 @@ func CreateContextExternalFunction(ctx context.Context, d *schema.ResourceData,
database := d.Get("database").(string)
schemaName := d.Get("schema").(string)
name := d.Get("name").(string)
id := sdk.NewSchemaObjectIdentifier(database, schemaName, name)

returnType := d.Get("return_type").(string)
resultDataType, err := sdk.ToDataType(returnType)
if err != nil {
return diag.FromErr(err)
}
apiIntegration := sdk.NewAccountObjectIdentifier(d.Get("api_integration").(string))
urlOfProxyAndResource := d.Get("url_of_proxy_and_resource").(string)
req := sdk.NewCreateExternalFunctionRequest(id, resultDataType, &apiIntegration, urlOfProxyAndResource)

// Set optionals
args := make([]sdk.ExternalFunctionArgumentRequest, 0)
if v, ok := d.GetOk("arg"); ok {
for _, arg := range v.([]interface{}) {
Expand All @@ -234,39 +227,55 @@ func CreateContextExternalFunction(ctx context.Context, d *schema.ResourceData,
args = append(args, sdk.ExternalFunctionArgumentRequest{ArgName: argName, ArgDataType: argDataType})
}
}
argTypes := make([]sdk.DataType, 0, len(args))
for _, item := range args {
argTypes = append(argTypes, item.ArgDataType)
}
id := sdk.NewSchemaObjectIdentifierWithArguments(database, schemaName, name, argTypes...)
sfc-gh-asawicki marked this conversation as resolved.
Show resolved Hide resolved

returnType := d.Get("return_type").(string)
resultDataType, err := sdk.ToDataType(returnType)
if err != nil {
return diag.FromErr(err)
}
apiIntegration := sdk.NewAccountObjectIdentifier(d.Get("api_integration").(string))
urlOfProxyAndResource := d.Get("url_of_proxy_and_resource").(string)
req := sdk.NewCreateExternalFunctionRequest(id.SchemaObjectId(), resultDataType, &apiIntegration, urlOfProxyAndResource)

// Set optionals
if len(args) > 0 {
req.WithArguments(args)
}

if v, ok := d.GetOk("return_null_allowed"); ok {
if v.(bool) {
req.WithReturnNullValues(&sdk.ReturnNullValuesNull)
req.WithReturnNullValues(sdk.ReturnNullValuesNull)
} else {
req.WithReturnNullValues(&sdk.ReturnNullValuesNotNull)
req.WithReturnNullValues(sdk.ReturnNullValuesNotNull)
}
}

if v, ok := d.GetOk("return_behavior"); ok {
if v.(string) == "VOLATILE" {
req.WithReturnResultsBehavior(&sdk.ReturnResultsBehaviorVolatile)
req.WithReturnResultsBehavior(sdk.ReturnResultsBehaviorVolatile)
} else {
req.WithReturnResultsBehavior(&sdk.ReturnResultsBehaviorImmutable)
req.WithReturnResultsBehavior(sdk.ReturnResultsBehaviorImmutable)
}
}

if v, ok := d.GetOk("null_input_behavior"); ok {
switch {
case v.(string) == "CALLED ON NULL INPUT":
req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehaviorCalledOnNullInput))
req.WithNullInputBehavior(sdk.NullInputBehaviorCalledOnNullInput)
case v.(string) == "RETURNS NULL ON NULL INPUT":
req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehaviorReturnNullInput))
req.WithNullInputBehavior(sdk.NullInputBehaviorReturnNullInput)
default:
req.WithNullInputBehavior(sdk.Pointer(sdk.NullInputBehaviorStrict))
req.WithNullInputBehavior(sdk.NullInputBehaviorStrict)
}
}

if v, ok := d.GetOk("comment"); ok {
req.WithComment(sdk.String(v.(string)))
req.WithComment(v.(string))
}

if _, ok := d.GetOk("header"); ok {
Expand Down Expand Up @@ -295,37 +304,38 @@ func CreateContextExternalFunction(ctx context.Context, d *schema.ResourceData,
}

if v, ok := d.GetOk("max_batch_rows"); ok {
req.WithMaxBatchRows(sdk.Int(v.(int)))
req.WithMaxBatchRows(v.(int))
}

if v, ok := d.GetOk("compression"); ok {
req.WithCompression(sdk.String(v.(string)))
req.WithCompression(v.(string))
}

if v, ok := d.GetOk("request_translator"); ok {
req.WithRequestTranslator(sdk.Pointer(sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(v.(string))))
req.WithRequestTranslator(sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(v.(string)))
}

if v, ok := d.GetOk("response_translator"); ok {
req.WithResponseTranslator(sdk.Pointer(sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(v.(string))))
req.WithResponseTranslator(sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(v.(string)))
}

if err := client.ExternalFunctions.Create(ctx, req); err != nil {
return diag.FromErr(err)
}
argTypes := make([]sdk.DataType, 0, len(args))
for _, item := range args {
argTypes = append(argTypes, item.ArgDataType)
}
sid := sdk.NewSchemaObjectIdentifierWithArgumentsOld(database, schemaName, name, argTypes)
d.SetId(sid.FullyQualifiedName())

d.SetId(id.FullyQualifiedName())

return ReadContextExternalFunction(ctx, d, meta)
}

func ReadContextExternalFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*provider.Context).Client

id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id())
id, err := sdk.ParseSchemaObjectIdentifierWithArguments(d.Id())
if err != nil {
return diag.FromErr(err)
}

externalFunction, err := client.ExternalFunctions.ShowByID(ctx, id)
if err != nil {
d.SetId("")
Expand Down Expand Up @@ -354,7 +364,7 @@ func ReadContextExternalFunction(ctx context.Context, d *schema.ResourceData, me
}

// Some properties come from the DESCRIBE FUNCTION call
externalFunctionPropertyRows, err := client.ExternalFunctions.Describe(ctx, sdk.NewDescribeExternalFunctionRequest(id.WithoutArguments(), id.Arguments()))
externalFunctionPropertyRows, err := client.ExternalFunctions.Describe(ctx, id)
if err != nil {
d.SetId("")
return nil
Expand Down Expand Up @@ -474,8 +484,12 @@ func ReadContextExternalFunction(ctx context.Context, d *schema.ResourceData, me
func UpdateContextExternalFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*provider.Context).Client

id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id())
req := sdk.NewAlterFunctionRequest(sdk.NewSchemaObjectIdentifierWithArguments(id.DatabaseName(), id.SchemaName(), id.Name(), id.Arguments()...))
id, err := sdk.ParseSchemaObjectIdentifierWithArguments(d.Id())
if err != nil {
return diag.FromErr(err)
}

req := sdk.NewAlterFunctionRequest(id)
if d.HasChange("comment") {
_, new := d.GetChange("comment")
if new == "" {
Expand All @@ -494,8 +508,12 @@ func UpdateContextExternalFunction(ctx context.Context, d *schema.ResourceData,
func DeleteContextExternalFunction(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*provider.Context).Client

id := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Id())
req := sdk.NewDropFunctionRequest(sdk.NewSchemaObjectIdentifierWithArguments(id.DatabaseName(), id.SchemaName(), id.Name(), id.Arguments()...))
id, err := sdk.ParseSchemaObjectIdentifierWithArguments(d.Id())
if err != nil {
return diag.FromErr(err)
}

req := sdk.NewDropFunctionRequest(id).WithIfExists(true)
if err := client.Functions.Drop(ctx, req); err != nil {
return diag.FromErr(err)
}
Expand Down
148 changes: 144 additions & 4 deletions pkg/resources/external_function_acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,13 @@ func TestAcc_ExternalFunction_migrateFromVersion085(t *testing.T) {
ExpectNonEmptyPlan: true,
},
{
ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories,
Config: externalFunctionConfig(acc.TestDatabaseName, acc.TestSchemaName, name),
ExternalProviders: map[string]resource.ExternalProvider{
"snowflake": {
VersionConstraint: "=0.94.1",
Source: "Snowflake-Labs/snowflake",
},
},
Config: externalFunctionConfig(acc.TestDatabaseName, acc.TestSchemaName, name),
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{plancheck.ExpectEmptyPlan()},
},
Expand Down Expand Up @@ -298,8 +303,13 @@ func TestAcc_ExternalFunction_migrateFromVersion085_issue2694_previousValuePrese
ExpectNonEmptyPlan: true,
},
{
ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories,
Config: externalFunctionConfig(acc.TestDatabaseName, acc.TestSchemaName, name),
ExternalProviders: map[string]resource.ExternalProvider{
"snowflake": {
VersionConstraint: "=0.94.1",
Source: "Snowflake-Labs/snowflake",
},
},
Config: externalFunctionConfig(acc.TestDatabaseName, acc.TestSchemaName, name),
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{plancheck.ExpectEmptyPlan()},
},
Expand Down Expand Up @@ -559,3 +569,133 @@ resource "snowflake_external_function" "f" {

`, id.DatabaseName(), id.SchemaName(), id.Name())
}

func TestAcc_ExternalFunction_EnsureSmoothResourceIdMigrationToV0950(t *testing.T) {
name := acc.TestClient().Ids.RandomAccountObjectIdentifier().Name()
resourceName := "snowflake_external_function.f"

resource.Test(t, resource.TestCase{
PreCheck: func() { acc.TestAccPreCheck(t) },
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.RequireAbove(tfversion.Version1_5_0),
},
CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction),
Steps: []resource.TestStep{
{
ExternalProviders: map[string]resource.ExternalProvider{
"snowflake": {
VersionConstraint: "=0.94.1",
Source: "Snowflake-Labs/snowflake",
},
},
Config: externalFunctionConfigWithMoreArguments(acc.TestDatabaseName, acc.TestSchemaName, name),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf(`"%s"."%s"."%s"(VARCHAR, FLOAT, NUMBER)`, acc.TestDatabaseName, acc.TestSchemaName, name)),
),
},
{
ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories,
Config: externalFunctionConfigWithMoreArguments(acc.TestDatabaseName, acc.TestSchemaName, name),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf(`"%s"."%s"."%s"(VARCHAR, FLOAT, NUMBER)`, acc.TestDatabaseName, acc.TestSchemaName, name)),
),
},
},
})
}

func externalFunctionConfigWithMoreArguments(database string, schema string, name string) string {
return fmt.Sprintf(`
resource "snowflake_api_integration" "test_api_int" {
name = "%[3]s"
api_provider = "aws_api_gateway"
api_aws_role_arn = "arn:aws:iam::000000000001:/role/test"
api_allowed_prefixes = ["https://123456.execute-api.us-west-2.amazonaws.com/prod/"]
enabled = true
}

resource "snowflake_external_function" "f" {
database = "%[1]s"
schema = "%[2]s"
name = "%[3]s"

arg {
name = "ARG1"
type = "VARCHAR"
}

arg {
name = "ARG2"
type = "FLOAT"
}

arg {
name = "ARG3"
type = "NUMBER"
}

return_type = "VARIANT"
return_behavior = "IMMUTABLE"
api_integration = snowflake_api_integration.test_api_int.name
url_of_proxy_and_resource = "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"
}
`, database, schema, name)
}

func TestAcc_ExternalFunction_EnsureSmoothResourceIdMigrationToV0950_WithoutArguments(t *testing.T) {
sfc-gh-asawicki marked this conversation as resolved.
Show resolved Hide resolved
name := acc.TestClient().Ids.RandomAccountObjectIdentifier().Name()
resourceName := "snowflake_external_function.f"

resource.Test(t, resource.TestCase{
PreCheck: func() { acc.TestAccPreCheck(t) },
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.RequireAbove(tfversion.Version1_5_0),
},
CheckDestroy: acc.CheckDestroy(t, resources.ExternalFunction),
Steps: []resource.TestStep{
{
ExternalProviders: map[string]resource.ExternalProvider{
"snowflake": {
VersionConstraint: "=0.94.1",
Source: "Snowflake-Labs/snowflake",
},
},
Config: externalFunctionConfigWithoutArguments(acc.TestDatabaseName, acc.TestSchemaName, name),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf(`"%s"."%s"."%s"`, acc.TestDatabaseName, acc.TestSchemaName, name)),
),
},
{
ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories,
Config: externalFunctionConfigWithoutArguments(acc.TestDatabaseName, acc.TestSchemaName, name),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf(`"%s"."%s"."%s"()`, acc.TestDatabaseName, acc.TestSchemaName, name)),
),
},
},
})
}

func externalFunctionConfigWithoutArguments(database string, schema string, name string) string {
return fmt.Sprintf(`
resource "snowflake_api_integration" "test_api_int" {
name = "%[3]s"
api_provider = "aws_api_gateway"
api_aws_role_arn = "arn:aws:iam::000000000001:/role/test"
api_allowed_prefixes = ["https://123456.execute-api.us-west-2.amazonaws.com/prod/"]
enabled = true
}

resource "snowflake_external_function" "f" {
database = "%[1]s"
schema = "%[2]s"
name = "%[3]s"

return_type = "VARIANT"
return_behavior = "IMMUTABLE"
api_integration = snowflake_api_integration.test_api_int.name
url_of_proxy_and_resource = "https://123456.execute-api.us-west-2.amazonaws.com/prod/test_func"
}

`, database, schema, name)
}
Loading
Loading