Skip to content

Commit 6d99fb3

Browse files
committed
Simplified and improved the constraints queries (primary key, unique constraint, foreign key)
1 parent 1e0da58 commit 6d99fb3

5 files changed

Lines changed: 78 additions & 168 deletions

File tree

column.go

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import "fmt"
44
import "strconv"
5+
import "strings"
56
import "database/sql"
67
import "github.com/joncrlsn/pgutil"
78

@@ -45,10 +46,9 @@ func (c ColumnSchema) Compare(obj interface{}) int {
4546
// Add returns SQL to add the column
4647
func (c ColumnSchema) Add() {
4748
if c.row["data_type"] == "character varying" {
48-
maxLength := c.row["character_maximum_length"]
49-
if maxLength == "null" {
50-
fmt.Println("-- WARNING: varchar column has no maximum length. Setting to 1024")
51-
maxLength = "1024"
49+
maxLength, valid := getMaxLength(c.row["character_maximum_length"])
50+
if !valid {
51+
fmt.Println("-- WARNING: varchar column has no maximum length. Set to 1024")
5252
}
5353
fmt.Printf("ALTER TABLE %s ADD COLUMN %s %s(%s)", c.row["table_name"], c.row["column_name"], c.row["data_type"], maxLength)
5454
} else {
@@ -80,32 +80,36 @@ func (c ColumnSchema) Change(obj interface{}) {
8080
// Detect column type change (mostly varchar length, or number size increase) (integer to/from bigint is OK)
8181
if c.row["data_type"] == c2.row["data_type"] {
8282
if c.row["data_type"] == "character varying" {
83-
if c.row["character_maximum_length"] != c2.row["character_maximum_length"] {
84-
max1 := c.row["character_maximum_length"]
85-
max2 := c2.row["character_maximum_length"]
86-
if max1 != "null" && max2 != "null" {
87-
cMax, err1 := strconv.Atoi(max1)
88-
check("converting string to int", err1)
89-
c2Max, err2 := strconv.Atoi(max2)
90-
check("converting string to int", err2)
91-
if cMax < c2Max {
92-
fmt.Println("-- WARNING: The next statement will shorten a character varying column.")
93-
}
94-
}
95-
maxLength := c.row["character_maximum_length"]
96-
if maxLength == "null" {
83+
max1, max1Valid := getMaxLength(c.row["character_maximum_length"])
84+
max2, max2Valid := getMaxLength(c2.row["character_maximum_length"])
85+
if (max1Valid || !max2Valid) && (max1 != c2.row["character_maximum_length"]) {
86+
if !max1Valid {
9787
fmt.Println("-- WARNING: varchar column has no maximum length. Setting to 1024")
98-
maxLength = "1024"
9988
}
100-
fmt.Printf("ALTER TABLE %s ALTER COLUMN %s TYPE character varying(%s);\n", c.row["table_name"], c.row["column_name"], maxLength)
89+
max1Int, err1 := strconv.Atoi(max1)
90+
check("converting string to int", err1)
91+
max2Int, err2 := strconv.Atoi(max2)
92+
check("converting string to int", err2)
93+
if max1Int < max2Int {
94+
fmt.Println("-- WARNING: The next statement will shorten a character varying column.")
95+
}
96+
fmt.Printf("ALTER TABLE %s ALTER COLUMN %s TYPE character varying(%s);\n", c.row["table_name"], c.row["column_name"], max1)
10197
}
10298
}
10399
}
104100

105101
// TODO: Code and test a column change from integer to bigint
106102
if c.row["data_type"] != c2.row["data_type"] {
107103
fmt.Printf("-- WARNING: This type change may not work well: (%s to %s).\n", c2.row["data_type"], c.row["data_type"])
108-
fmt.Printf("ALTER TABLE %s ALTER COLUMN %s TYPE %s;\n", c.row["table_name"], c.row["column_name"], c.row["data_type"])
104+
if strings.HasPrefix(c.row["data_type"], "character") {
105+
max1, max1Valid := getMaxLength(c.row["character_maximum_length"])
106+
if !max1Valid {
107+
fmt.Println("-- WARNING: varchar column has no maximum length. Setting to 1024")
108+
}
109+
fmt.Printf("ALTER TABLE %s ALTER COLUMN %s TYPE %s(%s);\n", c.row["table_name"], c.row["column_name"], c.row["data_type"], max1)
110+
} else {
111+
fmt.Printf("ALTER TABLE %s ALTER COLUMN %s TYPE %s;\n", c.row["table_name"], c.row["column_name"], c.row["data_type"])
112+
}
109113
}
110114

111115
// Detect column default change (or added, dropped)
@@ -153,3 +157,12 @@ ORDER by table_name, column_name COLLATE "C" ASC;`
153157
// Compare the columns
154158
doDiff(schema1, schema2)
155159
}
160+
161+
// getMaxLength returns the maximum length and whether or not it is valie
162+
func getMaxLength(maxLength string) (string, bool) {
163+
164+
if maxLength == "null" {
165+
return "1024", false
166+
}
167+
return maxLength, true
168+
}

foreignkey.go

Lines changed: 13 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,18 @@ func (c *ForeignKeySchema) Compare(obj interface{}) int {
3737
return val
3838
}
3939

40-
val = _compareString(c.row["constraint_name"], c2.row["constraint_name"])
40+
val = _compareString(c.row["constraint_def"], c2.row["constraint_def"])
4141
return val
4242
}
4343

4444
// Add returns SQL to add the foreign key
4545
func (c ForeignKeySchema) Add() {
46-
fmt.Printf("ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY(%s) REFERENCES %s(%s);\n", c.row["table_name"], c.row["constraint_name"], c.row["column_name"], c.row["foreign_table_name"], c.row["foreign_column_name"])
46+
fmt.Printf("ALTER TABLE %s ADD CONSTRAINT %s %s;\n", c.row["table_name"], c.row["constraint_name"], c.row["constraint_def"])
4747
}
4848

4949
// Drop returns SQL to drop the foreign key
5050
func (c ForeignKeySchema) Drop() {
51-
fmt.Printf("ALTER TABLE %s DROP CONSTRAINT IF EXISTS %s;\n", c.row["table_name"], c.row["constraint_name"])
51+
fmt.Printf("ALTER TABLE %s DROP CONSTRAINT %s; -- %s\n", c.row["table_name"], c.row["constraint_name"], c.row["constraint_def"])
5252
}
5353

5454
// Change handles the case where the table and foreign key name, but the details do not
@@ -57,59 +57,22 @@ func (c ForeignKeySchema) Change(obj interface{}) {
5757
if !ok {
5858
fmt.Println("Error!!!, change needs a ForeignKeySchema instance", c2)
5959
}
60-
//fmt.Printf("Change Table? %s - %s\n", c.row["table_name"], c2.row["table_name"])
60+
// There is no "changing" a foreign key. It either gets created or dropped (or left as-is).
6161
}
6262

6363
/*
64-
* Compare the columns in the two databases
64+
* Compare the foreign keys in the two databases. We do not recreate foreign keys if just the name is different.
6565
*/
6666
func compareForeignKeys(conn1 *sql.DB, conn2 *sql.DB) {
6767
sql := `
68-
SELECT tc.constraint_name
69-
, tc.table_name
70-
, kcu.column_name
71-
, ccu.table_name AS foreign_table_name
72-
, ccu.column_name AS foreign_column_name
73-
, rc.delete_rule AS on_delete
74-
, rc.update_rule AS on_update
75-
FROM information_schema.table_constraints AS tc
76-
JOIN information_schema.key_column_usage AS kcu
77-
ON (tc.constraint_name = kcu.constraint_name)
78-
JOIN information_schema.constraint_column_usage AS ccu
79-
ON (ccu.constraint_name = tc.constraint_name)
80-
JOIN information_schema.referential_constraints rc
81-
ON (tc.constraint_catalog = rc.constraint_catalog
82-
AND tc.constraint_schema = rc.constraint_schema
83-
AND tc.constraint_name = rc.constraint_name)
84-
WHERE tc.constraint_type = 'FOREIGN KEY'
85-
ORDER BY tc.table_name, tc.constraint_name COLLATE "C" ASC;
86-
87-
88-
-- Foreign Keys
89-
--SELECT
90-
-- con.relname AS child_table,
91-
-- att2.attname AS child_column,
92-
-- cl.relname AS parent_table,
93-
-- att.attname AS parent_column
94-
--FROM
95-
-- (SELECT
96-
-- unnest(con1.conkey) AS parent,
97-
-- unnest(con1.confkey) AS child,
98-
-- cl.relname,
99-
-- con1.confrelid,
100-
-- con1.conrelid
101-
-- FROM pg_class AS cl
102-
-- JOIN pg_namespace AS ns ON (cl.relnamespace = ns.oid)
103-
-- JOIN pg_constraint AS con1 ON (con1.conrelid = cl.oid)
104-
-- WHERE con1.contype = 'f'
105-
-- --AND cl.relname = 't_org'
106-
-- --AND ns.nspname = 'child_schema'
107-
-- ) con
108-
--JOIN pg_attribute AS att ON (att.attrelid = con.confrelid AND att.attnum = con.child)
109-
--JOIN pg_class AS cl ON (cl.oid = con.confrelid)
110-
--JOIN pg_attribute AS att2 ON (att2.attrelid = con.conrelid AND att2.attnum = con.parent)
111-
--ORDER BY con.relname, att2.attname;
112-
68+
SELECT c.conname AS constraint_name
69+
, c.contype AS constraint_type
70+
, cl.relname AS table_name
71+
, pg_catalog.pg_get_constraintdef(c.oid, true) as constraint_def
72+
FROM pg_catalog.pg_constraint c
73+
INNER JOIN pg_class AS cl ON (c.conrelid = cl.oid)
74+
WHERE c.contype = 'f'
75+
ORDER BY cl.relname::varchar, pg_catalog.pg_get_constraintdef(c.oid, true) COLLATE "C" ASC;
11376
`
11477

11578
rowChan1, _ := pgutil.QueryStrings(conn1, sql)

index.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func (c IndexSchema) Add() {
5252

5353
// Drop generates SQL to drop the index
5454
func (c IndexSchema) Drop() {
55-
fmt.Printf("DROP INDEX %s;\n", c.row["index_name"])
55+
fmt.Printf("DROP INDEX %s; -- %s ON (%s)\n", c.row["index_name"], c.row["table_name"], c.row["column_names"])
5656
}
5757

5858
// Change handles the case where the table and index name match, but the details do not

primarykey.go

Lines changed: 14 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,17 @@ func (c *PrimaryKeySchema) Compare(obj interface{}) int {
3535
return val
3636
}
3737

38-
return _compareString(c.row["constraint_name"], c2.row["constraint_name"])
38+
return _compareString(c.row["constraint_def"], c2.row["constraint_def"])
3939
}
4040

4141
// Add returns SQL to add the primary key
4242
func (c PrimaryKeySchema) Add() {
43-
// ALTER TABLE ONLY t_product ADD CONSTRAINT t_product_pkey PRIMARY KEY (product_id, seq_no);
44-
// ALTER TABLE ONLY t_product ADD CONSTRAINT t_product_pkey UNIQUE (product_id);
45-
fmt.Printf("ALTER TABLE %s ADD CONSTRAINT %s PRIMARY KEY (%s);\n", c.row["table_name"], c.row["constraint_name"], c.primaryKeyString())
43+
fmt.Printf("ALTER TABLE %s ADD CONSTRAINT %s %s;\n", c.row["table_name"], c.row["constraint_name"], c.row["constraint_def"])
4644
}
4745

4846
// Drop returns SQL to drop the foreign key
4947
func (c PrimaryKeySchema) Drop() {
50-
fmt.Printf("ALTER TABLE %s DROP CONSTRAINT IF EXISTS %s;\n", c.row["table_name"], c.row["constraint_name"])
48+
fmt.Printf("ALTER TABLE %s DROP CONSTRAINT %s; -- %s\n", c.row["table_name"], c.row["constraint_name"], c.row["constraint_def"])
5149
}
5250

5351
// Change handles the case where the table name matches, but the details do not
@@ -56,54 +54,23 @@ func (c PrimaryKeySchema) Change(obj interface{}) {
5654
if !ok {
5755
fmt.Println("Error!!!, change needs a PrimaryKeySchema instance", c2)
5856
}
59-
pk1 := c.primaryKeyString()
60-
pk2 := c.primaryKeyString()
61-
if pk1 != pk2 {
62-
fmt.Printf("-- Warning, primary key is different for table %s pk1:%s pk2:%s\n", c.row["table_name"], pk1, pk2)
63-
fmt.Printf("ALTER TABLE %s DROP CONSTRAINT %s;\n", c.row["table_name"], c2.row["constraint_name"])
64-
fmt.Printf("ALTER TABLE %s ADD CONSTRAINT %s PRIMARY KEY (%s);\n", c.row["table_name"], c.row["constraint_name"], c.primaryKeyString())
65-
}
66-
}
67-
68-
// primaryKeyString concatenates the primary key column names into one string.
69-
// It's possible this could be done with SQL, I just haven't figured it out yet
70-
func (c PrimaryKeySchema) primaryKeyString() string {
71-
pkey := ""
72-
for i := 1; i <= 5; i++ {
73-
colName := fmt.Sprintf("col%d", i)
74-
col := c.row[colName]
75-
//fmt.Printf("-- colName: %s val:'%s'\n", colName, col)
76-
if len(col) > 0 {
77-
if len(pkey) > 0 {
78-
pkey = pkey + ","
79-
}
80-
pkey = pkey + col
81-
}
82-
}
83-
return pkey
57+
// There is no "changing" a primary key. It either gets created or dropped (or left as-is).
8458
}
8559

8660
/*
87-
* Compare the primary keys in the two databases. This SQL can handle up to 5 columns
88-
* as part of the primary key
61+
* Compare the primary keys in the two databases. We do not recreate primary keys if just the name is different.
8962
*/
9063
func comparePrimaryKeys(conn1 *sql.DB, conn2 *sql.DB) {
9164
sql := `
92-
SELECT tc.table_name
93-
, kcu.constraint_name
94-
, MAX(CASE WHEN kcu.ordinal_position = 1 THEN kcu.column_name ELSE '' END) AS col1
95-
, MAX(CASE WHEN kcu.ordinal_position = 2 THEN kcu.column_name ELSE '' END) AS col2
96-
, MAX(CASE WHEN kcu.ordinal_position = 3 THEN kcu.column_name ELSE '' END) AS col3
97-
, MAX(CASE WHEN kcu.ordinal_position = 4 THEN kcu.column_name ELSE '' END) AS col4
98-
, MAX(CASE WHEN kcu.ordinal_position = 5 THEN kcu.column_name ELSE '' END) AS col5
99-
FROM information_schema.table_constraints AS tc
100-
LEFT JOIN information_schema.key_column_usage kcu
101-
ON tc.constraint_catalog = kcu.constraint_catalog
102-
AND tc.constraint_schema = kcu.constraint_schema
103-
AND tc.constraint_name = kcu.constraint_name
104-
WHERE tc.constraint_type = 'PRIMARY KEY'
105-
GROUP BY tc.table_name, kcu.constraint_name
106-
ORDER BY tc.table_name, kcu.constraint_name COLLATE "C" ASC;`
65+
SELECT c.conname AS constraint_name
66+
, c.contype AS constraint_type
67+
, cl.relname AS table_name
68+
, pg_catalog.pg_get_constraintdef(c.oid, true) as constraint_def
69+
FROM pg_catalog.pg_constraint c
70+
INNER JOIN pg_class AS cl ON (c.conrelid = cl.oid)
71+
WHERE c.contype = 'p'
72+
ORDER BY cl.relname::varchar, pg_catalog.pg_get_constraintdef(c.oid, true) COLLATE "C" ASC;
73+
`
10774

10875
rowChan1, _ := pgutil.QueryStrings(conn1, sql)
10976
rowChan2, _ := pgutil.QueryStrings(conn2, sql)

unique.go

Lines changed: 16 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,18 @@ func (c *UniqueSchema) Compare(obj interface{}) int {
3434
if val != 0 {
3535
return val
3636
}
37-
val = _compareString(c.row["constraint_name"], c2.row["constraint_name"])
37+
val = _compareString(c.row["constraint_def"], c2.row["constraint_def"])
3838
return val
3939
}
4040

41-
// Add returns SQL to add the primary key
41+
// Add returns SQL to add the unique constraint
4242
func (c UniqueSchema) Add() {
43-
// ALTER TABLE ONLY t_product ADD CONSTRAINT t_product_pkey PRIMARY KEY (product_id, seq_no);
44-
// ALTER TABLE ONLY t_product ADD CONSTRAINT t_product_pkey UNIQUE (product_id);
45-
fmt.Printf("ALTER TABLE %s ADD CONSTRAINT %s UNIQUE (%s);\n", c.row["table_name"], c.row["constraint_name"], c.uniqueColumnString())
43+
fmt.Printf("ALTER TABLE %s ADD CONSTRAINT %s %s;\n", c.row["table_name"], c.row["constraint_name"], c.row["constraint_def"])
4644
}
4745

48-
// Drop returns SQL to drop the foreign key
46+
// Drop returns SQL to drop the unique constraint
4947
func (c UniqueSchema) Drop() {
50-
fmt.Printf("ALTER TABLE %s DROP CONSTRAINT IF EXISTS %s;\n", c.row["table_name"], c.row["constraint_name"])
48+
fmt.Printf("ALTER TABLE %s DROP CONSTRAINT %s; -- %s\n", c.row["table_name"], c.row["constraint_name"], c.row["constraint_def"])
5149
}
5250

5351
// Change handles the case where the table name matches, but the details do not
@@ -56,54 +54,23 @@ func (c UniqueSchema) Change(obj interface{}) {
5654
if !ok {
5755
fmt.Println("Error!!!, change needs a UniqueSchema instance", c2)
5856
}
59-
pk1 := c.uniqueColumnString()
60-
pk2 := c.uniqueColumnString()
61-
if pk1 != pk2 {
62-
fmt.Printf("-- Warning, primary key is different for table %s pk1:%s pk2:%s\n", c.row["table_name"], pk1, pk2)
63-
fmt.Printf("ALTER TABLE %s DROP CONSTRAINT %s;\n", c.row["table_name"], c2.row["constraint_name"])
64-
fmt.Printf("ALTER TABLE %s ADD CONSTRAINT %s UNIQUE (%s);\n", c.row["table_name"], c.row["constraint_name"], c.uniqueColumnString())
65-
}
66-
}
67-
68-
// uniqueColumnString concatenates the primary key column names into one string.
69-
// It's possible this could be done with SQL, I just haven't figured it out yet
70-
func (c UniqueSchema) uniqueColumnString() string {
71-
pkey := ""
72-
for i := 1; i <= 5; i++ {
73-
colName := fmt.Sprintf("col%d", i)
74-
col := c.row[colName]
75-
//fmt.Printf("-- colName: %s val:'%s'\n", colName, col)
76-
if len(col) > 0 {
77-
if len(pkey) > 0 {
78-
pkey = pkey + ","
79-
}
80-
pkey = pkey + col
81-
}
82-
}
83-
return pkey
57+
// There is no "changing" a unique constraint. It either gets created or dropped (or left as-is).
8458
}
8559

8660
/*
87-
* Compare the primary keys in the two databases. This SQL can handle up to 5 columns
88-
* as part of the primary key
61+
* Compare the primary keys in the two databases. We do not recreate unique if just the name is different.
8962
*/
9063
func compareUniqueConstraints(conn1 *sql.DB, conn2 *sql.DB) {
9164
sql := `
92-
SELECT tc.table_name
93-
, kcu.constraint_name
94-
, MAX(CASE WHEN kcu.ordinal_position = 1 THEN kcu.column_name ELSE '' END) AS col1
95-
, MAX(CASE WHEN kcu.ordinal_position = 2 THEN kcu.column_name ELSE '' END) AS col2
96-
, MAX(CASE WHEN kcu.ordinal_position = 3 THEN kcu.column_name ELSE '' END) AS col3
97-
, MAX(CASE WHEN kcu.ordinal_position = 4 THEN kcu.column_name ELSE '' END) AS col4
98-
, MAX(CASE WHEN kcu.ordinal_position = 5 THEN kcu.column_name ELSE '' END) AS col5
99-
FROM information_schema.table_constraints AS tc
100-
LEFT JOIN information_schema.key_column_usage kcu
101-
ON tc.constraint_catalog = kcu.constraint_catalog
102-
AND tc.constraint_schema = kcu.constraint_schema
103-
AND tc.constraint_name = kcu.constraint_name
104-
WHERE tc.constraint_type = 'UNIQUE'
105-
GROUP BY tc.table_name, kcu.constraint_name
106-
ORDER BY tc.table_name, kcu.constraint_name COLLATE "C" ASC;`
65+
SELECT c.conname AS constraint_name
66+
, c.contype AS constraint_type
67+
, cl.relname AS table_name
68+
, pg_catalog.pg_get_constraintdef(c.oid, true) as constraint_def
69+
FROM pg_catalog.pg_constraint c
70+
INNER JOIN pg_class AS cl ON (c.conrelid = cl.oid)
71+
WHERE c.contype = 'u'
72+
ORDER BY cl.relname::varchar, pg_catalog.pg_get_constraintdef(c.oid, true) COLLATE "C" ASC;
73+
`
10774

10875
rowChan1, _ := pgutil.QueryStrings(conn1, sql)
10976
rowChan2, _ := pgutil.QueryStrings(conn2, sql)

0 commit comments

Comments
 (0)