Skip to content

Commit

Permalink
Merge pull request #17 from schemahero/char-var
Browse files Browse the repository at this point in the history
Support for character varying, move not notnullable to a pointer
  • Loading branch information
marccampbell committed May 21, 2019
2 parents 2c29d2a + 50c5fe2 commit 459924f
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 45 deletions.
5 changes: 4 additions & 1 deletion config/crds/schemas_v1alpha1_table.yaml
Expand Up @@ -45,10 +45,13 @@ spec:
properties:
constraints:
properties:
maxLength:
format: int64
type: integer
notNull:
type: boolean
required:
- notNull
- maxLength
type: object
default:
type: string
Expand Down
3 changes: 2 additions & 1 deletion pkg/apis/schemas/v1alpha1/table_types.go
Expand Up @@ -21,7 +21,8 @@ import (
)

type PostgresTableColumnConstraints struct {
NotNull bool `json:"notNull" yaml:"notNull"`
NotNull *bool `json:"notNull,omitempty" yaml:"notNull,omitempty"`
MaxLength *int64 `json:"maxLength,omitempt" yaml:"maxLength,omitempty"`
}

type PostgresTableColumn struct {
Expand Down
12 changes: 11 additions & 1 deletion pkg/apis/schemas/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 16 additions & 6 deletions pkg/controller/table/postgres.go
Expand Up @@ -10,6 +10,11 @@ import (
"github.com/schemahero/schemahero/pkg/database/postgres"
)

var (
trueValue = true
falseValue = false
)

func (r *ReconcileTable) deployPostgres(connection *databasesv1alpha1.PostgresConnection, tableName string, postgresTableSchema *schemasv1alpha1.PostgresTableSchema) error {
db, err := sql.Open("postgres", connection.URI.Value)
if err != nil {
Expand Down Expand Up @@ -68,17 +73,22 @@ func (r *ReconcileTable) deployPostgres(connection *databasesv1alpha1.PostgresCo
foundColumnNames = append(foundColumnNames, columnName)

existingColumn := postgres.Column{
Name: columnName,
DataType: dataType,
Constraints: &postgres.ColumnConstraints{
NotNull: isNullable == "NO",
},
Name: columnName,
DataType: dataType,
Constraints: &postgres.ColumnConstraints{},
}

if isNullable == "NO" {
existingColumn.Constraints.NotNull = &trueValue
} else {
existingColumn.Constraints.NotNull = &falseValue
}

if columnDefault.Valid {
existingColumn.ColumnDefault = &columnDefault.String
}
if charMaxLength.Valid {
existingColumn.CharMaxLength = &charMaxLength.Int64
existingColumn.Constraints.MaxLength = &charMaxLength.Int64
}

columnStatement, err := postgres.AlterColumnStatement(tableName, postgresTableSchema.Columns, &existingColumn)
Expand Down
6 changes: 3 additions & 3 deletions pkg/database/postgres/alter.go
Expand Up @@ -51,10 +51,10 @@ func AlterColumnStatement(tableName string, desiredColumns []*schemasv1alpha1.Po
}

if column.Constraints != nil {
if existingColumn.Constraints != nil {
if column.Constraints.NotNull && !existingColumn.Constraints.NotNull {
if existingColumn.Constraints != nil && column.Constraints.NotNull != nil {
if *column.Constraints.NotNull == true && *existingColumn.Constraints.NotNull == false {
columnStatement = fmt.Sprintf("%s set not null", columnStatement)
} else if !column.Constraints.NotNull && existingColumn.Constraints.NotNull {
} else if *column.Constraints.NotNull == false && *existingColumn.Constraints.NotNull == true {
columnStatement = fmt.Sprintf("%s drop not null", columnStatement)
}
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/database/postgres/alter_test.go
Expand Up @@ -81,7 +81,7 @@ func Test_AlterColumnStatment(t *testing.T) {
Name: "a",
Type: "integer",
Constraints: &schemasv1alpha1.PostgresTableColumnConstraints{
NotNull: true,
NotNull: &trueValue,
},
},
},
Expand All @@ -90,7 +90,7 @@ func Test_AlterColumnStatment(t *testing.T) {
DataType: "integer",
ColumnDefault: nil,
Constraints: &ColumnConstraints{
NotNull: false,
NotNull: &falseValue,
},
},
expectedStatement: `alter table "t" alter column "a" set not null`,
Expand All @@ -103,7 +103,7 @@ func Test_AlterColumnStatment(t *testing.T) {
Name: "a",
Type: "integer",
Constraints: &schemasv1alpha1.PostgresTableColumnConstraints{
NotNull: false,
NotNull: &falseValue,
},
},
},
Expand All @@ -112,7 +112,7 @@ func Test_AlterColumnStatment(t *testing.T) {
DataType: "integer",
ColumnDefault: nil,
Constraints: &ColumnConstraints{
NotNull: true,
NotNull: &trueValue,
},
},
expectedStatement: `alter table "t" alter column "a" drop not null`,
Expand Down
29 changes: 18 additions & 11 deletions pkg/database/postgres/column.go
Expand Up @@ -46,13 +46,13 @@ var unparameterizedColumnTypes = []string{
}

type ColumnConstraints struct {
NotNull bool
NotNull *bool
MaxLength *int64
}

type Column struct {
Name string
DataType string
CharMaxLength *int64
ColumnDefault *string
Constraints *ColumnConstraints
}
Expand All @@ -69,6 +69,7 @@ func maybeParseParameterizedColumnType(requestedType string) (string, *int64, er
// matchGroups := r.FindStringSubmatch(requestedType)
// masStr
// }

if strings.HasPrefix(requestedType, "character varying") {
columnType = "character varying"

Expand All @@ -85,7 +86,7 @@ func maybeParseParameterizedColumnType(requestedType string) (string, *int64, er
} else if strings.HasPrefix(requestedType, "timestamp") {
columnType = "timestamp"

withPrecisionWithoutTimeZone := regexp.MustCompile(`timestamp\s*\(\s*(?P<precision>.*)\s*\)without time zone`)
withPrecisionWithoutTimeZone := regexp.MustCompile(`timestamp\s*\(\s*(?P<precision>.*)\s*\)\s*without time zone`)
withPrecision := regexp.MustCompile(`timestamp\s*\(\s*(?P<precision>.*)\s*\)`)
withoutPrecisionWithoutTimeZone := regexp.MustCompile(`timestamp\s*without time zone`)
withoutPrecision := regexp.MustCompile(`timestamp\s*`)
Expand Down Expand Up @@ -163,7 +164,8 @@ func unaliasParameterizedColumnType(requestedType string) string {

return fmt.Sprintf("bit varying (%s)", matchGroups[1])
}
if strings.HasPrefix(requestedType, "char") {
if strings.HasPrefix(requestedType, "char ") || strings.HasPrefix(requestedType, "char(") ||
requestedType == "character" || requestedType == "char" {
r := regexp.MustCompile(`char\s*\((?P<len>\d*)\)`)

matchGroups := r.FindStringSubmatch(requestedType)
Expand Down Expand Up @@ -225,7 +227,8 @@ func unaliasParameterizedColumnType(requestedType string) string {

func PostgresColumnToSchemaColumn(column *Column) (*schemasv1alpha1.PostgresTableColumn, error) {
constraints := &schemasv1alpha1.PostgresTableColumnConstraints{
NotNull: column.Constraints.NotNull,
NotNull: column.Constraints.NotNull,
MaxLength: column.Constraints.MaxLength,
}

schemaColumn := &schemasv1alpha1.PostgresTableColumn{
Expand Down Expand Up @@ -271,10 +274,14 @@ func schemaColumnToPostgresColumn(schemaColumn *schemasv1alpha1.PostgresTableCol
return nil, err
}

if maxLength != nil {
column.Constraints = &ColumnConstraints{
MaxLength: maxLength,
}
}

if columnType != "" {
column.DataType = columnType
column.CharMaxLength = maxLength

return column, nil
}

Expand All @@ -295,12 +302,12 @@ func postgresColumnAsInsert(column *schemasv1alpha1.PostgresTableColumn) (string

formatted := fmt.Sprintf("%s %s", pq.QuoteIdentifier(column.Name), postgresColumn.DataType)

if postgresColumn.CharMaxLength != nil {
formatted = fmt.Sprintf("%s(%d)", formatted, *postgresColumn.CharMaxLength)
if postgresColumn.Constraints != nil && postgresColumn.Constraints.MaxLength != nil {
formatted = fmt.Sprintf("%s (%d)", formatted, *postgresColumn.Constraints.MaxLength)
}

if postgresColumn.Constraints != nil {
if postgresColumn.Constraints.NotNull {
if postgresColumn.Constraints != nil && postgresColumn.Constraints.NotNull != nil {
if *postgresColumn.Constraints.NotNull == true {
formatted = fmt.Sprintf("%s not null", formatted)
} else {
formatted = fmt.Sprintf("%s null", formatted)
Expand Down
78 changes: 66 additions & 12 deletions pkg/database/postgres/column_test.go
Expand Up @@ -43,18 +43,64 @@ func Test_unaliasParameterizedColumnType(t *testing.T) {
}

func Test_maybeParseParameterizedColumnType(t *testing.T) {
parameterizedTests := map[string]string{
"fake": "",
"timestamp": "timestamp",
"timestamp without time zone": "timestamp without time zone",
// "timestamp (01:02)": "timestamp (01:02)",
// "timestamp (01:02) without time zone": "timestamp (01:02) without time zone",
none := int64(-1)

tests := []struct {
name string
requestedType string
expectedColumnType string
expectedMaxLength int64
}{
{
name: "fake",
requestedType: "fake",
expectedColumnType: "",
expectedMaxLength: none,
},
{
name: "timestamp",
requestedType: "timestamp",
expectedColumnType: "timestamp",
expectedMaxLength: none,
},
{
name: "timestamp without time zone",
requestedType: "timestamp without time zone",
expectedColumnType: "timestamp without time zone",
expectedMaxLength: none,
},
{
name: "timestamp (01:02)",
requestedType: "timestamp (01:02)",
expectedColumnType: "timestamp (01:02)",
expectedMaxLength: none,
},
{
name: "timestamp (01:02) without time zone",
requestedType: "timestamp (01:02) without time zone",
expectedColumnType: "timestamp (01:02) without time zone",
expectedMaxLength: none,
},
{
name: "character varying (10)",
requestedType: "character varying (10)",
expectedColumnType: "character varying",
expectedMaxLength: int64(10),
},
}

for input, expectedOutput := range parameterizedTests {
t.Run(input, func(t *testing.T) {
output, _, _ := maybeParseParameterizedColumnType(input)
assert.Equal(t, expectedOutput, output)
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
columnType, maxLength, err := maybeParseParameterizedColumnType(test.requestedType)
req := require.New(t)
req.NoError(err)
assert.Equal(t, test.expectedColumnType, columnType)

if test.expectedMaxLength == none {
assert.Nil(t, maxLength)
} else {
assert.Equal(t, test.expectedMaxLength, *maxLength)
}
})
}
}
Expand Down Expand Up @@ -150,6 +196,14 @@ func Test_postgresColumnAsInsert(t *testing.T) {
},
expectedStatement: `"t" timestamp without time zone`,
},
{
name: "character varying (4)",
column: &schemasv1alpha1.PostgresTableColumn{
Name: "c",
Type: "character varying (4)",
},
expectedStatement: `"c" character varying (4)`,
},
}

for _, test := range tests {
Expand Down Expand Up @@ -186,7 +240,7 @@ func Test_InsertColumnStatement(t *testing.T) {
Name: "a",
Type: "integer",
Constraints: &schemasv1alpha1.PostgresTableColumnConstraints{
NotNull: true,
NotNull: &trueValue,
},
},
expectedStatement: `alter table "t" add column "a" integer not null`,
Expand All @@ -198,7 +252,7 @@ func Test_InsertColumnStatement(t *testing.T) {
Name: "a",
Type: "integer",
Constraints: &schemasv1alpha1.PostgresTableColumnConstraints{
NotNull: false,
NotNull: &falseValue,
},
},
expectedStatement: `alter table "t" add column "a" integer null`,
Expand Down
2 changes: 1 addition & 1 deletion pkg/database/postgres/create_test.go
Expand Up @@ -55,7 +55,7 @@ func Test_CreateTableStatement(t *testing.T) {
},
},
tableName: "composite_primary_key",
expectedStatement: `create table "composite_primary_key" ("one" integer, "two" integer, "three" character varying(255), primary key ("one", "two"))`,
expectedStatement: `create table "composite_primary_key" ("one" integer, "two" integer, "three" character varying (255), primary key ("one", "two"))`,
},
}

Expand Down
21 changes: 16 additions & 5 deletions pkg/database/postgres/tables.go
Expand Up @@ -6,6 +6,11 @@ import (
_ "github.com/lib/pq"
)

var (
trueValue = true
falseValue = false
)

func (pg *Postgres) ListTables() ([]string, error) {
query := "select table_name from information_schema.tables where table_catalog = $1 and table_schema = $2"

Expand Down Expand Up @@ -74,18 +79,24 @@ func (pg *Postgres) GetTableSchema(tableName string) ([]*Column, error) {
return nil, err
}

if isNullable == "NO" {
column.Constraints = &ColumnConstraints{
NotNull: &trueValue,
}
} else {
column.Constraints = &ColumnConstraints{
NotNull: &falseValue,
}
}

if maxLength.Valid {
column.CharMaxLength = &maxLength.Int64
column.Constraints.MaxLength = &maxLength.Int64
}

if columnDefault.Valid {
column.ColumnDefault = &columnDefault.String
}

column.Constraints = &ColumnConstraints{
NotNull: isNullable == "NO",
}

columns = append(columns, &column)
}

Expand Down

0 comments on commit 459924f

Please sign in to comment.