Added unique constraint to table state

This commit is contained in:
Patrick O'brien 2016-07-16 05:09:32 +10:00
parent 4fcfcfe24c
commit b7a04e849c
7 changed files with 202 additions and 219 deletions

View file

@ -16,6 +16,7 @@ type Column struct {
DBType string
Default string
Nullable bool
Unique bool
}
// ColumnNames of the columns.

View file

@ -81,9 +81,15 @@ func (p *PostgresDriver) Columns(tableName string) ([]bdb.Column, error) {
var columns []bdb.Column
rows, err := p.dbConn.Query(`
select column_name, data_type, column_default, is_nullable
from information_schema.columns
where table_name=$1 and table_schema = 'public'
select column_name, data_type, column_default, is_nullable,
(
select cast(count(*) as bit) as is_unique
from information_schema.constraint_column_usage as ccu
inner join information_schema.table_constraints tc on ccu.constraint_name = tc.constraint_name
where ccu.column_name = c.column_name and tc.constraint_type = 'UNIQUE'
) as is_unique
from information_schema.columns as c
where table_name=$1 and table_schema = 'public';
`, tableName)
if err != nil {
@ -92,9 +98,10 @@ func (p *PostgresDriver) Columns(tableName string) ([]bdb.Column, error) {
defer rows.Close()
for rows.Next() {
var colName, colType, colDefault, Nullable string
var colName, colType, colDefault, nullable string
var unique bool
var defaultPtr *string
if err := rows.Scan(&colName, &colType, &defaultPtr, &Nullable); err != nil {
if err := rows.Scan(&colName, &colType, &defaultPtr, &nullable, &unique); err != nil {
return nil, fmt.Errorf("unable to scan for table %s: %s", tableName, err)
}
@ -108,7 +115,8 @@ func (p *PostgresDriver) Columns(tableName string) ([]bdb.Column, error) {
Name: colName,
DBType: colType,
Default: colDefault,
Nullable: Nullable == "YES",
Nullable: nullable == "YES",
Unique: unique,
}
columns = append(columns, column)
}

View file

@ -59,7 +59,7 @@ func Tables(db Interface, names ...string) ([]Table, error) {
// Relationships have a dependency on foreign key nullability.
for i := range tables {
tbl := &tables[i]
setForeignKeyNullability(tbl, tables)
setForeignKeyConstraints(tbl, tables)
}
for i := range tables {
tbl := &tables[i]
@ -93,14 +93,16 @@ func setIsJoinTable(t *Table) {
t.IsJoinTable = true
}
func setForeignKeyNullability(t *Table, tables []Table) {
func setForeignKeyConstraints(t *Table, tables []Table) {
for i, fkey := range t.FKeys {
localColumn := t.GetColumn(fkey.Column)
foreignTable := GetTable(tables, fkey.ForeignTable)
foreignColumn := foreignTable.GetColumn(fkey.ForeignColumn)
t.FKeys[i].Nullable = localColumn.Nullable
t.FKeys[i].Unique = localColumn.Unique
t.FKeys[i].ForeignColumnNullable = foreignColumn.Nullable
t.FKeys[i].ForeignColumnUnique = foreignColumn.Unique
}
}

View file

@ -125,22 +125,22 @@ func TestSetIsJoinTable(t *testing.T) {
}
}
func TestSetForeignKeyNullability(t *testing.T) {
func TestSetForeignKeyConstraints(t *testing.T) {
t.Parallel()
tables := []Table{
Table{
Name: "one",
Columns: []Column{
Column{Name: "id1", Type: "string", Nullable: false},
Column{Name: "id2", Type: "string", Nullable: true},
Column{Name: "id1", Type: "string", Nullable: false, Unique: false},
Column{Name: "id2", Type: "string", Nullable: true, Unique: true},
},
},
Table{
Name: "other",
Columns: []Column{
Column{Name: "one_id_1", Type: "string", Nullable: false},
Column{Name: "one_id_2", Type: "string", Nullable: true},
Column{Name: "one_id_1", Type: "string", Nullable: false, Unique: false},
Column{Name: "one_id_2", Type: "string", Nullable: true, Unique: true},
},
FKeys: []ForeignKey{
{Column: "one_id_1", ForeignTable: "one", ForeignColumn: "id1"},
@ -149,23 +149,35 @@ func TestSetForeignKeyNullability(t *testing.T) {
},
}
setForeignKeyNullability(&tables[0], tables)
setForeignKeyNullability(&tables[1], tables)
setForeignKeyConstraints(&tables[0], tables)
setForeignKeyConstraints(&tables[1], tables)
first := tables[1].FKeys[0]
second := tables[1].FKeys[1]
if first.Nullable {
t.Error("should not be nullable")
}
if first.Unique {
t.Error("should not be unique")
}
if first.ForeignColumnNullable {
t.Error("should be nullable")
}
if first.ForeignColumnUnique {
t.Error("should be unique")
}
if !second.Nullable {
t.Error("should be nullable")
}
if !second.Unique {
t.Error("should be unique")
}
if !second.ForeignColumnNullable {
t.Error("should be nullable")
}
if !second.ForeignColumnUnique {
t.Error("should be unique")
}
}
func TestSetRelationships(t *testing.T) {

View file

@ -19,10 +19,12 @@ type ForeignKey struct {
Name string
Column string
Nullable bool
Unique bool
ForeignTable string
ForeignColumn string
ForeignColumnNullable bool
ForeignColumnUnique bool
}
// SQLColumnDef formats a column name and type like an SQL column definition.

View file

@ -6,17 +6,23 @@ package bdb
type ToManyRelationship struct {
Column string
Nullable bool
Unique bool
ForeignTable string
ForeignColumn string
ForeignColumnNullable bool
ForeignColumnUnique bool
ToJoinTable bool
JoinTable string
JoinLocalColumn string
JoinLocalColumnNullable bool
JoinLocalColumnUnique bool
ToJoinTable bool
JoinTable string
JoinLocalColumn string
JoinLocalColumnNullable bool
JoinForeignColumn string
JoinForeignColumnNullable bool
JoinForeignColumnUnique bool
}
// ToManyRelationships relationship lookups
@ -53,9 +59,11 @@ func buildRelationship(localTable Table, foreignKey ForeignKey, foreignTable Tab
return ToManyRelationship{
Column: foreignKey.ForeignColumn,
Nullable: col.Nullable,
Unique: col.Unique,
ForeignTable: foreignTable.Name,
ForeignColumn: foreignKey.Column,
ForeignColumnNullable: foreignKey.Nullable,
ForeignColumnUnique: foreignKey.Unique,
ToJoinTable: false,
}
}
@ -64,6 +72,7 @@ func buildRelationship(localTable Table, foreignKey ForeignKey, foreignTable Tab
relationship := ToManyRelationship{
Column: foreignKey.ForeignColumn,
Nullable: col.Nullable,
Unique: col.Unique,
ToJoinTable: true,
JoinTable: foreignTable.Name,
}
@ -72,15 +81,18 @@ func buildRelationship(localTable Table, foreignKey ForeignKey, foreignTable Tab
if fk.ForeignTable != localTable.Name {
relationship.JoinForeignColumn = fk.Column
relationship.JoinForeignColumnNullable = fk.Nullable
relationship.JoinForeignColumnUnique = fk.Unique
foreignTable := GetTable(tables, fk.ForeignTable)
foreignCol := foreignTable.GetColumn(fk.ForeignColumn)
relationship.ForeignTable = fk.ForeignTable
relationship.ForeignColumn = fk.ForeignColumn
relationship.ForeignColumnNullable = foreignCol.Nullable
relationship.ForeignColumnUnique = foreignCol.Unique
} else {
relationship.JoinLocalColumn = fk.Column
relationship.JoinLocalColumnNullable = fk.Nullable
relationship.JoinLocalColumnUnique = fk.Unique
}
}

View file

@ -1,6 +1,9 @@
package bdb
import "testing"
import (
"reflect"
"testing"
)
func TestToManyRelationships(t *testing.T) {
t.Parallel()
@ -46,103 +49,75 @@ func TestToManyRelationships(t *testing.T) {
}
relationships := ToManyRelationships("users", tables)
expected := []ToManyRelationship{
ToManyRelationship{
Column: "id",
Nullable: false,
Unique: false,
ForeignTable: "videos",
ForeignColumn: "user_id",
ForeignColumnNullable: false,
ForeignColumnUnique: false,
ToJoinTable: false,
},
ToManyRelationship{
Column: "id",
Nullable: false,
Unique: false,
ForeignTable: "notifications",
ForeignColumn: "user_id",
ForeignColumnNullable: false,
ForeignColumnUnique: false,
ToJoinTable: false,
},
ToManyRelationship{
Column: "id",
Nullable: false,
Unique: false,
ForeignTable: "notifications",
ForeignColumn: "source_id",
ForeignColumnNullable: false,
ForeignColumnUnique: false,
ToJoinTable: false,
},
ToManyRelationship{
Column: "id",
Nullable: false,
Unique: false,
ForeignTable: "videos",
ForeignColumn: "id",
ForeignColumnNullable: false,
ForeignColumnUnique: false,
ToJoinTable: true,
JoinTable: "users_video_tags",
JoinLocalColumn: "user_id",
JoinLocalColumnNullable: false,
JoinLocalColumnUnique: false,
JoinForeignColumn: "video_id",
JoinForeignColumnNullable: false,
JoinForeignColumnUnique: false,
},
}
if len(relationships) != 4 {
t.Error("wrong # of relationships:", len(relationships))
}
r := relationships[0]
if r.Column != "id" {
t.Error("wrong local column:", r.Column)
}
if r.Nullable {
t.Error("should not be nullable")
}
if r.ForeignTable != "videos" {
t.Error("wrong foreign table:", r.ForeignTable)
}
if r.ForeignColumn != "user_id" {
t.Error("wrong foreign column:", r.ForeignColumn)
}
if r.ForeignColumnNullable {
t.Error("should not be nullable")
}
if r.ToJoinTable {
t.Error("not a join table")
}
r = relationships[1]
if r.Column != "id" {
t.Error("wrong local column:", r.Column)
}
if r.Nullable {
t.Error("should not be nullable")
}
if r.ForeignTable != "notifications" {
t.Error("wrong foreign table:", r.ForeignTable)
}
if r.ForeignColumn != "user_id" {
t.Error("wrong foreign column:", r.ForeignColumn)
}
if r.ForeignColumnNullable {
t.Error("should not be nullable")
}
if r.ToJoinTable {
t.Error("not a join table")
}
r = relationships[2]
if r.Column != "id" {
t.Error("wrong local column:", r.Column)
}
if r.Nullable {
t.Error("should not be nullable")
}
if r.ForeignTable != "notifications" {
t.Error("wrong foreign table:", r.ForeignTable)
}
if r.ForeignColumn != "source_id" {
t.Error("wrong foreign column:", r.ForeignColumn)
}
if r.ForeignColumnNullable {
t.Error("should not be nullable")
}
if r.ToJoinTable {
t.Error("not a join table")
}
r = relationships[3]
if r.Column != "id" {
t.Error("wrong local column:", r.Column)
}
if r.Nullable {
t.Error("should not be nullable")
}
if r.ForeignColumn != "id" {
t.Error("wrong foreign column:", r.Column)
}
if r.ForeignColumnNullable {
t.Error("should not be nullable")
}
if r.ForeignTable != "videos" {
t.Error("wrong foreign table:", r.ForeignTable)
}
if r.JoinTable != "users_video_tags" {
t.Error("wrong join table:", r.ForeignTable)
}
if r.JoinLocalColumn != "user_id" {
t.Error("wrong local join column:", r.JoinLocalColumn)
}
if r.JoinLocalColumnNullable {
t.Error("should not be nullable")
}
if r.JoinForeignColumn != "video_id" {
t.Error("wrong foreign join column:", r.JoinForeignColumn)
}
if r.JoinForeignColumnNullable {
t.Error("should not be nullable")
}
if !r.ToJoinTable {
t.Error("expected a join table")
for i, v := range relationships {
if !reflect.DeepEqual(v, expected[i]) {
t.Errorf("[%d] Mismatch between relationships:\n\n%#v\n\n%#v\n\n", i, v, expected[i])
}
}
}
@ -150,41 +125,41 @@ func TestToManyRelationshipsNull(t *testing.T) {
t.Parallel()
tables := []Table{
Table{Name: "users", Columns: []Column{{Name: "id", Nullable: true}}},
Table{Name: "contests", Columns: []Column{{Name: "id", Nullable: true}}},
Table{Name: "users", Columns: []Column{{Name: "id", Nullable: true, Unique: true}}},
Table{Name: "contests", Columns: []Column{{Name: "id", Nullable: true, Unique: true}}},
Table{
Name: "videos",
Columns: []Column{
{Name: "id", Nullable: true},
{Name: "user_id", Nullable: true},
{Name: "contest_id", Nullable: true},
{Name: "id", Nullable: true, Unique: true},
{Name: "user_id", Nullable: true, Unique: true},
{Name: "contest_id", Nullable: true, Unique: true},
},
FKeys: []ForeignKey{
{Name: "videos_user_id_fk", Column: "user_id", ForeignTable: "users", ForeignColumn: "id", Nullable: true},
{Name: "videos_contest_id_fk", Column: "contest_id", ForeignTable: "contests", ForeignColumn: "id", Nullable: true},
{Name: "videos_user_id_fk", Column: "user_id", ForeignTable: "users", ForeignColumn: "id", Nullable: true, Unique: true},
{Name: "videos_contest_id_fk", Column: "contest_id", ForeignTable: "contests", ForeignColumn: "id", Nullable: true, Unique: true},
},
},
Table{
Name: "notifications",
Columns: []Column{
{Name: "user_id", Nullable: true},
{Name: "source_id", Nullable: true},
{Name: "user_id", Nullable: true, Unique: true},
{Name: "source_id", Nullable: true, Unique: true},
},
FKeys: []ForeignKey{
{Name: "notifications_user_id_fk", Column: "user_id", ForeignTable: "users", ForeignColumn: "id", Nullable: true},
{Name: "notifications_source_id_fk", Column: "source_id", ForeignTable: "users", ForeignColumn: "id", Nullable: true},
{Name: "notifications_user_id_fk", Column: "user_id", ForeignTable: "users", ForeignColumn: "id", Nullable: true, Unique: true},
{Name: "notifications_source_id_fk", Column: "source_id", ForeignTable: "users", ForeignColumn: "id", Nullable: true, Unique: true},
},
},
Table{
Name: "users_video_tags",
IsJoinTable: true,
Columns: []Column{
{Name: "user_id", Nullable: true},
{Name: "video_id", Nullable: true},
{Name: "user_id", Nullable: true, Unique: true},
{Name: "video_id", Nullable: true, Unique: true},
},
FKeys: []ForeignKey{
{Name: "user_id_fk", Column: "user_id", ForeignTable: "users", ForeignColumn: "id", Nullable: true},
{Name: "video_id_fk", Column: "video_id", ForeignTable: "videos", ForeignColumn: "id", Nullable: true},
{Name: "user_id_fk", Column: "user_id", ForeignTable: "users", ForeignColumn: "id", Nullable: true, Unique: true},
{Name: "video_id_fk", Column: "video_id", ForeignTable: "videos", ForeignColumn: "id", Nullable: true, Unique: true},
},
},
}
@ -194,98 +169,69 @@ func TestToManyRelationshipsNull(t *testing.T) {
t.Error("wrong # of relationships:", len(relationships))
}
r := relationships[0]
if r.Column != "id" {
t.Error("wrong local column:", r.Column)
}
if !r.Nullable {
t.Error("should be nullable")
}
if r.ForeignTable != "videos" {
t.Error("wrong foreign table:", r.ForeignTable)
}
if r.ForeignColumn != "user_id" {
t.Error("wrong foreign column:", r.ForeignColumn)
}
if !r.ForeignColumnNullable {
t.Error("should be nullable")
}
if r.ToJoinTable {
t.Error("not a join table")
expected := []ToManyRelationship{
ToManyRelationship{
Column: "id",
Nullable: true,
Unique: true,
ForeignTable: "videos",
ForeignColumn: "user_id",
ForeignColumnNullable: true,
ForeignColumnUnique: true,
ToJoinTable: false,
},
ToManyRelationship{
Column: "id",
Nullable: true,
Unique: true,
ForeignTable: "notifications",
ForeignColumn: "user_id",
ForeignColumnNullable: true,
ForeignColumnUnique: true,
ToJoinTable: false,
},
ToManyRelationship{
Column: "id",
Nullable: true,
Unique: true,
ForeignTable: "notifications",
ForeignColumn: "source_id",
ForeignColumnNullable: true,
ForeignColumnUnique: true,
ToJoinTable: false,
},
ToManyRelationship{
Column: "id",
Nullable: true,
Unique: true,
ForeignTable: "videos",
ForeignColumn: "id",
ForeignColumnNullable: true,
ForeignColumnUnique: true,
ToJoinTable: true,
JoinTable: "users_video_tags",
JoinLocalColumn: "user_id",
JoinLocalColumnNullable: true,
JoinLocalColumnUnique: true,
JoinForeignColumn: "video_id",
JoinForeignColumnNullable: true,
JoinForeignColumnUnique: true,
},
}
r = relationships[1]
if r.Column != "id" {
t.Error("wrong local column:", r.Column)
}
if !r.Nullable {
t.Error("should be nullable")
}
if r.ForeignTable != "notifications" {
t.Error("wrong foreign table:", r.ForeignTable)
}
if r.ForeignColumn != "user_id" {
t.Error("wrong foreign column:", r.ForeignColumn)
}
if !r.ForeignColumnNullable {
t.Error("should be nullable")
}
if r.ToJoinTable {
t.Error("not a join table")
}
r = relationships[2]
if r.Column != "id" {
t.Error("wrong local column:", r.Column)
}
if !r.Nullable {
t.Error("should be nullable")
}
if r.ForeignTable != "notifications" {
t.Error("wrong foreign table:", r.ForeignTable)
}
if r.ForeignColumn != "source_id" {
t.Error("wrong foreign column:", r.ForeignColumn)
}
if !r.ForeignColumnNullable {
t.Error("should be nullable")
}
if r.ToJoinTable {
t.Error("not a join table")
}
r = relationships[3]
if r.Column != "id" {
t.Error("wrong local column:", r.Column)
}
if !r.Nullable {
t.Error("should be nullable")
}
if r.ForeignColumn != "id" {
t.Error("wrong foreign column:", r.Column)
}
if !r.ForeignColumnNullable {
t.Error("should be nullable")
}
if r.ForeignTable != "videos" {
t.Error("wrong foreign table:", r.ForeignTable)
}
if r.JoinTable != "users_video_tags" {
t.Error("wrong join table:", r.ForeignTable)
}
if r.JoinLocalColumn != "user_id" {
t.Error("wrong local join column:", r.JoinLocalColumn)
}
if !r.JoinLocalColumnNullable {
t.Error("should be nullable")
}
if r.JoinForeignColumn != "video_id" {
t.Error("wrong foreign join column:", r.JoinForeignColumn)
}
if !r.JoinForeignColumnNullable {
t.Error("should be nullable")
}
if !r.ToJoinTable {
t.Error("expected a join table")
for i, v := range relationships {
if !reflect.DeepEqual(v, expected[i]) {
t.Errorf("[%d] Mismatch between relationships null:\n\n%#v\n\n%#v\n\n", i, v, expected[i])
}
}
}