Skip to content

Commit

Permalink
Add support for selecting language in snowflake_procedure
Browse files Browse the repository at this point in the history
Implements Snowflake-Labs#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`.
  • Loading branch information
emancu committed May 19, 2022
1 parent 1c5af87 commit cb3b6eb
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 8 deletions.
6 changes: 4 additions & 2 deletions docs/resources/procedure.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ resource "snowflake_procedure" "proc" {
name = "SAMPLEPROC"
database = snowflake_database.db.name
schema = snowflake_schema.schema.name
language = "JAVASCRIPT"
arguments {
name = "arg1"
type = "varchar"
type = "VARCHAR"
}
arguments {
name = "arg2"
Expand All @@ -57,7 +58,7 @@ EOT
- **name** (String) Specifies the identifier for the procedure; does not have to be unique for the schema in which the procedure is created. Don't use the | character.
- **return_type** (String) The return type of the procedure
- **schema** (String) The schema in which to create the procedure. Don't use the | character.
- **statement** (String) Specifies the javascript code used to create the procedure.
- **statement** (String) Specifies the code used to create the procedure.

### Optional

Expand All @@ -67,6 +68,7 @@ EOT
- **id** (String) The ID of this resource.
- **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
- **language** (String) Specifies the language of the stored procedure code.

<a id="nestedblock--arguments"></a>
### Nested Schema for `arguments`
Expand Down
20 changes: 19 additions & 1 deletion pkg/resources/procedure.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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))
}
Expand Down Expand Up @@ -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)
}
Expand Down
6 changes: 4 additions & 2 deletions pkg/resources/procedure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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").
Expand All @@ -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))

Expand Down
11 changes: 10 additions & 1 deletion pkg/snowflake/procedure.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)))
}
Expand Down
5 changes: 3 additions & 2 deletions pkg/snowflake/procedure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
Expand Down

0 comments on commit cb3b6eb

Please sign in to comment.