From d2e5ffd5405f10ff30c5ad9f7cd58bd54a5cc028 Mon Sep 17 00:00:00 2001 From: Scott Winkler Date: Tue, 23 Jan 2024 11:36:12 -0800 Subject: [PATCH] feat: add sequences to sdk (#2351) adds sequences to sdk https://docs.snowflake.com/en/sql-reference/sql/create-sequence --- pkg/sdk/client.go | 2 + pkg/sdk/common_types.go | 11 + pkg/sdk/poc/main.go | 1 + pkg/sdk/sequences_def.go | 124 +++++++++++ pkg/sdk/sequences_dto_builders_gen.go | 144 +++++++++++++ pkg/sdk/sequences_dto_gen.go | 55 +++++ pkg/sdk/sequences_gen.go | 123 +++++++++++ pkg/sdk/sequences_gen_test.go | 194 ++++++++++++++++++ pkg/sdk/sequences_impl_gen.go | 148 +++++++++++++ pkg/sdk/sequences_validations_gen.go | 75 +++++++ pkg/sdk/testint/sequences_integration_test.go | 171 +++++++++++++++ pkg/sdk/testint/setup_test.go | 1 + 12 files changed, 1049 insertions(+) create mode 100644 pkg/sdk/sequences_def.go create mode 100644 pkg/sdk/sequences_dto_builders_gen.go create mode 100644 pkg/sdk/sequences_dto_gen.go create mode 100644 pkg/sdk/sequences_gen.go create mode 100644 pkg/sdk/sequences_gen_test.go create mode 100644 pkg/sdk/sequences_impl_gen.go create mode 100644 pkg/sdk/sequences_validations_gen.go create mode 100644 pkg/sdk/testint/sequences_integration_test.go diff --git a/pkg/sdk/client.go b/pkg/sdk/client.go index 2accdbc55c..aede13ba58 100644 --- a/pkg/sdk/client.go +++ b/pkg/sdk/client.go @@ -63,6 +63,7 @@ type Client struct { Roles Roles RowAccessPolicies RowAccessPolicies Schemas Schemas + Sequences Sequences SessionPolicies SessionPolicies Sessions Sessions Shares Shares @@ -209,6 +210,7 @@ func (c *Client) initialize() { c.Roles = &roles{client: c} c.RowAccessPolicies = &rowAccessPolicies{client: c} c.Schemas = &schemas{client: c} + c.Sequences = &sequences{client: c} c.SessionPolicies = &sessionPolicies{client: c} c.Sessions = &sessions{client: c} c.Shares = &shares{client: c} diff --git a/pkg/sdk/common_types.go b/pkg/sdk/common_types.go index 517ace65ed..03fb6bb4b1 100644 --- a/pkg/sdk/common_types.go +++ b/pkg/sdk/common_types.go @@ -199,6 +199,17 @@ type Secret struct { Name string `ddl:"parameter,no_quotes"` } +type ValuesBehavior string + +var ( + ValuesBehaviorOrder ValuesBehavior = "ORDER" + ValuesBehaviorNoOrder ValuesBehavior = "NOORDER" +) + +func ValuesBehaviorPointer(v ValuesBehavior) *ValuesBehavior { + return &v +} + type Distribution string var ( diff --git a/pkg/sdk/poc/main.go b/pkg/sdk/poc/main.go index ab056a5c40..492bbfe201 100644 --- a/pkg/sdk/poc/main.go +++ b/pkg/sdk/poc/main.go @@ -31,6 +31,7 @@ var definitionMapping = map[string]*generator.Interface{ "storage_integration_def.go": sdk.StorageIntegrationDef, "managed_accounts_def.go": sdk.ManagedAccountsDef, "row_access_policies_def.go": sdk.RowAccessPoliciesDef, + "sequences_def.go": sdk.SequencesDef, } func main() { diff --git a/pkg/sdk/sequences_def.go b/pkg/sdk/sequences_def.go new file mode 100644 index 0000000000..c73ea4d506 --- /dev/null +++ b/pkg/sdk/sequences_def.go @@ -0,0 +1,124 @@ +package sdk + +import g "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/poc/generator" + +//go:generate go run ./poc/main.go + +var sequenceSet = g.NewQueryStruct("SequenceSet"). + PredefinedQueryStructField("ValuesBehavior", "*ValuesBehavior", g.KeywordOptions()). + OptionalTextAssignment("COMMENT", g.ParameterOptions().SingleQuotes()) + +var sequenceConstraint = g.NewQueryStruct("SequenceConstraint"). + OptionalSQL("CASCADE"). + OptionalSQL("RESTRICT"). + WithValidation(g.ExactlyOneValueSet, "Cascade", "Restrict") + +var SequencesDef = g.NewInterface( + "Sequences", + "Sequence", + g.KindOfT[SchemaObjectIdentifier](), +).CreateOperation( + "https://docs.snowflake.com/en/sql-reference/sql/create-sequence", + g.NewQueryStruct("CreateSequence"). + Create(). + OrReplace(). + SQL("SEQUENCE"). + IfNotExists(). + Name(). + OptionalNumberAssignment("START", g.ParameterOptions().NoQuotes()). + OptionalNumberAssignment("INCREMENT", g.ParameterOptions().NoQuotes()). + PredefinedQueryStructField("ValuesBehavior", "*ValuesBehavior", g.KeywordOptions()). + OptionalTextAssignment("COMMENT", g.ParameterOptions().SingleQuotes()). + WithValidation(g.ValidIdentifier, "name"). + WithValidation(g.ConflictingFields, "OrReplace", "IfNotExists"), +).AlterOperation( + "https://docs.snowflake.com/en/sql-reference/sql/alter-sequence", + g.NewQueryStruct("AlterSequence"). + Alter(). + SQL("SEQUENCE"). + IfExists(). + Name(). + Identifier("RenameTo", g.KindOfTPointer[SchemaObjectIdentifier](), g.IdentifierOptions().SQL("RENAME TO")). + OptionalNumberAssignment("SET INCREMENT", g.ParameterOptions().NoQuotes()). + OptionalQueryStructField( + "Set", + sequenceSet, + g.KeywordOptions().SQL("SET"), + ). + OptionalSQL("UNSET COMMENT"). + WithValidation(g.ValidIdentifier, "name"). + WithValidation(g.ValidIdentifierIfSet, "RenameTo"). + WithValidation(g.ExactlyOneValueSet, "RenameTo", "SetIncrement", "Set", "UnsetComment"), +).ShowOperation( + "https://docs.snowflake.com/en/sql-reference/sql/show-sequences", + g.DbStruct("sequenceRow"). + Field("created_on", "string"). + Field("name", "string"). + Field("schema_name", "string"). + Field("database_name", "string"). + Field("next_value", "int"). + Field("interval", "int"). + Field("owner", "string"). + Field("owner_role_type", "string"). + Field("comment", "string"). + Field("ordered", "string"), + g.PlainStruct("Sequence"). + Field("CreatedOn", "string"). + Field("Name", "string"). + Field("SchemaName", "string"). + Field("DatabaseName", "string"). + Field("NextValue", "int"). + Field("Interval", "int"). + Field("Owner", "string"). + Field("OwnerRoleType", "string"). + Field("Comment", "string"). + Field("Ordered", "bool"), + g.NewQueryStruct("ShowSequences"). + Show(). + SQL("SEQUENCES"). + OptionalLike(). + OptionalIn(), +).ShowByIdOperation().DescribeOperation( + g.DescriptionMappingKindSingleValue, + "https://docs.snowflake.com/en/sql-reference/sql/desc-sequence", + g.DbStruct("sequenceDetailRow"). + Field("created_on", "string"). + Field("name", "string"). + Field("schema_name", "string"). + Field("database_name", "string"). + Field("next_value", "int"). + Field("interval", "int"). + Field("owner", "string"). + Field("owner_role_type", "string"). + Field("comment", "string"). + Field("ordered", "string"), + g.PlainStruct("SequenceDetail"). + Field("CreatedOn", "string"). + Field("Name", "string"). + Field("SchemaName", "string"). + Field("DatabaseName", "string"). + Field("NextValue", "int"). + Field("Interval", "int"). + Field("Owner", "string"). + Field("OwnerRoleType", "string"). + Field("Comment", "string"). + Field("Ordered", "bool"), + g.NewQueryStruct("DescribeSequence"). + Describe(). + SQL("SEQUENCE"). + Name(). + WithValidation(g.ValidIdentifier, "name"), +).DropOperation( + "https://docs.snowflake.com/en/sql-reference/sql/drop-sequence", + g.NewQueryStruct("DropSequence"). + Drop(). + SQL("SEQUENCE"). + IfExists(). + Name(). + OptionalQueryStructField( + "Constraint", + sequenceConstraint, + g.KeywordOptions(), + ). + WithValidation(g.ValidIdentifier, "name"), +) diff --git a/pkg/sdk/sequences_dto_builders_gen.go b/pkg/sdk/sequences_dto_builders_gen.go new file mode 100644 index 0000000000..95b0355f95 --- /dev/null +++ b/pkg/sdk/sequences_dto_builders_gen.go @@ -0,0 +1,144 @@ +// Code generated by dto builder generator; DO NOT EDIT. + +package sdk + +import () + +func NewCreateSequenceRequest( + name SchemaObjectIdentifier, +) *CreateSequenceRequest { + s := CreateSequenceRequest{} + s.name = name + return &s +} + +func (s *CreateSequenceRequest) WithOrReplace(OrReplace *bool) *CreateSequenceRequest { + s.OrReplace = OrReplace + return s +} + +func (s *CreateSequenceRequest) WithIfNotExists(IfNotExists *bool) *CreateSequenceRequest { + s.IfNotExists = IfNotExists + return s +} + +func (s *CreateSequenceRequest) WithStart(Start *int) *CreateSequenceRequest { + s.Start = Start + return s +} + +func (s *CreateSequenceRequest) WithIncrement(Increment *int) *CreateSequenceRequest { + s.Increment = Increment + return s +} + +func (s *CreateSequenceRequest) WithValuesBehavior(ValuesBehavior *ValuesBehavior) *CreateSequenceRequest { + s.ValuesBehavior = ValuesBehavior + return s +} + +func (s *CreateSequenceRequest) WithComment(Comment *string) *CreateSequenceRequest { + s.Comment = Comment + return s +} + +func NewAlterSequenceRequest( + name SchemaObjectIdentifier, +) *AlterSequenceRequest { + s := AlterSequenceRequest{} + s.name = name + return &s +} + +func (s *AlterSequenceRequest) WithIfExists(IfExists *bool) *AlterSequenceRequest { + s.IfExists = IfExists + return s +} + +func (s *AlterSequenceRequest) WithRenameTo(RenameTo *SchemaObjectIdentifier) *AlterSequenceRequest { + s.RenameTo = RenameTo + return s +} + +func (s *AlterSequenceRequest) WithSetIncrement(SetIncrement *int) *AlterSequenceRequest { + s.SetIncrement = SetIncrement + return s +} + +func (s *AlterSequenceRequest) WithSet(Set *SequenceSetRequest) *AlterSequenceRequest { + s.Set = Set + return s +} + +func (s *AlterSequenceRequest) WithUnsetComment(UnsetComment *bool) *AlterSequenceRequest { + s.UnsetComment = UnsetComment + return s +} + +func NewSequenceSetRequest() *SequenceSetRequest { + return &SequenceSetRequest{} +} + +func (s *SequenceSetRequest) WithValuesBehavior(ValuesBehavior *ValuesBehavior) *SequenceSetRequest { + s.ValuesBehavior = ValuesBehavior + return s +} + +func (s *SequenceSetRequest) WithComment(Comment *string) *SequenceSetRequest { + s.Comment = Comment + return s +} + +func NewShowSequenceRequest() *ShowSequenceRequest { + return &ShowSequenceRequest{} +} + +func (s *ShowSequenceRequest) WithLike(Like *Like) *ShowSequenceRequest { + s.Like = Like + return s +} + +func (s *ShowSequenceRequest) WithIn(In *In) *ShowSequenceRequest { + s.In = In + return s +} + +func NewDescribeSequenceRequest( + name SchemaObjectIdentifier, +) *DescribeSequenceRequest { + s := DescribeSequenceRequest{} + s.name = name + return &s +} + +func NewDropSequenceRequest( + name SchemaObjectIdentifier, +) *DropSequenceRequest { + s := DropSequenceRequest{} + s.name = name + return &s +} + +func (s *DropSequenceRequest) WithIfExists(IfExists *bool) *DropSequenceRequest { + s.IfExists = IfExists + return s +} + +func (s *DropSequenceRequest) WithConstraint(Constraint *SequenceConstraintRequest) *DropSequenceRequest { + s.Constraint = Constraint + return s +} + +func NewSequenceConstraintRequest() *SequenceConstraintRequest { + return &SequenceConstraintRequest{} +} + +func (s *SequenceConstraintRequest) WithCascade(Cascade *bool) *SequenceConstraintRequest { + s.Cascade = Cascade + return s +} + +func (s *SequenceConstraintRequest) WithRestrict(Restrict *bool) *SequenceConstraintRequest { + s.Restrict = Restrict + return s +} diff --git a/pkg/sdk/sequences_dto_gen.go b/pkg/sdk/sequences_dto_gen.go new file mode 100644 index 0000000000..35509fae5d --- /dev/null +++ b/pkg/sdk/sequences_dto_gen.go @@ -0,0 +1,55 @@ +package sdk + +//go:generate go run ./dto-builder-generator/main.go + +var ( + _ optionsProvider[CreateSequenceOptions] = new(CreateSequenceRequest) + _ optionsProvider[AlterSequenceOptions] = new(AlterSequenceRequest) + _ optionsProvider[ShowSequenceOptions] = new(ShowSequenceRequest) + _ optionsProvider[DescribeSequenceOptions] = new(DescribeSequenceRequest) + _ optionsProvider[DropSequenceOptions] = new(DropSequenceRequest) +) + +type CreateSequenceRequest struct { + OrReplace *bool + IfNotExists *bool + name SchemaObjectIdentifier // required + Start *int + Increment *int + ValuesBehavior *ValuesBehavior + Comment *string +} + +type AlterSequenceRequest struct { + IfExists *bool + name SchemaObjectIdentifier // required + RenameTo *SchemaObjectIdentifier + SetIncrement *int + Set *SequenceSetRequest + UnsetComment *bool +} + +type SequenceSetRequest struct { + ValuesBehavior *ValuesBehavior + Comment *string +} + +type ShowSequenceRequest struct { + Like *Like + In *In +} + +type DescribeSequenceRequest struct { + name SchemaObjectIdentifier // required +} + +type DropSequenceRequest struct { + IfExists *bool + name SchemaObjectIdentifier // required + Constraint *SequenceConstraintRequest +} + +type SequenceConstraintRequest struct { + Cascade *bool + Restrict *bool +} diff --git a/pkg/sdk/sequences_gen.go b/pkg/sdk/sequences_gen.go new file mode 100644 index 0000000000..bb476dc300 --- /dev/null +++ b/pkg/sdk/sequences_gen.go @@ -0,0 +1,123 @@ +package sdk + +import "context" + +type Sequences interface { + Create(ctx context.Context, request *CreateSequenceRequest) error + Alter(ctx context.Context, request *AlterSequenceRequest) error + Show(ctx context.Context, request *ShowSequenceRequest) ([]Sequence, error) + ShowByID(ctx context.Context, id SchemaObjectIdentifier) (*Sequence, error) + Describe(ctx context.Context, id SchemaObjectIdentifier) (*SequenceDetail, error) + Drop(ctx context.Context, request *DropSequenceRequest) error +} + +// CreateSequenceOptions is based on https://docs.snowflake.com/en/sql-reference/sql/create-sequence. +type CreateSequenceOptions struct { + create bool `ddl:"static" sql:"CREATE"` + OrReplace *bool `ddl:"keyword" sql:"OR REPLACE"` + sequence bool `ddl:"static" sql:"SEQUENCE"` + IfNotExists *bool `ddl:"keyword" sql:"IF NOT EXISTS"` + name SchemaObjectIdentifier `ddl:"identifier"` + Start *int `ddl:"parameter,no_quotes" sql:"START"` + Increment *int `ddl:"parameter,no_quotes" sql:"INCREMENT"` + ValuesBehavior *ValuesBehavior `ddl:"keyword"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` +} + +// AlterSequenceOptions is based on https://docs.snowflake.com/en/sql-reference/sql/alter-sequence. +type AlterSequenceOptions struct { + alter bool `ddl:"static" sql:"ALTER"` + sequence bool `ddl:"static" sql:"SEQUENCE"` + IfExists *bool `ddl:"keyword" sql:"IF EXISTS"` + name SchemaObjectIdentifier `ddl:"identifier"` + RenameTo *SchemaObjectIdentifier `ddl:"identifier" sql:"RENAME TO"` + SetIncrement *int `ddl:"parameter,no_quotes" sql:"SET INCREMENT"` + Set *SequenceSet `ddl:"keyword" sql:"SET"` + UnsetComment *bool `ddl:"keyword" sql:"UNSET COMMENT"` +} + +type SequenceSet struct { + ValuesBehavior *ValuesBehavior `ddl:"keyword"` + Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` +} + +// ShowSequenceOptions is based on https://docs.snowflake.com/en/sql-reference/sql/show-sequences. +type ShowSequenceOptions struct { + show bool `ddl:"static" sql:"SHOW"` + sequences bool `ddl:"static" sql:"SEQUENCES"` + Like *Like `ddl:"keyword" sql:"LIKE"` + In *In `ddl:"keyword" sql:"IN"` +} + +type sequenceRow struct { + CreatedOn string `db:"created_on"` + Name string `db:"name"` + SchemaName string `db:"schema_name"` + DatabaseName string `db:"database_name"` + NextValue int `db:"next_value"` + Interval int `db:"interval"` + Owner string `db:"owner"` + OwnerRoleType string `db:"owner_role_type"` + Comment string `db:"comment"` + Ordered string `db:"ordered"` +} + +type Sequence struct { + CreatedOn string + Name string + SchemaName string + DatabaseName string + NextValue int + Interval int + Owner string + OwnerRoleType string + Comment string + Ordered bool +} + +// DescribeSequenceOptions is based on https://docs.snowflake.com/en/sql-reference/sql/desc-sequence. +type DescribeSequenceOptions struct { + describe bool `ddl:"static" sql:"DESCRIBE"` + sequence bool `ddl:"static" sql:"SEQUENCE"` + name SchemaObjectIdentifier `ddl:"identifier"` +} + +type sequenceDetailRow struct { + CreatedOn string `db:"created_on"` + Name string `db:"name"` + SchemaName string `db:"schema_name"` + DatabaseName string `db:"database_name"` + NextValue int `db:"next_value"` + Interval int `db:"interval"` + Owner string `db:"owner"` + OwnerRoleType string `db:"owner_role_type"` + Comment string `db:"comment"` + Ordered string `db:"ordered"` +} + +type SequenceDetail struct { + CreatedOn string + Name string + SchemaName string + DatabaseName string + NextValue int + Interval int + Owner string + OwnerRoleType string + Comment string + Ordered bool +} + +// DropSequenceOptions is based on https://docs.snowflake.com/en/sql-reference/sql/drop-sequence. +type DropSequenceOptions struct { + drop bool `ddl:"static" sql:"DROP"` + sequence bool `ddl:"static" sql:"SEQUENCE"` + IfExists *bool `ddl:"keyword" sql:"IF EXISTS"` + name SchemaObjectIdentifier `ddl:"identifier"` + Constraint *SequenceConstraint `ddl:"keyword"` +} + +type SequenceConstraint struct { + Cascade *bool `ddl:"keyword" sql:"CASCADE"` + Restrict *bool `ddl:"keyword" sql:"RESTRICT"` +} diff --git a/pkg/sdk/sequences_gen_test.go b/pkg/sdk/sequences_gen_test.go new file mode 100644 index 0000000000..d58b11245a --- /dev/null +++ b/pkg/sdk/sequences_gen_test.go @@ -0,0 +1,194 @@ +package sdk + +import ( + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/random" +) + +func TestSequences_Create(t *testing.T) { + id := RandomSchemaObjectIdentifier() + + defaultOpts := func() *CreateSequenceOptions { + return &CreateSequenceOptions{ + name: id, + } + } + + t.Run("validation: nil options", func(t *testing.T) { + var opts *CreateSequenceOptions = nil + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("validation: incorrect identifier", func(t *testing.T) { + opts := defaultOpts() + opts.name = NewSchemaObjectIdentifier("", "", "") + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("validation: conflicting fields", func(t *testing.T) { + opts := defaultOpts() + opts.OrReplace = Bool(true) + opts.IfNotExists = Bool(true) + assertOptsInvalidJoinedErrors(t, opts, errOneOf("CreateSequenceOptions", "OrReplace", "IfNotExists")) + }) + + t.Run("all options", func(t *testing.T) { + opts := defaultOpts() + opts.OrReplace = Bool(true) + opts.Start = Int(1) + opts.Increment = Int(1) + opts.ValuesBehavior = ValuesBehaviorPointer(ValuesBehaviorOrder) + opts.Comment = String("comment") + assertOptsValidAndSQLEquals(t, opts, `CREATE OR REPLACE SEQUENCE %s START = 1 INCREMENT = 1 ORDER COMMENT = 'comment'`, id.FullyQualifiedName()) + }) +} + +func TestSequences_Alter(t *testing.T) { + id := RandomSchemaObjectIdentifier() + + defaultOpts := func() *AlterSequenceOptions { + return &AlterSequenceOptions{ + name: id, + IfExists: Bool(true), + } + } + + t.Run("validation: nil options", func(t *testing.T) { + opts := (*AlterSequenceOptions)(nil) + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("validation: incorrect identifier", func(t *testing.T) { + opts := defaultOpts() + opts.name = NewSchemaObjectIdentifier("", "", "") + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("validation: exactly one field should be present", func(t *testing.T) { + opts := defaultOpts() + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("AlterSequenceOptions", "RenameTo", "SetIncrement", "Set", "UnsetComment")) + }) + + t.Run("validation: exactly one field should be present", func(t *testing.T) { + opts := defaultOpts() + opts.SetIncrement = Int(1) + opts.UnsetComment = Bool(true) + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("AlterSequenceOptions", "RenameTo", "SetIncrement", "Set", "UnsetComment")) + }) + + t.Run("alter: rename to", func(t *testing.T) { + opts := defaultOpts() + target := NewSchemaObjectIdentifier(id.DatabaseName(), id.SchemaName(), random.StringN(12)) + opts.RenameTo = &target + assertOptsValidAndSQLEquals(t, opts, `ALTER SEQUENCE IF EXISTS %s RENAME TO %s`, id.FullyQualifiedName(), opts.RenameTo.FullyQualifiedName()) + }) + + t.Run("alter: set options", func(t *testing.T) { + opts := defaultOpts() + opts.Set = &SequenceSet{ + Comment: String("comment"), + ValuesBehavior: ValuesBehaviorPointer(ValuesBehaviorOrder), + } + assertOptsValidAndSQLEquals(t, opts, `ALTER SEQUENCE IF EXISTS %s SET ORDER COMMENT = 'comment'`, id.FullyQualifiedName()) + }) + + t.Run("alter: unset comment", func(t *testing.T) { + opts := defaultOpts() + opts.UnsetComment = Bool(true) + assertOptsValidAndSQLEquals(t, opts, `ALTER SEQUENCE IF EXISTS %s UNSET COMMENT`, id.FullyQualifiedName()) + }) + + t.Run("alter: set increment", func(t *testing.T) { + opts := defaultOpts() + opts.SetIncrement = Int(1) + assertOptsValidAndSQLEquals(t, opts, `ALTER SEQUENCE IF EXISTS %s SET INCREMENT = 1`, id.FullyQualifiedName()) + }) +} + +func TestSequences_Show(t *testing.T) { + defaultOpts := func() *ShowSequenceOptions { + return &ShowSequenceOptions{} + } + + t.Run("validation: nil options", func(t *testing.T) { + opts := (*ShowSequenceOptions)(nil) + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("show with empty options", func(t *testing.T) { + opts := defaultOpts() + assertOptsValidAndSQLEquals(t, opts, `SHOW SEQUENCES`) + }) + + t.Run("show with like", func(t *testing.T) { + opts := defaultOpts() + opts.Like = &Like{ + Pattern: String("pattern"), + } + assertOptsValidAndSQLEquals(t, opts, `SHOW SEQUENCES LIKE 'pattern'`) + }) + + t.Run("show with in", func(t *testing.T) { + opts := defaultOpts() + opts.In = &In{ + Account: Bool(true), + } + assertOptsValidAndSQLEquals(t, opts, `SHOW SEQUENCES IN ACCOUNT`) + }) +} + +func TestSequences_Describe(t *testing.T) { + id := RandomSchemaObjectIdentifier() + + defaultOpts := func() *DescribeSequenceOptions { + return &DescribeSequenceOptions{ + name: id, + } + } + + t.Run("validation: nil options", func(t *testing.T) { + opts := (*DescribeSequenceOptions)(nil) + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("validation: incorrect identifier", func(t *testing.T) { + opts := defaultOpts() + opts.name = NewSchemaObjectIdentifier("", "", "") + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("all options", func(t *testing.T) { + opts := defaultOpts() + assertOptsValidAndSQLEquals(t, opts, `DESCRIBE SEQUENCE %s`, id.FullyQualifiedName()) + }) +} + +func TestSequences_Drop(t *testing.T) { + id := RandomSchemaObjectIdentifier() + + defaultOpts := func() *DropSequenceOptions { + return &DropSequenceOptions{ + name: id, + } + } + t.Run("validation: nil options", func(t *testing.T) { + var opts *DropSequenceOptions = nil + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("validation: incorrect identifier", func(t *testing.T) { + opts := defaultOpts() + opts.name = NewSchemaObjectIdentifier("", "", "") + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("all options", func(t *testing.T) { + opts := defaultOpts() + opts.IfExists = Bool(true) + opts.Constraint = &SequenceConstraint{ + Cascade: Bool(true), + } + assertOptsValidAndSQLEquals(t, opts, `DROP SEQUENCE IF EXISTS %s CASCADE`, id.FullyQualifiedName()) + }) +} diff --git a/pkg/sdk/sequences_impl_gen.go b/pkg/sdk/sequences_impl_gen.go new file mode 100644 index 0000000000..4cac023003 --- /dev/null +++ b/pkg/sdk/sequences_impl_gen.go @@ -0,0 +1,148 @@ +package sdk + +import ( + "context" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" +) + +var _ Sequences = (*sequences)(nil) + +type sequences struct { + client *Client +} + +func (v *sequences) Create(ctx context.Context, request *CreateSequenceRequest) error { + opts := request.toOpts() + return validateAndExec(v.client, ctx, opts) +} + +func (v *sequences) Alter(ctx context.Context, request *AlterSequenceRequest) error { + opts := request.toOpts() + return validateAndExec(v.client, ctx, opts) +} + +func (v *sequences) Show(ctx context.Context, request *ShowSequenceRequest) ([]Sequence, error) { + opts := request.toOpts() + dbRows, err := validateAndQuery[sequenceRow](v.client, ctx, opts) + if err != nil { + return nil, err + } + resultList := convertRows[sequenceRow, Sequence](dbRows) + return resultList, nil +} + +func (v *sequences) ShowByID(ctx context.Context, id SchemaObjectIdentifier) (*Sequence, error) { + request := NewShowSequenceRequest().WithIn(&In{Schema: NewDatabaseObjectIdentifier(id.DatabaseName(), id.SchemaName())}).WithLike(&Like{String(id.Name())}) + sequences, err := v.Show(ctx, request) + if err != nil { + return nil, err + } + return collections.FindOne(sequences, func(r Sequence) bool { return r.Name == id.Name() }) +} + +func (v *sequences) Describe(ctx context.Context, id SchemaObjectIdentifier) (*SequenceDetail, error) { + opts := &DescribeSequenceOptions{ + name: id, + } + result, err := validateAndQueryOne[sequenceDetailRow](v.client, ctx, opts) + if err != nil { + return nil, err + } + return result.convert(), nil +} + +func (v *sequences) Drop(ctx context.Context, request *DropSequenceRequest) error { + opts := request.toOpts() + return validateAndExec(v.client, ctx, opts) +} + +func (r *CreateSequenceRequest) toOpts() *CreateSequenceOptions { + opts := &CreateSequenceOptions{ + OrReplace: r.OrReplace, + IfNotExists: r.IfNotExists, + name: r.name, + Start: r.Start, + Increment: r.Increment, + ValuesBehavior: r.ValuesBehavior, + Comment: r.Comment, + } + return opts +} + +func (r *AlterSequenceRequest) toOpts() *AlterSequenceOptions { + opts := &AlterSequenceOptions{ + IfExists: r.IfExists, + name: r.name, + RenameTo: r.RenameTo, + SetIncrement: r.SetIncrement, + + UnsetComment: r.UnsetComment, + } + if r.Set != nil { + opts.Set = &SequenceSet{ + ValuesBehavior: r.Set.ValuesBehavior, + Comment: r.Set.Comment, + } + } + return opts +} + +func (r *ShowSequenceRequest) toOpts() *ShowSequenceOptions { + opts := &ShowSequenceOptions{ + Like: r.Like, + In: r.In, + } + return opts +} + +func (r sequenceRow) convert() *Sequence { + return &Sequence{ + CreatedOn: r.CreatedOn, + Name: r.Name, + SchemaName: r.SchemaName, + DatabaseName: r.DatabaseName, + NextValue: r.NextValue, + Interval: r.Interval, + Owner: r.Owner, + OwnerRoleType: r.OwnerRoleType, + Comment: r.Comment, + Ordered: r.Ordered == "Y", + } +} + +func (r *DescribeSequenceRequest) toOpts() *DescribeSequenceOptions { + opts := &DescribeSequenceOptions{ + name: r.name, + } + return opts +} + +func (r sequenceDetailRow) convert() *SequenceDetail { + return &SequenceDetail{ + CreatedOn: r.CreatedOn, + Name: r.Name, + SchemaName: r.SchemaName, + DatabaseName: r.DatabaseName, + NextValue: r.NextValue, + Interval: r.Interval, + Owner: r.Owner, + OwnerRoleType: r.OwnerRoleType, + Comment: r.Comment, + Ordered: r.Ordered == "Y", + } +} + +func (r *DropSequenceRequest) toOpts() *DropSequenceOptions { + opts := &DropSequenceOptions{ + IfExists: r.IfExists, + name: r.name, + } + if r.Constraint != nil { + opts.Constraint = &SequenceConstraint{ + Cascade: r.Constraint.Cascade, + Restrict: r.Constraint.Restrict, + } + } + return opts +} diff --git a/pkg/sdk/sequences_validations_gen.go b/pkg/sdk/sequences_validations_gen.go new file mode 100644 index 0000000000..c0bb99e9d1 --- /dev/null +++ b/pkg/sdk/sequences_validations_gen.go @@ -0,0 +1,75 @@ +package sdk + +var ( + _ validatable = new(CreateSequenceOptions) + _ validatable = new(AlterSequenceOptions) + _ validatable = new(ShowSequenceOptions) + _ validatable = new(DescribeSequenceOptions) + _ validatable = new(DropSequenceOptions) +) + +func (opts *CreateSequenceOptions) 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("CreateSequenceOptions", "OrReplace", "IfNotExists")) + } + return JoinErrors(errs...) +} + +func (opts *AlterSequenceOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + if !ValidObjectIdentifier(opts.name) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + if opts.RenameTo != nil && !ValidObjectIdentifier(opts.RenameTo) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + if !exactlyOneValueSet(opts.RenameTo, opts.SetIncrement, opts.Set, opts.UnsetComment) { + errs = append(errs, errExactlyOneOf("AlterSequenceOptions", "RenameTo", "SetIncrement", "Set", "UnsetComment")) + } + return JoinErrors(errs...) +} + +func (opts *ShowSequenceOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + return JoinErrors(errs...) +} + +func (opts *DescribeSequenceOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + if !ValidObjectIdentifier(opts.name) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + return JoinErrors(errs...) +} + +func (opts *DropSequenceOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + if !ValidObjectIdentifier(opts.name) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + if valueSet(opts.Constraint) { + if !exactlyOneValueSet(opts.Constraint.Cascade, opts.Constraint.Restrict) { + errs = append(errs, errExactlyOneOf("DropSequenceOptions.Constraint", "Cascade", "Restrict")) + } + } + return JoinErrors(errs...) +} diff --git a/pkg/sdk/testint/sequences_integration_test.go b/pkg/sdk/testint/sequences_integration_test.go new file mode 100644 index 0000000000..be1af06fa4 --- /dev/null +++ b/pkg/sdk/testint/sequences_integration_test.go @@ -0,0 +1,171 @@ +package testint + +import ( + "errors" + "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: `ALTER SEQUENCE [ IF EXISTS ] UNSET COMMENT` not works, and error: Syntax error: unexpected 'COMMENT'. (line 39) + */ + +func TestInt_Sequences(t *testing.T) { + client := testClient(t) + ctx := testContext(t) + + databaseTest, schemaTest := testDb(t), testSchema(t) + + cleanupSequenceHandle := func(t *testing.T, id sdk.SchemaObjectIdentifier) func() { + t.Helper() + return func() { + err := client.Sequences.Drop(ctx, sdk.NewDropSequenceRequest(id)) + if errors.Is(err, sdk.ErrObjectNotExistOrAuthorized) { + return + } + require.NoError(t, err) + } + } + + createSequenceHandle := func(t *testing.T) *sdk.Sequence { + t.Helper() + + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, random.StringN(4)) + sr := sdk.NewCreateSequenceRequest(id).WithStart(sdk.Int(1)).WithIncrement(sdk.Int(1)) + err := client.Sequences.Create(ctx, sr) + require.NoError(t, err) + t.Cleanup(cleanupSequenceHandle(t, id)) + + s, err := client.Sequences.ShowByID(ctx, id) + require.NoError(t, err) + return s + } + + assertSequence := func(t *testing.T, id sdk.SchemaObjectIdentifier, interval int, ordered bool, comment string) { + t.Helper() + + e, err := client.Sequences.ShowByID(ctx, id) + require.NoError(t, err) + require.NotEmpty(t, e.CreatedOn) + require.Equal(t, id.Name(), e.Name) + require.Equal(t, id.DatabaseName(), e.DatabaseName) + require.Equal(t, id.SchemaName(), e.SchemaName) + require.Equal(t, 1, e.NextValue) + require.Equal(t, interval, e.Interval) + require.Equal(t, "ACCOUNTADMIN", e.Owner) + require.Equal(t, "ROLE", e.OwnerRoleType) + require.Equal(t, comment, e.Comment) + require.Equal(t, ordered, e.Ordered) + } + + t.Run("create sequence", func(t *testing.T) { + name := random.StringN(4) + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, name) + + comment := random.StringN(4) + request := sdk.NewCreateSequenceRequest(id). + WithStart(sdk.Int(1)). + WithIncrement(sdk.Int(1)). + WithIfNotExists(sdk.Bool(true)). + WithValuesBehavior(sdk.ValuesBehaviorPointer(sdk.ValuesBehaviorOrder)). + WithComment(&comment) + err := client.Sequences.Create(ctx, request) + require.NoError(t, err) + t.Cleanup(cleanupSequenceHandle(t, id)) + assertSequence(t, id, 1, true, comment) + }) + + t.Run("show event table: without like", func(t *testing.T) { + e1 := createSequenceHandle(t) + e2 := createSequenceHandle(t) + + sequences, err := client.Sequences.Show(ctx, sdk.NewShowSequenceRequest()) + require.NoError(t, err) + require.Equal(t, 2, len(sequences)) + require.Contains(t, sequences, *e1) + require.Contains(t, sequences, *e2) + }) + + t.Run("show sequence: with like", func(t *testing.T) { + e1 := createSequenceHandle(t) + e2 := createSequenceHandle(t) + + sequences, err := client.Sequences.Show(ctx, sdk.NewShowSequenceRequest().WithLike(&sdk.Like{Pattern: &e1.Name})) + require.NoError(t, err) + require.Equal(t, 1, len(sequences)) + require.Contains(t, sequences, *e1) + require.NotContains(t, sequences, *e2) + }) + + t.Run("show sequence: no matches", func(t *testing.T) { + sequences, err := client.Sequences.Show(ctx, sdk.NewShowSequenceRequest().WithLike(&sdk.Like{Pattern: sdk.String("non-existent")})) + require.NoError(t, err) + require.Equal(t, 0, len(sequences)) + }) + + t.Run("describe sequence", func(t *testing.T) { + e := createSequenceHandle(t) + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, e.Name) + + details, err := client.Sequences.Describe(ctx, id) + require.NoError(t, err) + require.Equal(t, e.CreatedOn, details.CreatedOn) + require.Equal(t, e.Name, details.Name) + require.Equal(t, e.SchemaName, details.SchemaName) + require.Equal(t, e.DatabaseName, details.DatabaseName) + require.Equal(t, e.NextValue, details.NextValue) + require.Equal(t, e.Interval, details.Interval) + require.Equal(t, e.Owner, details.Owner) + require.Equal(t, e.OwnerRoleType, details.OwnerRoleType) + require.Equal(t, e.Comment, details.Comment) + require.Equal(t, e.Ordered, details.Ordered) + }) + + t.Run("alter sequence: set options", func(t *testing.T) { + e := createSequenceHandle(t) + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, e.Name) + + comment := random.StringN(4) + set := sdk.NewSequenceSetRequest().WithComment(&comment).WithValuesBehavior(sdk.ValuesBehaviorPointer(sdk.ValuesBehaviorNoOrder)) + err := client.Sequences.Alter(ctx, sdk.NewAlterSequenceRequest(id).WithSet(set)) + require.NoError(t, err) + + assertSequence(t, id, 1, false, comment) + }) + + t.Run("alter sequence: set increment", func(t *testing.T) { + e := createSequenceHandle(t) + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, e.Name) + + increment := 2 + err := client.Sequences.Alter(ctx, sdk.NewAlterSequenceRequest(id).WithSetIncrement(&increment)) + require.NoError(t, err) + assertSequence(t, id, 2, true, "") + }) + + t.Run("alter sequence: rename", func(t *testing.T) { + name := random.String() + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, name) + + err := client.Sequences.Create(ctx, sdk.NewCreateSequenceRequest(id)) + require.NoError(t, err) + nid := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, random.String()) + err = client.Sequences.Alter(ctx, sdk.NewAlterSequenceRequest(id).WithRenameTo(&nid)) + if err != nil { + t.Cleanup(cleanupSequenceHandle(t, id)) + } else { + t.Cleanup(cleanupSequenceHandle(t, nid)) + } + require.NoError(t, err) + + _, err = client.Sequences.ShowByID(ctx, id) + assert.ErrorIs(t, err, collections.ErrObjectNotFound) + _, err = client.Sequences.ShowByID(ctx, nid) + require.NoError(t, err) + }) +} diff --git a/pkg/sdk/testint/setup_test.go b/pkg/sdk/testint/setup_test.go index 2af6cbe572..2a0a8b5ee6 100644 --- a/pkg/sdk/testint/setup_test.go +++ b/pkg/sdk/testint/setup_test.go @@ -120,6 +120,7 @@ func (itc *integrationTestContext) initialize() error { if err != nil { return err } + secondaryClient, err := sdk.NewClient(config) if err != nil { return err