Skip to content

Commit

Permalink
feat: Table Column Defaults (#631)
Browse files Browse the repository at this point in the history
  • Loading branch information
robbruce committed Aug 11, 2021
1 parent 3fb14cf commit bcda1d9
Show file tree
Hide file tree
Showing 14 changed files with 629 additions and 21 deletions.
1 change: 1 addition & 0 deletions docs/resources/sequence.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ resource "snowflake_sequence" "test_sequence" {

### Read-Only

- **fully_qualified_name** (String) The fully qualified name of the sequence.
- **next_value** (Number) The next value the sequence will provide.


21 changes: 21 additions & 0 deletions docs/resources/table.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ resource "snowflake_schema" "schema" {
data_retention_days = 1
}
resource "snowflake_sequence" "sequence" {
database = snowflake_schema.schema.database
schema = snowflake_schema.schema.name
name = "sequence"
}
resource "snowflake_table" "table" {
database = snowflake_schema.schema.database
schema = snowflake_schema.schema.name
Expand All @@ -32,6 +38,10 @@ resource "snowflake_table" "table" {
name = "id"
type = "int"
nullable = true
default {
sequence = snowflake_sequence.sequence.fully_qualified_name
}
}
column {
Expand Down Expand Up @@ -92,8 +102,19 @@ Required:
Optional:

- **comment** (String) Column comment
- **default** (Block List, Max: 1) Defines the column default value; note due to limitations of Snowflake's ALTER TABLE ADD/MODIFY COLUMN updates to default will not be applied (see [below for nested schema](#nestedblock--column--default))
- **nullable** (Boolean) Whether this column can contain null values. **Note**: Depending on your Snowflake version, the default value will not suffice if this column is used in a primary key constraint.

<a id="nestedblock--column--default"></a>
### Nested Schema for `column.default`

Optional:

- **constant** (String) The default constant value for the column
- **expression** (String) The default expression value for the column
- **sequence** (String) The default sequence to use for the column



<a id="nestedblock--primary_key"></a>
### Nested Schema for `primary_key`
Expand Down
10 changes: 10 additions & 0 deletions examples/resources/snowflake_table/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ resource "snowflake_schema" "schema" {
data_retention_days = 1
}

resource "snowflake_sequence" "sequence" {
database = snowflake_schema.schema.database
schema = snowflake_schema.schema.name
name = "sequence"
}

resource "snowflake_table" "table" {
database = snowflake_schema.schema.database
schema = snowflake_schema.schema.name
Expand All @@ -17,6 +23,10 @@ resource "snowflake_table" "table" {
name = "id"
type = "int"
nullable = true

default {
sequence = snowflake_sequence.sequence.fully_qualified_name
}
}

column {
Expand Down
13 changes: 12 additions & 1 deletion pkg/resources/sequence.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ var sequenceSchema = map[string]*schema.Schema{
Description: "The next value the sequence will provide.",
Computed: true,
},
"fully_qualified_name": {
Type: schema.TypeString,
Description: "The fully qualified name of the sequence.",
Computed: true,
},
}

var sequenceProperties = []string{"comment", "data_retention_time_in_days"}
Expand Down Expand Up @@ -95,7 +100,8 @@ func ReadSequence(d *schema.ResourceData, meta interface{}) error {
schema := d.Get("schema").(string)
name := d.Get("name").(string)

stmt := snowflake.Sequence(name, database, schema).Show()
seq := snowflake.Sequence(name, database, schema)
stmt := seq.Show()
row := snowflake.QueryRow(db, stmt)

sequence, err := snowflake.ScanSequence(row)
Expand Down Expand Up @@ -145,6 +151,11 @@ func ReadSequence(d *schema.ResourceData, meta interface{}) error {
return err
}

err = d.Set("fully_qualified_name", seq.Address())
if err != nil {
return err
}

d.SetId(fmt.Sprintf(`%v|%v|%v`, sequence.DBName.String, sequence.SchemaName.String, sequence.Name.String))
if err != nil {
return err
Expand Down
3 changes: 3 additions & 0 deletions pkg/resources/sequence_acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func TestAcc_Sequence(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_sequence.test_sequence", "name", accName),
resource.TestCheckResourceAttr("snowflake_sequence.test_sequence", "next_value", "1"),
resource.TestCheckResourceAttr("snowflake_sequence.test_sequence", "fully_qualified_name", fmt.Sprintf(`%v.%v.%v`, accName, accName, accName)),
),
},
// Set comment and rename
Expand All @@ -31,6 +32,7 @@ func TestAcc_Sequence(t *testing.T) {
resource.TestCheckResourceAttr("snowflake_sequence.test_sequence", "name", accRename),
resource.TestCheckResourceAttr("snowflake_sequence.test_sequence", "comment", "look at me I am a comment"),
resource.TestCheckResourceAttr("snowflake_sequence.test_sequence", "next_value", "1"),
resource.TestCheckResourceAttr("snowflake_sequence.test_sequence", "fully_qualified_name", fmt.Sprintf(`%v.%v.%v`, accName, accName, accRename)),
),
},
// Unset comment and set increment
Expand All @@ -41,6 +43,7 @@ func TestAcc_Sequence(t *testing.T) {
resource.TestCheckResourceAttr("snowflake_sequence.test_sequence", "comment", ""),
resource.TestCheckResourceAttr("snowflake_sequence.test_sequence", "next_value", "1"),
resource.TestCheckResourceAttr("snowflake_sequence.test_sequence", "increment", "32"),
resource.TestCheckResourceAttr("snowflake_sequence.test_sequence", "fully_qualified_name", fmt.Sprintf(`%v.%v.%v`, accName, accName, accName)),
),
},
},
Expand Down
1 change: 1 addition & 0 deletions pkg/resources/sequence_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ func TestSequenceRead(t *testing.T) {
r.Equal(25, d.Get("increment").(int))
r.Equal(5, d.Get("next_value").(int))
r.Equal("database|schema|good_name", d.Id())
r.Equal(`"database"."schema"."good_name"`, d.Get("fully_qualified_name").(string))
})
}

Expand Down
143 changes: 139 additions & 4 deletions pkg/resources/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,35 @@ var tableSchema = map[string]*schema.Schema{
Default: true,
Description: "Whether this column can contain null values. **Note**: Depending on your Snowflake version, the default value will not suffice if this column is used in a primary key constraint.",
},
"default": {
Type: schema.TypeList,
Optional: true,
Description: "Defines the column default value; note due to limitations of Snowflake's ALTER TABLE ADD/MODIFY COLUMN updates to default will not be applied",
MinItems: 1,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"constant": {
Type: schema.TypeString,
Optional: true,
Description: "The default constant value for the column",
// ConflictsWith: []string{".expression", ".sequence"}, - can't use, nor ExactlyOneOf due to column type being TypeList
},
"expression": {
Type: schema.TypeString,
Optional: true,
Description: "The default expression value for the column",
// ConflictsWith: []string{".constant", ".sequence"}, - can't use, nor ExactlyOneOf due to column type being TypeList
},
"sequence": {
Type: schema.TypeString,
Optional: true,
Description: "The default sequence to use for the column",
// ConflictsWith: []string{".constant", ".expression"}, - can't use, nor ExactlyOneOf due to column type being TypeList
},
},
},
},
"comment": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -183,16 +212,63 @@ func tableIDFromString(stringID string) (*tableID, error) {
return tableResult, nil
}

type columnDefault struct {
constant *string
expression *string
sequence *string
}

func (cd *columnDefault) toSnowflakeColumnDefault() *snowflake.ColumnDefault {
if cd.constant != nil {
return snowflake.NewColumnDefaultWithConstant(*cd.constant)
}

if cd.expression != nil {
return snowflake.NewColumnDefaultWithExpression(*cd.expression)
}

if cd.sequence != nil {
return snowflake.NewColumnDefaultWithSequence(*cd.sequence)
}

return nil
}

func (cd *columnDefault) _type() string {
if cd.constant != nil {
return "constant"
}

if cd.expression != nil {
return "expression"
}

if cd.sequence != nil {
return "sequence"
}

return "unknown"
}

type column struct {
name string
dataType string
nullable bool
_default *columnDefault
comment string
}

func (c column) toSnowflakeColumn() snowflake.Column {
sC := snowflake.Column{}
return *sC.WithName(c.name).WithType(c.dataType).WithNullable(c.nullable).WithComment(c.comment)
sC := &snowflake.Column{}

if c._default != nil {
sC = sC.WithDefault(c._default.toSnowflakeColumnDefault())
}

return *sC.WithName(c.name).
WithType(c.dataType).
WithNullable(c.nullable).
WithComment(c.comment)
}

type columns []column
Expand Down Expand Up @@ -228,20 +304,25 @@ type changedColumn struct {
newColumn column //our new column
changedDataType bool
changedNullConstraint bool
dropedDefault bool
changedComment bool
}

func (old columns) getChangedColumnProperties(new columns) (changed changedColumns) {
changed = changedColumns{}
for _, cO := range old {
for _, cN := range new {
changeColumn := changedColumn{cN, false, false, false}
changeColumn := changedColumn{cN, false, false, false, false}
if cO.name == cN.name && cO.dataType != cN.dataType {
changeColumn.changedDataType = true
}
if cO.name == cN.name && cO.nullable != cN.nullable {
changeColumn.changedNullConstraint = true
}
if cO.name == cN.name && cO._default != nil && cN._default == nil {
changeColumn.dropedDefault = true
}

if cO.name == cN.name && cO.comment != cN.comment {
changeColumn.changedComment = true
}
Expand All @@ -256,12 +337,48 @@ func (old columns) diffs(new columns) (removed columns, added columns, changed c
return old.getNewIn(new), new.getNewIn(old), old.getChangedColumnProperties(new)
}

func getColumnDefault(def map[string]interface{}) *columnDefault {
if c, ok := def["constant"]; ok {
if constant, ok := c.(string); ok && len(constant) > 0 {
return &columnDefault{
constant: &constant,
}
}
}

if e, ok := def["expression"]; ok {
if expr, ok := e.(string); ok && len(expr) > 0 {
return &columnDefault{
expression: &expr,
}
}
}

if s, ok := def["sequence"]; ok {
if seq := s.(string); ok && len(seq) > 0 {
return &columnDefault{
sequence: &seq,
}
}
}

return nil
}

func getColumn(from interface{}) (to column) {
c := from.(map[string]interface{})
var cd *columnDefault

_default := c["default"].([]interface{})
if len(_default) == 1 {
cd = getColumnDefault(_default[0].(map[string]interface{}))
}

return column{
name: c["name"].(string),
dataType: c["type"].(string),
nullable: c["nullable"].(bool),
_default: cd,
comment: c["comment"].(string),
}
}
Expand Down Expand Up @@ -466,7 +583,17 @@ func UpdateTable(d *schema.ResourceData, meta interface{}) error {
}
}
for _, cA := range added {
q := builder.AddColumn(cA.name, cA.dataType, cA.nullable, cA.comment)
var q string
if cA._default == nil {
q = builder.AddColumn(cA.name, cA.dataType, cA.nullable, nil, cA.comment)
} else {
if cA._default._type() != "constant" {
return fmt.Errorf("Failed to add column %v => Only adding a column as a constant is supported by Snowflake", cA.name)
}

q = builder.AddColumn(cA.name, cA.dataType, cA.nullable, cA._default.toSnowflakeColumnDefault(), cA.comment)
}

err := snowflake.Exec(db, q)
if err != nil {
return errors.Wrapf(err, "error adding column on %v", d.Id())
Expand All @@ -492,6 +619,14 @@ func UpdateTable(d *schema.ResourceData, meta interface{}) error {

}
}
if cA.dropedDefault {
q := builder.DropColumnDefault(cA.newColumn.name)
err := snowflake.Exec(db, q)
if err != nil {
return errors.Wrapf(err, "error changing property on %v", d.Id())

}
}
if cA.changedComment {
q := builder.ChangeColumnComment(cA.newColumn.name, cA.newColumn.comment)
err := snowflake.Exec(db, q)
Expand Down

0 comments on commit bcda1d9

Please sign in to comment.