Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: supports collation of table column #2496

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions pkg/resources/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ var tableSchema = map[string]*schema.Schema{
Default: "",
Description: "Masking policy to apply on column. It has to be a fully qualified name.",
},
"collate": {
sfc-gh-asawicki marked this conversation as resolved.
Show resolved Hide resolved
Type: schema.TypeString,
Optional: true,
Default: "",
Description: "Column collation, e.g. utf8",
},
},
},
},
Expand Down Expand Up @@ -255,6 +261,7 @@ type column struct {
identity *columnIdentity
comment string
maskingPolicy string
collate string
}

type columns []column
Expand All @@ -268,13 +275,14 @@ type changedColumn struct {
dropedDefault bool
changedComment bool
changedMaskingPolicy bool
changedCollate bool
}

func (c columns) getChangedColumnProperties(new columns) (changed changedColumns) {
changed = changedColumns{}
for _, cO := range c {
for _, cN := range new {
changeColumn := changedColumn{cN, false, false, false, false, false}
changeColumn := changedColumn{cN, false, false, false, false, false, false}
if cO.name == cN.name && cO.dataType != cN.dataType {
changeColumn.changedDataType = true
}
Expand All @@ -293,6 +301,10 @@ func (c columns) getChangedColumnProperties(new columns) (changed changedColumns
changeColumn.changedMaskingPolicy = true
}

if cO.name == cN.name && cO.collate != cN.collate {
changeColumn.changedCollate = true
}

changed = append(changed, changeColumn)
}
}
Expand Down Expand Up @@ -363,6 +375,7 @@ func getColumn(from interface{}) (to column) {
_default: cd,
identity: id,
comment: c["comment"].(string),
collate: c["collate"].(string),
maskingPolicy: c["masking_policy"].(string),
}
}
Expand Down Expand Up @@ -423,6 +436,10 @@ func getTableColumnRequest(from interface{}) *sdk.TableColumnRequest {
request.WithMaskingPolicy(sdk.NewColumnMaskingPolicyRequest(sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(maskingPolicy)))
}

if strings.Contains(_type, "CHAR") || _type == "STRING" || _type == "TEXT" {
sfc-gh-jcieslak marked this conversation as resolved.
Show resolved Hide resolved
request.WithCollate(sdk.String(c["collate"].(string)))
}

return request.
WithNotNull(sdk.Bool(!c["nullable"].(bool))).
WithComment(sdk.String(c["comment"].(string)))
Expand Down Expand Up @@ -470,6 +487,10 @@ func toColumnConfig(descriptions []sdk.TableColumnDetails) []any {
flat["comment"] = *td.Comment
}

if td.Collation != nil {
flat["collate"] = *td.Collation
}

if td.PolicyName != nil {
// TODO [SNOW-867240]: SHOW TABLE returns last part of id without double quotes... we have to quote it again. Move it to SDK.
flat["masking_policy"] = sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(*td.PolicyName).FullyQualifiedName()
Expand Down Expand Up @@ -786,14 +807,18 @@ func UpdateTable(d *schema.ResourceData, meta interface{}) error {
addRequest.WithComment(sdk.String(cA.comment))
}

if cA.collate != "" && strings.Contains(cA.dataType, "CHAR") || cA.dataType == "STRING" || cA.dataType == "TEXT" {
sfc-gh-jcieslak marked this conversation as resolved.
Show resolved Hide resolved
addRequest.WithCollate(sdk.String(cA.collate))
}

err := client.Tables.Alter(ctx, sdk.NewAlterTableRequest(id).WithColumnAction(sdk.NewTableColumnActionRequest().WithAdd(addRequest)))
if err != nil {
return fmt.Errorf("error adding column: %w", err)
}
}
for _, cA := range changed {
if cA.changedDataType {
err := client.Tables.Alter(ctx, sdk.NewAlterTableRequest(id).WithColumnAction(sdk.NewTableColumnActionRequest().WithAlter([]sdk.TableColumnAlterActionRequest{*sdk.NewTableColumnAlterActionRequest(fmt.Sprintf("\"%s\"", cA.newColumn.name)).WithType(sdk.Pointer(sdk.DataType(cA.newColumn.dataType)))})))
if cA.changedDataType || cA.changedCollate {
err := client.Tables.Alter(ctx, sdk.NewAlterTableRequest(id).WithColumnAction(sdk.NewTableColumnActionRequest().WithAlter([]sdk.TableColumnAlterActionRequest{*sdk.NewTableColumnAlterActionRequest(fmt.Sprintf("\"%s\"", cA.newColumn.name)).WithType(sdk.Pointer(sdk.DataType(cA.newColumn.dataType))).WithCollate(sdk.String(cA.newColumn.collate))})))
sfc-gh-jcieslak marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return fmt.Errorf("error changing property on %v: err %w", d.Id(), err)
}
Expand Down
108 changes: 108 additions & 0 deletions pkg/resources/table_acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1228,6 +1228,114 @@ resource "snowflake_table" "test_table" {
return fmt.Sprintf(s, name, databaseName, schemaName, name, databaseName, schemaName)
}

func TestAcc_TableCollate(t *testing.T) {
accName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))

resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories,
PreCheck: func() { acc.TestAccPreCheck(t) },
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.RequireAbove(tfversion.Version1_5_0),
},
CheckDestroy: testAccCheckTableDestroy,
Steps: []resource.TestStep{
{
Config: tableColumnWithCollate(accName, acc.TestDatabaseName, acc.TestSchemaName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_table.test_table", "name", accName),
resource.TestCheckResourceAttr("snowflake_table.test_table", "database", acc.TestDatabaseName),
resource.TestCheckResourceAttr("snowflake_table.test_table", "schema", acc.TestSchemaName),
resource.TestCheckResourceAttr("snowflake_table.test_table", "comment", "Terraform acceptance test"),
resource.TestCheckResourceAttr("snowflake_table.test_table", "column.#", "3"),
resource.TestCheckResourceAttr("snowflake_table.test_table", "column.0.name", "column1"),
resource.TestCheckResourceAttr("snowflake_table.test_table", "column.0.collate", "en"),
resource.TestCheckResourceAttr("snowflake_table.test_table", "column.1.name", "column2"),
resource.TestCheckResourceAttr("snowflake_table.test_table", "column.1.collate", ""),
resource.TestCheckResourceAttr("snowflake_table.test_table", "column.2.name", "column3"),
sfc-gh-jcieslak marked this conversation as resolved.
Show resolved Hide resolved
resource.TestCheckResourceAttr("snowflake_table.test_table", "column.2.collate", ""),
),
},
{
Config: alterTableColumnWithCollate(accName, acc.TestDatabaseName, acc.TestSchemaName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_table.test_table", "name", accName),
resource.TestCheckResourceAttr("snowflake_table.test_table", "database", acc.TestDatabaseName),
resource.TestCheckResourceAttr("snowflake_table.test_table", "schema", acc.TestSchemaName),
resource.TestCheckResourceAttr("snowflake_table.test_table", "comment", "Terraform acceptance test"),
resource.TestCheckResourceAttr("snowflake_table.test_table", "column.#", "4"),
resource.TestCheckResourceAttr("snowflake_table.test_table", "column.0.name", "column1"),
resource.TestCheckResourceAttr("snowflake_table.test_table", "column.0.collate", "en"),
resource.TestCheckResourceAttr("snowflake_table.test_table", "column.1.name", "column2"),
resource.TestCheckResourceAttr("snowflake_table.test_table", "column.1.collate", ""),
resource.TestCheckResourceAttr("snowflake_table.test_table", "column.2.name", "column3"),
resource.TestCheckResourceAttr("snowflake_table.test_table", "column.2.collate", ""),
resource.TestCheckResourceAttr("snowflake_table.test_table", "column.3.name", "column4"),
resource.TestCheckResourceAttr("snowflake_table.test_table", "column.3.collate", "utf8"),
),
},
},
})
}

func tableColumnWithCollate(name string, databaseName string, schemaName string) string {
s := `
resource "snowflake_table" "test_table" {
name = "%s"
database = "%s"
schema = "%s"
comment = "Terraform acceptance test"

column {
name = "column1"
type = "VARCHAR(100)"
collate = "en"
}
column {
name = "column2"
type = "VARCHAR(100)"
collate = ""
}
column {
name = "column3"
type = "VARCHAR(100)"
}
}
`
return fmt.Sprintf(s, name, databaseName, schemaName)
}

func alterTableColumnWithCollate(name string, databaseName string, schemaName string) string {
s := `
resource "snowflake_table" "test_table" {
name = "%s"
database = "%s"
schema = "%s"
comment = "Terraform acceptance test"

column {
name = "column1"
type = "VARCHAR(200)"
collate = "en"
}
column {
name = "column2"
type = "VARCHAR(200)"
collate = ""
}
column {
name = "column3"
type = "VARCHAR(200)"
}
column {
name = "column4"
type = "VARCHAR"
collate = "utf8"
}
}
`
return fmt.Sprintf(s, name, databaseName, schemaName)
}

func TestAcc_TableRename(t *testing.T) {
oldTableName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
newTableName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
Expand Down
23 changes: 21 additions & 2 deletions pkg/sdk/tables.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"fmt"
"regexp"
"strings"
)

Expand Down Expand Up @@ -266,6 +267,7 @@ type TableColumnAddAction struct {
IfNotExists *bool `ddl:"keyword" sql:"IF NOT EXISTS"`
Name string `ddl:"keyword"`
Type DataType `ddl:"keyword"`
Collate *string `ddl:"parameter,no_equals,single_quotes" sql:"COLLATE"`
DefaultValue *ColumnDefaultValue `ddl:"keyword"`
InlineConstraint *TableColumnAddInlineConstraint `ddl:"keyword"`
MaskingPolicy *ColumnMaskingPolicy `ddl:"keyword"`
Expand Down Expand Up @@ -294,11 +296,12 @@ type TableColumnAlterAction struct {
column bool `ddl:"static" sql:"COLUMN"`
Name string `ddl:"keyword"`

// One of
// One of (except Collate)
DropDefault *bool `ddl:"keyword" sql:"DROP DEFAULT"`
SetDefault *SequenceName `ddl:"parameter,no_equals" sql:"SET DEFAULT"`
NotNullConstraint *TableColumnNotNullConstraint
Type *DataType `ddl:"parameter,no_equals" sql:"SET DATA TYPE"`
Collate *string `ddl:"parameter,no_equals,single_quotes" sql:"COLLATE"`
Comment *string `ddl:"parameter,no_equals,single_quotes" sql:"COMMENT"`
UnsetComment *bool `ddl:"keyword" sql:"UNSET COMMENT"`
}
Expand Down Expand Up @@ -657,6 +660,7 @@ type TableColumnDetails struct {
Expression *string
Comment *string
PolicyName *string
Collation *string
}

// tableColumnDetailsRow based on https://docs.snowflake.com/en/sql-reference/sql/desc-table
Expand All @@ -675,13 +679,16 @@ type tableColumnDetailsRow struct {
}

func (r tableColumnDetailsRow) convert() *TableColumnDetails {
type_, collation := r.splitTypeAndCollation()

details := &TableColumnDetails{
Name: r.Name,
Type: r.Type,
Type: type_,
Kind: r.Kind,
IsNullable: r.IsNullable == "Y",
IsPrimary: r.IsPrimary == "Y",
IsUnique: r.IsUnique == "Y",
Collation: collation,
}
if r.Default.Valid {
details.Default = String(r.Default.String)
Expand All @@ -701,6 +708,18 @@ func (r tableColumnDetailsRow) convert() *TableColumnDetails {
return details
}

func (r tableColumnDetailsRow) splitTypeAndCollation() (DataType, *string) {
collateRegexp := regexp.MustCompile(`COLLATE +'([a-zA-Z0-9_-]*)'`)
matches := collateRegexp.FindStringSubmatch(string(r.Type))

if len(matches) == 2 {
collation := matches[1]
type_ := DataType(strings.TrimSpace(collateRegexp.ReplaceAllString(string(r.Type), "")))
return type_, &collation
}
return r.Type, nil
}

type describeTableStageOptions struct {
describeTable bool `ddl:"static" sql:"DESCRIBE TABLE"`
name SchemaObjectIdentifier `ddl:"identifier"`
Expand Down
2 changes: 2 additions & 0 deletions pkg/sdk/tables_dto.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ type TableColumnAddActionRequest struct {
With *bool
Tags []TagAssociation
Comment *string
Collate *string
}

type TableColumnAddInlineConstraintRequest struct {
Expand Down Expand Up @@ -400,6 +401,7 @@ type TableColumnAlterActionRequest struct {
Type *DataType
Comment *string
UnsetComment *bool
Collate *string
}

type TableColumnAlterSetMaskingPolicyActionRequest struct {
Expand Down
10 changes: 10 additions & 0 deletions pkg/sdk/tables_dto_generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -1192,6 +1192,11 @@ func (s *TableColumnAddActionRequest) WithComment(comment *string) *TableColumnA
return s
}

func (s *TableColumnAddActionRequest) WithCollate(collate *string) *TableColumnAddActionRequest {
s.Collate = collate
return s
}

func NewTableColumnAddInlineConstraintRequest() *TableColumnAddInlineConstraintRequest {
return &TableColumnAddInlineConstraintRequest{}
}
Expand Down Expand Up @@ -1273,6 +1278,11 @@ func (s *TableColumnAlterActionRequest) WithComment(comment *string) *TableColum
return s
}

func (s *TableColumnAlterActionRequest) WithCollate(collate *string) *TableColumnAlterActionRequest {
s.Collate = collate
return s
}

func (s *TableColumnAlterActionRequest) WithUnsetComment(unsetComment *bool) *TableColumnAlterActionRequest {
s.UnsetComment = unsetComment
return s
Expand Down
2 changes: 2 additions & 0 deletions pkg/sdk/tables_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ func (r *TableColumnActionRequest) toOpts() *TableColumnAction {
DefaultValue: defaultValue,
InlineConstraint: inlineConstraint,
Comment: r.Add.Comment,
Collate: r.Add.Collate,
},
}
}
Expand Down Expand Up @@ -422,6 +423,7 @@ func (r *TableColumnActionRequest) toOpts() *TableColumnAction {
NotNullConstraint: notNullConstraint,
Type: alterAction.Type,
Comment: alterAction.Comment,
Collate: alterAction.Collate,
UnsetComment: alterAction.UnsetComment,
})
}
Expand Down
Loading