diff --git a/pkg/sdk/client.go b/pkg/sdk/client.go index 0e9db6dffc..eb056c0ec3 100644 --- a/pkg/sdk/client.go +++ b/pkg/sdk/client.go @@ -52,6 +52,7 @@ type Client struct { Tags Tags Tasks Tasks Users Users + Views Views Warehouses Warehouses } @@ -170,6 +171,7 @@ func (c *Client) initialize() { c.Tags = &tags{client: c} c.Tasks = &tasks{client: c} c.Users = &users{client: c} + c.Views = &views{client: c} c.Warehouses = &warehouses{client: c} } diff --git a/pkg/sdk/integration_test_imports.go b/pkg/sdk/integration_test_imports.go index 86f7dfc715..b109ec8aa6 100644 --- a/pkg/sdk/integration_test_imports.go +++ b/pkg/sdk/integration_test_imports.go @@ -20,6 +20,13 @@ func (c *Client) ExecForTests(ctx context.Context, sql string) (sql.Result, erro return result, decodeDriverError(err) } +// QueryOneForTests is an exact copy of queryOne (that is unexported), that some integration tests/helpers were using +// TODO: remove after introducing all resources using this +func (c *Client) QueryOneForTests(ctx context.Context, dest interface{}, sql string) error { + ctx = context.WithValue(ctx, snowflakeAccountLocatorContextKey, c.accountLocator) + return decodeDriverError(c.db.GetContext(ctx, dest, sql)) +} + func ErrorsEqual(t *testing.T, expected error, actual error) { t.Helper() var expectedErr *Error diff --git a/pkg/sdk/object_types.go b/pkg/sdk/object_types.go index 3f866bccbe..44363d0afb 100644 --- a/pkg/sdk/object_types.go +++ b/pkg/sdk/object_types.go @@ -58,6 +58,7 @@ const ( ObjectTypeApplicationPackage ObjectType = "APPLICATION PACKAGE" ObjectTypeApplicationRole ObjectType = "APPLICATION ROLE" ObjectTypeStreamlit ObjectType = "STREAMLIT" + ObjectTypeColumn ObjectType = "COLUMN" ) func (o ObjectType) String() string { diff --git a/pkg/sdk/poc/generator/db_struct.go b/pkg/sdk/poc/generator/db_struct.go index d42cf83d47..03490dc4ae 100644 --- a/pkg/sdk/poc/generator/db_struct.go +++ b/pkg/sdk/poc/generator/db_struct.go @@ -25,6 +25,22 @@ func (v *dbStruct) Field(dbName string, kind string) *dbStruct { return v } +func (v *dbStruct) Text(dbName string) *dbStruct { + return v.Field(dbName, "string") +} + +func (v *dbStruct) OptionalText(dbName string) *dbStruct { + return v.Field(dbName, "sql.NullString") +} + +func (v *dbStruct) Bool(dbName string) *dbStruct { + return v.Field(dbName, "bool") +} + +func (v *dbStruct) OptionalBool(dbName string) *dbStruct { + return v.Field(dbName, "sql.NullBool") +} + func (v *dbStruct) IntoField() *Field { f := NewField(v.name, v.name, nil, nil) for _, field := range v.fields { diff --git a/pkg/sdk/poc/generator/field_transformers.go b/pkg/sdk/poc/generator/field_transformers.go index 9e404814c2..cdf0299c95 100644 --- a/pkg/sdk/poc/generator/field_transformers.go +++ b/pkg/sdk/poc/generator/field_transformers.go @@ -7,9 +7,10 @@ type FieldTransformer interface { } type KeywordTransformer struct { - required bool - sqlPrefix string - quotes string + required bool + sqlPrefix string + quotes string + parentheses string } func KeywordOptions() *KeywordTransformer { @@ -41,6 +42,11 @@ func (v *KeywordTransformer) DoubleQuotes() *KeywordTransformer { return v } +func (v *KeywordTransformer) Parentheses() *KeywordTransformer { + v.parentheses = "parentheses" + return v +} + func (v *KeywordTransformer) Transform(f *Field) *Field { addTagIfMissing(f.Tags, "ddl", "keyword") if v.required { @@ -48,6 +54,7 @@ func (v *KeywordTransformer) Transform(f *Field) *Field { } addTagIfMissing(f.Tags, "sql", v.sqlPrefix) addTagIfMissing(f.Tags, "ddl", v.quotes) + addTagIfMissing(f.Tags, "ddl", v.parentheses) return f } diff --git a/pkg/sdk/poc/generator/keyword_builders.go b/pkg/sdk/poc/generator/keyword_builders.go index 4bcbb83d54..07b6f02882 100644 --- a/pkg/sdk/poc/generator/keyword_builders.go +++ b/pkg/sdk/poc/generator/keyword_builders.go @@ -62,18 +62,43 @@ func (v *QueryStruct) OptionalSessionParametersUnset() *QueryStruct { return v } -func (v *QueryStruct) WithTags() *QueryStruct { - v.fields = append(v.fields, NewField("Tag", "[]TagAssociation", Tags().Keyword().Parentheses().SQL("TAG"), nil)) +func (v *QueryStruct) NamedListWithParens(sqlPrefix string, listItemKind string, transformer *KeywordTransformer) *QueryStruct { + if transformer != nil { + transformer = transformer.Parentheses().SQL(sqlPrefix) + } else { + transformer = KeywordOptions().Parentheses().SQL(sqlPrefix) + } + v.fields = append(v.fields, NewField(sqlToFieldName(sqlPrefix, true), KindOfSlice(listItemKind), Tags().Keyword(), transformer)) return v } +func (v *QueryStruct) WithTags() *QueryStruct { + return v.NamedListWithParens("TAG", "TagAssociation", nil) +} + func (v *QueryStruct) SetTags() *QueryStruct { - v.fields = append(v.fields, NewField("SetTags", "[]TagAssociation", Tags().Keyword().SQL("SET TAG"), nil)) + return v.setTags(KeywordOptions().Required()) +} + +func (v *QueryStruct) OptionalSetTags() *QueryStruct { + return v.setTags(nil) +} + +func (v *QueryStruct) setTags(transformer *KeywordTransformer) *QueryStruct { + v.fields = append(v.fields, NewField("SetTags", "[]TagAssociation", Tags().Keyword().SQL("SET TAG"), transformer)) return v } func (v *QueryStruct) UnsetTags() *QueryStruct { - v.fields = append(v.fields, NewField("UnsetTags", "[]ObjectIdentifier", Tags().Keyword().SQL("UNSET TAG"), nil)) + return v.unsetTags(KeywordOptions().Required()) +} + +func (v *QueryStruct) OptionalUnsetTags() *QueryStruct { + return v.unsetTags(nil) +} + +func (v *QueryStruct) unsetTags(transformer *KeywordTransformer) *QueryStruct { + v.fields = append(v.fields, NewField("UnsetTags", "[]ObjectIdentifier", Tags().Keyword().SQL("UNSET TAG"), transformer)) return v } diff --git a/pkg/sdk/poc/generator/plain_struct.go b/pkg/sdk/poc/generator/plain_struct.go index c011326bbd..f6fa3eb155 100644 --- a/pkg/sdk/poc/generator/plain_struct.go +++ b/pkg/sdk/poc/generator/plain_struct.go @@ -25,6 +25,22 @@ func (v *plainStruct) Field(name string, kind string) *plainStruct { return v } +func (v *plainStruct) Text(name string) *plainStruct { + return v.Field(name, "string") +} + +func (v *plainStruct) OptionalText(name string) *plainStruct { + return v.Field(name, "*string") +} + +func (v *plainStruct) Bool(name string) *plainStruct { + return v.Field(name, "bool") +} + +func (v *plainStruct) OptionalBool(name string) *plainStruct { + return v.Field(name, "*bool") +} + func (v *plainStruct) IntoField() *Field { f := NewField(v.name, v.name, nil, nil) for _, field := range v.fields { diff --git a/pkg/sdk/poc/main.go b/pkg/sdk/poc/main.go index a634409e7e..3f445de947 100644 --- a/pkg/sdk/poc/main.go +++ b/pkg/sdk/poc/main.go @@ -22,6 +22,7 @@ var definitionMapping = map[string]*generator.Interface{ "tasks_def.go": sdk.TasksDef, "streams_def.go": sdk.StreamsDef, "application_roles_def.go": sdk.ApplicationRolesDef, + "views_def.go": sdk.ViewsDef, } func main() { diff --git a/pkg/sdk/session_policies_def.go b/pkg/sdk/session_policies_def.go index 76c36c204d..2053e48d22 100644 --- a/pkg/sdk/session_policies_def.go +++ b/pkg/sdk/session_policies_def.go @@ -40,8 +40,8 @@ var SessionPoliciesDef = g.NewInterface( WithValidation(g.AtLeastOneValueSet, "SessionIdleTimeoutMins", "SessionUiIdleTimeoutMins", "Comment"), g.KeywordOptions().SQL("SET"), ). - SetTags(). - UnsetTags(). + OptionalSetTags(). + OptionalUnsetTags(). OptionalQueryStructField( "Unset", g.NewQueryStruct("SessionPolicyUnset"). diff --git a/pkg/sdk/streams_def.go b/pkg/sdk/streams_def.go index db9f3a89b7..135ecbc767 100644 --- a/pkg/sdk/streams_def.go +++ b/pkg/sdk/streams_def.go @@ -158,8 +158,8 @@ var ( Name(). OptionalTextAssignment("SET COMMENT", g.ParameterOptions().SingleQuotes()). OptionalSQL("UNSET COMMENT"). - SetTags(). - UnsetTags(). + OptionalSetTags(). + OptionalUnsetTags(). WithValidation(g.ValidIdentifier, "name"). WithValidation(g.ConflictingFields, "IfExists", "UnsetTags"). WithValidation(g.ExactlyOneValueSet, "SetComment", "UnsetComment", "SetTags", "UnsetTags"), diff --git a/pkg/sdk/tasks_def.go b/pkg/sdk/tasks_def.go index 0e80f9b2e8..10e92697f9 100644 --- a/pkg/sdk/tasks_def.go +++ b/pkg/sdk/tasks_def.go @@ -143,8 +143,8 @@ var TasksDef = g.NewInterface( WithValidation(g.AtLeastOneValueSet, "Warehouse", "Schedule", "Config", "AllowOverlappingExecution", "UserTaskTimeoutMs", "SuspendTaskAfterNumFailures", "ErrorIntegration", "Comment", "SessionParametersUnset"), g.KeywordOptions().SQL("UNSET"), ). - SetTags(). - UnsetTags(). + OptionalSetTags(). + OptionalUnsetTags(). OptionalTextAssignment("MODIFY AS", g.ParameterOptions().NoQuotes().NoEquals()). OptionalTextAssignment("MODIFY WHEN", g.ParameterOptions().NoQuotes().NoEquals()). WithValidation(g.ValidIdentifier, "name"). diff --git a/pkg/sdk/tasks_gen_test.go b/pkg/sdk/tasks_gen_test.go index 56bb1f65e9..7d0ac3b700 100644 --- a/pkg/sdk/tasks_gen_test.go +++ b/pkg/sdk/tasks_gen_test.go @@ -71,7 +71,8 @@ func TestTasks_Create(t *testing.T) { WithConfig(String(`$${"output_dir": "/temp/test_directory/", "learning_rate": 0.1}$$`)). WithAllowOverlappingExecution(Bool(true)). WithSessionParameters(&SessionParameters{ - JSONIndent: Int(10), + JSONIndent: Int(10), + LockTimeout: Int(5), }). WithUserTaskTimeoutMs(Int(5)). WithSuspendTaskAfterNumFailures(Int(6)). @@ -85,7 +86,7 @@ func TestTasks_Create(t *testing.T) { }}). WithWhen(String(`SYSTEM$STREAM_HAS_DATA('MYSTREAM')`)) - assertOptsValidAndSQLEquals(t, req.toOpts(), "CREATE OR REPLACE TASK %s WAREHOUSE = %s SCHEDULE = '10 MINUTE' CONFIG = $${\"output_dir\": \"/temp/test_directory/\", \"learning_rate\": 0.1}$$ ALLOW_OVERLAPPING_EXECUTION = true JSON_INDENT = 10 USER_TASK_TIMEOUT_MS = 5 SUSPEND_TASK_AFTER_NUM_FAILURES = 6 ERROR_INTEGRATION = some_error_integration COPY GRANTS COMMENT = 'some comment' AFTER %s TAG (%s = 'v1') WHEN SYSTEM$STREAM_HAS_DATA('MYSTREAM') AS SELECT CURRENT_TIMESTAMP", id.FullyQualifiedName(), warehouseId.FullyQualifiedName(), otherTaskId.FullyQualifiedName(), tagId.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, req.toOpts(), "CREATE OR REPLACE TASK %s WAREHOUSE = %s SCHEDULE = '10 MINUTE' CONFIG = $${\"output_dir\": \"/temp/test_directory/\", \"learning_rate\": 0.1}$$ ALLOW_OVERLAPPING_EXECUTION = true JSON_INDENT = 10, LOCK_TIMEOUT = 5 USER_TASK_TIMEOUT_MS = 5 SUSPEND_TASK_AFTER_NUM_FAILURES = 6 ERROR_INTEGRATION = some_error_integration COPY GRANTS COMMENT = 'some comment' AFTER %s TAG (%s = 'v1') WHEN SYSTEM$STREAM_HAS_DATA('MYSTREAM') AS SELECT CURRENT_TIMESTAMP", id.FullyQualifiedName(), warehouseId.FullyQualifiedName(), otherTaskId.FullyQualifiedName(), tagId.FullyQualifiedName()) }) } diff --git a/pkg/sdk/testint/helpers_test.go b/pkg/sdk/testint/helpers_test.go index 9211c7c7aa..77c6b102ca 100644 --- a/pkg/sdk/testint/helpers_test.go +++ b/pkg/sdk/testint/helpers_test.go @@ -2,6 +2,7 @@ package testint import ( "context" + "database/sql" "errors" "fmt" "path/filepath" @@ -461,6 +462,19 @@ func createMaskingPolicy(t *testing.T, client *sdk.Client, database *sdk.Databas return createMaskingPolicyWithOptions(t, client, database, schema, signature, sdk.DataTypeVARCHAR, expression, &sdk.CreateMaskingPolicyOptions{}) } +func createMaskingPolicyIdentity(t *testing.T, client *sdk.Client, database *sdk.Database, schema *sdk.Schema, columnType sdk.DataType) (*sdk.MaskingPolicy, func()) { + t.Helper() + name := "a" + signature := []sdk.TableColumnSignature{ + { + Name: name, + Type: columnType, + }, + } + expression := "a" + return createMaskingPolicyWithOptions(t, client, database, schema, signature, columnType, expression, &sdk.CreateMaskingPolicyOptions{}) +} + func createMaskingPolicyWithOptions(t *testing.T, client *sdk.Client, database *sdk.Database, schema *sdk.Schema, signature []sdk.TableColumnSignature, returns sdk.DataType, expression string, options *sdk.CreateMaskingPolicyOptions) (*sdk.MaskingPolicy, func()) { t.Helper() var databaseCleanup func() @@ -714,3 +728,46 @@ func createApplication(t *testing.T, client *sdk.Client, name string, packageNam require.NoError(t, err) } } + +func createRowAccessPolicy(t *testing.T, client *sdk.Client, schema *sdk.Schema) (sdk.SchemaObjectIdentifier, func()) { + t.Helper() + ctx := context.Background() + id := sdk.NewSchemaObjectIdentifier(schema.DatabaseName, schema.Name, random.String()) + _, err := client.ExecForTests(ctx, fmt.Sprintf(`CREATE ROW ACCESS POLICY %s AS (A NUMBER) RETURNS BOOLEAN -> TRUE`, id.FullyQualifiedName())) + require.NoError(t, err) + + return id, func() { + _, err := client.ExecForTests(ctx, fmt.Sprintf(`DROP ROW ACCESS POLICY %s`, id.FullyQualifiedName())) + require.NoError(t, err) + } +} + +// TODO: extract getting row access policies as resource (like getting tag in system functions) +// getRowAccessPolicyFor is based on https://docs.snowflake.com/en/user-guide/security-row-intro#obtain-database-objects-with-a-row-access-policy. +func getRowAccessPolicyFor(t *testing.T, client *sdk.Client, id sdk.SchemaObjectIdentifier, objectType sdk.ObjectType) (*policyReference, error) { + t.Helper() + ctx := context.Background() + + s := &policyReference{} + policyReferencesId := sdk.NewSchemaObjectIdentifier(id.DatabaseName(), "INFORMATION_SCHEMA", "POLICY_REFERENCES") + err := client.QueryOneForTests(ctx, s, fmt.Sprintf(`SELECT * FROM TABLE(%s(REF_ENTITY_NAME => '%s', REF_ENTITY_DOMAIN => '%v'))`, policyReferencesId.FullyQualifiedName(), id.FullyQualifiedName(), objectType)) + + return s, err +} + +type policyReference struct { + PolicyDb string `db:"POLICY_DB"` + PolicySchema string `db:"POLICY_SCHEMA"` + PolicyName string `db:"POLICY_NAME"` + PolicyKind string `db:"POLICY_KIND"` + RefDatabaseName string `db:"REF_DATABASE_NAME"` + RefSchemaName string `db:"REF_SCHEMA_NAME"` + RefEntityName string `db:"REF_ENTITY_NAME"` + RefEntityDomain string `db:"REF_ENTITY_DOMAIN"` + RefColumnName sql.NullString `db:"REF_COLUMN_NAME"` + RefArgColumnNames string `db:"REF_ARG_COLUMN_NAMES"` + TagDatabase sql.NullString `db:"TAG_DATABASE"` + TagSchema sql.NullString `db:"TAG_SCHEMA"` + TagName sql.NullString `db:"TAG_NAME"` + PolicyStatus string `db:"POLICY_STATUS"` +} diff --git a/pkg/sdk/testint/views_gen_integration_test.go b/pkg/sdk/testint/views_gen_integration_test.go new file mode 100644 index 0000000000..39e250facf --- /dev/null +++ b/pkg/sdk/testint/views_gen_integration_test.go @@ -0,0 +1,496 @@ +package testint + +import ( + "fmt" + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/random" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TODO: add tests for setting masking policy on creation +// TODO: add tests for setting recursive on creation +func TestInt_Views(t *testing.T) { + client := testClient(t) + ctx := testContext(t) + + table, tableCleanup := createTable(t, client, testDb(t), testSchema(t)) + t.Cleanup(tableCleanup) + + sql := fmt.Sprintf("SELECT id FROM %s", table.ID().FullyQualifiedName()) + + assertViewWithOptions := func(t *testing.T, view *sdk.View, id sdk.SchemaObjectIdentifier, isSecure bool, comment string) { + t.Helper() + assert.NotEmpty(t, view.CreatedOn) + assert.Equal(t, id.Name(), view.Name) + // Kind is filled out only in TERSE response. + assert.Empty(t, view.Kind) + assert.Empty(t, view.Reserved) + assert.Equal(t, testDb(t).Name, view.DatabaseName) + assert.Equal(t, testSchema(t).Name, view.SchemaName) + assert.Equal(t, "ACCOUNTADMIN", view.Owner) + assert.Equal(t, comment, view.Comment) + assert.NotEmpty(t, view.Text) + assert.Equal(t, isSecure, view.IsSecure) + assert.Equal(t, false, view.IsMaterialized) + assert.Equal(t, "ROLE", view.OwnerRoleType) + assert.Equal(t, "OFF", view.ChangeTracking) + } + + assertView := func(t *testing.T, view *sdk.View, id sdk.SchemaObjectIdentifier) { + t.Helper() + assertViewWithOptions(t, view, id, false, "") + } + + assertViewTerse := func(t *testing.T, view *sdk.View, id sdk.SchemaObjectIdentifier) { + t.Helper() + assert.NotEmpty(t, view.CreatedOn) + assert.Equal(t, id.Name(), view.Name) + assert.Equal(t, "VIEW", view.Kind) + assert.Equal(t, testDb(t).Name, view.DatabaseName) + assert.Equal(t, testSchema(t).Name, view.SchemaName) + + // all below are not contained in the terse response, that's why all of them we expect to be empty + assert.Empty(t, view.Reserved) + assert.Empty(t, view.Owner) + assert.Empty(t, view.Comment) + assert.Empty(t, view.Text) + assert.Empty(t, view.IsSecure) + assert.Empty(t, view.IsMaterialized) + assert.Empty(t, view.OwnerRoleType) + assert.Empty(t, view.ChangeTracking) + } + + assertViewDetailsRow := func(t *testing.T, viewDetails *sdk.ViewDetails) { + t.Helper() + assert.Equal(t, sdk.ViewDetails{ + Name: "ID", + Type: "NUMBER(38,0)", + Kind: "COLUMN", + IsNullable: true, + Default: nil, + IsPrimary: false, + IsUnique: false, + Check: nil, + Expression: nil, + Comment: nil, + PolicyName: nil, + }, *viewDetails) + } + + cleanupViewProvider := func(id sdk.SchemaObjectIdentifier) func() { + return func() { + err := client.Views.Drop(ctx, sdk.NewDropViewRequest(id)) + require.NoError(t, err) + } + } + + createViewBasicRequest := func(t *testing.T) *sdk.CreateViewRequest { + t.Helper() + name := random.String() + id := sdk.NewSchemaObjectIdentifier(testDb(t).Name, testSchema(t).Name, name) + + return sdk.NewCreateViewRequest(id, sql) + } + + createViewWithRequest := func(t *testing.T, request *sdk.CreateViewRequest) *sdk.View { + t.Helper() + id := request.GetName() + + err := client.Views.Create(ctx, request) + require.NoError(t, err) + t.Cleanup(cleanupViewProvider(id)) + + view, err := client.Views.ShowByID(ctx, id) + require.NoError(t, err) + + return view + } + + createView := func(t *testing.T) *sdk.View { + t.Helper() + return createViewWithRequest(t, createViewBasicRequest(t)) + } + + t.Run("create view: no optionals", func(t *testing.T) { + request := createViewBasicRequest(t) + + view := createViewWithRequest(t, request) + + assertView(t, view, request.GetName()) + }) + + t.Run("create view: almost complete case", func(t *testing.T) { + rowAccessPolicyId, rowAccessPolicyCleanup := createRowAccessPolicy(t, client, testSchema(t)) + t.Cleanup(rowAccessPolicyCleanup) + + tag, tagCleanup := createTag(t, client, testDb(t), testSchema(t)) + t.Cleanup(tagCleanup) + + request := createViewBasicRequest(t). + WithOrReplace(sdk.Bool(true)). + WithSecure(sdk.Bool(true)). + WithTemporary(sdk.Bool(true)). + WithColumns([]sdk.ViewColumnRequest{ + *sdk.NewViewColumnRequest("COLUMN_WITH_COMMENT").WithComment(sdk.String("column comment")), + }). + WithCopyGrants(sdk.Bool(true)). + WithComment(sdk.String("comment")). + WithRowAccessPolicy(sdk.NewViewRowAccessPolicyRequest(rowAccessPolicyId, []string{"column_with_comment"})). + WithTag([]sdk.TagAssociation{{ + Name: tag.ID(), + Value: "v2", + }}) + + id := request.GetName() + + view := createViewWithRequest(t, request) + + assertViewWithOptions(t, view, id, true, "comment") + rowAccessPolicyReference, err := getRowAccessPolicyFor(t, client, view.ID(), sdk.ObjectTypeView) + require.NoError(t, err) + assert.Equal(t, rowAccessPolicyId.Name(), rowAccessPolicyReference.PolicyName) + assert.Equal(t, "ROW_ACCESS_POLICY", rowAccessPolicyReference.PolicyKind) + assert.Equal(t, view.ID().Name(), rowAccessPolicyReference.RefEntityName) + assert.Equal(t, "VIEW", rowAccessPolicyReference.RefEntityDomain) + assert.Equal(t, "ACTIVE", rowAccessPolicyReference.PolicyStatus) + }) + + t.Run("drop view: existing", func(t *testing.T) { + request := createViewBasicRequest(t) + id := request.GetName() + + err := client.Views.Create(ctx, request) + require.NoError(t, err) + + err = client.Views.Drop(ctx, sdk.NewDropViewRequest(id)) + require.NoError(t, err) + + _, err = client.Views.ShowByID(ctx, id) + assert.ErrorIs(t, err, collections.ErrObjectNotFound) + }) + + t.Run("drop view: non-existing", func(t *testing.T) { + id := sdk.NewSchemaObjectIdentifier(testDb(t).Name, testSchema(t).Name, "does_not_exist") + + err := client.Views.Drop(ctx, sdk.NewDropViewRequest(id)) + assert.ErrorIs(t, err, sdk.ErrObjectNotExistOrAuthorized) + }) + + t.Run("alter view: rename", func(t *testing.T) { + createRequest := createViewBasicRequest(t) + id := createRequest.GetName() + + err := client.Views.Create(ctx, createRequest) + require.NoError(t, err) + + newName := random.String() + newId := sdk.NewSchemaObjectIdentifier(testDb(t).Name, testSchema(t).Name, newName) + alterRequest := sdk.NewAlterViewRequest(id).WithRenameTo(&newId) + + err = client.Views.Alter(ctx, alterRequest) + if err != nil { + t.Cleanup(cleanupViewProvider(id)) + } else { + t.Cleanup(cleanupViewProvider(newId)) + } + require.NoError(t, err) + + _, err = client.Views.ShowByID(ctx, id) + assert.ErrorIs(t, err, collections.ErrObjectNotFound) + + view, err := client.Views.ShowByID(ctx, newId) + require.NoError(t, err) + + assertView(t, view, newId) + }) + + t.Run("alter view: set and unset values", func(t *testing.T) { + view := createView(t) + id := view.ID() + + alterRequest := sdk.NewAlterViewRequest(id).WithSetComment(sdk.String("new comment")) + err := client.Views.Alter(ctx, alterRequest) + require.NoError(t, err) + + alteredView, err := client.Views.ShowByID(ctx, id) + require.NoError(t, err) + + assert.Equal(t, "new comment", alteredView.Comment) + + alterRequest = sdk.NewAlterViewRequest(id).WithSetSecure(sdk.Bool(true)) + err = client.Views.Alter(ctx, alterRequest) + require.NoError(t, err) + + alteredView, err = client.Views.ShowByID(ctx, id) + require.NoError(t, err) + + assert.Equal(t, true, alteredView.IsSecure) + + alterRequest = sdk.NewAlterViewRequest(id).WithSetChangeTracking(sdk.Bool(true)) + err = client.Views.Alter(ctx, alterRequest) + require.NoError(t, err) + + alteredView, err = client.Views.ShowByID(ctx, id) + require.NoError(t, err) + + assert.Equal(t, "ON", alteredView.ChangeTracking) + + alterRequest = sdk.NewAlterViewRequest(id).WithUnsetComment(sdk.Bool(true)) + err = client.Views.Alter(ctx, alterRequest) + require.NoError(t, err) + + alteredView, err = client.Views.ShowByID(ctx, id) + require.NoError(t, err) + + assert.Equal(t, "", alteredView.Comment) + + alterRequest = sdk.NewAlterViewRequest(id).WithUnsetSecure(sdk.Bool(true)) + err = client.Views.Alter(ctx, alterRequest) + require.NoError(t, err) + + alteredView, err = client.Views.ShowByID(ctx, id) + require.NoError(t, err) + + assert.Equal(t, false, alteredView.IsSecure) + + alterRequest = sdk.NewAlterViewRequest(id).WithSetChangeTracking(sdk.Bool(false)) + err = client.Views.Alter(ctx, alterRequest) + require.NoError(t, err) + + alteredView, err = client.Views.ShowByID(ctx, id) + require.NoError(t, err) + + assert.Equal(t, "OFF", alteredView.ChangeTracking) + }) + + t.Run("alter view: set and unset tag", func(t *testing.T) { + tag, tagCleanup := createTag(t, client, testDb(t), testSchema(t)) + t.Cleanup(tagCleanup) + + view := createView(t) + id := view.ID() + + tagValue := "abc" + tags := []sdk.TagAssociation{ + { + Name: tag.ID(), + Value: tagValue, + }, + } + alterRequestSetTags := sdk.NewAlterViewRequest(id).WithSetTags(tags) + + err := client.Views.Alter(ctx, alterRequestSetTags) + require.NoError(t, err) + + // setting object type to view results in: + // SQL compilation error: Invalid value VIEW for argument OBJECT_TYPE. Please use object type TABLE for all kinds of table-like objects. + returnedTagValue, err := client.SystemFunctions.GetTag(ctx, tag.ID(), id, sdk.ObjectTypeTable) + require.NoError(t, err) + + assert.Equal(t, tagValue, returnedTagValue) + + unsetTags := []sdk.ObjectIdentifier{ + tag.ID(), + } + alterRequestUnsetTags := sdk.NewAlterViewRequest(id).WithUnsetTags(unsetTags) + + err = client.Views.Alter(ctx, alterRequestUnsetTags) + require.NoError(t, err) + + _, err = client.SystemFunctions.GetTag(ctx, tag.ID(), id, sdk.ObjectTypeTable) + require.Error(t, err) + }) + + t.Run("alter view: set and unset masking policy", func(t *testing.T) { + maskingPolicy, maskingPolicyCleanup := createMaskingPolicyIdentity(t, client, testDb(t), testSchema(t), sdk.DataTypeNumber) + t.Cleanup(maskingPolicyCleanup) + + view := createView(t) + id := view.ID() + + alterRequest := sdk.NewAlterViewRequest(id).WithSetMaskingPolicyOnColumn( + sdk.NewViewSetColumnMaskingPolicyRequest("id", maskingPolicy.ID()), + ) + err := client.Views.Alter(ctx, alterRequest) + require.NoError(t, err) + + alteredViewDetails, err := client.Views.Describe(ctx, id) + require.NoError(t, err) + + assert.Equal(t, 1, len(alteredViewDetails)) + assert.Equal(t, maskingPolicy.ID().FullyQualifiedName(), *alteredViewDetails[0].PolicyName) + + alterRequest = sdk.NewAlterViewRequest(id).WithUnsetMaskingPolicyOnColumn( + sdk.NewViewUnsetColumnMaskingPolicyRequest("id"), + ) + err = client.Views.Alter(ctx, alterRequest) + require.NoError(t, err) + + alteredViewDetails, err = client.Views.Describe(ctx, id) + require.NoError(t, err) + + assert.Equal(t, 1, len(alteredViewDetails)) + assert.Empty(t, alteredViewDetails[0].PolicyName) + }) + + t.Run("alter view: set and unset tags on column", func(t *testing.T) { + tag, tagCleanup := createTag(t, client, testDb(t), testSchema(t)) + t.Cleanup(tagCleanup) + + view := createView(t) + id := view.ID() + + tagValue := "abc" + tags := []sdk.TagAssociation{ + { + Name: tag.ID(), + Value: tagValue, + }, + } + + alterRequest := sdk.NewAlterViewRequest(id).WithSetTagsOnColumn( + sdk.NewViewSetColumnTagsRequest("id", tags), + ) + err := client.Views.Alter(ctx, alterRequest) + require.NoError(t, err) + + columnId := sdk.NewTableColumnIdentifier(id.DatabaseName(), id.SchemaName(), id.Name(), "ID") + returnedTagValue, err := client.SystemFunctions.GetTag(ctx, tag.ID(), columnId, sdk.ObjectTypeColumn) + require.NoError(t, err) + assert.Equal(t, tagValue, returnedTagValue) + + unsetTags := []sdk.ObjectIdentifier{ + tag.ID(), + } + + alterRequest = sdk.NewAlterViewRequest(id).WithUnsetTagsOnColumn( + sdk.NewViewUnsetColumnTagsRequest("id", unsetTags), + ) + err = client.Views.Alter(ctx, alterRequest) + require.NoError(t, err) + + _, err = client.SystemFunctions.GetTag(ctx, tag.ID(), columnId, sdk.ObjectTypeColumn) + require.Error(t, err) + }) + + t.Run("alter view: add and drop row access policies", func(t *testing.T) { + rowAccessPolicyId, rowAccessPolicyCleanup := createRowAccessPolicy(t, client, testSchema(t)) + t.Cleanup(rowAccessPolicyCleanup) + rowAccessPolicy2Id, rowAccessPolicy2Cleanup := createRowAccessPolicy(t, client, testSchema(t)) + t.Cleanup(rowAccessPolicy2Cleanup) + + view := createView(t) + id := view.ID() + + // add policy + alterRequest := sdk.NewAlterViewRequest(id).WithAddRowAccessPolicy(sdk.NewViewAddRowAccessPolicyRequest(rowAccessPolicyId, []string{"ID"})) + err := client.Views.Alter(ctx, alterRequest) + require.NoError(t, err) + + rowAccessPolicyReference, err := getRowAccessPolicyFor(t, client, view.ID(), sdk.ObjectTypeView) + require.NoError(t, err) + assert.Equal(t, rowAccessPolicyId.Name(), rowAccessPolicyReference.PolicyName) + assert.Equal(t, "ROW_ACCESS_POLICY", rowAccessPolicyReference.PolicyKind) + assert.Equal(t, view.ID().Name(), rowAccessPolicyReference.RefEntityName) + assert.Equal(t, "VIEW", rowAccessPolicyReference.RefEntityDomain) + assert.Equal(t, "ACTIVE", rowAccessPolicyReference.PolicyStatus) + + // remove policy + alterRequest = sdk.NewAlterViewRequest(id).WithDropRowAccessPolicy(sdk.NewViewDropRowAccessPolicyRequest(rowAccessPolicyId)) + err = client.Views.Alter(ctx, alterRequest) + require.NoError(t, err) + + _, err = getRowAccessPolicyFor(t, client, view.ID(), sdk.ObjectTypeView) + require.Error(t, err, "no rows in result set") + + // add policy again + alterRequest = sdk.NewAlterViewRequest(id).WithAddRowAccessPolicy(sdk.NewViewAddRowAccessPolicyRequest(rowAccessPolicyId, []string{"ID"})) + err = client.Views.Alter(ctx, alterRequest) + require.NoError(t, err) + + rowAccessPolicyReference, err = getRowAccessPolicyFor(t, client, view.ID(), sdk.ObjectTypeView) + require.NoError(t, err) + assert.Equal(t, rowAccessPolicyId.Name(), rowAccessPolicyReference.PolicyName) + + // drop and add other policy simultaneously + alterRequest = sdk.NewAlterViewRequest(id).WithDropAndAddRowAccessPolicy(sdk.NewViewDropAndAddRowAccessPolicyRequest( + *sdk.NewViewDropRowAccessPolicyRequest(rowAccessPolicyId), + *sdk.NewViewAddRowAccessPolicyRequest(rowAccessPolicy2Id, []string{"ID"}), + )) + err = client.Views.Alter(ctx, alterRequest) + require.NoError(t, err) + + rowAccessPolicyReference, err = getRowAccessPolicyFor(t, client, view.ID(), sdk.ObjectTypeView) + require.NoError(t, err) + assert.Equal(t, rowAccessPolicy2Id.Name(), rowAccessPolicyReference.PolicyName) + + // drop all policies + alterRequest = sdk.NewAlterViewRequest(id).WithDropAllRowAccessPolicies(sdk.Bool(true)) + err = client.Views.Alter(ctx, alterRequest) + require.NoError(t, err) + + _, err = getRowAccessPolicyFor(t, client, view.ID(), sdk.ObjectTypeView) + require.Error(t, err, "no rows in result set") + }) + + t.Run("show view: default", func(t *testing.T) { + view1 := createView(t) + view2 := createView(t) + + showRequest := sdk.NewShowViewRequest() + returnedViews, err := client.Views.Show(ctx, showRequest) + require.NoError(t, err) + + assert.Equal(t, 2, len(returnedViews)) + assert.Contains(t, returnedViews, *view1) + assert.Contains(t, returnedViews, *view2) + }) + + t.Run("show view: terse", func(t *testing.T) { + view := createView(t) + + showRequest := sdk.NewShowViewRequest().WithTerse(sdk.Bool(true)) + returnedViews, err := client.Views.Show(ctx, showRequest) + require.NoError(t, err) + + assert.Equal(t, 1, len(returnedViews)) + assertViewTerse(t, &returnedViews[0], view.ID()) + }) + + t.Run("show view: with options", func(t *testing.T) { + view1 := createView(t) + view2 := createView(t) + + showRequest := sdk.NewShowViewRequest(). + WithLike(&sdk.Like{Pattern: &view1.Name}). + WithIn(&sdk.In{Schema: sdk.NewDatabaseObjectIdentifier(testDb(t).Name, testSchema(t).Name)}). + WithLimit(&sdk.LimitFrom{Rows: sdk.Int(5)}) + returnedViews, err := client.Views.Show(ctx, showRequest) + + require.NoError(t, err) + assert.Equal(t, 1, len(returnedViews)) + assert.Contains(t, returnedViews, *view1) + assert.NotContains(t, returnedViews, *view2) + }) + + t.Run("describe view", func(t *testing.T) { + view := createView(t) + + returnedViewDetails, err := client.Views.Describe(ctx, view.ID()) + require.NoError(t, err) + + assert.Equal(t, 1, len(returnedViewDetails)) + assertViewDetailsRow(t, &returnedViewDetails[0]) + }) + + t.Run("describe view: non-existing", func(t *testing.T) { + id := sdk.NewSchemaObjectIdentifier(testDb(t).Name, testSchema(t).Name, "does_not_exist") + + _, err := client.Views.Describe(ctx, id) + assert.ErrorIs(t, err, sdk.ErrObjectNotExistOrAuthorized) + }) +} diff --git a/pkg/sdk/views_def.go b/pkg/sdk/views_def.go new file mode 100644 index 0000000000..7620e37f8a --- /dev/null +++ b/pkg/sdk/views_def.go @@ -0,0 +1,215 @@ +package sdk + +import g "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/poc/generator" + +//go:generate go run ./poc/main.go + +var viewDbRow = g.DbStruct("viewDBRow"). + Text("created_on"). + Text("name"). + OptionalText("kind"). + OptionalText("reserved"). + Text("database_name"). + Text("schema_name"). + OptionalText("owner"). + OptionalText("comment"). + OptionalText("text"). + OptionalBool("is_secure"). + OptionalBool("is_materialized"). + OptionalText("owner_role_type"). + OptionalText("change_tracking") + +var view = g.PlainStruct("View"). + Text("CreatedOn"). + Text("Name"). + Text("Kind"). + Text("Reserved"). + Text("DatabaseName"). + Text("SchemaName"). + Text("Owner"). + Text("Comment"). + Text("Text"). + Bool("IsSecure"). + Bool("IsMaterialized"). + Text("OwnerRoleType"). + Text("ChangeTracking") + +var viewDetailsDbRow = g.DbStruct("viewDetailsRow"). + Text("name"). + Field("type", "DataType"). + Text("kind"). + Text("null"). + OptionalText("default"). + Text("primary key"). + Text("unique key"). + OptionalText("check"). + OptionalText("expression"). + OptionalText("comment"). + OptionalText("policy name") + +var viewDetails = g.PlainStruct("ViewDetails"). + Text("Name"). + Field("Type", "DataType"). + Text("Kind"). + Bool("IsNullable"). + OptionalText("Default"). + Bool("IsPrimary"). + Bool("IsUnique"). + OptionalBool("Check"). + OptionalText("Expression"). + OptionalText("Comment"). + OptionalText("PolicyName") + +var viewColumn = g.NewQueryStruct("ViewColumn"). + Text("Name", g.KeywordOptions().DoubleQuotes().Required()). + OptionalTextAssignment("COMMENT", g.ParameterOptions().SingleQuotes().NoEquals()) + +var viewColumnMaskingPolicy = g.NewQueryStruct("ViewColumnMaskingPolicy"). + Text("Name", g.KeywordOptions().Required()). + Identifier("MaskingPolicy", g.KindOfT[SchemaObjectIdentifier](), g.IdentifierOptions().SQL("MASKING POLICY").Required()). + NamedListWithParens("USING", g.KindOfT[string](), nil). // TODO: double quotes here? + WithTags() + +var viewRowAccessPolicy = g.NewQueryStruct("ViewRowAccessPolicy"). + Identifier("RowAccessPolicy", g.KindOfT[SchemaObjectIdentifier](), g.IdentifierOptions().SQL("ROW ACCESS POLICY").Required()). + NamedListWithParens("ON", g.KindOfT[string](), g.KeywordOptions().Required()). // TODO: double quotes here? + WithValidation(g.ValidIdentifier, "RowAccessPolicy") + +var viewAddRowAccessPolicy = g.NewQueryStruct("ViewAddRowAccessPolicy"). + SQL("ADD"). + Identifier("RowAccessPolicy", g.KindOfT[SchemaObjectIdentifier](), g.IdentifierOptions().SQL("ROW ACCESS POLICY").Required()). + NamedListWithParens("ON", g.KindOfT[string](), g.KeywordOptions().Required()). // TODO: double quotes here? + WithValidation(g.ValidIdentifier, "RowAccessPolicy") + +var viewDropRowAccessPolicy = g.NewQueryStruct("ViewDropRowAccessPolicy"). + SQL("DROP"). + Identifier("RowAccessPolicy", g.KindOfT[SchemaObjectIdentifier](), g.IdentifierOptions().SQL("ROW ACCESS POLICY").Required()). + WithValidation(g.ValidIdentifier, "RowAccessPolicy") + +var viewDropAndAddRowAccessPolicy = g.NewQueryStruct("ViewDropAndAddRowAccessPolicy"). + QueryStructField("Drop", viewDropRowAccessPolicy, g.KeywordOptions().Required()). + QueryStructField("Add", viewAddRowAccessPolicy, g.KeywordOptions().Required()) + +var viewSetColumnMaskingPolicy = g.NewQueryStruct("ViewSetColumnMaskingPolicy"). + // In the docs there is a MODIFY alternative, but for simplicity only one is supported here. + SQL("ALTER"). + SQL("COLUMN"). + Text("Name", g.KeywordOptions().Required()). + SQL("SET"). + Identifier("MaskingPolicy", g.KindOfT[SchemaObjectIdentifier](), g.IdentifierOptions().SQL("MASKING POLICY").Required()). + NamedListWithParens("USING", g.KindOfT[string](), nil). // TODO: double quotes here? + OptionalSQL("FORCE") + +var viewUnsetColumnMaskingPolicy = g.NewQueryStruct("ViewUnsetColumnMaskingPolicy"). + // In the docs there is a MODIFY alternative, but for simplicity only one is supported here. + SQL("ALTER"). + SQL("COLUMN"). + Text("Name", g.KeywordOptions().Required()). + SQL("UNSET"). + SQL("MASKING POLICY") + +var viewSetColumnTags = g.NewQueryStruct("ViewSetColumnTags"). + // In the docs there is a MODIFY alternative, but for simplicity only one is supported here. + SQL("ALTER"). + SQL("COLUMN"). + Text("Name", g.KeywordOptions().Required()). + SetTags() + +var viewUnsetColumnTags = g.NewQueryStruct("ViewUnsetColumnTags"). + // In the docs there is a MODIFY alternative, but for simplicity only one is supported here. + SQL("ALTER"). + SQL("COLUMN"). + Text("Name", g.KeywordOptions().Required()). + UnsetTags() + +var ViewsDef = g.NewInterface( + "Views", + "View", + g.KindOfT[SchemaObjectIdentifier](), +). + CreateOperation( + "https://docs.snowflake.com/en/sql-reference/sql/create-view", + g.NewQueryStruct("CreateView"). + Create(). + OrReplace(). + OptionalSQL("SECURE"). + // There are multiple variants in the docs: { [ { LOCAL | GLOBAL } ] TEMP | TEMPORARY | VOLATILE } + // but from description they are all the same. For the sake of simplicity only one option is used here. + OptionalSQL("TEMPORARY"). + OptionalSQL("RECURSIVE"). + SQL("VIEW"). + IfNotExists(). + Name(). + ListQueryStructField("Columns", viewColumn, g.ListOptions().Parentheses()). + ListQueryStructField("ColumnsMaskingPolicies", viewColumnMaskingPolicy, g.ListOptions().NoParentheses().NoEquals()). + OptionalSQL("COPY GRANTS"). + OptionalTextAssignment("COMMENT", g.ParameterOptions().SingleQuotes()). + // In the current docs ROW ACCESS POLICY and TAG are specified twice. + // It is a mistake probably so here they are present only once. + OptionalQueryStructField("RowAccessPolicy", viewRowAccessPolicy, g.KeywordOptions()). + WithTags(). + SQL("AS"). + Text("sql", g.KeywordOptions().NoQuotes().Required()). + WithValidation(g.ValidIdentifier, "name"). + WithValidation(g.ConflictingFields, "OrReplace", "IfNotExists"), + ). + AlterOperation( + "https://docs.snowflake.com/en/sql-reference/sql/alter-view", + g.NewQueryStruct("AlterView"). + Alter(). + SQL("VIEW"). + IfExists(). + Name(). + OptionalIdentifier("RenameTo", g.KindOfT[SchemaObjectIdentifier](), g.IdentifierOptions().SQL("RENAME TO")). + OptionalTextAssignment("SET COMMENT", g.ParameterOptions().SingleQuotes()). + OptionalSQL("UNSET COMMENT"). + OptionalSQL("SET SECURE"). + OptionalBooleanAssignment("SET CHANGE_TRACKING", nil). + OptionalSQL("UNSET SECURE"). + OptionalSetTags(). + OptionalUnsetTags(). + OptionalQueryStructField("AddRowAccessPolicy", viewAddRowAccessPolicy, g.KeywordOptions()). + OptionalQueryStructField("DropRowAccessPolicy", viewDropRowAccessPolicy, g.KeywordOptions()). + OptionalQueryStructField("DropAndAddRowAccessPolicy", viewDropAndAddRowAccessPolicy, g.ListOptions().NoParentheses()). + OptionalSQL("DROP ALL ROW ACCESS POLICIES"). + OptionalQueryStructField("SetMaskingPolicyOnColumn", viewSetColumnMaskingPolicy, g.KeywordOptions()). + OptionalQueryStructField("UnsetMaskingPolicyOnColumn", viewUnsetColumnMaskingPolicy, g.KeywordOptions()). + OptionalQueryStructField("SetTagsOnColumn", viewSetColumnTags, g.KeywordOptions()). + OptionalQueryStructField("UnsetTagsOnColumn", viewUnsetColumnTags, g.KeywordOptions()). + WithValidation(g.ValidIdentifier, "name"). + WithValidation(g.ExactlyOneValueSet, "RenameTo", "SetComment", "UnsetComment", "SetSecure", "SetChangeTracking", "UnsetSecure", "SetTags", "UnsetTags", "AddRowAccessPolicy", "DropRowAccessPolicy", "DropAndAddRowAccessPolicy", "DropAllRowAccessPolicies", "SetMaskingPolicyOnColumn", "UnsetMaskingPolicyOnColumn", "SetTagsOnColumn", "UnsetTagsOnColumn"), + ). + DropOperation( + "https://docs.snowflake.com/en/sql-reference/sql/drop-view", + g.NewQueryStruct("DropView"). + Drop(). + SQL("VIEW"). + IfExists(). + Name(). + WithValidation(g.ValidIdentifier, "name"), + ). + ShowOperation( + "https://docs.snowflake.com/en/sql-reference/sql/show-views", + viewDbRow, + view, + g.NewQueryStruct("ShowViews"). + Show(). + Terse(). + SQL("VIEWS"). + OptionalLike(). + OptionalIn(). + OptionalStartsWith(). + OptionalLimit(), + ). + ShowByIdOperation(). + DescribeOperation( + g.DescriptionMappingKindSlice, + "https://docs.snowflake.com/en/sql-reference/sql/desc-view", + viewDetailsDbRow, + viewDetails, + g.NewQueryStruct("DescribeView"). + Describe(). + SQL("VIEW"). + Name(). + WithValidation(g.ValidIdentifier, "name"), + ) diff --git a/pkg/sdk/views_dto_builders_gen.go b/pkg/sdk/views_dto_builders_gen.go new file mode 100644 index 0000000000..d357ee3a27 --- /dev/null +++ b/pkg/sdk/views_dto_builders_gen.go @@ -0,0 +1,332 @@ +// Code generated by dto builder generator; DO NOT EDIT. + +package sdk + +import () + +func NewCreateViewRequest( + name SchemaObjectIdentifier, + sql string, +) *CreateViewRequest { + s := CreateViewRequest{} + s.name = name + s.sql = sql + return &s +} + +func (s *CreateViewRequest) WithOrReplace(OrReplace *bool) *CreateViewRequest { + s.OrReplace = OrReplace + return s +} + +func (s *CreateViewRequest) WithSecure(Secure *bool) *CreateViewRequest { + s.Secure = Secure + return s +} + +func (s *CreateViewRequest) WithTemporary(Temporary *bool) *CreateViewRequest { + s.Temporary = Temporary + return s +} + +func (s *CreateViewRequest) WithRecursive(Recursive *bool) *CreateViewRequest { + s.Recursive = Recursive + return s +} + +func (s *CreateViewRequest) WithIfNotExists(IfNotExists *bool) *CreateViewRequest { + s.IfNotExists = IfNotExists + return s +} + +func (s *CreateViewRequest) WithColumns(Columns []ViewColumnRequest) *CreateViewRequest { + s.Columns = Columns + return s +} + +func (s *CreateViewRequest) WithColumnsMaskingPolicies(ColumnsMaskingPolicies []ViewColumnMaskingPolicyRequest) *CreateViewRequest { + s.ColumnsMaskingPolicies = ColumnsMaskingPolicies + return s +} + +func (s *CreateViewRequest) WithCopyGrants(CopyGrants *bool) *CreateViewRequest { + s.CopyGrants = CopyGrants + return s +} + +func (s *CreateViewRequest) WithComment(Comment *string) *CreateViewRequest { + s.Comment = Comment + return s +} + +func (s *CreateViewRequest) WithRowAccessPolicy(RowAccessPolicy *ViewRowAccessPolicyRequest) *CreateViewRequest { + s.RowAccessPolicy = RowAccessPolicy + return s +} + +func (s *CreateViewRequest) WithTag(Tag []TagAssociation) *CreateViewRequest { + s.Tag = Tag + return s +} + +func NewViewColumnRequest( + Name string, +) *ViewColumnRequest { + s := ViewColumnRequest{} + s.Name = Name + return &s +} + +func (s *ViewColumnRequest) WithComment(Comment *string) *ViewColumnRequest { + s.Comment = Comment + return s +} + +func NewViewColumnMaskingPolicyRequest( + Name string, + MaskingPolicy SchemaObjectIdentifier, +) *ViewColumnMaskingPolicyRequest { + s := ViewColumnMaskingPolicyRequest{} + s.Name = Name + s.MaskingPolicy = MaskingPolicy + return &s +} + +func (s *ViewColumnMaskingPolicyRequest) WithUsing(Using []string) *ViewColumnMaskingPolicyRequest { + s.Using = Using + return s +} + +func (s *ViewColumnMaskingPolicyRequest) WithTag(Tag []TagAssociation) *ViewColumnMaskingPolicyRequest { + s.Tag = Tag + return s +} + +func NewViewRowAccessPolicyRequest( + RowAccessPolicy SchemaObjectIdentifier, + On []string, +) *ViewRowAccessPolicyRequest { + s := ViewRowAccessPolicyRequest{} + s.RowAccessPolicy = RowAccessPolicy + s.On = On + return &s +} + +func NewAlterViewRequest( + name SchemaObjectIdentifier, +) *AlterViewRequest { + s := AlterViewRequest{} + s.name = name + return &s +} + +func (s *AlterViewRequest) WithIfExists(IfExists *bool) *AlterViewRequest { + s.IfExists = IfExists + return s +} + +func (s *AlterViewRequest) WithRenameTo(RenameTo *SchemaObjectIdentifier) *AlterViewRequest { + s.RenameTo = RenameTo + return s +} + +func (s *AlterViewRequest) WithSetComment(SetComment *string) *AlterViewRequest { + s.SetComment = SetComment + return s +} + +func (s *AlterViewRequest) WithUnsetComment(UnsetComment *bool) *AlterViewRequest { + s.UnsetComment = UnsetComment + return s +} + +func (s *AlterViewRequest) WithSetSecure(SetSecure *bool) *AlterViewRequest { + s.SetSecure = SetSecure + return s +} + +func (s *AlterViewRequest) WithSetChangeTracking(SetChangeTracking *bool) *AlterViewRequest { + s.SetChangeTracking = SetChangeTracking + return s +} + +func (s *AlterViewRequest) WithUnsetSecure(UnsetSecure *bool) *AlterViewRequest { + s.UnsetSecure = UnsetSecure + return s +} + +func (s *AlterViewRequest) WithSetTags(SetTags []TagAssociation) *AlterViewRequest { + s.SetTags = SetTags + return s +} + +func (s *AlterViewRequest) WithUnsetTags(UnsetTags []ObjectIdentifier) *AlterViewRequest { + s.UnsetTags = UnsetTags + return s +} + +func (s *AlterViewRequest) WithAddRowAccessPolicy(AddRowAccessPolicy *ViewAddRowAccessPolicyRequest) *AlterViewRequest { + s.AddRowAccessPolicy = AddRowAccessPolicy + return s +} + +func (s *AlterViewRequest) WithDropRowAccessPolicy(DropRowAccessPolicy *ViewDropRowAccessPolicyRequest) *AlterViewRequest { + s.DropRowAccessPolicy = DropRowAccessPolicy + return s +} + +func (s *AlterViewRequest) WithDropAndAddRowAccessPolicy(DropAndAddRowAccessPolicy *ViewDropAndAddRowAccessPolicyRequest) *AlterViewRequest { + s.DropAndAddRowAccessPolicy = DropAndAddRowAccessPolicy + return s +} + +func (s *AlterViewRequest) WithDropAllRowAccessPolicies(DropAllRowAccessPolicies *bool) *AlterViewRequest { + s.DropAllRowAccessPolicies = DropAllRowAccessPolicies + return s +} + +func (s *AlterViewRequest) WithSetMaskingPolicyOnColumn(SetMaskingPolicyOnColumn *ViewSetColumnMaskingPolicyRequest) *AlterViewRequest { + s.SetMaskingPolicyOnColumn = SetMaskingPolicyOnColumn + return s +} + +func (s *AlterViewRequest) WithUnsetMaskingPolicyOnColumn(UnsetMaskingPolicyOnColumn *ViewUnsetColumnMaskingPolicyRequest) *AlterViewRequest { + s.UnsetMaskingPolicyOnColumn = UnsetMaskingPolicyOnColumn + return s +} + +func (s *AlterViewRequest) WithSetTagsOnColumn(SetTagsOnColumn *ViewSetColumnTagsRequest) *AlterViewRequest { + s.SetTagsOnColumn = SetTagsOnColumn + return s +} + +func (s *AlterViewRequest) WithUnsetTagsOnColumn(UnsetTagsOnColumn *ViewUnsetColumnTagsRequest) *AlterViewRequest { + s.UnsetTagsOnColumn = UnsetTagsOnColumn + return s +} + +func NewViewAddRowAccessPolicyRequest( + RowAccessPolicy SchemaObjectIdentifier, + On []string, +) *ViewAddRowAccessPolicyRequest { + s := ViewAddRowAccessPolicyRequest{} + s.RowAccessPolicy = RowAccessPolicy + s.On = On + return &s +} + +func NewViewDropRowAccessPolicyRequest( + RowAccessPolicy SchemaObjectIdentifier, +) *ViewDropRowAccessPolicyRequest { + s := ViewDropRowAccessPolicyRequest{} + s.RowAccessPolicy = RowAccessPolicy + return &s +} + +func NewViewDropAndAddRowAccessPolicyRequest( + Drop ViewDropRowAccessPolicyRequest, + Add ViewAddRowAccessPolicyRequest, +) *ViewDropAndAddRowAccessPolicyRequest { + s := ViewDropAndAddRowAccessPolicyRequest{} + s.Drop = Drop + s.Add = Add + return &s +} + +func NewViewSetColumnMaskingPolicyRequest( + Name string, + MaskingPolicy SchemaObjectIdentifier, +) *ViewSetColumnMaskingPolicyRequest { + s := ViewSetColumnMaskingPolicyRequest{} + s.Name = Name + s.MaskingPolicy = MaskingPolicy + return &s +} + +func (s *ViewSetColumnMaskingPolicyRequest) WithUsing(Using []string) *ViewSetColumnMaskingPolicyRequest { + s.Using = Using + return s +} + +func (s *ViewSetColumnMaskingPolicyRequest) WithForce(Force *bool) *ViewSetColumnMaskingPolicyRequest { + s.Force = Force + return s +} + +func NewViewUnsetColumnMaskingPolicyRequest( + Name string, +) *ViewUnsetColumnMaskingPolicyRequest { + s := ViewUnsetColumnMaskingPolicyRequest{} + s.Name = Name + return &s +} + +func NewViewSetColumnTagsRequest( + Name string, + SetTags []TagAssociation, +) *ViewSetColumnTagsRequest { + s := ViewSetColumnTagsRequest{} + s.Name = Name + s.SetTags = SetTags + return &s +} + +func NewViewUnsetColumnTagsRequest( + Name string, + UnsetTags []ObjectIdentifier, +) *ViewUnsetColumnTagsRequest { + s := ViewUnsetColumnTagsRequest{} + s.Name = Name + s.UnsetTags = UnsetTags + return &s +} + +func NewDropViewRequest( + name SchemaObjectIdentifier, +) *DropViewRequest { + s := DropViewRequest{} + s.name = name + return &s +} + +func (s *DropViewRequest) WithIfExists(IfExists *bool) *DropViewRequest { + s.IfExists = IfExists + return s +} + +func NewShowViewRequest() *ShowViewRequest { + return &ShowViewRequest{} +} + +func (s *ShowViewRequest) WithTerse(Terse *bool) *ShowViewRequest { + s.Terse = Terse + return s +} + +func (s *ShowViewRequest) WithLike(Like *Like) *ShowViewRequest { + s.Like = Like + return s +} + +func (s *ShowViewRequest) WithIn(In *In) *ShowViewRequest { + s.In = In + return s +} + +func (s *ShowViewRequest) WithStartsWith(StartsWith *string) *ShowViewRequest { + s.StartsWith = StartsWith + return s +} + +func (s *ShowViewRequest) WithLimit(Limit *LimitFrom) *ShowViewRequest { + s.Limit = Limit + return s +} + +func NewDescribeViewRequest( + name SchemaObjectIdentifier, +) *DescribeViewRequest { + s := DescribeViewRequest{} + s.name = name + return &s +} diff --git a/pkg/sdk/views_dto_gen.go b/pkg/sdk/views_dto_gen.go new file mode 100644 index 0000000000..a3b2ac4cee --- /dev/null +++ b/pkg/sdk/views_dto_gen.go @@ -0,0 +1,121 @@ +package sdk + +//go:generate go run ./dto-builder-generator/main.go + +var ( + _ optionsProvider[CreateViewOptions] = new(CreateViewRequest) + _ optionsProvider[AlterViewOptions] = new(AlterViewRequest) + _ optionsProvider[DropViewOptions] = new(DropViewRequest) + _ optionsProvider[ShowViewOptions] = new(ShowViewRequest) + _ optionsProvider[DescribeViewOptions] = new(DescribeViewRequest) +) + +type CreateViewRequest struct { + OrReplace *bool + Secure *bool + Temporary *bool + Recursive *bool + IfNotExists *bool + name SchemaObjectIdentifier // required + Columns []ViewColumnRequest + ColumnsMaskingPolicies []ViewColumnMaskingPolicyRequest + CopyGrants *bool + Comment *string + RowAccessPolicy *ViewRowAccessPolicyRequest + Tag []TagAssociation + sql string // required +} + +func (r *CreateViewRequest) GetName() SchemaObjectIdentifier { + return r.name +} + +type ViewColumnRequest struct { + Name string // required + Comment *string +} + +type ViewColumnMaskingPolicyRequest struct { + Name string // required + MaskingPolicy SchemaObjectIdentifier // required + Using []string + Tag []TagAssociation +} + +type ViewRowAccessPolicyRequest struct { + RowAccessPolicy SchemaObjectIdentifier // required + On []string // required +} + +type AlterViewRequest struct { + IfExists *bool + name SchemaObjectIdentifier // required + RenameTo *SchemaObjectIdentifier + SetComment *string + UnsetComment *bool + SetSecure *bool + SetChangeTracking *bool + UnsetSecure *bool + SetTags []TagAssociation + UnsetTags []ObjectIdentifier + AddRowAccessPolicy *ViewAddRowAccessPolicyRequest + DropRowAccessPolicy *ViewDropRowAccessPolicyRequest + DropAndAddRowAccessPolicy *ViewDropAndAddRowAccessPolicyRequest + DropAllRowAccessPolicies *bool + SetMaskingPolicyOnColumn *ViewSetColumnMaskingPolicyRequest + UnsetMaskingPolicyOnColumn *ViewUnsetColumnMaskingPolicyRequest + SetTagsOnColumn *ViewSetColumnTagsRequest + UnsetTagsOnColumn *ViewUnsetColumnTagsRequest +} + +type ViewAddRowAccessPolicyRequest struct { + RowAccessPolicy SchemaObjectIdentifier // required + On []string // required +} + +type ViewDropRowAccessPolicyRequest struct { + RowAccessPolicy SchemaObjectIdentifier // required +} + +type ViewDropAndAddRowAccessPolicyRequest struct { + Drop ViewDropRowAccessPolicyRequest // required + Add ViewAddRowAccessPolicyRequest // required +} + +type ViewSetColumnMaskingPolicyRequest struct { + Name string // required + MaskingPolicy SchemaObjectIdentifier // required + Using []string + Force *bool +} + +type ViewUnsetColumnMaskingPolicyRequest struct { + Name string // required +} + +type ViewSetColumnTagsRequest struct { + Name string // required + SetTags []TagAssociation // required +} + +type ViewUnsetColumnTagsRequest struct { + Name string // required + UnsetTags []ObjectIdentifier // required +} + +type DropViewRequest struct { + IfExists *bool + name SchemaObjectIdentifier // required +} + +type ShowViewRequest struct { + Terse *bool + Like *Like + In *In + StartsWith *string + Limit *LimitFrom +} + +type DescribeViewRequest struct { + name SchemaObjectIdentifier // required +} diff --git a/pkg/sdk/views_gen.go b/pkg/sdk/views_gen.go new file mode 100644 index 0000000000..0debdea3e4 --- /dev/null +++ b/pkg/sdk/views_gen.go @@ -0,0 +1,217 @@ +package sdk + +import ( + "context" + "database/sql" +) + +type Views interface { + Create(ctx context.Context, request *CreateViewRequest) error + Alter(ctx context.Context, request *AlterViewRequest) error + Drop(ctx context.Context, request *DropViewRequest) error + Show(ctx context.Context, request *ShowViewRequest) ([]View, error) + ShowByID(ctx context.Context, id SchemaObjectIdentifier) (*View, error) + Describe(ctx context.Context, id SchemaObjectIdentifier) ([]ViewDetails, error) +} + +// CreateViewOptions is based on https://docs.snowflake.com/en/sql-reference/sql/create-view. +type CreateViewOptions struct { + create bool `ddl:"static" sql:"CREATE"` + OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` + Secure *bool `ddl:"keyword" sql:"SECURE"` + Temporary *bool `ddl:"keyword" sql:"TEMPORARY"` + Recursive *bool `ddl:"keyword" sql:"RECURSIVE"` + view bool `ddl:"static" sql:"VIEW"` + IfNotExists *bool `ddl:"keyword" sql:"IF NOT EXISTS"` + name SchemaObjectIdentifier `ddl:"identifier"` + Columns []ViewColumn `ddl:"list,parentheses"` + ColumnsMaskingPolicies []ViewColumnMaskingPolicy `ddl:"list,no_parentheses,no_equals"` + CopyGrants *bool `ddl:"keyword" sql:"COPY GRANTS"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + RowAccessPolicy *ViewRowAccessPolicy `ddl:"keyword"` + Tag []TagAssociation `ddl:"keyword,parentheses" sql:"TAG"` + as bool `ddl:"static" sql:"AS"` + sql string `ddl:"keyword,no_quotes"` +} + +type ViewColumn struct { + Name string `ddl:"keyword,double_quotes"` + Comment *string `ddl:"parameter,single_quotes,no_equals" sql:"COMMENT"` +} + +type ViewColumnMaskingPolicy struct { + Name string `ddl:"keyword"` + MaskingPolicy SchemaObjectIdentifier `ddl:"identifier" sql:"MASKING POLICY"` + Using []string `ddl:"keyword,parentheses" sql:"USING"` + Tag []TagAssociation `ddl:"keyword,parentheses" sql:"TAG"` +} + +type ViewRowAccessPolicy struct { + RowAccessPolicy SchemaObjectIdentifier `ddl:"identifier" sql:"ROW ACCESS POLICY"` + On []string `ddl:"keyword,parentheses" sql:"ON"` +} + +// AlterViewOptions is based on https://docs.snowflake.com/en/sql-reference/sql/alter-view. +type AlterViewOptions struct { + alter bool `ddl:"static" sql:"ALTER"` + view bool `ddl:"static" sql:"VIEW"` + IfExists *bool `ddl:"keyword" sql:"IF EXISTS"` + name SchemaObjectIdentifier `ddl:"identifier"` + RenameTo *SchemaObjectIdentifier `ddl:"identifier" sql:"RENAME TO"` + SetComment *string `ddl:"parameter,single_quotes" sql:"SET COMMENT"` + UnsetComment *bool `ddl:"keyword" sql:"UNSET COMMENT"` + SetSecure *bool `ddl:"keyword" sql:"SET SECURE"` + SetChangeTracking *bool `ddl:"parameter" sql:"SET CHANGE_TRACKING"` + UnsetSecure *bool `ddl:"keyword" sql:"UNSET SECURE"` + SetTags []TagAssociation `ddl:"keyword" sql:"SET TAG"` + UnsetTags []ObjectIdentifier `ddl:"keyword" sql:"UNSET TAG"` + AddRowAccessPolicy *ViewAddRowAccessPolicy `ddl:"keyword"` + DropRowAccessPolicy *ViewDropRowAccessPolicy `ddl:"keyword"` + DropAndAddRowAccessPolicy *ViewDropAndAddRowAccessPolicy `ddl:"list,no_parentheses"` + DropAllRowAccessPolicies *bool `ddl:"keyword" sql:"DROP ALL ROW ACCESS POLICIES"` + SetMaskingPolicyOnColumn *ViewSetColumnMaskingPolicy `ddl:"keyword"` + UnsetMaskingPolicyOnColumn *ViewUnsetColumnMaskingPolicy `ddl:"keyword"` + SetTagsOnColumn *ViewSetColumnTags `ddl:"keyword"` + UnsetTagsOnColumn *ViewUnsetColumnTags `ddl:"keyword"` +} + +type ViewAddRowAccessPolicy struct { + add bool `ddl:"static" sql:"ADD"` + RowAccessPolicy SchemaObjectIdentifier `ddl:"identifier" sql:"ROW ACCESS POLICY"` + On []string `ddl:"keyword,parentheses" sql:"ON"` +} + +type ViewDropRowAccessPolicy struct { + drop bool `ddl:"static" sql:"DROP"` + RowAccessPolicy SchemaObjectIdentifier `ddl:"identifier" sql:"ROW ACCESS POLICY"` +} + +type ViewDropAndAddRowAccessPolicy struct { + Drop ViewDropRowAccessPolicy `ddl:"keyword"` + Add ViewAddRowAccessPolicy `ddl:"keyword"` +} + +type ViewSetColumnMaskingPolicy struct { + alter bool `ddl:"static" sql:"ALTER"` + column bool `ddl:"static" sql:"COLUMN"` + Name string `ddl:"keyword"` + set bool `ddl:"static" sql:"SET"` + MaskingPolicy SchemaObjectIdentifier `ddl:"identifier" sql:"MASKING POLICY"` + Using []string `ddl:"keyword,parentheses" sql:"USING"` + Force *bool `ddl:"keyword" sql:"FORCE"` +} + +type ViewUnsetColumnMaskingPolicy struct { + alter bool `ddl:"static" sql:"ALTER"` + column bool `ddl:"static" sql:"COLUMN"` + Name string `ddl:"keyword"` + unset bool `ddl:"static" sql:"UNSET"` + maskingPolicy bool `ddl:"static" sql:"MASKING POLICY"` +} + +type ViewSetColumnTags struct { + alter bool `ddl:"static" sql:"ALTER"` + column bool `ddl:"static" sql:"COLUMN"` + Name string `ddl:"keyword"` + SetTags []TagAssociation `ddl:"keyword" sql:"SET TAG"` +} + +type ViewUnsetColumnTags struct { + alter bool `ddl:"static" sql:"ALTER"` + column bool `ddl:"static" sql:"COLUMN"` + Name string `ddl:"keyword"` + UnsetTags []ObjectIdentifier `ddl:"keyword" sql:"UNSET TAG"` +} + +// DropViewOptions is based on https://docs.snowflake.com/en/sql-reference/sql/drop-view. +type DropViewOptions struct { + drop bool `ddl:"static" sql:"DROP"` + view bool `ddl:"static" sql:"VIEW"` + IfExists *bool `ddl:"keyword" sql:"IF EXISTS"` + name SchemaObjectIdentifier `ddl:"identifier"` +} + +// ShowViewOptions is based on https://docs.snowflake.com/en/sql-reference/sql/show-views. +type ShowViewOptions struct { + show bool `ddl:"static" sql:"SHOW"` + Terse *bool `ddl:"keyword" sql:"TERSE"` + views bool `ddl:"static" sql:"VIEWS"` + Like *Like `ddl:"keyword" sql:"LIKE"` + In *In `ddl:"keyword" sql:"IN"` + StartsWith *string `ddl:"parameter,no_equals,single_quotes" sql:"STARTS WITH"` + Limit *LimitFrom `ddl:"keyword" sql:"LIMIT"` +} + +type viewDBRow struct { + CreatedOn string `db:"created_on"` + Name string `db:"name"` + Kind sql.NullString `db:"kind"` + Reserved sql.NullString `db:"reserved"` + DatabaseName string `db:"database_name"` + SchemaName string `db:"schema_name"` + Owner sql.NullString `db:"owner"` + Comment sql.NullString `db:"comment"` + Text sql.NullString `db:"text"` + IsSecure sql.NullBool `db:"is_secure"` + IsMaterialized sql.NullBool `db:"is_materialized"` + OwnerRoleType sql.NullString `db:"owner_role_type"` + ChangeTracking sql.NullString `db:"change_tracking"` +} + +type View struct { + CreatedOn string + Name string + Kind string + Reserved string + DatabaseName string + SchemaName string + Owner string + Comment string + Text string + IsSecure bool + IsMaterialized bool + OwnerRoleType string + ChangeTracking string +} + +func (v *View) ID() SchemaObjectIdentifier { + return NewSchemaObjectIdentifier(v.DatabaseName, v.SchemaName, v.Name) +} + +// DescribeViewOptions is based on https://docs.snowflake.com/en/sql-reference/sql/desc-view. +type DescribeViewOptions struct { + describe bool `ddl:"static" sql:"DESCRIBE"` + view bool `ddl:"static" sql:"VIEW"` + name SchemaObjectIdentifier `ddl:"identifier"` +} + +// TODO [SNOW-965322]: extract common type for describe +// viewDetailsRow is a copy of externalTableColumnDetailsRow. +type viewDetailsRow struct { + Name string `db:"name"` + Type DataType `db:"type"` + Kind string `db:"kind"` + IsNullable string `db:"null?"` + Default sql.NullString `db:"default"` + IsPrimary string `db:"primary key"` + IsUnique string `db:"unique key"` + Check sql.NullString `db:"check"` + Expression sql.NullString `db:"expression"` + Comment sql.NullString `db:"comment"` + PolicyName sql.NullString `db:"policy name"` +} + +// ViewDetails is a copy of ExternalTableColumnDetails. +type ViewDetails struct { + Name string + Type DataType + Kind string + IsNullable bool + Default *string + IsPrimary bool + IsUnique bool + Check *bool + Expression *string + Comment *string + PolicyName *string +} diff --git a/pkg/sdk/views_gen_test.go b/pkg/sdk/views_gen_test.go new file mode 100644 index 0000000000..e7aa732e1f --- /dev/null +++ b/pkg/sdk/views_gen_test.go @@ -0,0 +1,441 @@ +package sdk + +import ( + "testing" +) + +func TestViews_Create(t *testing.T) { + id := RandomSchemaObjectIdentifier() + sql := "SELECT id FROM t" + + // Minimal valid CreateViewOptions + defaultOpts := func() *CreateViewOptions { + return &CreateViewOptions{ + name: id, + sql: sql, + } + } + + t.Run("validation: nil options", func(t *testing.T) { + var opts *CreateViewOptions = nil + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("validation: valid identifier for [opts.name]", func(t *testing.T) { + opts := defaultOpts() + opts.name = NewSchemaObjectIdentifier("", "", "") + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("validation: conflicting fields for [opts.OrReplace opts.IfNotExists]", func(t *testing.T) { + opts := defaultOpts() + opts.OrReplace = Bool(true) + opts.IfNotExists = Bool(true) + assertOptsInvalidJoinedErrors(t, opts, errOneOf("CreateViewOptions", "OrReplace", "IfNotExists")) + }) + + t.Run("validation: valid identifier for [opts.RowAccessPolicy.RowAccessPolicy]", func(t *testing.T) { + opts := defaultOpts() + opts.RowAccessPolicy = &ViewRowAccessPolicy{ + RowAccessPolicy: NewSchemaObjectIdentifier("", "", ""), + } + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("validation: empty columns for row access policy", func(t *testing.T) { + opts := defaultOpts() + opts.RowAccessPolicy = &ViewRowAccessPolicy{ + RowAccessPolicy: RandomSchemaObjectIdentifier(), + On: []string{}, + } + assertOptsInvalidJoinedErrors(t, opts, errNotSet("CreateViewOptions.RowAccessPolicy", "On")) + }) + + t.Run("basic", func(t *testing.T) { + opts := defaultOpts() + assertOptsValidAndSQLEquals(t, opts, "CREATE VIEW %s AS %s", id.FullyQualifiedName(), sql) + }) + + t.Run("all options", func(t *testing.T) { + rowAccessPolicyId := RandomSchemaObjectIdentifier() + tag1Id := RandomSchemaObjectIdentifier() + tag2Id := RandomSchemaObjectIdentifier() + maskingPolicy1Id := RandomSchemaObjectIdentifier() + maskingPolicy2Id := RandomSchemaObjectIdentifier() + + req := NewCreateViewRequest(id, sql). + WithOrReplace(Bool(true)). + WithSecure(Bool(true)). + WithTemporary(Bool(true)). + WithRecursive(Bool(true)). + WithColumns([]ViewColumnRequest{ + *NewViewColumnRequest("column_without_comment"), + *NewViewColumnRequest("column_with_comment").WithComment(String("column 2 comment")), + }). + WithColumnsMaskingPolicies([]ViewColumnMaskingPolicyRequest{ + *NewViewColumnMaskingPolicyRequest("column", maskingPolicy1Id). + WithUsing([]string{"a", "b"}). + WithTag([]TagAssociation{{ + Name: tag1Id, + Value: "v1", + }}), + *NewViewColumnMaskingPolicyRequest("column 2", maskingPolicy2Id), + }). + WithCopyGrants(Bool(true)). + WithComment(String("comment")). + WithRowAccessPolicy(NewViewRowAccessPolicyRequest(rowAccessPolicyId, []string{"c", "d"})). + WithTag([]TagAssociation{{ + Name: tag2Id, + Value: "v2", + }}) + + assertOptsValidAndSQLEquals(t, req.toOpts(), `CREATE OR REPLACE SECURE TEMPORARY RECURSIVE VIEW %s ("column_without_comment", "column_with_comment" COMMENT 'column 2 comment') column MASKING POLICY %s USING (a, b) TAG (%s = 'v1'), column 2 MASKING POLICY %s COPY GRANTS COMMENT = 'comment' ROW ACCESS POLICY %s ON (c, d) TAG (%s = 'v2') AS %s`, id.FullyQualifiedName(), maskingPolicy1Id.FullyQualifiedName(), tag1Id.FullyQualifiedName(), maskingPolicy2Id.FullyQualifiedName(), rowAccessPolicyId.FullyQualifiedName(), tag2Id.FullyQualifiedName(), sql) + }) +} + +func TestViews_Alter(t *testing.T) { + id := RandomSchemaObjectIdentifier() + + // Minimal valid AlterViewOptions + defaultOpts := func() *AlterViewOptions { + return &AlterViewOptions{ + name: id, + } + } + + t.Run("validation: nil options", func(t *testing.T) { + var opts *AlterViewOptions = nil + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("validation: valid identifier for [opts.name]", func(t *testing.T) { + opts := defaultOpts() + opts.name = NewSchemaObjectIdentifier("", "", "") + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("validation: exactly one field from [opts.RenameTo opts.SetComment opts.UnsetComment opts.SetSecure opts.SetChangeTracking opts.UnsetSecure opts.SetTags opts.UnsetTags opts.AddRowAccessPolicy opts.DropRowAccessPolicy opts.DropAndAddRowAccessPolicy opts.DropAllRowAccessPolicies opts.SetMaskingPolicyOnColumn opts.UnsetMaskingPolicyOnColumn opts.SetTagsOnColumn opts.UnsetTagsOnColumn] should be present", func(t *testing.T) { + opts := defaultOpts() + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("AlterViewOptions", "RenameTo", "SetComment", "UnsetComment", "SetSecure", "SetChangeTracking", "UnsetSecure", "SetTags", "UnsetTags", "AddRowAccessPolicy", "DropRowAccessPolicy", "DropAndAddRowAccessPolicy", "DropAllRowAccessPolicies", "SetMaskingPolicyOnColumn", "UnsetMaskingPolicyOnColumn", "SetTagsOnColumn", "UnsetTagsOnColumn")) + }) + + t.Run("validation: exactly one field from [opts.RenameTo opts.SetComment opts.UnsetComment opts.SetSecure opts.SetChangeTracking opts.UnsetSecure opts.SetTags opts.UnsetTags opts.AddRowAccessPolicy opts.DropRowAccessPolicy opts.DropAndAddRowAccessPolicy opts.DropAllRowAccessPolicies opts.SetMaskingPolicyOnColumn opts.UnsetMaskingPolicyOnColumn opts.SetTagsOnColumn opts.UnsetTagsOnColumn] should be present - more present", func(t *testing.T) { + opts := defaultOpts() + opts.SetChangeTracking = Bool(true) + opts.DropAllRowAccessPolicies = Bool(true) + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("AlterViewOptions", "RenameTo", "SetComment", "UnsetComment", "SetSecure", "SetChangeTracking", "UnsetSecure", "SetTags", "UnsetTags", "AddRowAccessPolicy", "DropRowAccessPolicy", "DropAndAddRowAccessPolicy", "DropAllRowAccessPolicies", "SetMaskingPolicyOnColumn", "UnsetMaskingPolicyOnColumn", "SetTagsOnColumn", "UnsetTagsOnColumn")) + }) + + t.Run("validation: valid identifier for [opts.DropRowAccessPolicy.RowAccessPolicy]", func(t *testing.T) { + opts := defaultOpts() + opts.DropRowAccessPolicy = &ViewDropRowAccessPolicy{ + RowAccessPolicy: NewSchemaObjectIdentifier("", "", ""), + } + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("validation: valid identifier for [opts.AddRowAccessPolicy.RowAccessPolicy]", func(t *testing.T) { + opts := defaultOpts() + opts.AddRowAccessPolicy = &ViewAddRowAccessPolicy{ + RowAccessPolicy: NewSchemaObjectIdentifier("", "", ""), + } + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("validation: empty columns for row access policy (add)", func(t *testing.T) { + opts := defaultOpts() + opts.AddRowAccessPolicy = &ViewAddRowAccessPolicy{ + RowAccessPolicy: RandomSchemaObjectIdentifier(), + On: []string{}, + } + assertOptsInvalidJoinedErrors(t, opts, errNotSet("AlterViewOptions.AddRowAccessPolicy", "On")) + }) + + t.Run("validation: valid identifier for [opts.DropAndAddRowAccessPolicy.Drop.RowAccessPolicy]", func(t *testing.T) { + opts := defaultOpts() + opts.DropAndAddRowAccessPolicy = &ViewDropAndAddRowAccessPolicy{ + Drop: ViewDropRowAccessPolicy{ + RowAccessPolicy: NewSchemaObjectIdentifier("", "", ""), + }, + Add: ViewAddRowAccessPolicy{ + RowAccessPolicy: RandomSchemaObjectIdentifier(), + }, + } + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("validation: valid identifier for [opts.DropAndAddRowAccessPolicy.Add.RowAccessPolicy]", func(t *testing.T) { + opts := defaultOpts() + opts.DropAndAddRowAccessPolicy = &ViewDropAndAddRowAccessPolicy{ + Drop: ViewDropRowAccessPolicy{ + RowAccessPolicy: RandomSchemaObjectIdentifier(), + }, + Add: ViewAddRowAccessPolicy{ + RowAccessPolicy: NewSchemaObjectIdentifier("", "", ""), + }, + } + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("validation: empty columns for row access policy (drop and add)", func(t *testing.T) { + opts := defaultOpts() + opts.DropAndAddRowAccessPolicy = &ViewDropAndAddRowAccessPolicy{ + Drop: ViewDropRowAccessPolicy{ + RowAccessPolicy: RandomSchemaObjectIdentifier(), + }, + Add: ViewAddRowAccessPolicy{ + RowAccessPolicy: RandomSchemaObjectIdentifier(), + On: []string{}, + }, + } + assertOptsInvalidJoinedErrors(t, opts, errNotSet("AlterViewOptions.DropAndAddRowAccessPolicy.Add", "On")) + }) + + t.Run("rename", func(t *testing.T) { + newId := RandomSchemaObjectIdentifier() + + opts := defaultOpts() + opts.RenameTo = &newId + assertOptsValidAndSQLEquals(t, opts, "ALTER VIEW %s RENAME TO %s", id.FullyQualifiedName(), newId.FullyQualifiedName()) + }) + + t.Run("set comment", func(t *testing.T) { + opts := defaultOpts() + opts.SetComment = String("comment") + assertOptsValidAndSQLEquals(t, opts, "ALTER VIEW %s SET COMMENT = 'comment'", id.FullyQualifiedName()) + }) + + t.Run("unset comment", func(t *testing.T) { + opts := defaultOpts() + opts.UnsetComment = Bool(true) + assertOptsValidAndSQLEquals(t, opts, "ALTER VIEW %s UNSET COMMENT", id.FullyQualifiedName()) + }) + + t.Run("set secure", func(t *testing.T) { + opts := defaultOpts() + opts.SetSecure = Bool(true) + assertOptsValidAndSQLEquals(t, opts, "ALTER VIEW %s SET SECURE", id.FullyQualifiedName()) + }) + + t.Run("set change tracking: true", func(t *testing.T) { + opts := defaultOpts() + opts.SetChangeTracking = Bool(true) + assertOptsValidAndSQLEquals(t, opts, "ALTER VIEW %s SET CHANGE_TRACKING = true", id.FullyQualifiedName()) + }) + + t.Run("set change tracking: false", func(t *testing.T) { + opts := defaultOpts() + opts.SetChangeTracking = Bool(false) + assertOptsValidAndSQLEquals(t, opts, "ALTER VIEW %s SET CHANGE_TRACKING = false", id.FullyQualifiedName()) + }) + + t.Run("unset secure", func(t *testing.T) { + opts := defaultOpts() + opts.UnsetSecure = Bool(true) + assertOptsValidAndSQLEquals(t, opts, "ALTER VIEW %s UNSET SECURE", id.FullyQualifiedName()) + }) + + t.Run("set tags", func(t *testing.T) { + opts := defaultOpts() + opts.SetTags = []TagAssociation{ + { + Name: NewAccountObjectIdentifier("tag1"), + Value: "value1", + }, + { + Name: NewAccountObjectIdentifier("tag2"), + Value: "value2", + }, + } + assertOptsValidAndSQLEquals(t, opts, `ALTER VIEW %s SET TAG "tag1" = 'value1', "tag2" = 'value2'`, id.FullyQualifiedName()) + }) + + t.Run("unset tags", func(t *testing.T) { + opts := defaultOpts() + opts.UnsetTags = []ObjectIdentifier{ + NewAccountObjectIdentifier("tag1"), + NewAccountObjectIdentifier("tag2"), + } + assertOptsValidAndSQLEquals(t, opts, `ALTER VIEW %s UNSET TAG "tag1", "tag2"`, id.FullyQualifiedName()) + }) + + t.Run("add row access policy", func(t *testing.T) { + rowAccessPolicyId := RandomSchemaObjectIdentifier() + + opts := defaultOpts() + opts.AddRowAccessPolicy = &ViewAddRowAccessPolicy{ + RowAccessPolicy: rowAccessPolicyId, + On: []string{"a", "b"}, + } + assertOptsValidAndSQLEquals(t, opts, "ALTER VIEW %s ADD ROW ACCESS POLICY %s ON (a, b)", id.FullyQualifiedName(), rowAccessPolicyId.FullyQualifiedName()) + }) + + t.Run("drop row access policy", func(t *testing.T) { + rowAccessPolicyId := RandomSchemaObjectIdentifier() + + opts := defaultOpts() + opts.DropRowAccessPolicy = &ViewDropRowAccessPolicy{ + RowAccessPolicy: rowAccessPolicyId, + } + assertOptsValidAndSQLEquals(t, opts, "ALTER VIEW %s DROP ROW ACCESS POLICY %s", id.FullyQualifiedName(), rowAccessPolicyId.FullyQualifiedName()) + }) + + t.Run("drop and add row access policy", func(t *testing.T) { + rowAccessPolicy1Id := RandomSchemaObjectIdentifier() + rowAccessPolicy2Id := RandomSchemaObjectIdentifier() + + opts := defaultOpts() + opts.DropAndAddRowAccessPolicy = &ViewDropAndAddRowAccessPolicy{ + Drop: ViewDropRowAccessPolicy{ + RowAccessPolicy: rowAccessPolicy1Id, + }, + Add: ViewAddRowAccessPolicy{ + RowAccessPolicy: rowAccessPolicy2Id, + On: []string{"a", "b"}, + }, + } + assertOptsValidAndSQLEquals(t, opts, "ALTER VIEW %s DROP ROW ACCESS POLICY %s, ADD ROW ACCESS POLICY %s ON (a, b)", id.FullyQualifiedName(), rowAccessPolicy1Id.FullyQualifiedName(), rowAccessPolicy2Id.FullyQualifiedName()) + }) + + t.Run("drop all row access policies", func(t *testing.T) { + opts := defaultOpts() + opts.DropAllRowAccessPolicies = Bool(true) + assertOptsValidAndSQLEquals(t, opts, "ALTER VIEW %s DROP ALL ROW ACCESS POLICIES", id.FullyQualifiedName()) + }) + + t.Run("set masking policy on column", func(t *testing.T) { + maskingPolicyId := RandomSchemaObjectIdentifier() + + opts := defaultOpts() + opts.SetMaskingPolicyOnColumn = &ViewSetColumnMaskingPolicy{ + Name: "column", + MaskingPolicy: maskingPolicyId, + Using: []string{"a", "b"}, + Force: Bool(true), + } + assertOptsValidAndSQLEquals(t, opts, "ALTER VIEW %s ALTER COLUMN column SET MASKING POLICY %s USING (a, b) FORCE", id.FullyQualifiedName(), maskingPolicyId.FullyQualifiedName()) + }) + + t.Run("unset masking policy on column", func(t *testing.T) { + opts := defaultOpts() + opts.UnsetMaskingPolicyOnColumn = &ViewUnsetColumnMaskingPolicy{ + Name: "column", + } + assertOptsValidAndSQLEquals(t, opts, "ALTER VIEW %s ALTER COLUMN column UNSET MASKING POLICY", id.FullyQualifiedName()) + }) + + t.Run("set tags on column", func(t *testing.T) { + opts := defaultOpts() + opts.SetTagsOnColumn = &ViewSetColumnTags{ + Name: "column", + SetTags: []TagAssociation{ + { + Name: NewAccountObjectIdentifier("tag1"), + Value: "value1", + }, + { + Name: NewAccountObjectIdentifier("tag2"), + Value: "value2", + }, + }, + } + assertOptsValidAndSQLEquals(t, opts, `ALTER VIEW %s ALTER COLUMN column SET TAG "tag1" = 'value1', "tag2" = 'value2'`, id.FullyQualifiedName()) + }) + + t.Run("unset tags on column", func(t *testing.T) { + opts := defaultOpts() + opts.UnsetTagsOnColumn = &ViewUnsetColumnTags{ + Name: "column", + UnsetTags: []ObjectIdentifier{ + NewAccountObjectIdentifier("tag1"), + NewAccountObjectIdentifier("tag2"), + }, + } + assertOptsValidAndSQLEquals(t, opts, `ALTER VIEW %s ALTER COLUMN column UNSET TAG "tag1", "tag2"`, id.FullyQualifiedName()) + }) +} + +func TestViews_Drop(t *testing.T) { + id := RandomSchemaObjectIdentifier() + + // Minimal valid DropViewOptions + defaultOpts := func() *DropViewOptions { + return &DropViewOptions{ + name: id, + } + } + + t.Run("validation: nil options", func(t *testing.T) { + var opts *DropViewOptions = nil + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("validation: valid identifier for [opts.name]", func(t *testing.T) { + opts := defaultOpts() + opts.name = NewSchemaObjectIdentifier("", "", "") + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("basic", func(t *testing.T) { + opts := defaultOpts() + assertOptsValidAndSQLEquals(t, opts, "DROP VIEW %s", id.FullyQualifiedName()) + }) +} + +func TestViews_Show(t *testing.T) { + // Minimal valid ShowViewOptions + defaultOpts := func() *ShowViewOptions { + return &ShowViewOptions{} + } + + t.Run("validation: nil options", func(t *testing.T) { + var opts *ShowViewOptions = nil + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("basic", func(t *testing.T) { + opts := defaultOpts() + assertOptsValidAndSQLEquals(t, opts, "SHOW VIEWS") + }) + + t.Run("all options", func(t *testing.T) { + opts := defaultOpts() + opts.Terse = Bool(true) + opts.Like = &Like{ + Pattern: String("myaccount"), + } + opts.In = &In{ + Account: Bool(true), + } + opts.StartsWith = String("abc") + opts.Limit = &LimitFrom{Rows: Int(10)} + assertOptsValidAndSQLEquals(t, opts, "SHOW TERSE VIEWS LIKE 'myaccount' IN ACCOUNT STARTS WITH 'abc' LIMIT 10") + }) +} + +func TestViews_Describe(t *testing.T) { + id := RandomSchemaObjectIdentifier() + + // Minimal valid DescribeViewOptions + defaultOpts := func() *DescribeViewOptions { + return &DescribeViewOptions{ + name: id, + } + } + + t.Run("validation: nil options", func(t *testing.T) { + var opts *DescribeViewOptions = nil + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("validation: valid identifier for [opts.name]", func(t *testing.T) { + opts := defaultOpts() + opts.name = NewSchemaObjectIdentifier("", "", "") + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("basic", func(t *testing.T) { + opts := defaultOpts() + assertOptsValidAndSQLEquals(t, opts, "DESCRIBE VIEW %s", id.FullyQualifiedName()) + }) +} diff --git a/pkg/sdk/views_impl_gen.go b/pkg/sdk/views_impl_gen.go new file mode 100644 index 0000000000..c77278b471 --- /dev/null +++ b/pkg/sdk/views_impl_gen.go @@ -0,0 +1,249 @@ +package sdk + +import ( + "context" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" +) + +var _ Views = (*views)(nil) + +type views struct { + client *Client +} + +func (v *views) Create(ctx context.Context, request *CreateViewRequest) error { + opts := request.toOpts() + return validateAndExec(v.client, ctx, opts) +} + +func (v *views) Alter(ctx context.Context, request *AlterViewRequest) error { + opts := request.toOpts() + return validateAndExec(v.client, ctx, opts) +} + +func (v *views) Drop(ctx context.Context, request *DropViewRequest) error { + opts := request.toOpts() + return validateAndExec(v.client, ctx, opts) +} + +func (v *views) Show(ctx context.Context, request *ShowViewRequest) ([]View, error) { + opts := request.toOpts() + dbRows, err := validateAndQuery[viewDBRow](v.client, ctx, opts) + if err != nil { + return nil, err + } + resultList := convertRows[viewDBRow, View](dbRows) + return resultList, nil +} + +func (v *views) ShowByID(ctx context.Context, id SchemaObjectIdentifier) (*View, error) { + request := NewShowViewRequest().WithIn(&In{Database: NewAccountObjectIdentifier(id.DatabaseName())}).WithLike(&Like{String(id.Name())}) + views, err := v.Show(ctx, request) + if err != nil { + return nil, err + } + return collections.FindOne(views, func(r View) bool { return r.Name == id.Name() }) +} + +func (v *views) Describe(ctx context.Context, id SchemaObjectIdentifier) ([]ViewDetails, error) { + opts := &DescribeViewOptions{ + name: id, + } + rows, err := validateAndQuery[viewDetailsRow](v.client, ctx, opts) + if err != nil { + return nil, err + } + return convertRows[viewDetailsRow, ViewDetails](rows), nil +} + +func (r *CreateViewRequest) toOpts() *CreateViewOptions { + opts := &CreateViewOptions{ + OrReplace: r.OrReplace, + Secure: r.Secure, + Temporary: r.Temporary, + Recursive: r.Recursive, + IfNotExists: r.IfNotExists, + name: r.name, + + CopyGrants: r.CopyGrants, + Comment: r.Comment, + + Tag: r.Tag, + sql: r.sql, + } + if r.Columns != nil { + s := make([]ViewColumn, len(r.Columns)) + for i, v := range r.Columns { + s[i] = ViewColumn(v) + } + opts.Columns = s + } + if r.ColumnsMaskingPolicies != nil { + s := make([]ViewColumnMaskingPolicy, len(r.ColumnsMaskingPolicies)) + for i, v := range r.ColumnsMaskingPolicies { + s[i] = ViewColumnMaskingPolicy(v) + } + opts.ColumnsMaskingPolicies = s + } + if r.RowAccessPolicy != nil { + opts.RowAccessPolicy = &ViewRowAccessPolicy{ + RowAccessPolicy: r.RowAccessPolicy.RowAccessPolicy, + On: r.RowAccessPolicy.On, + } + } + return opts +} + +func (r *AlterViewRequest) toOpts() *AlterViewOptions { + opts := &AlterViewOptions{ + IfExists: r.IfExists, + name: r.name, + RenameTo: r.RenameTo, + SetComment: r.SetComment, + UnsetComment: r.UnsetComment, + SetSecure: r.SetSecure, + SetChangeTracking: r.SetChangeTracking, + UnsetSecure: r.UnsetSecure, + SetTags: r.SetTags, + UnsetTags: r.UnsetTags, + DropAllRowAccessPolicies: r.DropAllRowAccessPolicies, + } + if r.AddRowAccessPolicy != nil { + opts.AddRowAccessPolicy = &ViewAddRowAccessPolicy{ + RowAccessPolicy: r.AddRowAccessPolicy.RowAccessPolicy, + On: r.AddRowAccessPolicy.On, + } + } + if r.DropRowAccessPolicy != nil { + opts.DropRowAccessPolicy = &ViewDropRowAccessPolicy{ + RowAccessPolicy: r.DropRowAccessPolicy.RowAccessPolicy, + } + } + if r.DropAndAddRowAccessPolicy != nil { + opts.DropAndAddRowAccessPolicy = &ViewDropAndAddRowAccessPolicy{} + opts.DropAndAddRowAccessPolicy.Drop = ViewDropRowAccessPolicy{ + RowAccessPolicy: r.DropAndAddRowAccessPolicy.Drop.RowAccessPolicy, + } + opts.DropAndAddRowAccessPolicy.Add = ViewAddRowAccessPolicy{ + RowAccessPolicy: r.DropAndAddRowAccessPolicy.Add.RowAccessPolicy, + On: r.DropAndAddRowAccessPolicy.Add.On, + } + } + if r.SetMaskingPolicyOnColumn != nil { + opts.SetMaskingPolicyOnColumn = &ViewSetColumnMaskingPolicy{ + Name: r.SetMaskingPolicyOnColumn.Name, + MaskingPolicy: r.SetMaskingPolicyOnColumn.MaskingPolicy, + Using: r.SetMaskingPolicyOnColumn.Using, + Force: r.SetMaskingPolicyOnColumn.Force, + } + } + if r.UnsetMaskingPolicyOnColumn != nil { + opts.UnsetMaskingPolicyOnColumn = &ViewUnsetColumnMaskingPolicy{ + Name: r.UnsetMaskingPolicyOnColumn.Name, + } + } + if r.SetTagsOnColumn != nil { + opts.SetTagsOnColumn = &ViewSetColumnTags{ + Name: r.SetTagsOnColumn.Name, + SetTags: r.SetTagsOnColumn.SetTags, + } + } + if r.UnsetTagsOnColumn != nil { + opts.UnsetTagsOnColumn = &ViewUnsetColumnTags{ + Name: r.UnsetTagsOnColumn.Name, + UnsetTags: r.UnsetTagsOnColumn.UnsetTags, + } + } + return opts +} + +func (r *DropViewRequest) toOpts() *DropViewOptions { + opts := &DropViewOptions{ + IfExists: r.IfExists, + name: r.name, + } + return opts +} + +func (r *ShowViewRequest) toOpts() *ShowViewOptions { + opts := &ShowViewOptions{ + Terse: r.Terse, + Like: r.Like, + In: r.In, + StartsWith: r.StartsWith, + Limit: r.Limit, + } + return opts +} + +func (r viewDBRow) convert() *View { + view := View{ + CreatedOn: r.CreatedOn, + Name: r.Name, + DatabaseName: r.DatabaseName, + SchemaName: r.SchemaName, + } + if r.Kind.Valid { + view.Kind = r.Kind.String + } + if r.Reserved.Valid { + view.Reserved = r.Reserved.String + } + if r.Owner.Valid { + view.Owner = r.Owner.String + } + if r.Comment.Valid { + view.Comment = r.Comment.String + } + if r.Text.Valid { + view.Text = r.Text.String + } + if r.IsSecure.Valid { + view.IsSecure = r.IsSecure.Bool + } + if r.IsMaterialized.Valid { + view.IsMaterialized = r.IsMaterialized.Bool + } + if r.OwnerRoleType.Valid { + view.OwnerRoleType = r.OwnerRoleType.String + } + if r.ChangeTracking.Valid { + view.ChangeTracking = r.ChangeTracking.String + } + return &view +} + +func (r *DescribeViewRequest) toOpts() *DescribeViewOptions { + opts := &DescribeViewOptions{ + name: r.name, + } + return opts +} + +func (r viewDetailsRow) convert() *ViewDetails { + details := &ViewDetails{ + Name: r.Name, + Type: r.Type, + Kind: r.Kind, + IsNullable: r.IsNullable == "Y", + IsPrimary: r.IsPrimary == "Y", + IsUnique: r.IsUnique == "Y", + } + if r.Default.Valid { + details.Default = String(r.Default.String) + } + if r.Check.Valid { + details.Check = Bool(r.Check.String == "Y") + } + if r.Expression.Valid { + details.Expression = String(r.Expression.String) + } + if r.Comment.Valid { + details.Comment = String(r.Comment.String) + } + if r.PolicyName.Valid { + details.PolicyName = String(r.PolicyName.String) + } + return details +} diff --git a/pkg/sdk/views_validations_gen.go b/pkg/sdk/views_validations_gen.go new file mode 100644 index 0000000000..cbc47adb61 --- /dev/null +++ b/pkg/sdk/views_validations_gen.go @@ -0,0 +1,103 @@ +package sdk + +var ( + _ validatable = new(CreateViewOptions) + _ validatable = new(AlterViewOptions) + _ validatable = new(DropViewOptions) + _ validatable = new(ShowViewOptions) + _ validatable = new(DescribeViewOptions) +) + +func (opts *CreateViewOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + if !ValidObjectIdentifier(opts.name) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + if everyValueSet(opts.OrReplace, opts.IfNotExists) { + errs = append(errs, errOneOf("CreateViewOptions", "OrReplace", "IfNotExists")) + } + if valueSet(opts.RowAccessPolicy) { + if !ValidObjectIdentifier(opts.RowAccessPolicy.RowAccessPolicy) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + if !valueSet(opts.RowAccessPolicy.On) { + errs = append(errs, errNotSet("CreateViewOptions.RowAccessPolicy", "On")) + } + } + return JoinErrors(errs...) +} + +func (opts *AlterViewOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + if !ValidObjectIdentifier(opts.name) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + if !exactlyOneValueSet(opts.RenameTo, opts.SetComment, opts.UnsetComment, opts.SetSecure, opts.SetChangeTracking, opts.UnsetSecure, opts.SetTags, opts.UnsetTags, opts.AddRowAccessPolicy, opts.DropRowAccessPolicy, opts.DropAndAddRowAccessPolicy, opts.DropAllRowAccessPolicies, opts.SetMaskingPolicyOnColumn, opts.UnsetMaskingPolicyOnColumn, opts.SetTagsOnColumn, opts.UnsetTagsOnColumn) { + errs = append(errs, errExactlyOneOf("AlterViewOptions", "RenameTo", "SetComment", "UnsetComment", "SetSecure", "SetChangeTracking", "UnsetSecure", "SetTags", "UnsetTags", "AddRowAccessPolicy", "DropRowAccessPolicy", "DropAndAddRowAccessPolicy", "DropAllRowAccessPolicies", "SetMaskingPolicyOnColumn", "UnsetMaskingPolicyOnColumn", "SetTagsOnColumn", "UnsetTagsOnColumn")) + } + if valueSet(opts.AddRowAccessPolicy) { + if !ValidObjectIdentifier(opts.AddRowAccessPolicy.RowAccessPolicy) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + if !valueSet(opts.AddRowAccessPolicy.On) { + errs = append(errs, errNotSet("AlterViewOptions.AddRowAccessPolicy", "On")) + } + } + if valueSet(opts.DropRowAccessPolicy) { + if !ValidObjectIdentifier(opts.DropRowAccessPolicy.RowAccessPolicy) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + } + if valueSet(opts.DropAndAddRowAccessPolicy) { + if valueSet(opts.DropAndAddRowAccessPolicy.Drop) { + if !ValidObjectIdentifier(opts.DropAndAddRowAccessPolicy.Drop.RowAccessPolicy) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + } + if valueSet(opts.DropAndAddRowAccessPolicy.Add) { + if !ValidObjectIdentifier(opts.DropAndAddRowAccessPolicy.Add.RowAccessPolicy) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + if !valueSet(opts.DropAndAddRowAccessPolicy.Add.On) { + errs = append(errs, errNotSet("AlterViewOptions.DropAndAddRowAccessPolicy.Add", "On")) + } + } + } + return JoinErrors(errs...) +} + +func (opts *DropViewOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + if !ValidObjectIdentifier(opts.name) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + return JoinErrors(errs...) +} + +func (opts *ShowViewOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + return JoinErrors(errs...) +} + +func (opts *DescribeViewOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + if !ValidObjectIdentifier(opts.name) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + return JoinErrors(errs...) +}