Skip to content

Commit 3510484

Browse files
committed
Sequences can now be added and dropped
1 parent eaa56d8 commit 3510484

6 files changed

Lines changed: 109 additions & 15 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# pgdiff - PostgreSQL schema diff
22

3-
Please note that this program never modifies any databases directly, and you are solely responsible for verifying the generated SQL *before* running it against your database.
3+
Please note that this program never modifies any database directly, and you are solely responsible for verifying the generated SQL *before* running it against your database. Now that you know about that, it should give you confidence that it is safe to try out and see what SQL gets generated.
44

55
Written in GoLang, this utility compares the schema between two PostgreSQL databases and generates alter statements to be *manually* run against the second database. Not everything in the schema is compared, but the things considered important (at the moment) are: tables, columns (and their default values), foreign keys... and soon constraints and user roles.
66

column.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,12 @@ func (c ColumnSchema) Compare(obj interface{}) int {
4444
// Add returns SQL to add the column
4545
func (c ColumnSchema) Add() {
4646
if c.row["data_type"] == "character varying" {
47-
fmt.Printf("ALTER TABLE %s ADD COLUMN %s %s(%s)", c.row["table_name"], c.row["column_name"], c.row["data_type"], c.row["character_maximum_length"])
47+
maxLength := c.row["character_maximum_length"]
48+
if maxLength == "null" {
49+
fmt.Println("-- WARNING: varchar column has no maximum length. Setting to 1024")
50+
maxLength = "1024"
51+
}
52+
fmt.Printf("ALTER TABLE %s ADD COLUMN %s %s(%s)", c.row["table_name"], c.row["column_name"], c.row["data_type"], maxLength)
4853
} else {
4954
fmt.Printf("ALTER TABLE %s ADD COLUMN %s %s", c.row["table_name"], c.row["column_name"], c.row["data_type"])
5055
}
@@ -78,7 +83,12 @@ func (c ColumnSchema) Change(obj interface{}) {
7883
if c.row["character_maximum_length"] < c2.row["character_maximum_length"] {
7984
fmt.Println("-- WARNING: The next statement will shorten a character varying column.")
8085
}
81-
fmt.Printf("ALTER TABLE %s ALTER COLUMN %s TYPE character varying(%s);\n", c.row["table_name"], c.row["column_name"], c.row["character_maximum_length"])
86+
maxLength := c.row["character_maximum_length"]
87+
if maxLength == "null" {
88+
fmt.Println("-- WARNING: varchar column has no maximum length. Setting to 1024")
89+
maxLength = "1024"
90+
}
91+
fmt.Printf("ALTER TABLE %s ALTER COLUMN %s TYPE character varying(%s);\n", c.row["table_name"], c.row["column_name"], maxLength)
8292
}
8393
}
8494
}
@@ -119,7 +129,8 @@ SELECT table_name
119129
, column_default
120130
, character_maximum_length
121131
FROM information_schema.columns
122-
WHERE table_schema = 'public'
132+
WHERE table_schema = 'public'
133+
AND is_updatable = 'YES'
123134
ORDER by table_name, column_name;`
124135

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

foreignkey.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,18 @@ SELECT tc.constraint_name
7070
, kcu.column_name
7171
, ccu.table_name AS foreign_table_name
7272
, ccu.column_name AS foreign_column_name
73-
FROM
74-
information_schema.table_constraints AS tc
75-
JOIN information_schema.key_column_usage AS kcu
76-
ON tc.constraint_name = kcu.constraint_name
77-
JOIN information_schema.constraint_column_usage AS ccu
78-
ON ccu.constraint_name = tc.constraint_name
79-
WHERE constraint_type = 'FOREIGN KEY'
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'
8085
ORDER BY tc.table_name, tc.constraint_name; `
8186

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

pgdiff.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,12 @@ func main() {
5454
conn2, err := dbInfo2.Open()
5555
check("opening database", err)
5656

57-
// This section will be improved so that you do not need to choose the type
57+
// This section needs to be improved so that you do not need to choose the type
5858
// of alter statements to generate. Rather, all should be generated in the
5959
// proper order.
60-
if schemaType == "TABLE" {
60+
if schemaType == "SEQUENCE" {
61+
compareSequences(conn1, conn2)
62+
} else if schemaType == "TABLE" {
6163
compareTables(conn1, conn2)
6264
} else if schemaType == "COLUMN" {
6365
compareColumns(conn1, conn2)

sequence.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package main
2+
3+
import "fmt"
4+
import "database/sql"
5+
import "github.com/joncrlsn/pgutil"
6+
7+
// SequenceSchema holds a channel streaming table information from one of the databases as well as
8+
// a reference to the current row of data we're viewing.
9+
//
10+
// SequenceSchema implements the Schema interface defined in pgdiff.go
11+
type SequenceSchema struct {
12+
channel chan map[string]string
13+
row map[string]string
14+
done bool
15+
}
16+
17+
// NextRow reads from the channel and tells you if there are (probably) more or not
18+
func (c *SequenceSchema) NextRow() bool {
19+
c.row = <-c.channel
20+
if len(c.row) == 0 {
21+
c.done = true
22+
}
23+
return !c.done
24+
}
25+
26+
// Compare tells you, in one pass, whether or not the first row matches, is less than, or greater than the second row
27+
func (c *SequenceSchema) Compare(obj interface{}) int {
28+
c2, ok := obj.(*SequenceSchema)
29+
if !ok {
30+
fmt.Println("Error!!!, Change(...) needs a SequenceSchema instance", c2)
31+
return +999
32+
}
33+
34+
val := _compareString(c.row["sequence_name"], c2.row["sequence_name"])
35+
return val
36+
}
37+
38+
// Add returns SQL to add the table
39+
func (c SequenceSchema) Add() {
40+
fmt.Printf("CREATE SEQUENCE %s INCREMENT %s MINVALUE %s MAXVALUE %s START %s;\n", c.row["sequence_name"], c.row["increment"], c.row["minimum_value"], c.row["maximum_value"], c.row["start_value"])
41+
42+
}
43+
44+
// Drop returns SQL to drop the table
45+
func (c SequenceSchema) Drop() {
46+
fmt.Printf("DROP SEQUENCE IF EXISTS %s;\n", c.row["sequence_name"])
47+
}
48+
49+
// Change handles the case where the table and column match, but the details do not
50+
func (c SequenceSchema) Change(obj interface{}) {
51+
c2, ok := obj.(*SequenceSchema)
52+
if !ok {
53+
fmt.Println("Error!!!, change needs a SequenceSchema instance", c2)
54+
}
55+
}
56+
57+
// compareSequences outputs SQL to make the sequences match between DBs
58+
func compareSequences(conn1 *sql.DB, conn2 *sql.DB) {
59+
sql := `
60+
SELECT sequence_name, data_type, start_value
61+
, minimum_value, maximum_value
62+
, increment, cycle_option
63+
FROM information_schema.sequences
64+
WHERE sequence_schema = 'public'
65+
ORDER BY sequence_name ASC;`
66+
67+
rowChan1, _ := pgutil.QueryStrings(conn1, sql)
68+
rowChan2, _ := pgutil.QueryStrings(conn2, sql)
69+
70+
// We have to explicitly type this as Schema for some reason
71+
var schema1 Schema = &SequenceSchema{channel: rowChan1}
72+
var schema2 Schema = &SequenceSchema{channel: rowChan2}
73+
74+
// Compare the tables
75+
doDiff(schema1, schema2)
76+
}

table.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func (c *TableSchema) Compare(obj interface{}) int {
3838

3939
// Add returns SQL to add the table
4040
func (c TableSchema) Add() {
41-
fmt.Printf("CREATE TABLE %s;\n", c.row["table_name"])
41+
fmt.Printf("CREATE TABLE %s();\n", c.row["table_name"])
4242
}
4343

4444
// Drop returns SQL to drop the table
@@ -64,7 +64,7 @@ SELECT table_name
6464
FROM information_schema.tables
6565
WHERE table_schema = 'public'
6666
AND table_type = 'BASE TABLE'
67-
ORDER by table_name;`
67+
ORDER BY table_name ASC;`
6868

6969
rowChan1, _ := pgutil.QueryStrings(conn1, sql)
7070
rowChan2, _ := pgutil.QueryStrings(conn2, sql)

0 commit comments

Comments
 (0)