diff --git a/examples/data-sources/snowflake_materialized_views/data-source.tf b/examples/data-sources/snowflake_materialized_views/data-source.tf new file mode 100644 index 0000000000..bcc9a64f49 --- /dev/null +++ b/examples/data-sources/snowflake_materialized_views/data-source.tf @@ -0,0 +1,4 @@ +data "snowflake_materialized_views" "current" { + database = "MYDB" + schema = "MYSCHEMA" +} \ No newline at end of file diff --git a/examples/data-sources/snowflake_schemas/data-source.tf b/examples/data-sources/snowflake_schemas/data-source.tf new file mode 100644 index 0000000000..314ba572d4 --- /dev/null +++ b/examples/data-sources/snowflake_schemas/data-source.tf @@ -0,0 +1,3 @@ +data "snowflake_schemas" "current" { + database = "MYDB" +} \ No newline at end of file diff --git a/examples/data-sources/snowflake_tables/data-source.tf b/examples/data-sources/snowflake_tables/data-source.tf new file mode 100644 index 0000000000..5e4b16adfe --- /dev/null +++ b/examples/data-sources/snowflake_tables/data-source.tf @@ -0,0 +1,4 @@ +data "snowflake_tables" "current" { + database = "MYDB" + schema = "MYSCHEMA" +} \ No newline at end of file diff --git a/examples/data-sources/snowflake_views/data-source.tf b/examples/data-sources/snowflake_views/data-source.tf new file mode 100644 index 0000000000..e4a977c759 --- /dev/null +++ b/examples/data-sources/snowflake_views/data-source.tf @@ -0,0 +1,4 @@ +data "snowflake_views" "current" { + database = "MYDB" + schema = "MYSCHEMA" +} \ No newline at end of file diff --git a/pkg/datasources/materialized_views.go b/pkg/datasources/materialized_views.go new file mode 100644 index 0000000000..82183b9fbf --- /dev/null +++ b/pkg/datasources/materialized_views.go @@ -0,0 +1,90 @@ +package datasources + +import ( + "database/sql" + "fmt" + "log" + + "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/snowflake" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var materializedViewsSchema = map[string]*schema.Schema{ + "database": { + Type: schema.TypeString, + Required: true, + Description: "The database from which to return the schemas from.", + }, + "schema": { + Type: schema.TypeString, + Required: true, + Description: "The schema from which to return the views from.", + }, + "materialized_views": { + Type: schema.TypeList, + Computed: true, + Description: "The views in the schema", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + "database": { + Type: schema.TypeString, + Computed: true, + }, + "schema": { + Type: schema.TypeString, + Computed: true, + }, + "comment": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + }, + }, +} + +func MaterializedViews() *schema.Resource { + return &schema.Resource{ + Read: ReadMaterializedViews, + Schema: materializedViewsSchema, + } +} + +func ReadMaterializedViews(d *schema.ResourceData, meta interface{}) error { + db := meta.(*sql.DB) + databaseName := d.Get("database").(string) + schemaName := d.Get("schema").(string) + + currentViews, err := snowflake.ListMaterializedViews(databaseName, schemaName, db) + if err == sql.ErrNoRows { + // If not found, mark resource to be removed from statefile during apply or refresh + log.Printf("[DEBUG] materialized views in schema (%s) not found", d.Id()) + d.SetId("") + return nil + } else if err != nil { + log.Printf("[DEBUG] materialized unable to parse views in schema (%s)", d.Id()) + d.SetId("") + return nil + } + + views := []map[string]interface{}{} + + for _, view := range currentViews { + viewMap := map[string]interface{}{} + + viewMap["name"] = view.Name.String + viewMap["database"] = view.DatabaseName.String + viewMap["schema"] = view.SchemaName.String + viewMap["comment"] = view.Comment.String + + views = append(views, viewMap) + } + + d.SetId(fmt.Sprintf(`%v|%v`, databaseName, schemaName)) + return d.Set("materialized_views", views) +} diff --git a/pkg/datasources/materialized_views_acceptance_test.go b/pkg/datasources/materialized_views_acceptance_test.go new file mode 100644 index 0000000000..1942291209 --- /dev/null +++ b/pkg/datasources/materialized_views_acceptance_test.go @@ -0,0 +1,77 @@ +package datasources_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccMaterializedViews(t *testing.T) { + warehouseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + tableName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + viewName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + resource.ParallelTest(t, resource.TestCase{ + Providers: providers(), + Steps: []resource.TestStep{ + { + Config: materializedViews(warehouseName, databaseName, schemaName, tableName, viewName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.snowflake_materialized_views.v", "database", databaseName), + resource.TestCheckResourceAttr("data.snowflake_materialized_views.v", "schema", schemaName), + resource.TestCheckResourceAttrSet("data.snowflake_materialized_views.v", "materialized_views.#"), + resource.TestCheckResourceAttr("data.snowflake_materialized_views.v", "materialized_views.#", "1"), + resource.TestCheckResourceAttr("data.snowflake_materialized_views.v", "materialized_views.0.name", viewName), + ), + }, + }, + }) +} + +func materializedViews(warehouseName string, databaseName string, schemaName string, tableName string, viewName string) string { + return fmt.Sprintf(` + resource "snowflake_warehouse" "w" { + name = "%v" + initially_suspended = false + } + + resource snowflake_database "d" { + name = "%v" + } + + resource snowflake_schema "s"{ + name = "%v" + database = snowflake_database.d.name + } + + resource snowflake_table "t"{ + name = "%v" + database = snowflake_schema.s.database + schema = snowflake_schema.s.name + column { + name = "column2" + type = "VARCHAR(16)" + } + } + + resource snowflake_materialized_view "v"{ + name = "%v" + comment = "Terraform test resource" + database = snowflake_schema.s.database + schema = snowflake_schema.s.name + is_secure = true + or_replace = false + statement = "SELECT * FROM ${snowflake_table.t.name}" + warehouse = snowflake_warehouse.w.name + } + + data snowflake_materialized_views "v" { + database = snowflake_materialized_view.v.database + schema = snowflake_materialized_view.v.schema + } + `, warehouseName, databaseName, schemaName, tableName, viewName) +} diff --git a/pkg/datasources/schemas.go b/pkg/datasources/schemas.go new file mode 100644 index 0000000000..499587ca8d --- /dev/null +++ b/pkg/datasources/schemas.go @@ -0,0 +1,80 @@ +package datasources + +import ( + "database/sql" + "log" + + "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/snowflake" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var schemasSchema = map[string]*schema.Schema{ + "database": { + Type: schema.TypeString, + Required: true, + Description: "The database from which to return the schemas from.", + }, + "schemas": { + Type: schema.TypeList, + Computed: true, + Description: "The schemas in the database", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + "database": { + Type: schema.TypeString, + Computed: true, + }, + "comment": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + }, + }, +} + +func Schemas() *schema.Resource { + return &schema.Resource{ + Read: ReadSchemas, + Schema: schemasSchema, + } +} + +func ReadSchemas(d *schema.ResourceData, meta interface{}) error { + db := meta.(*sql.DB) + databaseName := d.Get("database").(string) + + log.Printf("[DEBUG] database name %s", databaseName) + + currentSchemas, err := snowflake.ListSchemas(databaseName, db) + if err == sql.ErrNoRows { + // If not found, mark resource to be removed from statefile during apply or refresh + log.Printf("[DEBUG] schemas in database (%s) not found", d.Id()) + d.SetId("") + return nil + } else if err != nil { + log.Printf("[DEBUG] unable to parse schemas in database (%s)", d.Id()) + d.SetId("") + return nil + } + + schemas := []map[string]interface{}{} + + for _, schema := range currentSchemas { + schemaMap := map[string]interface{}{} + + schemaMap["name"] = schema.Name.String + schemaMap["database"] = schema.DatabaseName.String + schemaMap["comment"] = schema.Comment.String + + schemas = append(schemas, schemaMap) + } + + d.SetId(databaseName) + return d.Set("schemas", schemas) +} diff --git a/pkg/datasources/schemas_acceptance_test.go b/pkg/datasources/schemas_acceptance_test.go new file mode 100644 index 0000000000..3967ce3d28 --- /dev/null +++ b/pkg/datasources/schemas_acceptance_test.go @@ -0,0 +1,46 @@ +package datasources_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccSchemas(t *testing.T) { + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + resource.ParallelTest(t, resource.TestCase{ + Providers: providers(), + Steps: []resource.TestStep{ + { + Config: schemas(databaseName, schemaName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.snowflake_schemas.s", "database", databaseName), + resource.TestCheckResourceAttrSet("data.snowflake_schemas.s", "schemas.#"), + resource.TestCheckResourceAttr("data.snowflake_schemas.s", "schemas.#", "3"), + ), + }, + }, + }) +} + +func schemas(databaseName string, schemaName string) string { + return fmt.Sprintf(` + + resource snowflake_database "d" { + name = "%v" + } + + resource snowflake_schema "s"{ + name = "%v" + database = snowflake_database.d.name + } + + data snowflake_schemas "s" { + database = snowflake_schema.s.database + } + `, databaseName, schemaName) +} diff --git a/pkg/datasources/tables.go b/pkg/datasources/tables.go new file mode 100644 index 0000000000..b22e7dac1d --- /dev/null +++ b/pkg/datasources/tables.go @@ -0,0 +1,90 @@ +package datasources + +import ( + "database/sql" + "fmt" + "log" + + "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/snowflake" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var tablesSchema = map[string]*schema.Schema{ + "database": { + Type: schema.TypeString, + Required: true, + Description: "The database from which to return the schemas from.", + }, + "schema": { + Type: schema.TypeString, + Required: true, + Description: "The schema from which to return the tables from.", + }, + "tables": { + Type: schema.TypeList, + Computed: true, + Description: "The tables in the schema", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + "database": { + Type: schema.TypeString, + Computed: true, + }, + "schema": { + Type: schema.TypeString, + Computed: true, + }, + "comment": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + }, + }, +} + +func Tables() *schema.Resource { + return &schema.Resource{ + Read: ReadTables, + Schema: tablesSchema, + } +} + +func ReadTables(d *schema.ResourceData, meta interface{}) error { + db := meta.(*sql.DB) + databaseName := d.Get("database").(string) + schemaName := d.Get("schema").(string) + + currentTables, err := snowflake.ListTables(databaseName, schemaName, db) + if err == sql.ErrNoRows { + // If not found, mark resource to be removed from statefile during apply or refresh + log.Printf("[DEBUG] tables in schema (%s) not found", d.Id()) + d.SetId("") + return nil + } else if err != nil { + log.Printf("[DEBUG] unable to parse tables in schema (%s)", d.Id()) + d.SetId("") + return nil + } + + tables := []map[string]interface{}{} + + for _, table := range currentTables { + tableMap := map[string]interface{}{} + + tableMap["name"] = table.TableName.String + tableMap["database"] = table.DatabaseName.String + tableMap["schema"] = table.SchemaName.String + tableMap["comment"] = table.Comment.String + + tables = append(tables, tableMap) + } + + d.SetId(fmt.Sprintf(`%v|%v`, databaseName, schemaName)) + return d.Set("tables", tables) +} diff --git a/pkg/datasources/tables_acceptance_test.go b/pkg/datasources/tables_acceptance_test.go new file mode 100644 index 0000000000..fd163a800c --- /dev/null +++ b/pkg/datasources/tables_acceptance_test.go @@ -0,0 +1,60 @@ +package datasources_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccTables(t *testing.T) { + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + tableName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + resource.ParallelTest(t, resource.TestCase{ + Providers: providers(), + Steps: []resource.TestStep{ + { + Config: tables(databaseName, schemaName, tableName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.snowflake_tables.t", "database", databaseName), + resource.TestCheckResourceAttr("data.snowflake_tables.t", "schema", schemaName), + resource.TestCheckResourceAttrSet("data.snowflake_tables.t", "tables.#"), + resource.TestCheckResourceAttr("data.snowflake_tables.t", "tables.#", "1"), + resource.TestCheckResourceAttr("data.snowflake_tables.t", "tables.0.name", tableName), + ), + }, + }, + }) +} + +func tables(databaseName string, schemaName string, tableName string) string { + return fmt.Sprintf(` + + resource snowflake_database "d" { + name = "%v" + } + + resource snowflake_schema "s"{ + name = "%v" + database = snowflake_database.d.name + } + + resource snowflake_table "t"{ + name = "%v" + database = snowflake_schema.s.database + schema = snowflake_schema.s.name + column { + name = "column2" + type = "VARCHAR(16)" + } + } + + data snowflake_tables "t" { + database = snowflake_table.t.database + schema = snowflake_table.t.schema + } + `, databaseName, schemaName, tableName) +} diff --git a/pkg/datasources/views.go b/pkg/datasources/views.go new file mode 100644 index 0000000000..6d5da73a1f --- /dev/null +++ b/pkg/datasources/views.go @@ -0,0 +1,90 @@ +package datasources + +import ( + "database/sql" + "fmt" + "log" + + "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/snowflake" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var viewsSchema = map[string]*schema.Schema{ + "database": { + Type: schema.TypeString, + Required: true, + Description: "The database from which to return the schemas from.", + }, + "schema": { + Type: schema.TypeString, + Required: true, + Description: "The schema from which to return the views from.", + }, + "views": { + Type: schema.TypeList, + Computed: true, + Description: "The views in the schema", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + "database": { + Type: schema.TypeString, + Computed: true, + }, + "schema": { + Type: schema.TypeString, + Computed: true, + }, + "comment": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + }, + }, +} + +func Views() *schema.Resource { + return &schema.Resource{ + Read: ReadViews, + Schema: viewsSchema, + } +} + +func ReadViews(d *schema.ResourceData, meta interface{}) error { + db := meta.(*sql.DB) + databaseName := d.Get("database").(string) + schemaName := d.Get("schema").(string) + + currentViews, err := snowflake.ListViews(databaseName, schemaName, db) + if err == sql.ErrNoRows { + // If not found, mark resource to be removed from statefile during apply or refresh + log.Printf("[DEBUG] views in schema (%s) not found", d.Id()) + d.SetId("") + return nil + } else if err != nil { + log.Printf("[DEBUG] unable to parse views in schema (%s)", d.Id()) + d.SetId("") + return nil + } + + views := []map[string]interface{}{} + + for _, view := range currentViews { + viewMap := map[string]interface{}{} + + viewMap["name"] = view.Name.String + viewMap["database"] = view.DatabaseName.String + viewMap["schema"] = view.SchemaName.String + viewMap["comment"] = view.Comment.String + + views = append(views, viewMap) + } + + d.SetId(fmt.Sprintf(`%v|%v`, databaseName, schemaName)) + return d.Set("views", views) +} diff --git a/pkg/datasources/views_acceptance_test.go b/pkg/datasources/views_acceptance_test.go new file mode 100644 index 0000000000..29c3a74768 --- /dev/null +++ b/pkg/datasources/views_acceptance_test.go @@ -0,0 +1,57 @@ +package datasources_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccViews(t *testing.T) { + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + viewName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + resource.ParallelTest(t, resource.TestCase{ + Providers: providers(), + Steps: []resource.TestStep{ + { + Config: views(databaseName, schemaName, viewName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.snowflake_views.v", "database", databaseName), + resource.TestCheckResourceAttr("data.snowflake_views.v", "schema", schemaName), + resource.TestCheckResourceAttrSet("data.snowflake_views.v", "views.#"), + resource.TestCheckResourceAttr("data.snowflake_views.v", "views.#", "1"), + resource.TestCheckResourceAttr("data.snowflake_views.v", "views.0.name", viewName), + ), + }, + }, + }) +} + +func views(databaseName string, schemaName string, viewName string) string { + return fmt.Sprintf(` + + resource snowflake_database "d" { + name = "%v" + } + + resource snowflake_schema "s"{ + name = "%v" + database = snowflake_database.d.name + } + + resource snowflake_view "v"{ + name = "%v" + database = snowflake_schema.s.database + schema = snowflake_schema.s.name + statement = "SELECT ROLE_NAME, ROLE_OWNER FROM INFORMATION_SCHEMA.APPLICABLE_ROLES where ROLE_OWNER like 'foo%%'" + } + + data snowflake_views "v" { + database = snowflake_view.v.database + schema = snowflake_view.v.schema + } + `, databaseName, schemaName, viewName) +} diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 2673bf947e..f5e7d58017 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -197,6 +197,10 @@ func getDataSources() map[string]*schema.Resource { "snowflake_system_get_aws_sns_iam_policy": datasources.SystemGetAWSSNSIAMPolicy(), "snowflake_system_get_privatelink_config": datasources.SystemGetPrivateLinkConfig(), "snowflake_system_get_snowflake_platform_info": datasources.SystemGetSnowflakePlatformInfo(), + "snowflake_schemas": datasources.Schemas(), + "snowflake_tables": datasources.Tables(), + "snowflake_views": datasources.Views(), + "snowflake_materialized_views": datasources.MaterializedViews(), } return dataSources diff --git a/pkg/snowflake/materialized_view.go b/pkg/snowflake/materialized_view.go index c058142bea..ecacdc33f0 100644 --- a/pkg/snowflake/materialized_view.go +++ b/pkg/snowflake/materialized_view.go @@ -3,9 +3,11 @@ package snowflake import ( "database/sql" "fmt" + "log" "strings" "github.com/jmoiron/sqlx" + pe "github.com/pkg/errors" ) // MaterializedViewBuilder abstracts the creation of SQL queries for a Snowflake Materialized View @@ -191,3 +193,20 @@ func ScanMaterializedView(row *sqlx.Row) (*materializedView, error) { err := row.StructScan(r) return r, err } + +func ListMaterializedViews(databaseName string, schemaName string, db *sql.DB) ([]materializedView, error) { + stmt := fmt.Sprintf(`SHOW MATERIALIZED VIEWS IN SCHEMA "%s"."%v"`, databaseName, schemaName) + rows, err := Query(db, stmt) + if err != nil { + return nil, err + } + defer rows.Close() + + dbs := []materializedView{} + err = sqlx.StructScan(rows, &dbs) + if err == sql.ErrNoRows { + log.Printf("[DEBUG] no materialized views found") + return nil, nil + } + return dbs, pe.Wrapf(err, "unable to scan row for %s", stmt) +} diff --git a/pkg/snowflake/schema.go b/pkg/snowflake/schema.go index c75292c976..6ff94ee6fe 100644 --- a/pkg/snowflake/schema.go +++ b/pkg/snowflake/schema.go @@ -3,9 +3,11 @@ package snowflake import ( "database/sql" "fmt" + "log" "strings" "github.com/jmoiron/sqlx" + "github.com/pkg/errors" ) // SchemaBuilder abstracts the creation of SQL queries for a Snowflake schema @@ -189,3 +191,20 @@ func ScanSchema(row *sqlx.Row) (*schema, error) { err := row.StructScan(r) return r, err } + +func ListSchemas(databaseName string, db *sql.DB) ([]schema, error) { + stmt := fmt.Sprintf(`SHOW SCHEMAS IN DATABASE "%v"`, databaseName) + rows, err := Query(db, stmt) + if err != nil { + return nil, err + } + defer rows.Close() + + dbs := []schema{} + err = sqlx.StructScan(rows, &dbs) + if err == sql.ErrNoRows { + log.Printf("[DEBUG] no schemas found") + return nil, nil + } + return dbs, errors.Wrapf(err, "unable to scan row for %s", stmt) +} diff --git a/pkg/snowflake/table.go b/pkg/snowflake/table.go index 16b76ee4fa..de3343634b 100644 --- a/pkg/snowflake/table.go +++ b/pkg/snowflake/table.go @@ -3,11 +3,13 @@ package snowflake import ( "database/sql" "fmt" + "log" "sort" "strconv" "strings" "github.com/jmoiron/sqlx" + "github.com/pkg/errors" ) type PrimaryKey struct { @@ -466,3 +468,20 @@ func ScanPrimaryKeyDescription(rows *sqlx.Rows) ([]primaryKeyDescription, error) } return pkds, rows.Err() } + +func ListTables(databaseName string, schemaName string, db *sql.DB) ([]table, error) { + stmt := fmt.Sprintf(`SHOW TABLES IN SCHEMA "%s"."%v"`, databaseName, schemaName) + rows, err := Query(db, stmt) + if err != nil { + return nil, err + } + defer rows.Close() + + dbs := []table{} + err = sqlx.StructScan(rows, &dbs) + if err == sql.ErrNoRows { + log.Printf("[DEBUG] no tables found") + return nil, nil + } + return dbs, errors.Wrapf(err, "unable to scan row for %s", stmt) +} diff --git a/pkg/snowflake/view.go b/pkg/snowflake/view.go index 010efbd82a..1ccbac7e48 100644 --- a/pkg/snowflake/view.go +++ b/pkg/snowflake/view.go @@ -4,9 +4,11 @@ import ( "database/sql" "errors" "fmt" + "log" "strings" "github.com/jmoiron/sqlx" + pe "github.com/pkg/errors" ) // ViewBuilder abstracts the creation of SQL queries for a Snowflake View @@ -196,3 +198,20 @@ func ScanView(row *sqlx.Row) (*view, error) { err := row.StructScan(r) return r, err } + +func ListViews(databaseName string, schemaName string, db *sql.DB) ([]view, error) { + stmt := fmt.Sprintf(`SHOW VIEWS IN SCHEMA "%s"."%v"`, databaseName, schemaName) + rows, err := Query(db, stmt) + if err != nil { + return nil, err + } + defer rows.Close() + + dbs := []view{} + err = sqlx.StructScan(rows, &dbs) + if err == sql.ErrNoRows { + log.Printf("[DEBUG] no views found") + return nil, nil + } + return dbs, pe.Wrapf(err, "unable to scan row for %s", stmt) +}