From ee4c2c1b3b2fecf7319a5d58d17ae87ff4bcf685 Mon Sep 17 00:00:00 2001 From: sfc-gh-kmaurya <98528579+sfc-gh-kmaurya@users.noreply.github.com> Date: Sat, 18 Jun 2022 10:51:14 +0530 Subject: [PATCH] feat: add python language support for functions (#1063) * adding python language support and runtime_version attribute * adding packages attribute for python / java * updated function_acceptance_test * testing by adding warehouse attribute * testing by adding warehouse attribute * updating null input and return behaviour * updating test functions * running go fmt * changing runtime_version type from float64 to string * updating function acceptance test * updating function acceptance test * adding sql in the list and updating the description of attributes * updating function acceptance test * minor changes * testing * testing --- docs/resources/function.md | 11 ++- pkg/resources/function.go | 80 ++++++++++++++++--- pkg/resources/function_acceptance_test.go | 43 +++++++++- pkg/resources/function_test.go | 39 +++++++--- pkg/snowflake/function.go | 60 ++++++++++++-- pkg/snowflake/function_test.go | 95 +++++++++++++++++++++++ 6 files changed, 294 insertions(+), 34 deletions(-) diff --git a/docs/resources/function.md b/docs/resources/function.md index 992fffd471..6aa1ff78e6 100644 --- a/docs/resources/function.md +++ b/docs/resources/function.md @@ -21,18 +21,21 @@ description: |- - `name` (String) Specifies the identifier for the function; does not have to be unique for the schema in which the function is created. Don't use the | character. - `return_type` (String) The return type of the function - `schema` (String) The schema in which to create the function. Don't use the | character. -- `statement` (String) Specifies the javascript / java / sql code used to create the function. +- `statement` (String) Specifies the javascript / java / sql / python code used to create the function. ### Optional - `arguments` (Block List) List of the arguments for the function (see [below for nested schema](#nestedblock--arguments)) - `comment` (String) Specifies a comment for the function. -- `handler` (String) the handler method for Java function. -- `imports` (List of String) jar files to import for Java 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. - `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'). - `return_behavior` (String) Specifies the behavior of the function when returning results -- `target_path` (String) the target path for compiled jar file for Java function. +- `runtime_version` (String) Required for Python functions. Specifies Python runtime version. +- `target_path` (String) The target path for the Java / Python functions. For Java, it is the path of compiled jar files and for the Python it is the path of the Python files. +- `warehouse` (String) The warehouse in which to create the function. Only for Python language. ### Read-Only diff --git a/pkg/resources/function.go b/pkg/resources/function.go index 4cc818d5b0..f08ffcedf9 100644 --- a/pkg/resources/function.go +++ b/pkg/resources/function.go @@ -13,7 +13,7 @@ import ( "github.com/pkg/errors" ) -var languages = []string{"javascript", "java", "sql"} +var languages = []string{"javascript", "java", "sql", "python"} var functionSchema = map[string]*schema.Schema{ "name": { @@ -21,6 +21,12 @@ var functionSchema = map[string]*schema.Schema{ Required: true, Description: "Specifies the identifier for the function; does not have to be unique for the schema in which the function is created. Don't use the | character.", }, + "warehouse": { + Type: schema.TypeString, + Optional: true, + Description: "The warehouse in which to create the function. Only for Python language.", + ForceNew: true, + }, "database": { Type: schema.TypeString, Required: true, @@ -68,7 +74,7 @@ var functionSchema = map[string]*schema.Schema{ "statement": { Type: schema.TypeString, Required: true, - Description: "Specifies the javascript / java / sql code used to create the function.", + Description: "Specifies the javascript / java / sql / python code used to create the function.", ForceNew: true, DiffSuppressFunc: DiffSuppressStatement, }, @@ -102,6 +108,21 @@ var functionSchema = map[string]*schema.Schema{ Default: "user-defined function", Description: "Specifies a comment for the function.", }, + "runtime_version": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "Required for Python functions. Specifies Python runtime version.", + }, + "packages": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + ForceNew: true, + Description: "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').", + }, "imports": { Type: schema.TypeList, Elem: &schema.Schema{ @@ -109,19 +130,19 @@ var functionSchema = map[string]*schema.Schema{ }, Optional: true, ForceNew: true, - Description: "jar files to import for Java function.", + Description: "Imports for Java / Python functions. For Java this a list of jar files, for Python this is a list of Python files.", }, "handler": { Type: schema.TypeString, Optional: true, ForceNew: true, - Description: "the handler method for Java function.", + Description: "The handler method for Java / Python function.", }, "target_path": { Type: schema.TypeString, Optional: true, ForceNew: true, - Description: "the target path for compiled jar file for Java function.", + Description: "The target path for the Java / Python functions. For Java, it is the path of compiled jar files and for the Python it is the path of the Python files.", }, } @@ -179,11 +200,38 @@ func CreateFunction(d *schema.ResourceData, meta interface{}) error { builder.WithLanguage(v.(string)) } + // Set optionals, runtime version for python + if v, ok := d.GetOk("runtime_version"); ok { + builder.WithRuntimeVersion(v.(string)) + } + + // Set optionals, warehouse in which to create the function + if v, ok := d.GetOk("warehouse"); ok { + builder.WithWarehouse(v.(string)) + q, err := builder.UseWarehouse() + if err != nil { + return err + } + err = snowflake.Exec(db, q) + if err != nil { + return errors.Wrapf(err, "error using warehouse %v", v.(string)) + } + } + if v, ok := d.GetOk("comment"); ok { builder.WithComment(v.(string)) } - // Set optionals, imports for Java + // Set optionals, packages for Java / python + if _, ok := d.GetOk("packages"); ok { + packages := []string{} + for _, pack := range d.Get("packages").([]interface{}) { + packages = append(packages, pack.(string)) + } + builder.WithPackages(packages) + } + + // Set optionals, imports for Java / python if _, ok := d.GetOk("imports"); ok { imports := []string{} for _, imp := range d.Get("imports").([]interface{}) { @@ -192,12 +240,12 @@ func CreateFunction(d *schema.ResourceData, meta interface{}) error { builder.WithImports(imports) } - // handler for Java + // handler for Java / python if v, ok := d.GetOk("handler"); ok { builder.WithHandler(v.(string)) } - // target path for Java + // target path for Java / python if v, ok := d.GetOk("target_path"); ok { builder.WithTargetPath(v.(string)) } @@ -303,6 +351,14 @@ func ReadFunction(d *schema.ResourceData, meta interface{}) error { return err } } + case "packages": + packagesString := strings.ReplaceAll(strings.ReplaceAll(desc.Value.String, "[", ""), "]", "") + if packagesString != "" { // Do nothing for Java / Python functions without packages + packages := strings.Split(packagesString, ", ") + if err = d.Set("packages", packages); err != nil { + return err + } + } case "imports": importsString := strings.ReplaceAll(strings.ReplaceAll(desc.Value.String, "[", ""), "]", "") if importsString != "" { // Do nothing for Java functions without imports @@ -315,12 +371,18 @@ func ReadFunction(d *schema.ResourceData, meta interface{}) error { if err = d.Set("handler", desc.Value.String); err != nil { return err } + case "warehouse": + if err = d.Set("warehouse", desc.Value.String); err != nil { + return err + } case "target_path": if err = d.Set("target_path", desc.Value.String); err != nil { return err } case "runtime_version": - // runtime version for Java function. currently not used. + if err = d.Set("runtime_version", desc.Value.String); err != nil { + return err + } default: log.Printf("[WARN] unexpected function property %v returned from Snowflake", desc.Property.String) } diff --git a/pkg/resources/function_acceptance_test.go b/pkg/resources/function_acceptance_test.go index 124b55c30c..b2a5428bf2 100644 --- a/pkg/resources/function_acceptance_test.go +++ b/pkg/resources/function_acceptance_test.go @@ -18,17 +18,20 @@ func TestAcc_Function(t *testing.T) { dbName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) functName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + warehouseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + expBody1 := "3.141592654::FLOAT" expBody2 := "var X=3\nreturn X" expBody3 := "select 1, 2\nunion all\nselect 3, 4\n" expBody4 := `class CoolFunc {public static String test(int n) {return "hello!";}}` + expBody5 := "def add_py(i, j): return i+j" resource.Test(t, resource.TestCase{ Providers: providers(), CheckDestroy: nil, Steps: []resource.TestStep{ { - Config: functionConfig(dbName, schemaName, functName), + Config: functionConfig(dbName, schemaName, functName, warehouseName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("snowflake_function.test_funct", "name", functName), resource.TestCheckResourceAttr("snowflake_function.test_funct", "comment", "Terraform acceptance test"), @@ -54,13 +57,20 @@ 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"), + + resource.TestCheckResourceAttr("snowflake_function.test_funct_python", "name", functName), + resource.TestCheckResourceAttr("snowflake_function.test_funct_python", "comment", "Terraform acceptance test for python"), + resource.TestCheckResourceAttr("snowflake_function.test_funct_python", "statement", expBody5), + resource.TestCheckResourceAttr("snowflake_function.test_funct_python", "arguments.#", "2"), + resource.TestCheckResourceAttr("snowflake_function.test_funct_python", "arguments.0.name", "ARG1"), + resource.TestCheckResourceAttr("snowflake_function.test_funct_python", "arguments.0.type", "NUMBER"), ), }, }, }) } -func functionConfig(db, schema, name string) string { +func functionConfig(db, schema, name, warehouse string) string { return fmt.Sprintf(` resource "snowflake_database" "test_database" { name = "%s" @@ -73,6 +83,7 @@ func functionConfig(db, schema, name string) string { comment = "Terraform acceptance test" } + resource "snowflake_function" "test_funct_simple" { name = "%s" database = snowflake_database.test_database.name @@ -110,6 +121,32 @@ func functionConfig(db, schema, name string) string { statement = "class CoolFunc {public static String test(int n) {return \"hello!\";}}" } + resource "snowflake_warehouse" "test_wh" { + name = "%s" + comment = "Warehouse for terraform acceptance test" + } + + resource "snowflake_function" "test_funct_python" { + name = "%s" + database = snowflake_database.test_database.name + schema = snowflake_schema.test_schema.name + warehouse = snowflake_warehouse.test_wh.name + arguments { + name = "ARG1" + type = "NUMBER" + } + arguments { + name = "ARG2" + type = "NUMBER" + } + comment = "Terraform acceptance test for python" + return_type = "NUMBER(38,0)" + language = "python" + runtime_version = "3.8" + handler = "add_py" + statement = "def add_py(i, j): return i+j" + } + resource "snowflake_function" "test_funct_complex" { name = "%s" database = snowflake_database.test_database.name @@ -130,5 +167,5 @@ union all select 3, 4 EOT } - `, db, schema, name, name, name, name) + `, db, schema, name, name, name, warehouse, name, name) } diff --git a/pkg/resources/function_test.go b/pkg/resources/function_test.go index 8ee6452edf..0e21d2b715 100644 --- a/pkg/resources/function_test.go +++ b/pkg/resources/function_test.go @@ -13,19 +13,24 @@ import ( "github.com/stretchr/testify/require" ) -const functionBody string = "hi" +const functionBody string = "def add_py(i, j): return i+j" func prepDummyFunctionResource(t *testing.T) *schema.ResourceData { argument1 := map[string]interface{}{"name": "data", "type": "varchar"} argument2 := map[string]interface{}{"name": "event_dt", "type": "date"} in := map[string]interface{}{ - "name": "my_funct", - "database": "my_db", - "schema": "my_schema", - "arguments": []interface{}{argument1, argument2}, - "return_type": "varchar", - "return_behavior": "IMMUTABLE", - "statement": functionBody, //var message = DATA + DATA;return message + "name": "my_funct", + "database": "my_db", + "schema": "my_schema", + "arguments": []interface{}{argument1, argument2}, + "language": "PYTHON", + "null_input_behaviour": "CALLED ON NULL INPUT", + "return_behavior": "VOLATILE", + "runtime_version": "3.8", + "packages": []interface{}{"numpy", "pandas"}, + "handler": "add_py", + "return_type": "varchar", + "statement": functionBody, //var message = DATA + DATA;return message } d := function(t, "my_db|my_schema|my_funct|VARCHAR-DATE", in) return d @@ -42,7 +47,7 @@ func TestFunctionCreate(t *testing.T) { d := prepDummyFunctionResource(t) WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) { - mock.ExpectExec(`CREATE OR REPLACE FUNCTION "my_db"."my_schema"."my_funct"\(data VARCHAR, event_dt DATE\) RETURNS VARCHAR CALLED ON NULL INPUT IMMUTABLE COMMENT = 'user-defined function' AS \$\$hi\$\$`).WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectExec(`CREATE OR REPLACE FUNCTION "my_db"."my_schema"."my_funct"\(data VARCHAR, event_dt DATE\) RETURNS VARCHAR LANGUAGE PYTHON CALLED ON NULL INPUT VOLATILE RUNTIME_VERSION = '3.8' PACKAGES = \('numpy', 'pandas'\) COMMENT = 'user-defined function' HANDLER = 'add_py' AS \$\$def add_py\(i, j\)\: return i\+j\$\$`).WillReturnResult(sqlmock.NewResult(1, 1)) expectFunctionRead(mock) err := resources.CreateFunction(d, db) r.NoError(err) @@ -60,9 +65,7 @@ func expectFunctionRead(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", "SQL"). - AddRow("null handling", "CALLED ON NULL INPUT"). - AddRow("volatility", "IMMUTABLE"). + AddRow("language", "PYTHON"). AddRow("body", functionBody) mock.ExpectQuery(`DESCRIBE FUNCTION "my_db"."my_schema"."my_funct"\(VARCHAR, DATE\)`).WillReturnRows(describeRows) @@ -82,6 +85,18 @@ func TestFunctionRead(t *testing.T) { r.Equal("user-defined function", d.Get("comment").(string)) r.Equal("VARCHAR", d.Get("return_type").(string)) r.Equal(functionBody, d.Get("statement").(string)) + r.Equal("PYTHON", d.Get("language").(string)) + r.Equal("3.8", d.Get("runtime_version").(string)) + r.Equal("add_py", d.Get("handler").(string)) + r.Equal("CALLED ON NULL INPUT", d.Get("null_input_behavior").(string)) + r.Equal("VOLATILE", d.Get("return_behavior").(string)) + + pkgs := d.Get("packages").([]interface{}) + r.Len(pkgs, 2) + test_funct_pkg1 := pkgs[0].(string) + test_funct_pkg2 := pkgs[1].(string) + r.Equal("numpy", test_funct_pkg1) + r.Equal("pandas", test_funct_pkg2) args := d.Get("arguments").([]interface{}) r.Len(args, 2) diff --git a/pkg/snowflake/function.go b/pkg/snowflake/function.go index cebe2111cb..351e4d154c 100644 --- a/pkg/snowflake/function.go +++ b/pkg/snowflake/function.go @@ -22,11 +22,14 @@ type FunctionBuilder struct { nullInputBehavior string // "CALLED ON NULL INPUT" or "RETURNS NULL ON NULL INPUT" returnType string language string - imports []string // for Java imports - handler string // for Java handler - targetPath string // for Java target path for compiled jar file + packages []string + imports []string // for Java / Python imports + handler string // for Java / Python handler + targetPath string // for Java / Python target path comment string statement string + runtimeVersion string // for Python runtime version + warehouse string } // QualifiedName prepends the db and schema and appends argument types @@ -62,6 +65,12 @@ func (pb *FunctionBuilder) WithArgs(args []map[string]string) *FunctionBuilder { return pb } +// WithRuntimeVersion +func (pb *FunctionBuilder) WithRuntimeVersion(r string) *FunctionBuilder { + pb.runtimeVersion = r + return pb +} + // WithReturnBehavior func (pb *FunctionBuilder) WithReturnBehavior(s string) *FunctionBuilder { pb.returnBehavior = s @@ -86,19 +95,31 @@ func (pb *FunctionBuilder) WithLanguage(s string) *FunctionBuilder { return pb } -// WithImports adds jar files to import for Java function +// Withwarehouse +func (pb *FunctionBuilder) WithWarehouse(s string) *FunctionBuilder { + pb.warehouse = s + return pb +} + +// WithPackages +func (pb *FunctionBuilder) WithPackages(s []string) *FunctionBuilder { + pb.packages = s + return pb +} + +// WithImports adds jar files to import for Java function or Python file for Python function func (pb *FunctionBuilder) WithImports(s []string) *FunctionBuilder { pb.imports = s return pb } -// WithHandler sets the handler method for Java function +// WithHandler sets the handler method for Java / Python function func (pb *FunctionBuilder) WithHandler(s string) *FunctionBuilder { pb.handler = s return pb } -// WithTargetPath sets the target path for compiled jar file for Java function +// WithTargetPath sets the target path for compiled jar file for Java function or Python file for Python function func (pb *FunctionBuilder) WithTargetPath(s string) *FunctionBuilder { pb.targetPath = s return pb @@ -140,6 +161,13 @@ func Function(db, schema, name string, argTypes []string) *FunctionBuilder { } } +func (pb *FunctionBuilder) UseWarehouse() (string, error) { + var q strings.Builder + + q.WriteString(fmt.Sprintf("USE WAREHOUSE %v", pb.warehouse)) + return q.String(), nil +} + // Create returns the SQL query that will create a new function. func (pb *FunctionBuilder) Create() (string, error) { var q strings.Builder @@ -165,15 +193,32 @@ func (pb *FunctionBuilder) Create() (string, error) { if pb.language != "" { q.WriteString(fmt.Sprintf(" LANGUAGE %v", pb.language)) } + if pb.nullInputBehavior != "" { q.WriteString(fmt.Sprintf(` %v`, EscapeString(pb.nullInputBehavior))) } if pb.returnBehavior != "" { q.WriteString(fmt.Sprintf(` %v`, EscapeString(pb.returnBehavior))) } + + if pb.runtimeVersion != "" { + q.WriteString(fmt.Sprintf(" RUNTIME_VERSION = '%v'", EscapeString(pb.runtimeVersion))) + } + + if len(pb.packages) > 0 { + q.WriteString(` PACKAGES = (`) + packages := []string{} + for _, pack := range pb.packages { + packages = append(packages, fmt.Sprintf(`'%v'`, pack)) + } + q.WriteString(strings.Join(packages, ", ")) + q.WriteString(`)`) + } + if pb.comment != "" { q.WriteString(fmt.Sprintf(" COMMENT = '%v'", EscapeString(pb.comment))) } + if len(pb.imports) > 0 { q.WriteString(` IMPORTS = (`) imports := []string{} @@ -183,12 +228,15 @@ func (pb *FunctionBuilder) Create() (string, error) { q.WriteString(strings.Join(imports, ", ")) q.WriteString(`)`) } + if pb.handler != "" { q.WriteString(fmt.Sprintf(" HANDLER = '%v'", pb.handler)) } + if pb.targetPath != "" { q.WriteString(fmt.Sprintf(" TARGET_PATH = '%v'", pb.targetPath)) } + q.WriteString(fmt.Sprintf(" AS $$%v$$", pb.statement)) return q.String(), nil } diff --git a/pkg/snowflake/function_test.go b/pkg/snowflake/function_test.go index 14216506b4..94adf92473 100644 --- a/pkg/snowflake/function_test.go +++ b/pkg/snowflake/function_test.go @@ -36,6 +36,20 @@ func getJavaFuction(withArgs bool) *FunctionBuilder { return s } +const pythonfunc = `def add_py(i):` + "\n " + + ` return i+1` + +func getPythonFuction(withArgs bool) *FunctionBuilder { + s := Function("test_db", "test_schema", "test_func", []string{}) + s.WithReturnType("int") + s.WithStatement(pythonfunc) + if withArgs { + s.WithArgs([]map[string]string{ + {"name": "arg", "type": "int"}}) + } + return s +} + func TestFunctionQualifiedName(t *testing.T) { r := require.New(t) s := getJavaScriptFuction(true) @@ -124,6 +138,87 @@ func TestFunctionCreateWithJavaFunctionWithTargetPath(t *testing.T) { r.Equal(expected, createStmnt) } +func TestFunctionCreateWithPythonFunction(t *testing.T) { + r := require.New(t) + s := getPythonFuction(true) + s.WithComment("this is cool func!") + s.WithLanguage("PYTHON") + s.WithRuntimeVersion("3.8") + s.WithHandler("CoolFunc.test") + createStmnt, _ := s.Create() + expected := `CREATE OR REPLACE FUNCTION "test_db"."test_schema"."test_func"` + + `(arg INT) RETURNS INT` + + ` LANGUAGE PYTHON RUNTIME_VERSION = '3.8' COMMENT = 'this is cool func!'` + + ` HANDLER = 'CoolFunc.test' AS $$` + pythonfunc + `$$` + r.Equal(expected, createStmnt) +} + +func TestFunctionCreateWithPythonFunctionWithPackages(t *testing.T) { + r := require.New(t) + s := getPythonFuction(true) + + pkgs := []string{"numpy", "pandas"} + + s.WithComment("this is cool func!") + s.WithLanguage("PYTHON") + s.WithRuntimeVersion("3.8") + s.WithPackages(pkgs) + s.WithHandler("CoolFunc.test") + + createStmnt, _ := s.Create() + + expected := `CREATE OR REPLACE FUNCTION "test_db"."test_schema"."test_func"` + + `(arg INT) RETURNS INT` + + ` LANGUAGE PYTHON RUNTIME_VERSION = '3.8' PACKAGES = ('numpy', 'pandas') COMMENT = 'this is cool func!'` + + ` HANDLER = 'CoolFunc.test' AS $$` + pythonfunc + `$$` + r.Equal(expected, createStmnt) +} + +func TestFunctionCreateWithPythonFunctionWithImports(t *testing.T) { + r := require.New(t) + s := getPythonFuction(true) + s.WithComment("this is cool func!") + s.WithLanguage("PYTHON") + s.WithRuntimeVersion("3.8") + s.WithHandler("CoolFunc.test") + s.WithImports([]string{"@~/stage/myudf1.py", "@~/stage/myudf2.py"}) + + createStmnt, _ := s.Create() + expected := `CREATE OR REPLACE FUNCTION "test_db"."test_schema"."test_func"` + + `(arg INT) RETURNS INT` + + ` LANGUAGE PYTHON RUNTIME_VERSION = '3.8' COMMENT = 'this is cool func!'` + + ` IMPORTS = ('@~/stage/myudf1.py', '@~/stage/myudf2.py') HANDLER = 'CoolFunc.test'` + + ` AS $$` + pythonfunc + `$$` + r.Equal(expected, createStmnt) +} + +func TestFunctionCreateWithPythonFunctionWithTargetPath(t *testing.T) { + r := require.New(t) + s := getPythonFuction(true) + s.WithComment("this is cool func!") + s.WithLanguage("PYTHON") + s.WithRuntimeVersion("3.8") + s.WithTargetPath("@~/stage/myudf1.py") + s.WithHandler("CoolFunc.test") + + createStmnt, _ := s.Create() + expected := `CREATE OR REPLACE FUNCTION "test_db"."test_schema"."test_func"` + + `(arg INT) RETURNS INT` + + ` LANGUAGE PYTHON RUNTIME_VERSION = '3.8' COMMENT = 'this is cool func!'` + + ` HANDLER = 'CoolFunc.test' TARGET_PATH = '@~/stage/myudf1.py'` + + ` AS $$` + pythonfunc + `$$` + r.Equal(expected, createStmnt) +} + +func TestFuctionUseWarehouse(t *testing.T) { + r := require.New(t) + s := getPythonFuction(false) + + s.WithWarehouse("test_wh") + stmnt, _ := s.UseWarehouse() + r.Equal(stmnt, `USE WAREHOUSE test_wh`) +} + func TestFunctionDrop(t *testing.T) { r := require.New(t)