From 31618278866604e8bfd7d2fa984ec9502c0b7bbb Mon Sep 17 00:00:00 2001 From: Emiliano Mancuso Date: Thu, 26 May 2022 20:57:49 +0100 Subject: [PATCH] feat: Support for selecting language in snowflake_procedure (#1010) * Add support for selecting language in snowflake_procedure Implements https://github.com/chanzuckerberg/terraform-provider-snowflake/issues/948 The previous behaviour was always setting `JAVASCRIPT` as language. The implementation in this PR breaks the "backwards compatibility" with that rule to honor the Snowflake default: `SQL`. * Update documentation for `procedure_grant` and `function_grant` as requested * Generate docs with `make check-docs` * Update docs Co-authored-by: Allison Doami Co-authored-by: Scott Winkler --- docs/resources/function_grant.md | 18 ++++++++--------- docs/resources/procedure.md | 2 ++ docs/resources/procedure_grant.md | 18 ++++++++--------- .../snowflake_function_grant/resource.tf | 18 ++++++++--------- .../resources/snowflake_procedure/resource.tf | 1 + .../snowflake_procedure_grant/resource.tf | 18 ++++++++--------- pkg/resources/procedure.go | 20 ++++++++++++++++++- pkg/resources/procedure_test.go | 6 ++++-- pkg/snowflake/procedure.go | 11 +++++++++- pkg/snowflake/procedure_test.go | 5 +++-- 10 files changed, 71 insertions(+), 46 deletions(-) diff --git a/docs/resources/function_grant.md b/docs/resources/function_grant.md index 215784035f..7557213e12 100644 --- a/docs/resources/function_grant.md +++ b/docs/resources/function_grant.md @@ -18,16 +18,14 @@ resource snowflake_function_grant grant { schema_name = "schema" function_name = "function" - arguments = [ - { - "name": "a", - "type": "array" - }, - { - "name": "b", - "type": "string" - } - ] + arguments { + name = "a" + type = "array" + } + arguments { + name = "b" + type = "string" + } return_type = "string" privilege = "USAGE" diff --git a/docs/resources/procedure.md b/docs/resources/procedure.md index 7d49f6b3f4..eebbc48e94 100644 --- a/docs/resources/procedure.md +++ b/docs/resources/procedure.md @@ -28,6 +28,7 @@ resource "snowflake_procedure" "proc" { name = "SAMPLEPROC" database = snowflake_database.db.name schema = snowflake_schema.schema.name + language = "JAVASCRIPT" arguments { name = "arg1" type = "varchar" @@ -64,6 +65,7 @@ EOT - `arguments` (Block List) List of the arguments for the procedure (see [below for nested schema](#nestedblock--arguments)) - `comment` (String) Specifies a comment for the procedure. - `execute_as` (String) Sets execute context - see caller's rights and owner's rights +- `language` (String) Specifies the language of the stored procedure code. - `null_input_behavior` (String) Specifies the behavior of the procedure when called with null inputs. - `return_behavior` (String) Specifies the behavior of the function when returning results diff --git a/docs/resources/procedure_grant.md b/docs/resources/procedure_grant.md index 7910a7ee1d..a7ede6b066 100644 --- a/docs/resources/procedure_grant.md +++ b/docs/resources/procedure_grant.md @@ -18,16 +18,14 @@ resource snowflake_procedure_grant grant { schema_name = "schema" procedure_name = "procedure" - arguments = [ - { - "name": "a", - "type": "array" - }, - { - "name": "b", - "type": "string" - } - ] + arguments { + name = "a" + type = "array" + } + arguments { + name = "b" + type = "string" + } return_type = "string" privilege = "select" diff --git a/examples/resources/snowflake_function_grant/resource.tf b/examples/resources/snowflake_function_grant/resource.tf index 4b1a14dcea..b811db19fe 100644 --- a/examples/resources/snowflake_function_grant/resource.tf +++ b/examples/resources/snowflake_function_grant/resource.tf @@ -3,16 +3,14 @@ resource snowflake_function_grant grant { schema_name = "schema" function_name = "function" - arguments = [ - { - "name": "a", - "type": "array" - }, - { - "name": "b", - "type": "string" - } - ] + arguments { + name = "a" + type = "array" + } + arguments { + name = "b" + type = "string" + } return_type = "string" privilege = "USAGE" diff --git a/examples/resources/snowflake_procedure/resource.tf b/examples/resources/snowflake_procedure/resource.tf index d728e9025e..86f2c3d558 100644 --- a/examples/resources/snowflake_procedure/resource.tf +++ b/examples/resources/snowflake_procedure/resource.tf @@ -13,6 +13,7 @@ resource "snowflake_procedure" "proc" { name = "SAMPLEPROC" database = snowflake_database.db.name schema = snowflake_schema.schema.name + language = "JAVASCRIPT" arguments { name = "arg1" type = "varchar" diff --git a/examples/resources/snowflake_procedure_grant/resource.tf b/examples/resources/snowflake_procedure_grant/resource.tf index 4bf787bad2..39a853fdc6 100644 --- a/examples/resources/snowflake_procedure_grant/resource.tf +++ b/examples/resources/snowflake_procedure_grant/resource.tf @@ -3,16 +3,14 @@ resource snowflake_procedure_grant grant { schema_name = "schema" procedure_name = "procedure" - arguments = [ - { - "name": "a", - "type": "array" - }, - { - "name": "b", - "type": "string" - } - ] + arguments { + name = "a" + type = "array" + } + arguments { + name = "b" + type = "string" + } return_type = "string" privilege = "select" diff --git a/pkg/resources/procedure.go b/pkg/resources/procedure.go index 612616ecdf..c634f52f0a 100644 --- a/pkg/resources/procedure.go +++ b/pkg/resources/procedure.go @@ -13,6 +13,8 @@ import ( "github.com/pkg/errors" ) +var procedureLanguages = []string{"JAVASCRIPT", "JAVA", "SCALA", "SQL"} + var procedureSchema = map[string]*schema.Schema{ "name": { Type: schema.TypeString, @@ -70,6 +72,13 @@ var procedureSchema = map[string]*schema.Schema{ ForceNew: true, DiffSuppressFunc: DiffSuppressStatement, }, + "language": { + Type: schema.TypeString, + Optional: true, + Default: "SQL", + ValidateFunc: validation.StringInSlice(procedureLanguages, false), + Description: "Specifies the language of the stored procedure code.", + }, "execute_as": { Type: schema.TypeString, Optional: true, @@ -159,6 +168,11 @@ func CreateProcedure(d *schema.ResourceData, meta interface{}) error { builder.WithExecuteAs(v.(string)) } + // Set optionals, default is SQL + if v, ok := d.GetOk("language"); ok { + builder.WithLanguage(v.(string)) + } + if v, ok := d.GetOk("comment"); ok { builder.WithComment(v.(string)) } @@ -259,7 +273,11 @@ func ReadProcedure(d *schema.ResourceData, meta interface{}) error { return err } case "language": - // To ignore + if snowflake.Contains(languages, desc.Value.String) { + if err = d.Set("language", desc.Value.String); err != nil { + return err + } + } default: log.Printf("[WARN] unexpected procedure property %v returned from Snowflake", desc.Property.String) } diff --git a/pkg/resources/procedure_test.go b/pkg/resources/procedure_test.go index 7a408b38c5..72d017fd6a 100644 --- a/pkg/resources/procedure_test.go +++ b/pkg/resources/procedure_test.go @@ -24,6 +24,7 @@ func prepDummyProcedureResource(t *testing.T) *schema.ResourceData { "schema": "my_schema", "arguments": []interface{}{argument1, argument2}, "return_type": "varchar", + "language": "SCALA", "comment": "mock comment", "return_behavior": "IMMUTABLE", "statement": procedureBody, //var message = DATA + DATA;return message @@ -43,7 +44,7 @@ func TestProcedureCreate(t *testing.T) { d := prepDummyProcedureResource(t) WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) { - mock.ExpectExec(`CREATE OR REPLACE PROCEDURE "my_db"."my_schema"."my_proc"\(data VARCHAR, event_dt DATE\) RETURNS VARCHAR LANGUAGE javascript CALLED ON NULL INPUT IMMUTABLE COMMENT = 'mock comment' EXECUTE AS OWNER AS \$\$hi\$\$`).WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectExec(`CREATE OR REPLACE PROCEDURE "my_db"."my_schema"."my_proc"\(data VARCHAR, event_dt DATE\) RETURNS VARCHAR LANGUAGE SCALA CALLED ON NULL INPUT IMMUTABLE COMMENT = 'mock comment' EXECUTE AS OWNER AS \$\$hi\$\$`).WillReturnResult(sqlmock.NewResult(1, 1)) expectProcedureRead(mock) err := resources.CreateProcedure(d, db) r.NoError(err) @@ -61,7 +62,7 @@ func expectProcedureRead(mock sqlmock.Sqlmock) { describeRows := sqlmock.NewRows([]string{"property", "value"}). AddRow("signature", "(data VARCHAR, event_dt DATE)"). AddRow("returns", "VARCHAR(123456789)"). // This is how return type is stored in Snowflake DB - AddRow("language", "JAVASCRIPT"). + AddRow("language", "SQL"). AddRow("null handling", "CALLED ON NULL INPUT"). AddRow("volatility", "IMMUTABLE"). AddRow("execute as", "CALLER"). @@ -85,6 +86,7 @@ func TestProcedureRead(t *testing.T) { r.Equal("MY_SCHEMA", d.Get("schema").(string)) r.Equal("mock comment", d.Get("comment").(string)) r.Equal("VARCHAR", d.Get("return_type").(string)) + r.Equal("SQL", d.Get("language").(string)) r.Equal("IMMUTABLE", d.Get("return_behavior").(string)) r.Equal(procedureBody, d.Get("statement").(string)) diff --git a/pkg/snowflake/procedure.go b/pkg/snowflake/procedure.go index ac7ca265f8..deb72b18ee 100644 --- a/pkg/snowflake/procedure.go +++ b/pkg/snowflake/procedure.go @@ -20,6 +20,7 @@ type ProcedureBuilder struct { args []map[string]string returnBehavior string // VOLATILE, IMMUTABLE nullInputBehavior string // "CALLED ON NULL INPUT" or "RETURNS NULL ON NULL INPUT" + language string // SQL, JAVASCRIPT, JAVA, SCALA returnType string executeAs string comment string @@ -83,6 +84,12 @@ func (pb *ProcedureBuilder) WithExecuteAs(s string) *ProcedureBuilder { return pb } +// WithLanguage sets the language to SQL, JAVA, SCALA or JAVASCRIPT +func (pb *ProcedureBuilder) WithLanguage(s string) *ProcedureBuilder { + pb.language = s + return pb +} + // WithComment adds a comment to the ProcedureBuilder func (pb *ProcedureBuilder) WithComment(c string) *ProcedureBuilder { pb.comment = c @@ -141,7 +148,9 @@ func (pb *ProcedureBuilder) Create() (string, error) { q.WriteString(`)`) q.WriteString(fmt.Sprintf(" RETURNS %v", pb.returnType)) - q.WriteString(" LANGUAGE javascript") + if pb.language != "" { + q.WriteString(fmt.Sprintf(" LANGUAGE %v", EscapeString(pb.language))) + } if pb.nullInputBehavior != "" { q.WriteString(fmt.Sprintf(` %v`, EscapeString(pb.nullInputBehavior))) } diff --git a/pkg/snowflake/procedure_test.go b/pkg/snowflake/procedure_test.go index 40b03db3b9..44c1388ef6 100644 --- a/pkg/snowflake/procedure_test.go +++ b/pkg/snowflake/procedure_test.go @@ -35,7 +35,7 @@ func TestProcedureCreate(t *testing.T) { r.Equal([]string{"VARCHAR", "DATE"}, s.ArgTypes()) createStmnt, _ := s.Create() expected := `CREATE OR REPLACE PROCEDURE "test_db"."test_schema"."test_proc"` + - `(user VARCHAR, eventdt DATE) RETURNS VARCHAR LANGUAGE javascript EXECUTE AS CALLER AS $$` + + `(user VARCHAR, eventdt DATE) RETURNS VARCHAR EXECUTE AS CALLER AS $$` + `var message = "Hi"` + "\nreturn message$$" r.Equal(expected, createStmnt) } @@ -45,10 +45,11 @@ func TestProcedureCreateWithOptionalParams(t *testing.T) { s := getProcedure(true) s.WithNullInputBehavior("RETURNS NULL ON NULL INPUT") s.WithReturnBehavior("IMMUTABLE") + s.WithLanguage("JAVASCRIPT") s.WithComment("this is cool proc!") createStmnt, _ := s.Create() expected := `CREATE OR REPLACE PROCEDURE "test_db"."test_schema"."test_proc"` + - `(user VARCHAR, eventdt DATE) RETURNS VARCHAR LANGUAGE javascript RETURNS NULL ON NULL INPUT` + + `(user VARCHAR, eventdt DATE) RETURNS VARCHAR LANGUAGE JAVASCRIPT RETURNS NULL ON NULL INPUT` + ` IMMUTABLE COMMENT = 'this is cool proc!' EXECUTE AS CALLER AS $$` + `var message = "Hi"` + "\nreturn message$$" r.Equal(expected, createStmnt)