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
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)
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])
}
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")
}
}
@ -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])
}
}
}