From ffe28c3668a890c45e8c9c2bb76e5ecc48855aeb Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Wed, 17 Jul 2024 16:26:36 +0200 Subject: [PATCH 1/3] adjust sdk --- pkg/acceptance/helpers/parameter_client.go | 11 + pkg/acceptance/helpers/schema_client.go | 7 +- pkg/resources/schema.go | 6 +- pkg/schemas/schema.go | 33 ++ pkg/schemas/schema_gen.go | 9 +- pkg/schemas/schema_parameters.go | 48 +++ pkg/sdk/parameters.go | 1 + pkg/sdk/schemas.go | 269 ++++++++++++--- pkg/sdk/schemas_test.go | 351 ++++++++++++++++--- pkg/sdk/testint/schemas_integration_test.go | 355 ++++++++++++++++---- 10 files changed, 924 insertions(+), 166 deletions(-) create mode 100644 pkg/schemas/schema.go create mode 100644 pkg/schemas/schema_parameters.go diff --git a/pkg/acceptance/helpers/parameter_client.go b/pkg/acceptance/helpers/parameter_client.go index 56d6b47bf3..36f1df78c8 100644 --- a/pkg/acceptance/helpers/parameter_client.go +++ b/pkg/acceptance/helpers/parameter_client.go @@ -58,6 +58,17 @@ func (c *ParameterClient) ShowWarehouseParameters(t *testing.T, id sdk.AccountOb return params } +func (c *ParameterClient) ShowSchemaParameters(t *testing.T, id sdk.DatabaseObjectIdentifier) []*sdk.Parameter { + t.Helper() + params, err := c.client().ShowParameters(context.Background(), &sdk.ShowParametersOptions{ + In: &sdk.ParametersIn{ + Schema: id, + }, + }) + require.NoError(t, err) + return params +} + func (c *ParameterClient) UpdateAccountParameterTemporarily(t *testing.T, parameter sdk.AccountParameter, newValue string) func() { t.Helper() ctx := context.Background() diff --git a/pkg/acceptance/helpers/schema_client.go b/pkg/acceptance/helpers/schema_client.go index 2aabbe94eb..390b736894 100644 --- a/pkg/acceptance/helpers/schema_client.go +++ b/pkg/acceptance/helpers/schema_client.go @@ -40,10 +40,15 @@ func (c *SchemaClient) CreateSchemaWithName(t *testing.T, name string) (*sdk.Sch } func (c *SchemaClient) CreateSchemaWithIdentifier(t *testing.T, id sdk.DatabaseObjectIdentifier) (*sdk.Schema, func()) { + t.Helper() + return c.CreateSchemaWithOpts(t, id, nil) +} + +func (c *SchemaClient) CreateSchemaWithOpts(t *testing.T, id sdk.DatabaseObjectIdentifier, opts *sdk.CreateSchemaOptions) (*sdk.Schema, func()) { t.Helper() ctx := context.Background() - err := c.client().Create(ctx, id, nil) + err := c.client().Create(ctx, id, opts) require.NoError(t, err) schema, err := c.client().ShowByID(ctx, id) require.NoError(t, err) diff --git a/pkg/resources/schema.go b/pkg/resources/schema.go index bf76545066..fc184b889a 100644 --- a/pkg/resources/schema.go +++ b/pkg/resources/schema.go @@ -142,13 +142,11 @@ func ReadSchema(d *schema.ResourceData, meta interface{}) error { values := map[string]any{ "name": s.Name, "database": s.DatabaseName, + "comment": s.Comment, // reset the options before reading back from the DB "is_transient": false, "is_managed": false, } - if s.Comment != nil { - values["comment"] = *s.Comment - } for k, v := range values { if err := d.Set(k, v); err != nil { @@ -184,7 +182,7 @@ func UpdateSchema(d *schema.ResourceData, meta interface{}) error { newId := sdk.NewDatabaseObjectIdentifier(id.DatabaseName(), d.Get("name").(string)) err := client.Schemas.Alter(ctx, id, &sdk.AlterSchemaOptions{ - NewName: newId, + NewName: sdk.Pointer(newId), }) if err != nil { return fmt.Errorf("error updating schema name on %v err = %w", d.Id(), err) diff --git a/pkg/schemas/schema.go b/pkg/schemas/schema.go new file mode 100644 index 0000000000..85daf3d80f --- /dev/null +++ b/pkg/schemas/schema.go @@ -0,0 +1,33 @@ +package schemas + +import ( + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var SchemaDescribeSchema = map[string]*schema.Schema{ + "created_on": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "kind": { + Type: schema.TypeString, + Computed: true, + }, +} + +func SchemaDescriptionToSchema(description []sdk.SchemaDetails) []map[string]any { + result := make([]map[string]any, len(description)) + for i, row := range description { + result[i] = map[string]any{ + "created_on": row.CreatedOn.String(), + "name": row.Name, + "kind": row.Kind, + } + } + return result +} diff --git a/pkg/schemas/schema_gen.go b/pkg/schemas/schema_gen.go index 9587726d19..adbfe107fd 100644 --- a/pkg/schemas/schema_gen.go +++ b/pkg/schemas/schema_gen.go @@ -13,6 +13,10 @@ var ShowSchemaSchema = map[string]*schema.Schema{ Type: schema.TypeString, Computed: true, }, + "dropped_on": { + Type: schema.TypeString, + Computed: true, + }, "name": { Type: schema.TypeString, Computed: true, @@ -56,14 +60,13 @@ var _ = ShowSchemaSchema func SchemaToSchema(schema *sdk.Schema) map[string]any { schemaSchema := make(map[string]any) schemaSchema["created_on"] = schema.CreatedOn.String() + schemaSchema["dropped_on"] = schema.DroppedOn.String() schemaSchema["name"] = schema.Name schemaSchema["is_default"] = schema.IsDefault schemaSchema["is_current"] = schema.IsCurrent schemaSchema["database_name"] = schema.DatabaseName schemaSchema["owner"] = schema.Owner - if schema.Comment != nil { - schemaSchema["comment"] = schema.Comment - } + schemaSchema["comment"] = schema.Comment if schema.Options != nil { schemaSchema["options"] = schema.Options } diff --git a/pkg/schemas/schema_parameters.go b/pkg/schemas/schema_parameters.go new file mode 100644 index 0000000000..7ddfb6b3e5 --- /dev/null +++ b/pkg/schemas/schema_parameters.go @@ -0,0 +1,48 @@ +package schemas + +import ( + "slices" + "strings" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var ( + ShowSchemaParametersSchema = make(map[string]*schema.Schema) + schemaParameters = []sdk.AccountParameter{ + sdk.AccountParameterDataRetentionTimeInDays, + sdk.AccountParameterMaxDataExtensionTimeInDays, + sdk.AccountParameterExternalVolume, + sdk.AccountParameterCatalog, + sdk.AccountParameterReplaceInvalidCharacters, + sdk.AccountParameterDefaultDDLCollation, + sdk.AccountParameterStorageSerializationPolicy, + sdk.AccountParameterLogLevel, + sdk.AccountParameterTraceLevel, + sdk.AccountParameterSuspendTaskAfterNumFailures, + sdk.AccountParameterTaskAutoRetryAttempts, + sdk.AccountParameterUserTaskManagedInitialWarehouseSize, + sdk.AccountParameterUserTaskTimeoutMs, + sdk.AccountParameterUserTaskMinimumTriggerIntervalInSeconds, + sdk.AccountParameterQuotedIdentifiersIgnoreCase, + sdk.AccountParameterEnableConsoleOutput, + sdk.AccountParameterPipeExecutionPaused, + } +) + +func init() { + for _, param := range schemaParameters { + ShowSchemaParametersSchema[strings.ToLower(string(param))] = ParameterListSchema + } +} + +func SchemaParametersToSchema(parameters []*sdk.Parameter) map[string]any { + schemaParametersValue := make(map[string]any) + for _, param := range parameters { + if slices.Contains(schemaParameters, sdk.AccountParameter(param.Key)) { + schemaParametersValue[strings.ToLower(param.Key)] = []map[string]any{ParameterToSchema(param)} + } + } + return schemaParametersValue +} diff --git a/pkg/sdk/parameters.go b/pkg/sdk/parameters.go index 75ec5d62e7..8e18e6b417 100644 --- a/pkg/sdk/parameters.go +++ b/pkg/sdk/parameters.go @@ -1000,6 +1000,7 @@ const ( ParameterTypeObject ParameterType = "OBJECT" ParameterTypeWarehouse ParameterType = "WAREHOUSE" ParameterTypeDatabase ParameterType = "DATABASE" + ParameterTypeSchema ParameterType = "SCHEMA" ) type Parameter struct { diff --git a/pkg/sdk/schemas.go b/pkg/sdk/schemas.go index 30f6e34c90..fd7d9b0334 100644 --- a/pkg/sdk/schemas.go +++ b/pkg/sdk/schemas.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "errors" + "slices" "time" ) @@ -35,17 +36,32 @@ type schemas struct { type Schema struct { CreatedOn time.Time + DroppedOn time.Time Name string IsDefault bool IsCurrent bool DatabaseName string Owner string - Comment *string + Comment string Options *string RetentionTime string OwnerRoleType string } +func (s *Schema) IsTransient() bool { + if s.Options == nil { + return false + } + return slices.Contains(ParseCommaSeparatedStringArray(*s.Options, false), "TRANSIENT") +} + +func (s *Schema) IsManagedAccess() bool { + if s.Options == nil { + return false + } + return slices.Contains(ParseCommaSeparatedStringArray(*s.Options, false), "MANAGED ACCESS") +} + func (v *Schema) ID() DatabaseObjectIdentifier { return NewDatabaseObjectIdentifier(v.DatabaseName, v.Name) } @@ -56,6 +72,7 @@ func (v *Schema) ObjectType() ObjectType { type schemaDBRow struct { CreatedOn time.Time `db:"created_on"` + DroppedOn sql.NullTime `db:"dropped_on"` Name string `db:"name"` IsDefault string `db:"is_default"` IsCurrent string `db:"is_current"` @@ -68,43 +85,60 @@ type schemaDBRow struct { } func (row schemaDBRow) toSchema() Schema { - var comment *string - var options *string - if row.Comment.Valid { - comment = &row.Comment.String - } - if row.Options.Valid { - options = &row.Options.String - } - return Schema{ + schema := Schema{ CreatedOn: row.CreatedOn, Name: row.Name, IsDefault: row.IsDefault == "Y", IsCurrent: row.IsCurrent == "Y", DatabaseName: row.DatabaseName, Owner: row.Owner, - Comment: comment, - Options: options, RetentionTime: row.RetentionTime, OwnerRoleType: row.OwnerRoleType, } + if row.Comment.Valid { + schema.Comment = row.Comment.String + } + if row.Options.Valid { + schema.Options = &row.Options.String + } + if row.DroppedOn.Valid { + schema.DroppedOn = row.DroppedOn.Time + } + return schema } // CreateSchemaOptions based on https://docs.snowflake.com/en/sql-reference/sql/create-schema type CreateSchemaOptions struct { - create bool `ddl:"static" sql:"CREATE"` - OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` - Transient *bool `ddl:"keyword" sql:"TRANSIENT"` - schema bool `ddl:"static" sql:"SCHEMA"` - IfNotExists *bool `ddl:"keyword" sql:"IF NOT EXISTS"` - name DatabaseObjectIdentifier `ddl:"identifier"` - Clone *Clone `ddl:"-"` - WithManagedAccess *bool `ddl:"keyword" sql:"WITH MANAGED ACCESS"` - DataRetentionTimeInDays *int `ddl:"parameter" sql:"DATA_RETENTION_TIME_IN_DAYS"` - MaxDataExtensionTimeInDays *int `ddl:"parameter" sql:"MAX_DATA_EXTENSION_TIME_IN_DAYS"` - DefaultDDLCollation *string `ddl:"parameter,single_quotes" sql:"DEFAULT_DDL_COLLATION"` - Tag []TagAssociation `ddl:"keyword,parentheses" sql:"TAG"` - Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + create bool `ddl:"static" sql:"CREATE"` + OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` + Transient *bool `ddl:"keyword" sql:"TRANSIENT"` + schema bool `ddl:"static" sql:"SCHEMA"` + IfNotExists *bool `ddl:"keyword" sql:"IF NOT EXISTS"` + name DatabaseObjectIdentifier `ddl:"identifier"` + Clone *Clone `ddl:"-"` + WithManagedAccess *bool `ddl:"keyword" sql:"WITH MANAGED ACCESS"` + + // Parameters + DataRetentionTimeInDays *int `ddl:"parameter" sql:"DATA_RETENTION_TIME_IN_DAYS"` + MaxDataExtensionTimeInDays *int `ddl:"parameter" sql:"MAX_DATA_EXTENSION_TIME_IN_DAYS"` + ExternalVolume *AccountObjectIdentifier `ddl:"identifier,equals" sql:"EXTERNAL_VOLUME"` + Catalog *AccountObjectIdentifier `ddl:"identifier,equals" sql:"CATALOG"` + PipeExecutionPaused *bool `ddl:"parameter" sql:"PIPE_EXECUTION_PAUSED"` + ReplaceInvalidCharacters *bool `ddl:"parameter" sql:"REPLACE_INVALID_CHARACTERS"` + DefaultDDLCollation *string `ddl:"parameter,single_quotes" sql:"DEFAULT_DDL_COLLATION"` + StorageSerializationPolicy *StorageSerializationPolicy `ddl:"parameter" sql:"STORAGE_SERIALIZATION_POLICY"` + LogLevel *LogLevel `ddl:"parameter,single_quotes" sql:"LOG_LEVEL"` + TraceLevel *TraceLevel `ddl:"parameter,single_quotes" sql:"TRACE_LEVEL"` + SuspendTaskAfterNumFailures *int `ddl:"parameter" sql:"SUSPEND_TASK_AFTER_NUM_FAILURES"` + TaskAutoRetryAttempts *int `ddl:"parameter" sql:"TASK_AUTO_RETRY_ATTEMPTS"` + UserTaskManagedInitialWarehouseSize *WarehouseSize `ddl:"parameter" sql:"USER_TASK_MANAGED_INITIAL_WAREHOUSE_SIZE"` + UserTaskTimeoutMs *int `ddl:"parameter" sql:"USER_TASK_TIMEOUT_MS"` + UserTaskMinimumTriggerIntervalInSeconds *int `ddl:"parameter" sql:"USER_TASK_MINIMUM_TRIGGER_INTERVAL_IN_SECONDS"` + QuotedIdentifiersIgnoreCase *bool `ddl:"parameter" sql:"QUOTED_IDENTIFIERS_IGNORE_CASE"` + EnableConsoleOutput *bool `ddl:"parameter" sql:"ENABLE_CONSOLE_OUTPUT"` + + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + Tag []TagAssociation `ddl:"keyword,parentheses" sql:"TAG"` } func (opts *CreateSchemaOptions) validate() error { @@ -123,6 +157,12 @@ func (opts *CreateSchemaOptions) validate() error { if everyValueSet(opts.OrReplace, opts.IfNotExists) { errs = append(errs, errOneOf("CreateSchemaOptions", "IfNotExists", "OrReplace")) } + if opts.ExternalVolume != nil && !ValidObjectIdentifier(opts.ExternalVolume) { + errs = append(errs, errInvalidIdentifier("CreateSchemaOptions", "ExternalVolume")) + } + if opts.Catalog != nil && !ValidObjectIdentifier(opts.Catalog) { + errs = append(errs, errInvalidIdentifier("CreateSchemaOptions", "Catalog")) + } return errors.Join(errs...) } @@ -144,16 +184,16 @@ func (v *schemas) Create(ctx context.Context, id DatabaseObjectIdentifier, opts // AlterSchemaOptions based on https://docs.snowflake.com/en/sql-reference/sql/alter-schema type AlterSchemaOptions struct { - alter bool `ddl:"static" sql:"ALTER"` - schema bool `ddl:"static" sql:"SCHEMA"` - IfExists *bool `ddl:"keyword" sql:"IF EXISTS"` - name DatabaseObjectIdentifier `ddl:"identifier"` - NewName DatabaseObjectIdentifier `ddl:"identifier" sql:"RENAME TO"` - SwapWith DatabaseObjectIdentifier `ddl:"identifier" sql:"SWAP WITH"` - Set *SchemaSet `ddl:"list,no_parentheses" sql:"SET"` - Unset *SchemaUnset `ddl:"list,no_parentheses" sql:"UNSET"` - SetTag []TagAssociation `ddl:"keyword" sql:"SET TAG"` - UnsetTag []ObjectIdentifier `ddl:"keyword" sql:"UNSET TAG"` + alter bool `ddl:"static" sql:"ALTER"` + schema bool `ddl:"static" sql:"SCHEMA"` + IfExists *bool `ddl:"keyword" sql:"IF EXISTS"` + name DatabaseObjectIdentifier `ddl:"identifier"` + NewName *DatabaseObjectIdentifier `ddl:"identifier" sql:"RENAME TO"` + SwapWith *DatabaseObjectIdentifier `ddl:"identifier" sql:"SWAP WITH"` + Set *SchemaSet `ddl:"list,no_parentheses" sql:"SET"` + Unset *SchemaUnset `ddl:"list,no_parentheses" sql:"UNSET"` + SetTag []TagAssociation `ddl:"keyword" sql:"SET TAG"` + UnsetTag []ObjectIdentifier `ddl:"keyword" sql:"UNSET TAG"` // One of EnableManagedAccess *bool `ddl:"keyword" sql:"ENABLE MANAGED ACCESS"` DisableManagedAccess *bool `ddl:"keyword" sql:"DISABLE MANAGED ACCESS"` @@ -167,8 +207,14 @@ func (opts *AlterSchemaOptions) validate() error { if !ValidObjectIdentifier(opts.name) { errs = append(errs, ErrInvalidObjectIdentifier) } + if opts.NewName != nil && !ValidObjectIdentifier(opts.NewName) { + errs = append(errs, errInvalidIdentifier("AlterSchemaOptions", "NewName")) + } + if opts.SwapWith != nil && !ValidObjectIdentifier(opts.SwapWith) { + errs = append(errs, errInvalidIdentifier("AlterSchemaOptions", "SwapWith")) + } if !exactlyOneValueSet(opts.NewName, opts.SwapWith, opts.Set, opts.Unset, opts.SetTag, opts.UnsetTag, opts.EnableManagedAccess, opts.DisableManagedAccess) { - errs = append(errs, errOneOf("NewName", "SwapWith", "Set", "Unset", "SetTag", "UnsetTag", "EnableManagedAccess", "DisableManagedAccess")) + errs = append(errs, errExactlyOneOf("AlterSchemaOptions", "NewName", "SwapWith", "Set", "Unset", "SetTag", "UnsetTag", "EnableManagedAccess", "DisableManagedAccess")) } if valueSet(opts.Set) { if err := opts.Set.validate(); err != nil { @@ -184,29 +230,146 @@ func (opts *AlterSchemaOptions) validate() error { } type SchemaSet struct { - DataRetentionTimeInDays *int `ddl:"parameter" sql:"DATA_RETENTION_TIME_IN_DAYS"` - MaxDataExtensionTimeInDays *int `ddl:"parameter" sql:"MAX_DATA_EXTENSION_TIME_IN_DAYS"` - DefaultDDLCollation *string `ddl:"parameter,single_quotes" sql:"DEFAULT_DDL_COLLATION"` - Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + // Parameters + DataRetentionTimeInDays *int `ddl:"parameter" sql:"DATA_RETENTION_TIME_IN_DAYS"` + MaxDataExtensionTimeInDays *int `ddl:"parameter" sql:"MAX_DATA_EXTENSION_TIME_IN_DAYS"` + ExternalVolume *AccountObjectIdentifier `ddl:"identifier,equals" sql:"EXTERNAL_VOLUME"` + Catalog *AccountObjectIdentifier `ddl:"identifier,equals" sql:"CATALOG"` + PipeExecutionPaused *bool `ddl:"parameter" sql:"PIPE_EXECUTION_PAUSED"` + ReplaceInvalidCharacters *bool `ddl:"parameter" sql:"REPLACE_INVALID_CHARACTERS"` + DefaultDDLCollation *string `ddl:"parameter,single_quotes" sql:"DEFAULT_DDL_COLLATION"` + StorageSerializationPolicy *StorageSerializationPolicy `ddl:"parameter" sql:"STORAGE_SERIALIZATION_POLICY"` + LogLevel *LogLevel `ddl:"parameter,single_quotes" sql:"LOG_LEVEL"` + TraceLevel *TraceLevel `ddl:"parameter,single_quotes" sql:"TRACE_LEVEL"` + SuspendTaskAfterNumFailures *int `ddl:"parameter" sql:"SUSPEND_TASK_AFTER_NUM_FAILURES"` + TaskAutoRetryAttempts *int `ddl:"parameter" sql:"TASK_AUTO_RETRY_ATTEMPTS"` + UserTaskManagedInitialWarehouseSize *WarehouseSize `ddl:"parameter" sql:"USER_TASK_MANAGED_INITIAL_WAREHOUSE_SIZE"` + UserTaskTimeoutMs *int `ddl:"parameter" sql:"USER_TASK_TIMEOUT_MS"` + UserTaskMinimumTriggerIntervalInSeconds *int `ddl:"parameter" sql:"USER_TASK_MINIMUM_TRIGGER_INTERVAL_IN_SECONDS"` + QuotedIdentifiersIgnoreCase *bool `ddl:"parameter" sql:"QUOTED_IDENTIFIERS_IGNORE_CASE"` + EnableConsoleOutput *bool `ddl:"parameter" sql:"ENABLE_CONSOLE_OUTPUT"` + + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } func (v *SchemaSet) validate() error { - if !anyValueSet(v.DataRetentionTimeInDays, v.MaxDataExtensionTimeInDays, v.DefaultDDLCollation, v.Comment) { - return errAtLeastOneOf("SchemaSet", "DataRetentionTimeInDays", "MaxDataExtensionTimeInDays", "DefaultDDLCollation", "Comment") + var errs []error + if v.ExternalVolume != nil && !ValidObjectIdentifier(v.ExternalVolume) { + errs = append(errs, errInvalidIdentifier("SchemaSet", "ExternalVolume")) + } + if v.Catalog != nil && !ValidObjectIdentifier(v.Catalog) { + errs = append(errs, errInvalidIdentifier("SchemaSet", "Catalog")) + } + if !anyValueSet( + v.DataRetentionTimeInDays, + v.MaxDataExtensionTimeInDays, + v.ExternalVolume, + v.Catalog, + v.ReplaceInvalidCharacters, + v.DefaultDDLCollation, + v.StorageSerializationPolicy, + v.LogLevel, + v.TraceLevel, + v.SuspendTaskAfterNumFailures, + v.TaskAutoRetryAttempts, + v.UserTaskManagedInitialWarehouseSize, + v.UserTaskTimeoutMs, + v.UserTaskMinimumTriggerIntervalInSeconds, + v.QuotedIdentifiersIgnoreCase, + v.EnableConsoleOutput, + v.PipeExecutionPaused, + v.Comment, + ) { + errs = append(errs, errAtLeastOneOf( + "SchemaSet", + "DataRetentionTimeInDays", + "MaxDataExtensionTimeInDays", + "ExternalVolume", + "Catalog", + "ReplaceInvalidCharacters", + "DefaultDDLCollation", + "StorageSerializationPolicy", + "LogLevel", + "TraceLevel", + "SuspendTaskAfterNumFailures", + "TaskAutoRetryAttempts", + "UserTaskManagedInitialWarehouseSize", + "UserTaskTimeoutMs", + "UserTaskMinimumTriggerIntervalInSeconds", + "QuotedIdentifiersIgnoreCase", + "EnableConsoleOutput", + "PipeExecutionPaused", + "Comment", + )) } - return nil + return errors.Join(errs...) } type SchemaUnset struct { - DataRetentionTimeInDays *bool `ddl:"keyword" sql:"DATA_RETENTION_TIME_IN_DAYS"` - MaxDataExtensionTimeInDays *bool `ddl:"keyword" sql:"MAX_DATA_EXTENSION_TIME_IN_DAYS"` - DefaultDDLCollation *bool `ddl:"keyword" sql:"DEFAULT_DDL_COLLATION"` - Comment *bool `ddl:"keyword" sql:"COMMENT"` + // Parameters + DataRetentionTimeInDays *bool `ddl:"keyword" sql:"DATA_RETENTION_TIME_IN_DAYS"` + MaxDataExtensionTimeInDays *bool `ddl:"keyword" sql:"MAX_DATA_EXTENSION_TIME_IN_DAYS"` + ExternalVolume *bool `ddl:"keyword" sql:"EXTERNAL_VOLUME"` + Catalog *bool `ddl:"keyword" sql:"CATALOG"` + PipeExecutionPaused *bool `ddl:"keyword" sql:"PIPE_EXECUTION_PAUSED"` + ReplaceInvalidCharacters *bool `ddl:"keyword" sql:"REPLACE_INVALID_CHARACTERS"` + DefaultDDLCollation *bool `ddl:"keyword" sql:"DEFAULT_DDL_COLLATION"` + StorageSerializationPolicy *bool `ddl:"keyword" sql:"STORAGE_SERIALIZATION_POLICY"` + LogLevel *bool `ddl:"keyword" sql:"LOG_LEVEL"` + TraceLevel *bool `ddl:"keyword" sql:"TRACE_LEVEL"` + SuspendTaskAfterNumFailures *bool `ddl:"keyword" sql:"SUSPEND_TASK_AFTER_NUM_FAILURES"` + TaskAutoRetryAttempts *bool `ddl:"keyword" sql:"TASK_AUTO_RETRY_ATTEMPTS"` + UserTaskManagedInitialWarehouseSize *bool `ddl:"keyword" sql:"USER_TASK_MANAGED_INITIAL_WAREHOUSE_SIZE"` + UserTaskTimeoutMs *bool `ddl:"keyword" sql:"USER_TASK_TIMEOUT_MS"` + UserTaskMinimumTriggerIntervalInSeconds *bool `ddl:"keyword" sql:"USER_TASK_MINIMUM_TRIGGER_INTERVAL_IN_SECONDS"` + QuotedIdentifiersIgnoreCase *bool `ddl:"keyword" sql:"QUOTED_IDENTIFIERS_IGNORE_CASE"` + EnableConsoleOutput *bool `ddl:"keyword" sql:"ENABLE_CONSOLE_OUTPUT"` + + Comment *bool `ddl:"keyword" sql:"COMMENT"` } func (v *SchemaUnset) validate() error { - if !anyValueSet(v.DataRetentionTimeInDays, v.MaxDataExtensionTimeInDays, v.DefaultDDLCollation, v.Comment) { - return errAtLeastOneOf("SchemaUnset", "DataRetentionTimeInDays", "MaxDataExtensionTimeInDays", "DefaultDDLCollation", "Comment") + if !anyValueSet( + v.DataRetentionTimeInDays, + v.MaxDataExtensionTimeInDays, + v.ExternalVolume, + v.Catalog, + v.ReplaceInvalidCharacters, + v.DefaultDDLCollation, + v.StorageSerializationPolicy, + v.LogLevel, + v.TraceLevel, + v.SuspendTaskAfterNumFailures, + v.TaskAutoRetryAttempts, + v.UserTaskManagedInitialWarehouseSize, + v.UserTaskTimeoutMs, + v.UserTaskMinimumTriggerIntervalInSeconds, + v.QuotedIdentifiersIgnoreCase, + v.EnableConsoleOutput, + v.PipeExecutionPaused, + v.Comment, + ) { + return errAtLeastOneOf( + "SchemaUnset", + "DataRetentionTimeInDays", + "MaxDataExtensionTimeInDays", + "ExternalVolume", + "Catalog", + "ReplaceInvalidCharacters", + "DefaultDDLCollation", + "StorageSerializationPolicy", + "LogLevel", + "TraceLevel", + "SuspendTaskAfterNumFailures", + "TaskAutoRetryAttempts", + "UserTaskManagedInitialWarehouseSize", + "UserTaskTimeoutMs", + "UserTaskMinimumTriggerIntervalInSeconds", + "QuotedIdentifiersIgnoreCase", + "EnableConsoleOutput", + "PipeExecutionPaused", + "Comment", + ) } return nil } @@ -343,9 +506,11 @@ func (v *schemas) Describe(ctx context.Context, id DatabaseObjectIdentifier) ([] } type SchemaIn struct { - Account *bool `ddl:"keyword" sql:"ACCOUNT"` - Database *bool `ddl:"keyword" sql:"DATABASE"` - Name AccountObjectIdentifier `ddl:"identifier"` + Account *bool `ddl:"keyword" sql:"ACCOUNT"` + Database *bool `ddl:"keyword" sql:"DATABASE"` + Application *bool `ddl:"keyword" sql:"APPLICATION"` + ApplicationPackage *bool `ddl:"keyword" sql:"APPLICATION_PACKAGE"` + Name AccountObjectIdentifier `ddl:"identifier"` } // ShowSchemaOptions based on https://docs.snowflake.com/en/sql-reference/sql/show-schemas diff --git a/pkg/sdk/schemas_test.go b/pkg/sdk/schemas_test.go index da4d8ebcf1..d1001fa357 100644 --- a/pkg/sdk/schemas_test.go +++ b/pkg/sdk/schemas_test.go @@ -8,6 +8,39 @@ import ( func TestSchemasCreate(t *testing.T) { id := randomDatabaseObjectIdentifier() + t.Run("validation: invalid name", func(t *testing.T) { + opts := &CreateSchemaOptions{ + name: emptyDatabaseObjectIdentifier, + } + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("validation: nil options", func(t *testing.T) { + var opts *CreateSchemaOptions + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("validation: conflicting fields for [opts.OrReplace opts.IfNotExists]", func(t *testing.T) { + opts := &CreateSchemaOptions{ + name: id, + OrReplace: Bool(true), + IfNotExists: Bool(true), + } + assertOptsInvalidJoinedErrors(t, opts, errOneOf("CreateSchemaOptions", "IfNotExists", "OrReplace")) + }) + + t.Run("validation: invalid external volume and catalog", func(t *testing.T) { + opts := &CreateSchemaOptions{ + name: id, + ExternalVolume: Pointer(emptyAccountObjectIdentifier), + Catalog: Pointer(emptyAccountObjectIdentifier), + } + assertOptsInvalidJoinedErrors(t, opts, + errInvalidIdentifier("CreateSchemaOptions", "ExternalVolume"), + errInvalidIdentifier("CreateSchemaOptions", "Catalog"), + ) + }) + t.Run("clone", func(t *testing.T) { opts := &CreateSchemaOptions{ name: id, @@ -24,23 +57,39 @@ func TestSchemasCreate(t *testing.T) { t.Run("complete", func(t *testing.T) { tagId := randomSchemaObjectIdentifier() + externalVolumeId := randomAccountObjectIdentifier() + catalogId := randomAccountObjectIdentifier() opts := &CreateSchemaOptions{ - name: id, - Transient: Bool(true), - IfNotExists: Bool(true), - WithManagedAccess: Bool(true), - DataRetentionTimeInDays: Int(1), - MaxDataExtensionTimeInDays: Int(1), - DefaultDDLCollation: String("en_US-trim"), - Tag: []TagAssociation{ - { - Name: tagId, - Value: "v1", - }, - }, - Comment: String("comment"), + Transient: Bool(true), + IfNotExists: Bool(true), + name: id, + WithManagedAccess: Bool(true), + DataRetentionTimeInDays: Int(1), + MaxDataExtensionTimeInDays: Int(1), + ExternalVolume: &externalVolumeId, + Catalog: &catalogId, + PipeExecutionPaused: Bool(true), + ReplaceInvalidCharacters: Bool(true), + DefaultDDLCollation: String("en_US-trim"), + StorageSerializationPolicy: Pointer(StorageSerializationPolicyCompatible), + LogLevel: Pointer(LogLevelInfo), + TraceLevel: Pointer(TraceLevelOnEvent), + SuspendTaskAfterNumFailures: Int(10), + TaskAutoRetryAttempts: Int(10), + UserTaskManagedInitialWarehouseSize: Pointer(WarehouseSizeMedium), + UserTaskTimeoutMs: Int(12000), + UserTaskMinimumTriggerIntervalInSeconds: Int(30), + QuotedIdentifiersIgnoreCase: Bool(true), + EnableConsoleOutput: Bool(true), + Comment: String("comment"), + Tag: []TagAssociation{{Name: tagId, Value: "v1"}}, } - assertOptsValidAndSQLEquals(t, opts, `CREATE TRANSIENT SCHEMA IF NOT EXISTS %s WITH MANAGED ACCESS DATA_RETENTION_TIME_IN_DAYS = 1 MAX_DATA_EXTENSION_TIME_IN_DAYS = 1 DEFAULT_DDL_COLLATION = 'en_US-trim' TAG (%s = 'v1') COMMENT = 'comment'`, id.FullyQualifiedName(), tagId.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, opts, `CREATE TRANSIENT SCHEMA IF NOT EXISTS %s WITH MANAGED ACCESS DATA_RETENTION_TIME_IN_DAYS = 1 MAX_DATA_EXTENSION_TIME_IN_DAYS = 1 `+ + `EXTERNAL_VOLUME = "%s" CATALOG = "%s" PIPE_EXECUTION_PAUSED = true REPLACE_INVALID_CHARACTERS = true DEFAULT_DDL_COLLATION = 'en_US-trim' STORAGE_SERIALIZATION_POLICY = COMPATIBLE `+ + `LOG_LEVEL = 'INFO' TRACE_LEVEL = 'ON_EVENT' SUSPEND_TASK_AFTER_NUM_FAILURES = 10 TASK_AUTO_RETRY_ATTEMPTS = 10 USER_TASK_MANAGED_INITIAL_WAREHOUSE_SIZE = MEDIUM `+ + `USER_TASK_TIMEOUT_MS = 12000 USER_TASK_MINIMUM_TRIGGER_INTERVAL_IN_SECONDS = 30 QUOTED_IDENTIFIERS_IGNORE_CASE = true ENABLE_CONSOLE_OUTPUT = true `+ + `COMMENT = 'comment' TAG (%s = 'v1')`, + id.FullyQualifiedName(), externalVolumeId.Name(), catalogId.Name(), tagId.FullyQualifiedName()) }) } @@ -48,11 +97,136 @@ func TestSchemasAlter(t *testing.T) { schemaId := randomDatabaseObjectIdentifier() newSchemaId := randomDatabaseObjectIdentifierInDatabase(schemaId.DatabaseId()) + t.Run("validation: invalid name", func(t *testing.T) { + opts := &AlterSchemaOptions{ + name: emptyDatabaseObjectIdentifier, + } + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("validation: invalid external volume and catalog", func(t *testing.T) { + opts := &AlterSchemaOptions{ + name: emptyDatabaseObjectIdentifier, + Set: &SchemaSet{ + ExternalVolume: Pointer(emptyAccountObjectIdentifier), + Catalog: Pointer(emptyAccountObjectIdentifier), + }, + } + assertOptsInvalidJoinedErrors(t, opts, errInvalidIdentifier("SchemaSet", "ExternalVolume"), errInvalidIdentifier("SchemaSet", "Catalog")) + }) + + t.Run("validation: at least one of actions", func(t *testing.T) { + opts := &AlterSchemaOptions{ + name: schemaId, + } + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("AlterSchemaOptions", "NewName", "SwapWith", "Set", "Unset", "SetTag", "UnsetTag", "EnableManagedAccess", "DisableManagedAccess")) + }) + + t.Run("validation: exactly one of actions", func(t *testing.T) { + opts := &AlterSchemaOptions{ + name: schemaId, + Set: &SchemaSet{}, + Unset: &SchemaUnset{}, + } + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("AlterSchemaOptions", "NewName", "SwapWith", "Set", "Unset", "SetTag", "UnsetTag", "EnableManagedAccess", "DisableManagedAccess")) + }) + + t.Run("validation: at least one set option", func(t *testing.T) { + opts := &AlterSchemaOptions{ + name: schemaId, + Set: &SchemaSet{}, + } + assertOptsInvalidJoinedErrors(t, opts, errAtLeastOneOf( + "SchemaSet", + "DataRetentionTimeInDays", + "MaxDataExtensionTimeInDays", + "ExternalVolume", + "Catalog", + "ReplaceInvalidCharacters", + "DefaultDDLCollation", + "StorageSerializationPolicy", + "LogLevel", + "TraceLevel", + "SuspendTaskAfterNumFailures", + "TaskAutoRetryAttempts", + "UserTaskManagedInitialWarehouseSize", + "UserTaskTimeoutMs", + "UserTaskMinimumTriggerIntervalInSeconds", + "QuotedIdentifiersIgnoreCase", + "EnableConsoleOutput", + "PipeExecutionPaused", + "Comment", + )) + }) + + t.Run("validation: at least one unset option", func(t *testing.T) { + opts := &AlterSchemaOptions{ + name: schemaId, + Unset: &SchemaUnset{}, + } + assertOptsInvalidJoinedErrors(t, opts, errAtLeastOneOf( + "SchemaUnset", + "DataRetentionTimeInDays", + "MaxDataExtensionTimeInDays", + "ExternalVolume", + "Catalog", + "ReplaceInvalidCharacters", + "DefaultDDLCollation", + "StorageSerializationPolicy", + "LogLevel", + "TraceLevel", + "SuspendTaskAfterNumFailures", + "TaskAutoRetryAttempts", + "UserTaskManagedInitialWarehouseSize", + "UserTaskTimeoutMs", + "UserTaskMinimumTriggerIntervalInSeconds", + "QuotedIdentifiersIgnoreCase", + "EnableConsoleOutput", + "PipeExecutionPaused", + "Comment", + )) + }) + + t.Run("validation: invalid external volume identifier", func(t *testing.T) { + opts := &AlterSchemaOptions{ + name: schemaId, + Set: &SchemaSet{ + ExternalVolume: Pointer(emptyAccountObjectIdentifier), + }, + } + assertOptsInvalidJoinedErrors(t, opts, errInvalidIdentifier("SchemaSet", "ExternalVolume")) + }) + + t.Run("validation: invalid catalog integration identifier", func(t *testing.T) { + opts := &AlterSchemaOptions{ + name: schemaId, + Set: &SchemaSet{ + Catalog: Pointer(emptyAccountObjectIdentifier), + }, + } + assertOptsInvalidJoinedErrors(t, opts, errInvalidIdentifier("SchemaSet", "Catalog")) + }) + + t.Run("validation: invalid NewName identifier", func(t *testing.T) { + opts := &AlterSchemaOptions{ + name: schemaId, + NewName: Pointer(emptyDatabaseObjectIdentifier), + } + assertOptsInvalidJoinedErrors(t, opts, errInvalidIdentifier("AlterSchemaOptions", "NewName")) + }) + + t.Run("validation: invalid SwapWith identifier", func(t *testing.T) { + opts := &AlterSchemaOptions{ + name: schemaId, + SwapWith: Pointer(emptyDatabaseObjectIdentifier), + } + assertOptsInvalidJoinedErrors(t, opts, errInvalidIdentifier("AlterSchemaOptions", "SwapWith")) + }) t.Run("rename to", func(t *testing.T) { opts := &AlterSchemaOptions{ name: schemaId, IfExists: Bool(true), - NewName: newSchemaId, + NewName: Pointer(newSchemaId), } assertOptsValidAndSQLEquals(t, opts, `ALTER SCHEMA IF EXISTS %s RENAME TO %s`, schemaId.FullyQualifiedName(), newSchemaId.FullyQualifiedName()) }) @@ -61,22 +235,73 @@ func TestSchemasAlter(t *testing.T) { opts := &AlterSchemaOptions{ name: schemaId, IfExists: Bool(false), - SwapWith: newSchemaId, + SwapWith: Pointer(newSchemaId), } assertOptsValidAndSQLEquals(t, opts, `ALTER SCHEMA %s SWAP WITH %s`, schemaId.FullyQualifiedName(), newSchemaId.FullyQualifiedName()) }) t.Run("set options", func(t *testing.T) { + externalVolumeId := randomAccountObjectIdentifier() + catalogId := randomAccountObjectIdentifier() opts := &AlterSchemaOptions{ name: schemaId, Set: &SchemaSet{ - DataRetentionTimeInDays: Int(3), - MaxDataExtensionTimeInDays: Int(2), - DefaultDDLCollation: String("en_US-trim"), - Comment: String("comment"), + DataRetentionTimeInDays: Int(1), + MaxDataExtensionTimeInDays: Int(1), + ExternalVolume: &externalVolumeId, + Catalog: &catalogId, + PipeExecutionPaused: Bool(true), + ReplaceInvalidCharacters: Bool(true), + DefaultDDLCollation: String("en_US-trim"), + StorageSerializationPolicy: Pointer(StorageSerializationPolicyCompatible), + LogLevel: Pointer(LogLevelInfo), + TraceLevel: Pointer(TraceLevelOnEvent), + SuspendTaskAfterNumFailures: Int(10), + TaskAutoRetryAttempts: Int(10), + UserTaskManagedInitialWarehouseSize: Pointer(WarehouseSizeMedium), + UserTaskTimeoutMs: Int(12000), + UserTaskMinimumTriggerIntervalInSeconds: Int(30), + QuotedIdentifiersIgnoreCase: Bool(true), + EnableConsoleOutput: Bool(true), + Comment: String("comment"), }, } - assertOptsValidAndSQLEquals(t, opts, `ALTER SCHEMA %s SET DATA_RETENTION_TIME_IN_DAYS = 3, MAX_DATA_EXTENSION_TIME_IN_DAYS = 2, DEFAULT_DDL_COLLATION = 'en_US-trim', COMMENT = 'comment'`, schemaId.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, opts, `ALTER SCHEMA %s SET DATA_RETENTION_TIME_IN_DAYS = 1, MAX_DATA_EXTENSION_TIME_IN_DAYS = 1, `+ + `EXTERNAL_VOLUME = "%s", CATALOG = "%s", PIPE_EXECUTION_PAUSED = true, REPLACE_INVALID_CHARACTERS = true, DEFAULT_DDL_COLLATION = 'en_US-trim', STORAGE_SERIALIZATION_POLICY = COMPATIBLE, `+ + `LOG_LEVEL = 'INFO', TRACE_LEVEL = 'ON_EVENT', SUSPEND_TASK_AFTER_NUM_FAILURES = 10, TASK_AUTO_RETRY_ATTEMPTS = 10, USER_TASK_MANAGED_INITIAL_WAREHOUSE_SIZE = MEDIUM, `+ + `USER_TASK_TIMEOUT_MS = 12000, USER_TASK_MINIMUM_TRIGGER_INTERVAL_IN_SECONDS = 30, QUOTED_IDENTIFIERS_IGNORE_CASE = true, ENABLE_CONSOLE_OUTPUT = true, `+ + `COMMENT = 'comment'`, + schemaId.FullyQualifiedName(), externalVolumeId.Name(), catalogId.Name(), + ) + }) + + t.Run("unset", func(t *testing.T) { + opts := &AlterSchemaOptions{ + name: schemaId, + Unset: &SchemaUnset{ + DataRetentionTimeInDays: Bool(true), + MaxDataExtensionTimeInDays: Bool(true), + ExternalVolume: Bool(true), + Catalog: Bool(true), + PipeExecutionPaused: Bool(true), + ReplaceInvalidCharacters: Bool(true), + DefaultDDLCollation: Bool(true), + StorageSerializationPolicy: Bool(true), + LogLevel: Bool(true), + TraceLevel: Bool(true), + SuspendTaskAfterNumFailures: Bool(true), + TaskAutoRetryAttempts: Bool(true), + UserTaskManagedInitialWarehouseSize: Bool(true), + UserTaskTimeoutMs: Bool(true), + UserTaskMinimumTriggerIntervalInSeconds: Bool(true), + QuotedIdentifiersIgnoreCase: Bool(true), + EnableConsoleOutput: Bool(true), + Comment: Bool(true), + }, + } + assertOptsValidAndSQLEquals(t, opts, `ALTER SCHEMA %s UNSET DATA_RETENTION_TIME_IN_DAYS, MAX_DATA_EXTENSION_TIME_IN_DAYS, EXTERNAL_VOLUME, CATALOG, PIPE_EXECUTION_PAUSED, `+ + `REPLACE_INVALID_CHARACTERS, DEFAULT_DDL_COLLATION, STORAGE_SERIALIZATION_POLICY, LOG_LEVEL, TRACE_LEVEL, SUSPEND_TASK_AFTER_NUM_FAILURES, TASK_AUTO_RETRY_ATTEMPTS, `+ + `USER_TASK_MANAGED_INITIAL_WAREHOUSE_SIZE, USER_TASK_TIMEOUT_MS, USER_TASK_MINIMUM_TRIGGER_INTERVAL_IN_SECONDS, QUOTED_IDENTIFIERS_IGNORE_CASE, ENABLE_CONSOLE_OUTPUT, COMMENT`, opts.name.FullyQualifiedName()) }) t.Run("set tags", func(t *testing.T) { @@ -107,19 +332,6 @@ func TestSchemasAlter(t *testing.T) { assertOptsValidAndSQLEquals(t, opts, `ALTER SCHEMA %s UNSET TAG "tag1", "tag2"`, schemaId.FullyQualifiedName()) }) - t.Run("unset options", func(t *testing.T) { - opts := &AlterSchemaOptions{ - name: schemaId, - Unset: &SchemaUnset{ - DataRetentionTimeInDays: Bool(true), - MaxDataExtensionTimeInDays: Bool(true), - DefaultDDLCollation: Bool(true), - Comment: Bool(true), - }, - } - assertOptsValidAndSQLEquals(t, opts, `ALTER SCHEMA %s UNSET DATA_RETENTION_TIME_IN_DAYS, MAX_DATA_EXTENSION_TIME_IN_DAYS, DEFAULT_DDL_COLLATION, COMMENT`, schemaId.FullyQualifiedName()) - }) - t.Run("enable managed access", func(t *testing.T) { opts := &AlterSchemaOptions{ name: schemaId, @@ -140,40 +352,83 @@ func TestSchemasAlter(t *testing.T) { func TestSchemasDrop(t *testing.T) { schemaId := randomDatabaseObjectIdentifier() - t.Run("cascade", func(t *testing.T) { + t.Run("validation: invalid name", func(t *testing.T) { + opts := &DropSchemaOptions{ + name: emptyDatabaseObjectIdentifier, + } + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("minimal", func(t *testing.T) { + opts := &DropSchemaOptions{ + name: schemaId, + } + assertOptsValidAndSQLEquals(t, opts, `DROP SCHEMA %s`, opts.name.FullyQualifiedName()) + }) + + t.Run("all options - cascade", func(t *testing.T) { opts := &DropSchemaOptions{ - IfExists: Bool(true), name: schemaId, + IfExists: Bool(true), Cascade: Bool(true), } - assertOptsValidAndSQLEquals(t, opts, `DROP SCHEMA IF EXISTS %s CASCADE`, schemaId.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, opts, `DROP SCHEMA IF EXISTS %s CASCADE`, opts.name.FullyQualifiedName()) + }) + + t.Run("all options - restrict", func(t *testing.T) { + opts := &DropSchemaOptions{ + name: schemaId, + IfExists: Bool(true), + Restrict: Bool(true), + } + assertOptsValidAndSQLEquals(t, opts, `DROP SCHEMA IF EXISTS %s RESTRICT`, opts.name.FullyQualifiedName()) }) - t.Run("restrict", func(t *testing.T) { + t.Run("validation: cascade and restrict set together", func(t *testing.T) { opts := &DropSchemaOptions{ name: schemaId, + IfExists: Bool(true), + Cascade: Bool(true), Restrict: Bool(true), } - assertOptsValidAndSQLEquals(t, opts, `DROP SCHEMA %s RESTRICT`, schemaId.FullyQualifiedName()) + assertOptsInvalidJoinedErrors(t, opts, errOneOf("DropSchemaOptions", "Cascade", "Restrict")) }) } func TestSchemasUndrop(t *testing.T) { schemaId := randomDatabaseObjectIdentifier() - opts := &undropSchemaOptions{ - name: schemaId, - } - assertOptsValidAndSQLEquals(t, opts, `UNDROP SCHEMA %s`, schemaId.FullyQualifiedName()) + t.Run("validation: invalid name", func(t *testing.T) { + opts := &undropSchemaOptions{ + name: emptyDatabaseObjectIdentifier, + } + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("minimal", func(t *testing.T) { + opts := &undropSchemaOptions{ + name: schemaId, + } + assertOptsValidAndSQLEquals(t, opts, `UNDROP SCHEMA %s`, opts.name.FullyQualifiedName()) + }) } func TestSchemasDescribe(t *testing.T) { schemaId := randomDatabaseObjectIdentifier() - opts := &describeSchemaOptions{ - name: schemaId, - } - assertOptsValidAndSQLEquals(t, opts, `DESCRIBE SCHEMA %s`, schemaId.FullyQualifiedName()) + t.Run("validation: invalid name", func(t *testing.T) { + opts := &describeSchemaOptions{ + name: emptyDatabaseObjectIdentifier, + } + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("complete", func(t *testing.T) { + opts := &describeSchemaOptions{ + name: schemaId, + } + assertOptsValidAndSQLEquals(t, opts, `DESCRIBE SCHEMA %s`, opts.name.FullyQualifiedName()) + }) } func TestSchemasShow(t *testing.T) { diff --git a/pkg/sdk/testint/schemas_integration_test.go b/pkg/sdk/testint/schemas_integration_test.go index ef789bf30e..4aa48f5a23 100644 --- a/pkg/sdk/testint/schemas_integration_test.go +++ b/pkg/sdk/testint/schemas_integration_test.go @@ -3,8 +3,10 @@ package testint import ( "testing" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers/random" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -13,11 +15,22 @@ func TestInt_SchemasCreate(t *testing.T) { client := testClient(t) ctx := testContext(t) - // new schema created on purpose - schema, cleanupSchema := testClientHelper().Schema.CreateSchema(t) - t.Cleanup(cleanupSchema) + t.Run("minimal", func(t *testing.T) { + schemaId := testClientHelper().Ids.RandomDatabaseObjectIdentifier() + err := client.Schemas.Create(ctx, schemaId, &sdk.CreateSchemaOptions{ + OrReplace: sdk.Bool(true), + }) + require.NoError(t, err) + t.Cleanup(testClientHelper().Schema.DropSchemaFunc(t, schemaId)) + + database, err := client.Schemas.ShowByID(ctx, schemaId) + require.NoError(t, err) + assert.Equal(t, schemaId.Name(), database.Name) + }) t.Run("replace", func(t *testing.T) { + schema, cleanupSchema := testClientHelper().Schema.CreateSchema(t) + t.Cleanup(cleanupSchema) comment := "replaced" err := client.Schemas.Create(ctx, schema.ID(), &sdk.CreateSchemaOptions{ OrReplace: sdk.Bool(true), @@ -32,10 +45,12 @@ func TestInt_SchemasCreate(t *testing.T) { require.NoError(t, err) assert.Equal(t, schema.Name, s.Name) assert.Equal(t, "MANAGED ACCESS", *s.Options) - assert.Equal(t, comment, *s.Comment) + assert.Equal(t, comment, s.Comment) }) t.Run("if not exists", func(t *testing.T) { + schema, cleanupSchema := testClientHelper().Schema.CreateSchema(t) + t.Cleanup(cleanupSchema) comment := "some_comment" err := client.Schemas.Create(ctx, schema.ID(), &sdk.CreateSchemaOptions{ IfNotExists: sdk.Bool(true), @@ -44,7 +59,7 @@ func TestInt_SchemasCreate(t *testing.T) { require.NoError(t, err) s, err := client.Schemas.ShowByID(ctx, schema.ID()) require.NoError(t, err) - assert.NotEqual(t, comment, *s.Comment) + assert.NotEqual(t, comment, s.Comment) }) t.Run("clone", func(t *testing.T) { @@ -69,7 +84,7 @@ func TestInt_SchemasCreate(t *testing.T) { cs, err := client.Schemas.ShowByID(ctx, clonedSchemaID) require.NoError(t, err) - assert.Equal(t, *s.Comment, *cs.Comment) + assert.Equal(t, s.Comment, cs.Comment) t.Cleanup(func() { err = client.Schemas.Drop(ctx, schemaID, nil) @@ -103,12 +118,122 @@ func TestInt_SchemasCreate(t *testing.T) { require.NoError(t, err) assert.Equal(t, tagValue, tv) }) + + t.Run("complete", func(t *testing.T) { + schemaId := testClientHelper().Ids.RandomDatabaseObjectIdentifier() + + databaseTest, databaseCleanup := testClientHelper().Database.CreateDatabase(t) + t.Cleanup(databaseCleanup) + + schemaTest, schemaCleanup := testClientHelper().Schema.CreateSchemaInDatabase(t, databaseTest.ID()) + t.Cleanup(schemaCleanup) + + tagTest, tagCleanup := testClientHelper().Tag.CreateTagInSchema(t, schemaTest.ID()) + t.Cleanup(tagCleanup) + + tag2Test, tag2Cleanup := testClientHelper().Tag.CreateTagInSchema(t, schemaTest.ID()) + t.Cleanup(tag2Cleanup) + + externalVolume, externalVolumeCleanup := testClientHelper().ExternalVolume.Create(t) + t.Cleanup(externalVolumeCleanup) + + catalog, catalogCleanup := testClientHelper().CatalogIntegration.Create(t) + t.Cleanup(catalogCleanup) + + comment := random.Comment() + err := client.Schemas.Create(ctx, schemaId, &sdk.CreateSchemaOptions{ + Transient: sdk.Bool(true), + IfNotExists: sdk.Bool(true), + DataRetentionTimeInDays: sdk.Int(0), + MaxDataExtensionTimeInDays: sdk.Int(10), + ExternalVolume: &externalVolume, + Catalog: &catalog, + ReplaceInvalidCharacters: sdk.Bool(true), + DefaultDDLCollation: sdk.String("en_US"), + StorageSerializationPolicy: sdk.Pointer(sdk.StorageSerializationPolicyCompatible), + LogLevel: sdk.Pointer(sdk.LogLevelInfo), + TraceLevel: sdk.Pointer(sdk.TraceLevelOnEvent), + SuspendTaskAfterNumFailures: sdk.Int(10), + TaskAutoRetryAttempts: sdk.Int(10), + UserTaskManagedInitialWarehouseSize: sdk.Pointer(sdk.WarehouseSizeMedium), + UserTaskTimeoutMs: sdk.Int(12_000), + UserTaskMinimumTriggerIntervalInSeconds: sdk.Int(30), + QuotedIdentifiersIgnoreCase: sdk.Bool(true), + EnableConsoleOutput: sdk.Bool(true), + PipeExecutionPaused: sdk.Bool(true), + Comment: sdk.String(comment), + Tag: []sdk.TagAssociation{ + { + Name: tagTest.ID(), + Value: "v1", + }, + { + Name: tag2Test.ID(), + Value: "v2", + }, + }, + }) + require.NoError(t, err) + t.Cleanup(testClientHelper().Schema.DropSchemaFunc(t, schemaId)) + + schema, err := client.Schemas.ShowByID(ctx, schemaId) + require.NoError(t, err) + assert.Equal(t, schemaId.Name(), schema.Name) + assert.Equal(t, comment, schema.Comment) + + params := testClientHelper().Parameter.ShowSchemaParameters(t, schemaId) + assertParameterEquals := func(t *testing.T, parameterName sdk.AccountParameter, expected string) { + t.Helper() + assert.Equal(t, expected, helpers.FindParameter(t, params, parameterName).Value) + } + + assertParameterEquals(t, sdk.AccountParameterDataRetentionTimeInDays, "0") + assertParameterEquals(t, sdk.AccountParameterMaxDataExtensionTimeInDays, "10") + assertParameterEquals(t, sdk.AccountParameterDefaultDDLCollation, "en_US") + assertParameterEquals(t, sdk.AccountParameterExternalVolume, externalVolume.Name()) + assertParameterEquals(t, sdk.AccountParameterCatalog, catalog.Name()) + assertParameterEquals(t, sdk.AccountParameterLogLevel, string(sdk.LogLevelInfo)) + assertParameterEquals(t, sdk.AccountParameterTraceLevel, string(sdk.TraceLevelOnEvent)) + assertParameterEquals(t, sdk.AccountParameterReplaceInvalidCharacters, "true") + assertParameterEquals(t, sdk.AccountParameterStorageSerializationPolicy, string(sdk.StorageSerializationPolicyCompatible)) + assertParameterEquals(t, sdk.AccountParameterSuspendTaskAfterNumFailures, "10") + assertParameterEquals(t, sdk.AccountParameterTaskAutoRetryAttempts, "10") + assertParameterEquals(t, sdk.AccountParameterUserTaskManagedInitialWarehouseSize, string(sdk.WarehouseSizeMedium)) + assertParameterEquals(t, sdk.AccountParameterUserTaskTimeoutMs, "12000") + assertParameterEquals(t, sdk.AccountParameterUserTaskMinimumTriggerIntervalInSeconds, "30") + assertParameterEquals(t, sdk.AccountParameterQuotedIdentifiersIgnoreCase, "true") + assertParameterEquals(t, sdk.AccountParameterEnableConsoleOutput, "true") + assertParameterEquals(t, sdk.AccountParameterPipeExecutionPaused, "true") + + tag1Value, err := client.SystemFunctions.GetTag(ctx, tagTest.ID(), schema.ID(), sdk.ObjectTypeSchema) + require.NoError(t, err) + assert.Equal(t, "v1", tag1Value) + + tag2Value, err := client.SystemFunctions.GetTag(ctx, tag2Test.ID(), schema.ID(), sdk.ObjectTypeSchema) + require.NoError(t, err) + assert.Equal(t, "v2", tag2Value) + }) } func TestInt_SchemasAlter(t *testing.T) { client := testClient(t) ctx := testContext(t) + assertParameterEquals := func(t *testing.T, params []*sdk.Parameter, parameterName sdk.AccountParameter, expected string) { + t.Helper() + assert.Equal(t, expected, helpers.FindParameter(t, params, parameterName).Value) + } + + assertParameterEqualsToDefaultValue := func(t *testing.T, params []*sdk.Parameter, parameterName sdk.ObjectParameter) { + t.Helper() + param, err := collections.FindOne(params, func(param *sdk.Parameter) bool { return param.Key == string(parameterName) }) + assert.NoError(t, err) + assert.NotNil(t, param) + if param != nil && (*param).Level == "" { + param := *param + assert.Equal(t, param.Default, param.Value) + } + } t.Run("rename to", func(t *testing.T) { // new schema created on purpose schema, _ := testClientHelper().Schema.CreateSchema(t) @@ -118,7 +243,7 @@ func TestInt_SchemasAlter(t *testing.T) { }) newID := testClientHelper().Ids.RandomDatabaseObjectIdentifier() err := client.Schemas.Alter(ctx, schema.ID(), &sdk.AlterSchemaOptions{ - NewName: newID, + NewName: sdk.Pointer(newID), }) require.NoError(t, err) s, err := client.Schemas.ShowByID(ctx, newID) @@ -145,7 +270,7 @@ func TestInt_SchemasAlter(t *testing.T) { }) err := client.Schemas.Alter(ctx, schema.ID(), &sdk.AlterSchemaOptions{ - SwapWith: swapSchema.ID(), + SwapWith: sdk.Pointer(swapSchema.ID()), }) require.NoError(t, err) @@ -156,25 +281,100 @@ func TestInt_SchemasAlter(t *testing.T) { assert.Equal(t, table.Name, schemaDetails[0].Name) }) - t.Run("set", func(t *testing.T) { + t.Run("set and unset parameters", func(t *testing.T) { // new schema created on purpose - schema, cleanupSchema := testClientHelper().Schema.CreateSchema(t) - t.Cleanup(cleanupSchema) + schemaTest, cleanupSchemaTest := testClientHelper().Schema.CreateSchema(t) + t.Cleanup(cleanupSchemaTest) - comment := random.Comment() - err := client.Schemas.Alter(ctx, schema.ID(), &sdk.AlterSchemaOptions{ + externalVolumeTest, externalVolumeTestCleanup := testClientHelper().ExternalVolume.Create(t) + t.Cleanup(externalVolumeTestCleanup) + + catalogIntegrationTest, catalogIntegrationTestCleanup := testClientHelper().CatalogIntegration.Create(t) + t.Cleanup(catalogIntegrationTestCleanup) + + err := client.Schemas.Alter(ctx, schemaTest.ID(), &sdk.AlterSchemaOptions{ Set: &sdk.SchemaSet{ - DataRetentionTimeInDays: sdk.Int(3), - MaxDataExtensionTimeInDays: sdk.Int(3), - DefaultDDLCollation: sdk.String("en_US-trim"), - Comment: sdk.String(comment), + DataRetentionTimeInDays: sdk.Int(42), + MaxDataExtensionTimeInDays: sdk.Int(42), + ExternalVolume: &externalVolumeTest, + Catalog: &catalogIntegrationTest, + ReplaceInvalidCharacters: sdk.Bool(true), + DefaultDDLCollation: sdk.String("en_US"), + StorageSerializationPolicy: sdk.Pointer(sdk.StorageSerializationPolicyCompatible), + LogLevel: sdk.Pointer(sdk.LogLevelInfo), + TraceLevel: sdk.Pointer(sdk.TraceLevelOnEvent), + SuspendTaskAfterNumFailures: sdk.Int(10), + TaskAutoRetryAttempts: sdk.Int(10), + UserTaskManagedInitialWarehouseSize: sdk.Pointer(sdk.WarehouseSizeMedium), + UserTaskTimeoutMs: sdk.Int(12_000), + UserTaskMinimumTriggerIntervalInSeconds: sdk.Int(30), + QuotedIdentifiersIgnoreCase: sdk.Bool(true), + EnableConsoleOutput: sdk.Bool(true), + PipeExecutionPaused: sdk.Bool(true), }, }) require.NoError(t, err) - s, err := client.Schemas.ShowByID(ctx, schema.ID()) + params := testClientHelper().Parameter.ShowSchemaParameters(t, schemaTest.ID()) + assertParameterEquals(t, params, sdk.AccountParameterDataRetentionTimeInDays, "42") + assertParameterEquals(t, params, sdk.AccountParameterMaxDataExtensionTimeInDays, "42") + assertParameterEquals(t, params, sdk.AccountParameterExternalVolume, externalVolumeTest.Name()) + assertParameterEquals(t, params, sdk.AccountParameterCatalog, catalogIntegrationTest.Name()) + assertParameterEquals(t, params, sdk.AccountParameterReplaceInvalidCharacters, "true") + assertParameterEquals(t, params, sdk.AccountParameterDefaultDDLCollation, "en_US") + assertParameterEquals(t, params, sdk.AccountParameterStorageSerializationPolicy, string(sdk.StorageSerializationPolicyCompatible)) + assertParameterEquals(t, params, sdk.AccountParameterLogLevel, string(sdk.LogLevelInfo)) + assertParameterEquals(t, params, sdk.AccountParameterTraceLevel, string(sdk.TraceLevelOnEvent)) + assertParameterEquals(t, params, sdk.AccountParameterSuspendTaskAfterNumFailures, "10") + assertParameterEquals(t, params, sdk.AccountParameterTaskAutoRetryAttempts, "10") + assertParameterEquals(t, params, sdk.AccountParameterUserTaskManagedInitialWarehouseSize, string(sdk.WarehouseSizeMedium)) + assertParameterEquals(t, params, sdk.AccountParameterUserTaskTimeoutMs, "12000") + assertParameterEquals(t, params, sdk.AccountParameterUserTaskMinimumTriggerIntervalInSeconds, "30") + assertParameterEquals(t, params, sdk.AccountParameterQuotedIdentifiersIgnoreCase, "true") + assertParameterEquals(t, params, sdk.AccountParameterEnableConsoleOutput, "true") + assertParameterEquals(t, params, sdk.AccountParameterPipeExecutionPaused, "true") + + err = client.Schemas.Alter(ctx, schemaTest.ID(), &sdk.AlterSchemaOptions{ + Unset: &sdk.SchemaUnset{ + DataRetentionTimeInDays: sdk.Bool(true), + MaxDataExtensionTimeInDays: sdk.Bool(true), + ExternalVolume: sdk.Bool(true), + Catalog: sdk.Bool(true), + ReplaceInvalidCharacters: sdk.Bool(true), + DefaultDDLCollation: sdk.Bool(true), + StorageSerializationPolicy: sdk.Bool(true), + LogLevel: sdk.Bool(true), + TraceLevel: sdk.Bool(true), + SuspendTaskAfterNumFailures: sdk.Bool(true), + TaskAutoRetryAttempts: sdk.Bool(true), + UserTaskManagedInitialWarehouseSize: sdk.Bool(true), + UserTaskTimeoutMs: sdk.Bool(true), + UserTaskMinimumTriggerIntervalInSeconds: sdk.Bool(true), + QuotedIdentifiersIgnoreCase: sdk.Bool(true), + EnableConsoleOutput: sdk.Bool(true), + PipeExecutionPaused: sdk.Bool(true), + }, + }) require.NoError(t, err) - assert.Equal(t, comment, *s.Comment) + + params = testClientHelper().Parameter.ShowSchemaParameters(t, schemaTest.ID()) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterDataRetentionTimeInDays) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterMaxDataExtensionTimeInDays) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterExternalVolume) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterCatalog) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterReplaceInvalidCharacters) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterDefaultDDLCollation) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterStorageSerializationPolicy) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterLogLevel) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterTraceLevel) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterSuspendTaskAfterNumFailures) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterTaskAutoRetryAttempts) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterUserTaskManagedInitialWarehouseSize) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterUserTaskTimeoutMs) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterUserTaskMinimumTriggerIntervalInSeconds) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterQuotedIdentifiersIgnoreCase) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterEnableConsoleOutput) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterPipeExecutionPaused) }) t.Run("unset", func(t *testing.T) { @@ -194,7 +394,7 @@ func TestInt_SchemasAlter(t *testing.T) { s, err := client.Schemas.ShowByID(ctx, schemaID) require.NoError(t, err) - assert.Empty(t, *s.Comment) + assert.Empty(t, s.Comment) t.Cleanup(func() { err := client.Schemas.Drop(ctx, schemaID, nil) @@ -265,7 +465,6 @@ func TestInt_SchemasAlter(t *testing.T) { }) t.Run("enable managed access", func(t *testing.T) { - // new schema created on purpose schema, cleanupSchema := testClientHelper().Schema.CreateSchema(t) t.Cleanup(cleanupSchema) @@ -277,7 +476,25 @@ func TestInt_SchemasAlter(t *testing.T) { s, err := client.Schemas.ShowByID(ctx, schema.ID()) require.NoError(t, err) assert.Equal(t, schema.Name, s.Name) - assert.Equal(t, "MANAGED ACCESS", *s.Options) + assert.True(t, true, s.IsManagedAccess()) + }) + + t.Run("disable managed access", func(t *testing.T) { + id := testClientHelper().Ids.RandomDatabaseObjectIdentifier() + schema, cleanupSchema := testClientHelper().Schema.CreateSchemaWithOpts(t, id, &sdk.CreateSchemaOptions{ + WithManagedAccess: sdk.Pointer(true), + }) + t.Cleanup(cleanupSchema) + + err := client.Schemas.Alter(ctx, schema.ID(), &sdk.AlterSchemaOptions{ + DisableManagedAccess: sdk.Bool(true), + }) + require.NoError(t, err) + + s, err := client.Schemas.ShowByID(ctx, schema.ID()) + require.NoError(t, err) + assert.Equal(t, schema.Name, s.Name) + assert.False(t, s.IsManagedAccess()) }) } @@ -285,18 +502,56 @@ func TestInt_SchemasShow(t *testing.T) { client := testClient(t) ctx := testContext(t) - // new schema created on purpose - schema, cleanupSchema := testClientHelper().Schema.CreateSchema(t) - t.Cleanup(cleanupSchema) + schema1, cleanupSchema1 := testClientHelper().Schema.CreateSchema(t) + t.Cleanup(cleanupSchema1) + schema2, cleanupSchema2 := testClientHelper().Schema.CreateSchema(t) + t.Cleanup(cleanupSchema2) t.Run("no options", func(t *testing.T) { schemas, err := client.Schemas.Show(ctx, nil) require.NoError(t, err) - schemaNames := make([]string, len(schemas)) - for i, s := range schemas { - schemaNames[i] = s.Name + assert.GreaterOrEqual(t, len(schemas), 2) + schemaIds := make([]sdk.DatabaseObjectIdentifier, len(schemas)) + for i, schema := range schemas { + schemaIds[i] = schema.ID() } - assert.Contains(t, schemaNames, schema.Name) + assert.Contains(t, schemaIds, schema1.ID()) + assert.Contains(t, schemaIds, schema2.ID()) + }) + + t.Run("with terse", func(t *testing.T) { + schemas, err := client.Schemas.Show(ctx, &sdk.ShowSchemaOptions{ + Terse: sdk.Bool(true), + Like: &sdk.Like{ + Pattern: sdk.String(schema1.Name), + }, + }) + require.NoError(t, err) + + schema, err := collections.FindOne(schemas, func(schema sdk.Schema) bool { return schema.Name == schema1.Name }) + require.NoError(t, err) + + assert.Equal(t, schema1.Name, schema.Name) + assert.NotEmpty(t, schema.CreatedOn) + assert.Empty(t, schema.Owner) + }) + + t.Run("with history", func(t *testing.T) { + schema3, cleanupSchema3 := testClientHelper().Schema.CreateSchema(t) + cleanupSchema3() + schemas, err := client.Schemas.Show(ctx, &sdk.ShowSchemaOptions{ + History: sdk.Bool(true), + Like: &sdk.Like{ + Pattern: sdk.String(schema3.Name), + }, + }) + require.NoError(t, err) + + droppedSchema, err := collections.FindOne(schemas, func(schema sdk.Schema) bool { return schema.Name == schema3.Name }) + require.NoError(t, err) + + assert.Equal(t, schema3.Name, droppedSchema.Name) + assert.NotEmpty(t, droppedSchema.DroppedOn) }) t.Run("with options", func(t *testing.T) { @@ -304,12 +559,12 @@ func TestInt_SchemasShow(t *testing.T) { Terse: sdk.Bool(true), History: sdk.Bool(true), Like: &sdk.Like{ - Pattern: sdk.String(schema.Name), + Pattern: sdk.String(schema1.Name), }, In: &sdk.SchemaIn{ Account: sdk.Bool(true), }, - StartsWith: sdk.String(schema.Name), + StartsWith: sdk.String(schema1.Name), LimitFrom: &sdk.LimitFrom{ Rows: sdk.Int(1), }, @@ -319,8 +574,8 @@ func TestInt_SchemasShow(t *testing.T) { for i, s := range schemas { schemaNames[i] = s.Name } - assert.Contains(t, schemaNames, schema.Name) - assert.Equal(t, "ROLE", schema.OwnerRoleType) + assert.Contains(t, schemaNames, schema1.Name) + assert.Equal(t, "ROLE", schema1.OwnerRoleType) }) } @@ -328,12 +583,8 @@ func TestInt_SchemasDrop(t *testing.T) { client := testClient(t) ctx := testContext(t) - // new schema created on purpose - schema, _ := testClientHelper().Schema.CreateSchema(t) - t.Cleanup(func() { - err := client.Sessions.UseSchema(ctx, testSchema(t).ID()) - require.NoError(t, err) - }) + schema, cleanupSchema := testClientHelper().Schema.CreateSchema(t) + t.Cleanup(cleanupSchema) s, err := client.Schemas.ShowByID(ctx, schema.ID()) require.NoError(t, err) @@ -342,25 +593,16 @@ func TestInt_SchemasDrop(t *testing.T) { err = client.Schemas.Drop(ctx, schema.ID(), nil) require.NoError(t, err) - schemas, err := client.Schemas.Show(ctx, &sdk.ShowSchemaOptions{ - Like: &sdk.Like{ - Pattern: &schema.Name, - }, - }) - require.NoError(t, err) - assert.Equal(t, 0, len(schemas)) + _, err = client.Schemas.ShowByID(ctx, schema.ID()) + assert.ErrorIs(t, err, sdk.ErrObjectNotExistOrAuthorized) } -/* -todo: this test is failing, need to fix func TestInt_SchemasUndrop(t *testing.T) { client := testClient(t) ctx := testContext(t) - db, cleanupDb := createDatabase(t, client) - t.Cleanup(cleanupDb) - - schema, _ := createSchema(t, client, db) + schema, cleanupSchema := testClientHelper().Schema.CreateSchema(t) + t.Cleanup(cleanupSchema) before, err := client.Schemas.ShowByID(ctx, schema.ID()) require.NoError(t, err) @@ -369,16 +611,13 @@ func TestInt_SchemasUndrop(t *testing.T) { err = client.Schemas.Drop(ctx, schema.ID(), nil) require.NoError(t, err) + _, err = client.Schemas.ShowByID(ctx, schema.ID()) + assert.ErrorIs(t, err, sdk.ErrObjectNotExistOrAuthorized) + err = client.Schemas.Undrop(ctx, schema.ID()) require.NoError(t, err) after, err := client.Schemas.ShowByID(ctx, schema.ID()) require.NoError(t, err) assert.Equal(t, schema.Name, after.Name) - - t.Cleanup(func() { - err = client.Schemas.Drop(ctx, schema.ID(), nil) - require.NoError(t, err) - }) } -*/ From 45f6c984223b9d06438dfc1de6e9ed49b9dc796c Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Fri, 19 Jul 2024 13:08:12 +0200 Subject: [PATCH 2/3] Review suggestions --- pkg/sdk/testint/schemas_integration_test.go | 187 +++++++++++--------- 1 file changed, 105 insertions(+), 82 deletions(-) diff --git a/pkg/sdk/testint/schemas_integration_test.go b/pkg/sdk/testint/schemas_integration_test.go index 4aa48f5a23..40508d7769 100644 --- a/pkg/sdk/testint/schemas_integration_test.go +++ b/pkg/sdk/testint/schemas_integration_test.go @@ -11,11 +11,28 @@ import ( "github.com/stretchr/testify/require" ) -func TestInt_SchemasCreate(t *testing.T) { +func TestInt_Schemas(t *testing.T) { client := testClient(t) ctx := testContext(t) + schema1, cleanupSchema1 := testClientHelper().Schema.CreateSchema(t) + t.Cleanup(cleanupSchema1) + schema2, cleanupSchema2 := testClientHelper().Schema.CreateSchema(t) + t.Cleanup(cleanupSchema2) + + assertParameterEquals := func(t *testing.T, params []*sdk.Parameter, parameterName sdk.AccountParameter, expected string) { + t.Helper() + assert.Equal(t, expected, helpers.FindParameter(t, params, parameterName).Value) + } + + assertParameterEqualsToDefaultValue := func(t *testing.T, params []*sdk.Parameter, parameterName sdk.ObjectParameter) { + t.Helper() + param, err := collections.FindOne(params, func(param *sdk.Parameter) bool { return param.Key == string(parameterName) }) + assert.NoError(t, err) + assert.NotNil(t, param) + assert.Equal(t, (*param).Default, (*param).Value) + } - t.Run("minimal", func(t *testing.T) { + t.Run("create: minimal", func(t *testing.T) { schemaId := testClientHelper().Ids.RandomDatabaseObjectIdentifier() err := client.Schemas.Create(ctx, schemaId, &sdk.CreateSchemaOptions{ OrReplace: sdk.Bool(true), @@ -26,9 +43,28 @@ func TestInt_SchemasCreate(t *testing.T) { database, err := client.Schemas.ShowByID(ctx, schemaId) require.NoError(t, err) assert.Equal(t, schemaId.Name(), database.Name) + + params := testClientHelper().Parameter.ShowSchemaParameters(t, schemaId) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterDataRetentionTimeInDays) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterMaxDataExtensionTimeInDays) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterExternalVolume) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterCatalog) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterReplaceInvalidCharacters) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterDefaultDDLCollation) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterStorageSerializationPolicy) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterLogLevel) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterTraceLevel) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterSuspendTaskAfterNumFailures) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterTaskAutoRetryAttempts) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterUserTaskManagedInitialWarehouseSize) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterUserTaskTimeoutMs) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterUserTaskMinimumTriggerIntervalInSeconds) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterQuotedIdentifiersIgnoreCase) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterEnableConsoleOutput) + assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterPipeExecutionPaused) }) - t.Run("replace", func(t *testing.T) { + t.Run("create: or replace", func(t *testing.T) { schema, cleanupSchema := testClientHelper().Schema.CreateSchema(t) t.Cleanup(cleanupSchema) comment := "replaced" @@ -48,7 +84,7 @@ func TestInt_SchemasCreate(t *testing.T) { assert.Equal(t, comment, s.Comment) }) - t.Run("if not exists", func(t *testing.T) { + t.Run("create: if not exists", func(t *testing.T) { schema, cleanupSchema := testClientHelper().Schema.CreateSchema(t) t.Cleanup(cleanupSchema) comment := "some_comment" @@ -62,7 +98,7 @@ func TestInt_SchemasCreate(t *testing.T) { assert.NotEqual(t, comment, s.Comment) }) - t.Run("clone", func(t *testing.T) { + t.Run("create: clone", func(t *testing.T) { comment := "some_comment" schemaID := testClientHelper().Ids.RandomDatabaseObjectIdentifier() err := client.Schemas.Create(ctx, schemaID, &sdk.CreateSchemaOptions{ @@ -94,7 +130,7 @@ func TestInt_SchemasCreate(t *testing.T) { }) }) - t.Run("with tags", func(t *testing.T) { + t.Run("create: with tags", func(t *testing.T) { tag, tagCleanup := testClientHelper().Tag.CreateTag(t) t.Cleanup(tagCleanup) @@ -119,7 +155,7 @@ func TestInt_SchemasCreate(t *testing.T) { assert.Equal(t, tagValue, tv) }) - t.Run("complete", func(t *testing.T) { + t.Run("create: complete", func(t *testing.T) { schemaId := testClientHelper().Ids.RandomDatabaseObjectIdentifier() databaseTest, databaseCleanup := testClientHelper().Database.CreateDatabase(t) @@ -213,28 +249,8 @@ func TestInt_SchemasCreate(t *testing.T) { require.NoError(t, err) assert.Equal(t, "v2", tag2Value) }) -} - -func TestInt_SchemasAlter(t *testing.T) { - client := testClient(t) - ctx := testContext(t) - assertParameterEquals := func(t *testing.T, params []*sdk.Parameter, parameterName sdk.AccountParameter, expected string) { - t.Helper() - assert.Equal(t, expected, helpers.FindParameter(t, params, parameterName).Value) - } - - assertParameterEqualsToDefaultValue := func(t *testing.T, params []*sdk.Parameter, parameterName sdk.ObjectParameter) { - t.Helper() - param, err := collections.FindOne(params, func(param *sdk.Parameter) bool { return param.Key == string(parameterName) }) - assert.NoError(t, err) - assert.NotNil(t, param) - if param != nil && (*param).Level == "" { - param := *param - assert.Equal(t, param.Default, param.Value) - } - } - t.Run("rename to", func(t *testing.T) { + t.Run("alter: rename to", func(t *testing.T) { // new schema created on purpose schema, _ := testClientHelper().Schema.CreateSchema(t) t.Cleanup(func() { @@ -255,7 +271,7 @@ func TestInt_SchemasAlter(t *testing.T) { }) }) - t.Run("swap with", func(t *testing.T) { + t.Run("alter: swap with", func(t *testing.T) { // new schemas created on purpose schema, cleanupSchema := testClientHelper().Schema.CreateSchema(t) t.Cleanup(cleanupSchema) @@ -281,7 +297,7 @@ func TestInt_SchemasAlter(t *testing.T) { assert.Equal(t, table.Name, schemaDetails[0].Name) }) - t.Run("set and unset parameters", func(t *testing.T) { + t.Run("alter: set and unset parameters", func(t *testing.T) { // new schema created on purpose schemaTest, cleanupSchemaTest := testClientHelper().Schema.CreateSchema(t) t.Cleanup(cleanupSchemaTest) @@ -377,7 +393,30 @@ func TestInt_SchemasAlter(t *testing.T) { assertParameterEqualsToDefaultValue(t, params, sdk.ObjectParameterPipeExecutionPaused) }) - t.Run("unset", func(t *testing.T) { + t.Run("alter: set non-parameters", func(t *testing.T) { + schemaID := testClientHelper().Ids.RandomDatabaseObjectIdentifier() + comment := random.Comment() + err := client.Schemas.Create(ctx, schemaID, nil) + require.NoError(t, err) + + err = client.Schemas.Alter(ctx, schemaID, &sdk.AlterSchemaOptions{ + Set: &sdk.SchemaSet{ + Comment: sdk.Pointer(comment), + }, + }) + require.NoError(t, err) + + s, err := client.Schemas.ShowByID(ctx, schemaID) + require.NoError(t, err) + assert.Equal(t, comment, s.Comment) + + t.Cleanup(func() { + err := client.Schemas.Drop(ctx, schemaID, nil) + require.NoError(t, err) + }) + }) + + t.Run("alter: unset non-parameters", func(t *testing.T) { schemaID := testClientHelper().Ids.RandomDatabaseObjectIdentifier() comment := random.Comment() err := client.Schemas.Create(ctx, schemaID, &sdk.CreateSchemaOptions{ @@ -402,7 +441,7 @@ func TestInt_SchemasAlter(t *testing.T) { }) }) - t.Run("set tags", func(t *testing.T) { + t.Run("alter: set tags", func(t *testing.T) { schemaID := testClientHelper().Ids.RandomDatabaseObjectIdentifier() err := client.Schemas.Create(ctx, schemaID, nil) require.NoError(t, err) @@ -433,7 +472,7 @@ func TestInt_SchemasAlter(t *testing.T) { assert.Equal(t, tagValue, tv) }) - t.Run("unset tags", func(t *testing.T) { + t.Run("alter: unset tags", func(t *testing.T) { tag, tagCleanup := testClientHelper().Tag.CreateTag(t) t.Cleanup(tagCleanup) @@ -464,7 +503,7 @@ func TestInt_SchemasAlter(t *testing.T) { require.Error(t, err) }) - t.Run("enable managed access", func(t *testing.T) { + t.Run("alter: enable managed access", func(t *testing.T) { schema, cleanupSchema := testClientHelper().Schema.CreateSchema(t) t.Cleanup(cleanupSchema) @@ -479,7 +518,7 @@ func TestInt_SchemasAlter(t *testing.T) { assert.True(t, true, s.IsManagedAccess()) }) - t.Run("disable managed access", func(t *testing.T) { + t.Run("alter: disable managed access", func(t *testing.T) { id := testClientHelper().Ids.RandomDatabaseObjectIdentifier() schema, cleanupSchema := testClientHelper().Schema.CreateSchemaWithOpts(t, id, &sdk.CreateSchemaOptions{ WithManagedAccess: sdk.Pointer(true), @@ -496,18 +535,8 @@ func TestInt_SchemasAlter(t *testing.T) { assert.Equal(t, schema.Name, s.Name) assert.False(t, s.IsManagedAccess()) }) -} - -func TestInt_SchemasShow(t *testing.T) { - client := testClient(t) - ctx := testContext(t) - schema1, cleanupSchema1 := testClientHelper().Schema.CreateSchema(t) - t.Cleanup(cleanupSchema1) - schema2, cleanupSchema2 := testClientHelper().Schema.CreateSchema(t) - t.Cleanup(cleanupSchema2) - - t.Run("no options", func(t *testing.T) { + t.Run("show: no options", func(t *testing.T) { schemas, err := client.Schemas.Show(ctx, nil) require.NoError(t, err) assert.GreaterOrEqual(t, len(schemas), 2) @@ -519,7 +548,7 @@ func TestInt_SchemasShow(t *testing.T) { assert.Contains(t, schemaIds, schema2.ID()) }) - t.Run("with terse", func(t *testing.T) { + t.Run("show: with terse", func(t *testing.T) { schemas, err := client.Schemas.Show(ctx, &sdk.ShowSchemaOptions{ Terse: sdk.Bool(true), Like: &sdk.Like{ @@ -536,7 +565,7 @@ func TestInt_SchemasShow(t *testing.T) { assert.Empty(t, schema.Owner) }) - t.Run("with history", func(t *testing.T) { + t.Run("show: with history", func(t *testing.T) { schema3, cleanupSchema3 := testClientHelper().Schema.CreateSchema(t) cleanupSchema3() schemas, err := client.Schemas.Show(ctx, &sdk.ShowSchemaOptions{ @@ -554,7 +583,7 @@ func TestInt_SchemasShow(t *testing.T) { assert.NotEmpty(t, droppedSchema.DroppedOn) }) - t.Run("with options", func(t *testing.T) { + t.Run("show: with options", func(t *testing.T) { schemas, err := client.Schemas.Show(ctx, &sdk.ShowSchemaOptions{ Terse: sdk.Bool(true), History: sdk.Bool(true), @@ -577,47 +606,41 @@ func TestInt_SchemasShow(t *testing.T) { assert.Contains(t, schemaNames, schema1.Name) assert.Equal(t, "ROLE", schema1.OwnerRoleType) }) -} - -func TestInt_SchemasDrop(t *testing.T) { - client := testClient(t) - ctx := testContext(t) - schema, cleanupSchema := testClientHelper().Schema.CreateSchema(t) - t.Cleanup(cleanupSchema) - - s, err := client.Schemas.ShowByID(ctx, schema.ID()) - require.NoError(t, err) - assert.Equal(t, schema.Name, s.Name) + t.Run("drop", func(t *testing.T) { + schema, cleanupSchema := testClientHelper().Schema.CreateSchema(t) + t.Cleanup(cleanupSchema) - err = client.Schemas.Drop(ctx, schema.ID(), nil) - require.NoError(t, err) + s, err := client.Schemas.ShowByID(ctx, schema.ID()) + require.NoError(t, err) + assert.Equal(t, schema.Name, s.Name) - _, err = client.Schemas.ShowByID(ctx, schema.ID()) - assert.ErrorIs(t, err, sdk.ErrObjectNotExistOrAuthorized) -} + err = client.Schemas.Drop(ctx, schema.ID(), nil) + require.NoError(t, err) -func TestInt_SchemasUndrop(t *testing.T) { - client := testClient(t) - ctx := testContext(t) + _, err = client.Schemas.ShowByID(ctx, schema.ID()) + assert.ErrorIs(t, err, sdk.ErrObjectNotExistOrAuthorized) + }) - schema, cleanupSchema := testClientHelper().Schema.CreateSchema(t) - t.Cleanup(cleanupSchema) + t.Run("undrop", func(t *testing.T) { + schema, cleanupSchema := testClientHelper().Schema.CreateSchema(t) + t.Cleanup(cleanupSchema) - before, err := client.Schemas.ShowByID(ctx, schema.ID()) - require.NoError(t, err) - assert.Equal(t, schema.Name, before.Name) + before, err := client.Schemas.ShowByID(ctx, schema.ID()) + require.NoError(t, err) + assert.Equal(t, schema.Name, before.Name) - err = client.Schemas.Drop(ctx, schema.ID(), nil) - require.NoError(t, err) + err = client.Schemas.Drop(ctx, schema.ID(), nil) + require.NoError(t, err) - _, err = client.Schemas.ShowByID(ctx, schema.ID()) - assert.ErrorIs(t, err, sdk.ErrObjectNotExistOrAuthorized) + _, err = client.Schemas.ShowByID(ctx, schema.ID()) + assert.ErrorIs(t, err, sdk.ErrObjectNotExistOrAuthorized) - err = client.Schemas.Undrop(ctx, schema.ID()) - require.NoError(t, err) + err = client.Schemas.Undrop(ctx, schema.ID()) + require.NoError(t, err) - after, err := client.Schemas.ShowByID(ctx, schema.ID()) - require.NoError(t, err) - assert.Equal(t, schema.Name, after.Name) + after, err := client.Schemas.ShowByID(ctx, schema.ID()) + require.NoError(t, err) + assert.Equal(t, schema.Name, after.Name) + }) } From 6b22ebd3c56d08ed886a74373a0bfca36cbeb97f Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Tue, 23 Jul 2024 10:30:53 +0200 Subject: [PATCH 3/3] Always run acceptance tests --- .github/workflows/tests.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 007af12b92..f994b9b86d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -44,7 +44,7 @@ jobs: run: mkdir $HOME/.snowflake && echo "${{ secrets.SNOWFLAKE_CONFIG_FILE }}" > $HOME/.snowflake/config - run: make test - if: steps.create_config.conclusion == 'success' + if: ${{ !cancelled() && steps.create_config.conclusion == 'success' }} env: SNOWFLAKE_BUSINESS_CRITICAL_ACCOUNT: ${{ secrets.SNOWFLAKE_BUSINESS_CRITICAL_ACCOUNT }} TEST_SF_TF_AWS_EXTERNAL_BUCKET_URL: ${{ secrets.TEST_SF_TF_AWS_EXTERNAL_BUCKET_URL }} @@ -57,14 +57,15 @@ jobs: TEST_SF_TF_GCS_EXTERNAL_BUCKET_URL: ${{ secrets.TEST_SF_TF_GCS_EXTERNAL_BUCKET_URL }} - name: Setup Terraform - if: steps.create_config.conclusion == 'success' + if: ${{ !cancelled() && steps.create_config.conclusion == 'success' }} uses: hashicorp/setup-terraform@v3 + id: setup_terraform with: terraform_version: 1.7.4 terraform_wrapper: false - run: make test-acceptance - if: steps.create_config.conclusion == 'success' + if: ${{ !cancelled() && steps.setup_terraform.conclusion == 'success' }} env: SNOWFLAKE_BUSINESS_CRITICAL_ACCOUNT: ${{ secrets.SNOWFLAKE_BUSINESS_CRITICAL_ACCOUNT }} TEST_SF_TF_AWS_EXTERNAL_BUCKET_URL: ${{ secrets.TEST_SF_TF_AWS_EXTERNAL_BUCKET_URL }}