Skip to content

Commit

Permalink
feat: Add support for is_secure to snowflake_function resource (#1575)
Browse files Browse the repository at this point in the history
Co-authored-by: Onel Harrison <[email protected]>
  • Loading branch information
onelharrison and onelharrison authored Mar 16, 2023
1 parent 2ca9fe7 commit c41b6a3
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 2 deletions.
17 changes: 17 additions & 0 deletions docs/resources/function.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,22 @@ resource "snowflake_function" "python_test" {
handler = "add_py"
statement = "def add_py(i): return i+1"
}
// Example SQL language
resource "snowflake_function" "sql_test" {
name = "MY_SQL_FUNC"
database = "MY_DB"
schema = "MY_SCHEMA"
arguments {
name = "arg1"
type = "number"
}
comment = "Example for SQL language"
return_type = "NUMBER(38,0)"
null_input_behavior = "CALLED ON NULL INPUT"
return_behavior = "VOLATILE"
statement = "select arg1 + 1"
}
```

<!-- schema generated by tfplugindocs -->
Expand All @@ -89,6 +105,7 @@ resource "snowflake_function" "python_test" {
- `comment` (String) Specifies a comment for the function.
- `handler` (String) The handler method for Java / Python function.
- `imports` (List of String) Imports for Java / Python functions. For Java this a list of jar files, for Python this is a list of Python files.
- `is_secure` (Boolean) Specifies that the function is secure.
- `language` (String) The language of the statement
- `null_input_behavior` (String) Specifies the behavior of the function when called with null inputs.
- `packages` (List of String) List of package imports to use for Java / Python functions. For Java, package imports should be of the form: package_name:version_number, where package_name is snowflake_domain:package. For Python use it should be: ('numpy','pandas','xgboost==1.5.0').
Expand Down
16 changes: 16 additions & 0 deletions examples/resources/snowflake_function/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,19 @@ resource "snowflake_function" "python_test" {
handler = "add_py"
statement = "def add_py(i): return i+1"
}

// Example SQL language
resource "snowflake_function" "sql_test" {
name = "MY_SQL_FUNC"
database = "MY_DB"
schema = "MY_SCHEMA"
arguments {
name = "arg1"
type = "number"
}
comment = "Example for SQL language"
return_type = "NUMBER(38,0)"
null_input_behavior = "CALLED ON NULL INPUT"
return_behavior = "VOLATILE"
statement = "select arg1 + 1"
}
40 changes: 40 additions & 0 deletions pkg/resources/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ var functionSchema = map[string]*schema.Schema{
ValidateFunc: validation.StringInSlice([]string{"VOLATILE", "IMMUTABLE"}, false),
Description: "Specifies the behavior of the function when returning results",
},
"is_secure": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "Specifies that the function is secure.",
},
"comment": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -199,6 +205,11 @@ func CreateFunction(d *schema.ResourceData, meta interface{}) error {
builder.WithRuntimeVersion(v.(string))
}

// Set optionals, default is false
if v, ok := d.GetOk("is_secure"); ok && v.(bool) {
builder.WithSecure()
}

if v, ok := d.GetOk("comment"); ok {
builder.WithComment(v.(string))
}
Expand Down Expand Up @@ -388,11 +399,19 @@ func ReadFunction(d *schema.ResourceData, meta interface{}) error {
// iterate over and find the correct one
argSig, _ := funct.ArgumentsSignature()

functionIsSecure := map[string]bool{
"Y": true,
"N": false,
}

for _, v := range foundFunctions {
if v.Arguments.String == argSig {
if err := d.Set("comment", v.Comment.String); err != nil {
return err
}
if err = d.Set("is_secure", functionIsSecure[v.IsSecure.String]); err != nil {
return err
}
}
}

Expand Down Expand Up @@ -430,6 +449,27 @@ func UpdateFunction(d *schema.ResourceData, meta interface{}) error {
}
d.SetId(newID.String())
}
if d.HasChange("is_secure") {
secure := d.Get("is_secure")

if secure.(bool) {
q, err := builder.Secure()
if err != nil {
return err
}
if err = snowflake.Exec(db, q); err != nil {
return fmt.Errorf("error setting secure for function %v", d.Id())
}
} else {
q, err := builder.Unsecure()
if err != nil {
return err
}
if err = snowflake.Exec(db, q); err != nil {
return fmt.Errorf("error unsetting secure for function %v", d.Id())
}
}
}

if d.HasChange("comment") {
comment := d.Get("comment")
Expand Down
1 change: 1 addition & 0 deletions pkg/resources/function_acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ func TestAcc_Function(t *testing.T) {
resource.TestCheckResourceAttr("snowflake_function.test_funct_java", "arguments.#", "1"),
resource.TestCheckResourceAttr("snowflake_function.test_funct_java", "arguments.0.name", "ARG1"),
resource.TestCheckResourceAttr("snowflake_function.test_funct_java", "arguments.0.type", "NUMBER"),
checkBool("snowflake_function.test_funct_java", "is_secure", true), // this is from user_acceptance_test.go

// TODO: temporarily remove unit tests to allow for urgent release
// resource.TestCheckResourceAttr("snowflake_function.test_funct_python", "name", functName),
Expand Down
34 changes: 32 additions & 2 deletions pkg/snowflake/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type FunctionBuilder struct {
comment string
statement string
runtimeVersion string // for Python runtime version
secure bool
}

// QualifiedName prepends the db and schema and appends argument types.
Expand Down Expand Up @@ -117,6 +118,13 @@ func (pb *FunctionBuilder) WithTargetPath(s string) *FunctionBuilder {
return pb
}

// WithSecure sets the secure boolean to true
// [Snowflake Reference](https://docs.snowflake.com/en/sql-reference/sql/create-function)
func (pb *FunctionBuilder) WithSecure() *FunctionBuilder {
pb.secure = true
return pb
}

// WithComment adds a comment to the FunctionBuilder.
func (pb *FunctionBuilder) WithComment(c string) *FunctionBuilder {
pb.comment = c
Expand Down Expand Up @@ -159,6 +167,10 @@ func (pb *FunctionBuilder) Create() (string, error) {

q.WriteString("CREATE OR REPLACE")

if pb.secure {
q.WriteString(" SECURE")
}

qn, err := pb.QualifiedNameWithoutArguments()
if err != nil {
return "", err
Expand Down Expand Up @@ -241,6 +253,24 @@ func (pb *FunctionBuilder) Rename(newName string) (string, error) {
return fmt.Sprintf(`ALTER FUNCTION %v RENAME TO %v`, oldName, qn), nil
}

// Secure returns the SQL query that will change the function to a secure function.
func (pb *FunctionBuilder) Secure() (string, error) {
qn, err := pb.QualifiedName()
if err != nil {
return "", err
}
return fmt.Sprintf(`ALTER FUNCTION %v SET SECURE`, qn), nil
}

// Unsecure returns the SQL query that will change the function to a normal (unsecured) function.
func (pb *FunctionBuilder) Unsecure() (string, error) {
qn, err := pb.QualifiedName()
if err != nil {
return "", err
}
return fmt.Sprintf(`ALTER FUNCTION %v UNSET SECURE`, qn), nil
}

// ChangeComment returns the SQL query that will update the comment on the function.
func (pb *FunctionBuilder) ChangeComment(c string) (string, error) {
qn, err := pb.QualifiedName()
Expand Down Expand Up @@ -286,8 +316,8 @@ func (pb *FunctionBuilder) Drop() (string, error) {
}

type Function struct {
Comment sql.NullString `db:"description"`
// Snowflake returns is_secure in the show function output, but it is irrelevant
Comment sql.NullString `db:"description"`
IsSecure sql.NullString `db:"is_secure"`
Name sql.NullString `db:"name"`
SchemaName sql.NullString `db:"schema_name"`
Text sql.NullString `db:"text"`
Expand Down
31 changes: 31 additions & 0 deletions pkg/snowflake/function_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ func TestFunctionCreate(t *testing.T) {
r.Equal(expected, createStmnt)
}

func TestFunctionCreateWithSecure(t *testing.T) {
r := require.New(t)
s := getJavaScriptFuction(true)
s.WithSecure()

r.Equal([]string{"VARCHAR", "DATE"}, s.ArgTypes())
createStmnt, _ := s.Create()
expected := `CREATE OR REPLACE SECURE FUNCTION "test_db"."test_schema"."test_func"` +
`(user VARCHAR, eventdt DATE) RETURNS VARCHAR AS $$` +
`var message = "Hi"` + "\nreturn message$$"
r.Equal(expected, createStmnt)
}

func TestFunctionCreateWithJavaScriptFunction(t *testing.T) {
r := require.New(t)
s := getJavaScriptFuction(true)
Expand Down Expand Up @@ -243,6 +256,24 @@ func TestFunctionRename(t *testing.T) {
r.Equal(expected, stmnt)
}

func TestFunctionSecure(t *testing.T) {
r := require.New(t)
s := getJavaScriptFuction(true)

stmnt, _ := s.Secure()
expected := `ALTER FUNCTION "test_db"."test_schema"."test_func"(VARCHAR, DATE) SET SECURE`
r.Equal(expected, stmnt)
}

func TestFunctionUnsecure(t *testing.T) {
r := require.New(t)
s := getJavaScriptFuction(true)

stmnt, _ := s.Unsecure()
expected := `ALTER FUNCTION "test_db"."test_schema"."test_func"(VARCHAR, DATE) UNSET SECURE`
r.Equal(expected, stmnt)
}

func TestFunctionChangeComment(t *testing.T) {
r := require.New(t)
s := getJavaScriptFuction(true)
Expand Down

0 comments on commit c41b6a3

Please sign in to comment.