Skip to content

Commit 167a5a6

Browse files
committed
First commit... of many
1 parent 6695ee9 commit 167a5a6

7 files changed

Lines changed: 537 additions & 3 deletions

File tree

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2014 Jon Carlson
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,28 @@
1-
pgdiff
2-
======
1+
# pgdiff - PostgreSQL schema diff
32

4-
Compares the postgres schema between two databases and generates SQL. This is still a work in progress.
3+
Written in GoLang, this utility compares the schema between two PostgreSQL databases and generates alter statements to be run against the second database. Not everything in the schema is compared, but the thinngs I considered important are: tables, columns (and their default values), foreign keys, and (soon) constraints and user roles.
4+
5+
It is written to be easy to add and improve the accuracy of the comparison. Please let me know if you think this goal has not been met. I'm very interested in suggestions and contributions to improve this program. I'm not a GoLang expert yet, but each program I write gets me closer to that goal.
6+
7+
I'm a big fan of GoLang because of how easy it is to deliver a single executable on almost any platform. But, just as important I love the design choices and the concurrency features which I've only begun to delve into. Streaming objects back (via a channel) from a method one by one is far better than returning a potentially massive list of objects.
8+
9+
A couple of binaries to save you the effort:
10+
[Mac](https://github.com/joncrlsn/pgdiff/raw/master/bin-osx/pgdiff "OSX version")
11+
12+
## usage
13+
14+
pgdiff [database flags]
15+
16+
17+
program flags | Explanation
18+
-------------: | ------------------------------------
19+
-U1 | first db postgres user
20+
-pw1 | first db password
21+
-h1 | first db host -- default is localhost
22+
-p1 | first db port number. defaults to 5432
23+
-d1 | first db name
24+
-U2 | second db postgres user
25+
-pw2 | second db password
26+
-h2 | second db host -- default is localhost
27+
-p2 | second db port number. defaults to 5432
28+
-d2 | second db name

column.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package main
2+
3+
import "fmt"
4+
5+
type ColumnSchema struct {
6+
channel chan map[string]string
7+
row map[string]string
8+
}
9+
10+
// Reads from the channel and converts the end-of-channel value into a boolean
11+
func (c *ColumnSchema) NextRow(more bool) bool {
12+
c.row = <-c.channel
13+
if !more || len(c.row) == 0 {
14+
return false
15+
}
16+
return true
17+
}
18+
19+
// Compare
20+
func (c ColumnSchema) Compare(obj interface{}) int {
21+
c2, ok := obj.(*ColumnSchema)
22+
if !ok {
23+
fmt.Println("Error!!!, change needs a ColumnSchema instance", c2)
24+
}
25+
26+
val := _compareString(c.row["table_name"], c2.row["table_name"])
27+
if val != 0 {
28+
// Table name differed so return that value
29+
return val
30+
}
31+
32+
// Table name was the same so compare column name
33+
val = _compareString(c.row["column_name"], c2.row["column_name"])
34+
return val
35+
}
36+
37+
// Return SQL to add the column
38+
func (c ColumnSchema) Add() {
39+
if c.row["data_type"] == "character varying" {
40+
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"])
41+
} else {
42+
fmt.Printf("ALTER TABLE %s ADD COLUMN %s %s", c.row["table_name"], c.row["column_name"], c.row["data_type"])
43+
}
44+
45+
if c.row["is_nullable"] == "NO" {
46+
fmt.Printf(" NOT NULL")
47+
}
48+
if c.row["column_default"] != "null" {
49+
fmt.Printf(" DEFAULT %s", c.row["column_default"])
50+
}
51+
fmt.Printf(";\n")
52+
}
53+
54+
// Return SQL to drop the column
55+
func (c ColumnSchema) Drop() {
56+
// if dropping column
57+
fmt.Printf("ALTER TABLE %s DROP COLUMN %s;\n", c.row["table_name"], c.row["column_name"])
58+
}
59+
60+
// Handle the case where the table and column match, but the details do not
61+
func (c ColumnSchema) Change(obj interface{}) {
62+
c2, ok := obj.(*ColumnSchema)
63+
if !ok {
64+
fmt.Println("Error!!!, change needs a ColumnSchema instance", c2)
65+
}
66+
// fmt.Printf("Changes? ")
67+
// // if changing type
68+
// if c.row["data_type"] == "character varying" {
69+
// // varchar needs a length specified
70+
// fmt.Printf("ALTER TABLE %s ALTER COLUMN %s TYPE %s(%s);\n", c.row["table_name"], c.row["column_name"], c.row["data_type"], c.row["character_maximum_length"])
71+
// } else {
72+
// fmt.Printf("ALTER TABLE %s ALTER COLUMN %s TYPE %s;\n", c.row["table_name"], c.row["column_name"], c.row["data_type"])
73+
// }
74+
//
75+
// // if changing/adding default value
76+
// fmt.Printf("ALTER TABLE %s ALTER COLUMN %s SET DEFAULT %s;\n", c.row["table_name"], c.row["column_name"], c.row["column_default"])
77+
//
78+
// // if dropping default value
79+
// fmt.Printf("ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT;\n", c.row["table_name"], c.row["column_name"])
80+
//
81+
// // if adding not null
82+
// fmt.Printf("ALTER TABLE %s ALTER COLUMN %s SET NOT NULL;\n", c.row["table_name"], c.row["column_name"])
83+
//
84+
// // if dropping not null
85+
// fmt.Printf("ALTER TABLE %s ALTER COLUMN %s DROP NOT NULL;\n", c.row["table_name"], c.row["column_name"])
86+
// return "Change"
87+
}

flags.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package main
2+
3+
import "flag"
4+
import "github.com/joncrlsn/pgutil"
5+
6+
func ParseFlags() (pgutil.DbInfo, pgutil.DbInfo) {
7+
8+
var dbUser1 = flag.String("U1", "", "db user")
9+
var dbPass1 = flag.String("pw1", "", "db password")
10+
var dbHost1 = flag.String("h1", "localhost", "db host")
11+
var dbPort1 = flag.Int("p1", 5432, "db port")
12+
var dbName1 = flag.String("d1", "", "db name")
13+
var dbOptions1 = flag.String("o1", "", "db options (eg. sslmode=disable)")
14+
15+
var dbUser2 = flag.String("U2", "", "db user")
16+
var dbPass2 = flag.String("pw2", "", "db password")
17+
var dbHost2 = flag.String("h2", "localhost", "db host")
18+
var dbPort2 = flag.Int("p2", 5432, "db port")
19+
var dbName2 = flag.String("d2", "", "db name")
20+
var dbOptions2 = flag.String("o2", "", "db options (eg. sslmode=disable)")
21+
22+
flag.Parse()
23+
24+
dbInfo1 := pgutil.DbInfo{DbName:*dbName1, DbHost:*dbHost1, DbPort:int32(*dbPort1), DbUser:*dbUser1, DbPass:*dbPass1, DbOptions:*dbOptions1}
25+
26+
dbInfo2 := pgutil.DbInfo{DbName:*dbName2, DbHost:*dbHost2, DbPort:int32(*dbPort2), DbUser:*dbUser2, DbPass:*dbPass2, DbOptions:*dbOptions2}
27+
28+
return dbInfo1, dbInfo2
29+
}
30+

foreignkey.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package main
2+
3+
import "fmt"
4+
5+
type ForeignKeySchema struct {
6+
channel chan map[string]string
7+
row map[string]string
8+
}
9+
10+
// Reads from the channel and converts the end-of-channel value into a boolean
11+
func (c *ForeignKeySchema) NextRow(more bool) bool {
12+
c.row = <-c.channel
13+
//fmt.Println("Found ", c.row["table_name"])
14+
15+
if !more || len(c.row) == 0 {
16+
return false
17+
}
18+
return true
19+
}
20+
21+
// Compare
22+
func (c *ForeignKeySchema) Compare(obj interface{}) int {
23+
c2, ok := obj.(*ForeignKeySchema)
24+
if !ok {
25+
fmt.Println("Error!!!, Change(...) needs a ForeignKeySchema instance", c2)
26+
return +999
27+
}
28+
29+
//fmt.Printf("Comparing %s with %s", c.row["table_name"], c2.row["table_name"])
30+
val := _compareString(c.row["table_name"], c2.row["table_name"])
31+
if val != 0 {
32+
return val
33+
}
34+
35+
val = _compareString(c.row["constraint_name"], c2.row["constraint_name"])
36+
return val
37+
}
38+
39+
// Return SQL to add the table
40+
func (c ForeignKeySchema) Add() {
41+
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"], )
42+
}
43+
44+
// Return SQL to drop the table
45+
func (c ForeignKeySchema) Drop() {
46+
fmt.Printf("ALTER TABLE %s DROP CONSTRAINT %s;\n", c.row["table_name"], c.row["constraint_name"])
47+
}
48+
49+
// Handle the case where the table and column match, but the details do not
50+
func (c ForeignKeySchema) Change(obj interface{}) {
51+
c2, ok := obj.(*ForeignKeySchema)
52+
if !ok {
53+
fmt.Println("Error!!!, change needs a ForeignKeySchema instance", c2)
54+
}
55+
//fmt.Printf("Change Table? %s - %s\n", c.row["table_name"], c2.row["table_name"])
56+
}

0 commit comments

Comments
 (0)