Skip to content

Commit 765fb92

Browse files
committed
Work In Progress: Adding the ability to compare two different schemas in the same database
Former-commit-id: 07d956e
1 parent a8e8437 commit 765fb92

22 files changed

+427
-117
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
bin-linux
2+
bin-win
3+
bin-osx
4+
pgdiff

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,14 @@ options | explanation
5858
-u, --user2 | second postgres user
5959
-W, --password1 | first db password
6060
-w, --password2 | second db password
61-
-H, --host1 | first db host. default is localhost
61+
-H, --host1 | first db host. default is localhost
6262
-h, --host2 | second db host. default is localhost
63-
-P, --port1 | first db port number. default is 5432
63+
-P, --port1 | first db port number. default is 5432
6464
-p, --port2 | second db port number. default is 5432
6565
-D, --dbname1 | first db name
6666
-d, --dbname2 | second db name
67+
-S, --schema1 | first schema name. default is public
68+
-s, --schema2 | second schema name. default is public
6769
-O, --option1 | first db options. example: sslmode=disable
6870
-o, --option2 | second db options. example: sslmode=disable
6971

@@ -92,8 +94,7 @@ linux and osx binaries are packaged with an extra, optional bash script and pgru
9294
1. 0.9.0 - Implemented ROLE, SEQUENCE, TABLE, COLUMN, INDEX, FOREIGN\_KEY, OWNER, GRANT\_RELATIONSHIP, GRANT\_ATTRIBUTE
9395
1. 0.9.1 - Added VIEW, FUNCTION, and TRIGGER (Thank you, Shawn Carroll AKA SparkeyG)
9496
1. 0.9.2 - Fixed bug when using the non-default port
95-
1. 0.9.3 - Added support for schemas other than public. Fixed VARCHAR bug when no max length
96-
specified
97+
1. 0.9.3 - Added support for schemas other than public. Fixed VARCHAR bug when no max length specified
9798

9899

99100
### todo

column.go

Lines changed: 86 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,48 @@
66

77
package main
88

9-
import "sort"
10-
import "fmt"
11-
import "strconv"
12-
import "strings"
13-
import "database/sql"
14-
import "github.com/joncrlsn/pgutil"
15-
import "github.com/joncrlsn/misc"
9+
import (
10+
"sort"
11+
"fmt"
12+
"strconv"
13+
"strings"
14+
"database/sql"
15+
"github.com/joncrlsn/pgutil"
16+
"github.com/joncrlsn/misc"
17+
"text/template"
18+
"bytes"
19+
)
20+
21+
var (
22+
columnSqlTemplate = initColumnSqlTemplate()
23+
)
24+
25+
// Initializes the Sql template
26+
func initColumnSqlTemplate() *template.Template {
27+
sql := `
28+
SELECT table_schema
29+
, {{if eq $.DbSchema "*" }}table_schema || '.' || {{end}}table_name AS compare_name
30+
, table_name
31+
, column_name
32+
, data_type
33+
, is_nullable
34+
, column_default
35+
, character_maximum_length
36+
FROM information_schema.columns
37+
WHERE is_updatable = 'YES'
38+
{{if eq $.DbSchema "*" }}
39+
AND table_schema NOT LIKE 'pg_%'
40+
AND table_schema <> 'information_schema'
41+
{{else}}
42+
AND table_schema = '{{$.DbSchema}}'
43+
{{end}}
44+
ORDER BY compare_name, column_name;
45+
`
46+
t := template.New("ColumnSqlTmpl")
47+
template.Must(t.Parse(sql))
48+
return t
49+
}
50+
1651

1752
// ==================================
1853
// Column Rows definition
@@ -26,8 +61,8 @@ func (slice ColumnRows) Len() int {
2661
}
2762

2863
func (slice ColumnRows) Less(i, j int) bool {
29-
if slice[i]["table_name"] != slice[j]["table_name"] {
30-
return slice[i]["table_name"] < slice[j]["table_name"]
64+
if slice[i]["compare_name"] != slice[j]["compare_name"] {
65+
return slice[i]["column_name"] < slice[j]["compare_name"]
3166
}
3267
return slice[i]["column_name"] < slice[j]["column_name"]
3368
}
@@ -73,7 +108,7 @@ func (c *ColumnSchema) Compare(obj interface{}) int {
73108
fmt.Println("Error!!!, Compare needs a ColumnSchema instance", c2)
74109
}
75110

76-
val := misc.CompareStrings(c.get("table_name"), c2.get("table_name"))
111+
val := misc.CompareStrings(c.get("compare_name"), c2.get("compare_name"))
77112
if val != 0 {
78113
// Table name differed so return that value
79114
return val
@@ -85,19 +120,24 @@ func (c *ColumnSchema) Compare(obj interface{}) int {
85120
}
86121

87122
// Add prints SQL to add the column
88-
func (c *ColumnSchema) Add() {
123+
func (c *ColumnSchema) Add(obj interface{}) {
124+
c2, ok := obj.(*ColumnSchema)
125+
if !ok {
126+
fmt.Println("Error!!!, ColumnSchema.Add(obj) needs a ColumnSchema instance", c2)
127+
}
128+
89129
if c.get("data_type") == "character varying" {
90130
maxLength, valid := getMaxLength(c.get("character_maximum_length"))
91131
if !valid {
92-
fmt.Printf("ALTER TABLE %s ADD COLUMN %s character varying", c.get("table_name"), c.get("column_name"))
132+
fmt.Printf("ALTER TABLE %s.%s ADD COLUMN %s character varying", c2.get("table_schema"), c.get("table_name"), c.get("column_name"))
93133
} else {
94-
fmt.Printf("ALTER TABLE %s ADD COLUMN %s character varying(%s)", c.get("table_name"), c.get("column_name"), maxLength)
134+
fmt.Printf("ALTER TABLE %s.%s ADD COLUMN %s character varying(%s)", c2.get("table_schema"), c.get("table_name"), c.get("column_name"), maxLength)
95135
}
96136
} else {
97137
if c.get("data_type") == "ARRAY" {
98138
fmt.Println("-- Note that adding of array data types are not yet generated properly.")
99139
}
100-
fmt.Printf("ALTER TABLE %s ADD COLUMN %s %s", c.get("table_name"), c.get("column_name"), c.get("data_type"))
140+
fmt.Printf("ALTER TABLE %s.%s ADD COLUMN %s %s", c2.get("table_schema"), c.get("table_name"), c.get("column_name"), c.get("data_type"))
101141
}
102142

103143
if c.get("is_nullable") == "NO" {
@@ -110,9 +150,13 @@ func (c *ColumnSchema) Add() {
110150
}
111151

112152
// Drop prints SQL to drop the column
113-
func (c *ColumnSchema) Drop() {
153+
func (c *ColumnSchema) Drop(obj interface{}) {
154+
c2, ok := obj.(*ColumnSchema)
155+
if !ok {
156+
fmt.Println("Error!!!, ColumnSchema.Drop(obj) needs a ColumnSchema instance", c2)
157+
}
114158
// if dropping column
115-
fmt.Printf("ALTER TABLE %s DROP COLUMN IF EXISTS %s;\n", c.get("table_name"), c.get("column_name"))
159+
fmt.Printf("ALTER TABLE %s.%s DROP COLUMN IF EXISTS %s;\n", c2.get("table_schema"), c2.get("table_name"), c2.get("column_name"))
116160
}
117161

118162
// Change handles the case where the table and column match, but the details do not
@@ -122,7 +166,7 @@ func (c *ColumnSchema) Change(obj interface{}) {
122166
fmt.Println("Error!!!, ColumnSchema.Change(obj) needs a ColumnSchema instance", c2)
123167
}
124168

125-
// Detect column type change (mostly varchar length, or number size increase)
169+
// Detect column type change (mostly varchar length, or number size increase)
126170
// (integer to/from bigint is OK)
127171
if c.get("data_type") == c2.get("data_type") {
128172
if c.get("data_type") == "character varying" {
@@ -141,8 +185,8 @@ func (c *ColumnSchema) Change(obj interface{}) {
141185
if max1Int < max2Int {
142186
fmt.Println("-- WARNING: The next statement will shorten a character varying column, which may result in data loss.")
143187
}
144-
fmt.Printf("-- max1Valid: %v max2Valid: %v ", max1Valid, max2Valid)
145-
fmt.Printf("ALTER TABLE %s ALTER COLUMN %s TYPE character varying(%s);\n", c.get("table_name"), c.get("column_name"), max1)
188+
fmt.Printf("-- max1Valid: %v max2Valid: %v \n", max1Valid, max2Valid)
189+
fmt.Printf("ALTER TABLE %s.%s ALTER COLUMN %s TYPE character varying(%s);\n", c2.get("table_schema"), c.get("table_name"), c.get("column_name"), max1)
146190
}
147191
}
148192
}
@@ -155,27 +199,27 @@ func (c *ColumnSchema) Change(obj interface{}) {
155199
if !max1Valid {
156200
fmt.Println("-- WARNING: varchar column has no maximum length. Setting to 1024")
157201
}
158-
fmt.Printf("ALTER TABLE %s ALTER COLUMN %s TYPE %s(%s);\n", c.get("table_name"), c.get("column_name"), c.get("data_type"), max1)
202+
fmt.Printf("ALTER TABLE %s.%s ALTER COLUMN %s TYPE %s(%s);\n", c2.get("table_schema"), c.get("table_name"), c.get("column_name"), c.get("data_type"), max1)
159203
} else {
160-
fmt.Printf("ALTER TABLE %s ALTER COLUMN %s TYPE %s;\n", c.get("table_name"), c.get("column_name"), c.get("data_type"))
204+
fmt.Printf("ALTER TABLE %s.%s ALTER COLUMN %s TYPE %s;\n", c2.get("table_schema"), c.get("table_name"), c.get("column_name"), c.get("data_type"))
161205
}
162206
}
163207

164208
// Detect column default change (or added, dropped)
165209
if c.get("column_default") == "null" {
166210
if c.get("column_default") != "null" {
167-
fmt.Printf("ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT;\n", c.get("table_name"), c.get("column_name"))
211+
fmt.Printf("ALTER TABLE %s.%s ALTER COLUMN %s DROP DEFAULT;\n", c2.get("table_schema"), c.get("table_name"), c.get("column_name"))
168212
}
169213
} else if c.get("column_default") != c2.get("column_default") {
170-
fmt.Printf("ALTER TABLE %s ALTER COLUMN %s SET DEFAULT %s;\n", c.get("table_name"), c.get("column_name"), c.get("column_default"))
214+
fmt.Printf("ALTER TABLE %s.%s ALTER COLUMN %s SET DEFAULT %s;\n", c2.get("table_schema"), c.get("table_name"), c.get("column_name"), c.get("column_default"))
171215
}
172216

173217
// Detect not-null and nullable change
174218
if c.get("is_nullable") != c2.get("is_nullable") {
175219
if c.get("is_nullable") == "YES" {
176-
fmt.Printf("ALTER TABLE %s ALTER COLUMN %s DROP NOT NULL;\n", c.get("table_name"), c.get("column_name"))
220+
fmt.Printf("ALTER TABLE %s.%s ALTER COLUMN %s DROP NOT NULL;\n", c2.get("table_schema"), c.get("table_name"), c.get("column_name"))
177221
} else {
178-
fmt.Printf("ALTER TABLE %s ALTER COLUMN %s SET NOT NULL;\n", c.get("table_name"), c.get("column_name"))
222+
fmt.Printf("ALTER TABLE %s.%s ALTER COLUMN %s SET NOT NULL;\n", c2.get("table_schema"), c.get("table_name"), c.get("column_name"))
179223
}
180224
}
181225
}
@@ -188,21 +232,15 @@ func (c *ColumnSchema) Change(obj interface{}) {
188232
* Compare the columns in the two databases
189233
*/
190234
func compareColumns(conn1 *sql.DB, conn2 *sql.DB) {
191-
sql := `
192-
SELECT table_schema || '.' || table_name AS table_name
193-
, column_name
194-
, data_type
195-
, is_nullable
196-
, column_default
197-
, character_maximum_length
198-
FROM information_schema.columns
199-
WHERE table_schema NOT LIKE 'pg_%'
200-
AND table_schema <> 'information_schema'
201-
AND is_updatable = 'YES'
202-
ORDER BY table_name, column_name;`
235+
236+
buf1 := new(bytes.Buffer)
237+
columnSqlTemplate.Execute(buf1, dbInfo1)
203238

204-
rowChan1, _ := pgutil.QueryStrings(conn1, sql)
205-
rowChan2, _ := pgutil.QueryStrings(conn2, sql)
239+
buf2 := new(bytes.Buffer)
240+
columnSqlTemplate.Execute(buf2, dbInfo2)
241+
242+
rowChan1, _ := pgutil.QueryStrings(conn1, buf1.String())
243+
rowChan2, _ := pgutil.QueryStrings(conn2, buf2.String())
206244

207245
//rows1 := make([]map[string]string, 500)
208246
rows1 := make(ColumnRows, 0)
@@ -218,6 +256,15 @@ ORDER BY table_name, column_name;`
218256
}
219257
sort.Sort(&rows2)
220258

259+
//for _, val := range rows1 {
260+
//fmt.Println("list1: ", val["table_schema"], val["compare_name"], val["column_name"], val["character_maximum_length"] )
261+
//}
262+
//fmt.Println()
263+
264+
//for _, val := range rows2 {
265+
//fmt.Println("list2: ", val["table_schema"], val["compare_name"], val["column_name"], val["character_maximum_length"])
266+
//}
267+
221268
// We have to explicitly type this as Schema here for some unknown reason
222269
var schema1 Schema = &ColumnSchema{rows: rows1, rowNum: -1}
223270
var schema2 Schema = &ColumnSchema{rows: rows2, rowNum: -1}

flags.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,22 @@ func parseFlags() (pgutil.DbInfo, pgutil.DbInfo) {
1818
var dbHost1 = flag.StringP("host1", "H", "localhost", "db host")
1919
var dbPort1 = flag.IntP("port1", "P", 5432, "db port")
2020
var dbName1 = flag.StringP("dbname1", "D", "", "db name")
21+
var dbSchema1 = flag.StringP("schema1", "S", "public", "schema name or * for all schemas")
2122
var dbOptions1 = flag.StringP("options1", "O", "", "db options (eg. sslmode=disable)")
2223

2324
var dbUser2 = flag.StringP("user2", "u", "", "db user")
2425
var dbPass2 = flag.StringP("password2", "w", "", "db password")
2526
var dbHost2 = flag.StringP("host2", "h", "localhost", "db host")
2627
var dbPort2 = flag.IntP("port2", "p", 5432, "db port")
2728
var dbName2 = flag.StringP("dbname2", "d", "", "db name")
29+
var dbSchema2 = flag.StringP("schema2", "s", "public", "schema name or * for all schemas")
2830
var dbOptions2 = flag.StringP("options2", "o", "", "db options (eg. sslmode=disable)")
2931

3032
flag.Parse()
3133

32-
dbInfo1 := pgutil.DbInfo{DbName: *dbName1, DbHost: *dbHost1, DbPort: int32(*dbPort1), DbUser: *dbUser1, DbPass: *dbPass1, DbOptions: *dbOptions1}
34+
dbInfo1 := pgutil.DbInfo{DbName: *dbName1, DbHost: *dbHost1, DbPort: int32(*dbPort1), DbUser: *dbUser1, DbPass: *dbPass1, DbSchema: *dbSchema1, DbOptions: *dbOptions1}
3335

34-
dbInfo2 := pgutil.DbInfo{DbName: *dbName2, DbHost: *dbHost2, DbPort: int32(*dbPort2), DbUser: *dbUser2, DbPass: *dbPass2, DbOptions: *dbOptions2}
36+
dbInfo2 := pgutil.DbInfo{DbName: *dbName2, DbHost: *dbHost2, DbPort: int32(*dbPort2), DbUser: *dbUser2, DbPass: *dbPass2, DbSchema: *dbSchema2, DbOptions: *dbOptions2}
3537

3638
return dbInfo1, dbInfo2
3739
}

foreignkey.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,20 +90,28 @@ func (c *ForeignKeySchema) Compare(obj interface{}) int {
9090
}
9191

9292
// Add returns SQL to add the foreign key
93-
func (c *ForeignKeySchema) Add() {
93+
func (c *ForeignKeySchema) Add(obj interface{}) {
94+
c2, ok := obj.(*ForeignKeySchema)
95+
if !ok {
96+
fmt.Println("Error!!!, ForeignKeySchema.Add(obj) needs a ForeignKeySchema instance", c2)
97+
}
9498
fmt.Printf("ALTER TABLE %s ADD CONSTRAINT %s %s;\n", c.get("table_name"), c.get("fk_name"), c.get("constraint_def"))
9599
}
96100

97101
// Drop returns SQL to drop the foreign key
98-
func (c ForeignKeySchema) Drop() {
102+
func (c ForeignKeySchema) Drop(obj interface{}) {
103+
c2, ok := obj.(*ForeignKeySchema)
104+
if !ok {
105+
fmt.Println("Error!!!, ForeignKeySchema.Drop(obj) needs a ForeignKeySchema instance", c2)
106+
}
99107
fmt.Printf("ALTER TABLE %s DROP CONSTRAINT %s; -- %s\n", c.get("table_name"), c.get("fk_name"), c.get("constraint_def"))
100108
}
101109

102110
// Change handles the case where the table and foreign key name, but the details do not
103111
func (c *ForeignKeySchema) Change(obj interface{}) {
104112
c2, ok := obj.(*ForeignKeySchema)
105113
if !ok {
106-
fmt.Println("Error!!!, Change(obj) needs a ForeignKeySchema instance", c2)
114+
fmt.Println("Error!!!, ForeignKeySchema.Change(obj) needs a ForeignKeySchema instance", c2)
107115
}
108116
// There is no "changing" a foreign key. It either gets created or dropped (or left as-is).
109117
}

function.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,22 @@ func (c *FunctionSchema) Compare(obj interface{}) int {
7272
}
7373

7474
// Add returns SQL to create the function
75-
func (c FunctionSchema) Add() {
75+
func (c FunctionSchema) Add(obj interface{}) {
76+
c2, ok := obj.(*FunctionSchema)
77+
if !ok {
78+
fmt.Println("Error!!!, FunctionSchema.Add needs a FunctionSchema instance", c2)
79+
}
7680
fmt.Println("-- STATEMENT-BEGIN")
7781
fmt.Println(c.get("definition"))
7882
fmt.Println("-- STATEMENT-END")
7983
}
8084

8185
// Drop returns SQL to drop the function
82-
func (c FunctionSchema) Drop() {
86+
func (c FunctionSchema) Drop(obj interface{}) {
87+
c2, ok := obj.(*FunctionSchema)
88+
if !ok {
89+
fmt.Println("Error!!!, FunctionSchema.Drop needs a FunctionSchema instance", c2)
90+
}
8391
fmt.Println("-- Note that CASCADE in the statement below will also drop any triggers depending on this function.")
8492
fmt.Println("-- Also, if there are two functions with this name, you will need to add arguments to identify the correct one to drop.")
8593
fmt.Println("-- (See http://www.postgresql.org/docs/9.4/interactive/sql-dropfunction.html) ")

grant-attribute.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,23 @@ func (c *GrantAttributeSchema) Compare(obj interface{}) int {
119119
}
120120

121121
// Add prints SQL to add the column
122-
func (c *GrantAttributeSchema) Add() {
122+
func (c *GrantAttributeSchema) Add(obj interface{}) {
123+
c2, ok := obj.(*GrantAttributeSchema)
124+
if !ok {
125+
fmt.Println("-- Error!!!, Add needs a GrantAttributeSchema instance", c2)
126+
}
127+
123128
role, grants := parseGrants(c.get("attribute_acl"))
124129
fmt.Printf("GRANT %s (%s) ON %s TO %s; -- Add\n", strings.Join(grants, ", "), c.get("attribute_name"), c.get("relationship_name"), role)
125130
}
126131

127132
// Drop prints SQL to drop the column
128-
func (c *GrantAttributeSchema) Drop() {
133+
func (c *GrantAttributeSchema) Drop(obj interface{}) {
134+
c2, ok := obj.(*GrantAttributeSchema)
135+
if !ok {
136+
fmt.Println("-- Error!!!, Drop needs a GrantAttributeSchema instance", c2)
137+
}
138+
129139
role, grants := parseGrants(c.get("attribute_acl"))
130140
fmt.Printf("REVOKE %s (%s) ON %s FROM %s; -- Drop\n", strings.Join(grants, ", "), c.get("attribute_name"), c.get("relationship_name"), role)
131141
}

grant-relationship.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,23 @@ func (c *GrantRelationshipSchema) Compare(obj interface{}) int {
112112
}
113113

114114
// Add prints SQL to add the column
115-
func (c *GrantRelationshipSchema) Add() {
115+
func (c *GrantRelationshipSchema) Add(obj interface{}) {
116+
c2, ok := obj.(*GrantRelationshipSchema)
117+
if !ok {
118+
fmt.Println("-- Error!!!, Add needs a GrantRelationshipSchema instance", c2)
119+
}
120+
116121
role, grants := parseGrants(c.get("relationship_acl"))
117122
fmt.Printf("GRANT %s ON %s TO %s; -- Add\n", strings.Join(grants, ", "), c.get("relationship_name"), role)
118123
}
119124

120125
// Drop prints SQL to drop the column
121-
func (c *GrantRelationshipSchema) Drop() {
126+
func (c *GrantRelationshipSchema) Drop(obj interface{}) {
127+
c2, ok := obj.(*GrantRelationshipSchema)
128+
if !ok {
129+
fmt.Println("-- Error!!!, Drop needs a GrantRelationshipSchema instance", c2)
130+
}
131+
122132
role, grants := parseGrants(c.get("relationship_acl"))
123133
fmt.Printf("REVOKE %s ON %s FROM %s; -- Drop\n", strings.Join(grants, ", "), c.get("relationship_name"), role)
124134
}
@@ -127,7 +137,7 @@ func (c *GrantRelationshipSchema) Drop() {
127137
func (c *GrantRelationshipSchema) Change(obj interface{}) {
128138
c2, ok := obj.(*GrantRelationshipSchema)
129139
if !ok {
130-
fmt.Println("-- Error!!!, change needs a GrantRelationshipSchema instance", c2)
140+
fmt.Println("-- Error!!!, Change needs a GrantRelationshipSchema instance", c2)
131141
}
132142

133143
role, grants1 := parseGrants(c.get("relationship_acl"))

0 commit comments

Comments
 (0)