From 6ee6b02db280eed17fe131aca3152622b6f870fa Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Sat, 21 Jan 2017 21:30:01 -0600
Subject: [PATCH 01/74] 0.9.3 beta

Former-commit-id: a07badcdfd5e4f87bf4f855bb6eed4bdc6ab0874
---
 README.md                           | 2 ++
 bin-linux/pgdiff.tgz.REMOVED.git-id | 2 +-
 bin-osx/pgdiff.tgz.REMOVED.git-id   | 2 +-
 bin-win/pgdiff.exe.REMOVED.git-id   | 2 +-
 build.sh                            | 2 +-
 pgdiff.go                           | 2 +-
 6 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/README.md b/README.md
index ac1319f..bd6451f 100644
--- a/README.md
+++ b/README.md
@@ -92,6 +92,8 @@ linux and osx binaries are packaged with an extra, optional bash script and pgru
 1. 0.9.0 - Implemented ROLE, SEQUENCE, TABLE, COLUMN, INDEX, FOREIGN\_KEY, OWNER, GRANT\_RELATIONSHIP, GRANT\_ATTRIBUTE
 1. 0.9.1 - Added VIEW, FUNCTION, and TRIGGER (Thank you, Shawn Carroll AKA SparkeyG)
 1. 0.9.2 - Fixed bug when using the non-default port
+1. 0.9.3 - Added support for schemas other than public. Fixed VARCHAR bug when no max length
+   specified
 
 
 ### todo
diff --git a/bin-linux/pgdiff.tgz.REMOVED.git-id b/bin-linux/pgdiff.tgz.REMOVED.git-id
index 7a217a3..e582dfc 100644
--- a/bin-linux/pgdiff.tgz.REMOVED.git-id
+++ b/bin-linux/pgdiff.tgz.REMOVED.git-id
@@ -1 +1 @@
-ad224e038d165562a2deafc0216717379f8beadb
\ No newline at end of file
+8dd82d980d95c6f21d3d6176ba0aaf6c8e311fc9
\ No newline at end of file
diff --git a/bin-osx/pgdiff.tgz.REMOVED.git-id b/bin-osx/pgdiff.tgz.REMOVED.git-id
index cef9b8c..01fbca4 100644
--- a/bin-osx/pgdiff.tgz.REMOVED.git-id
+++ b/bin-osx/pgdiff.tgz.REMOVED.git-id
@@ -1 +1 @@
-85ad23c83c6c3cdb2cf777c8eaaec0a6d70cfb37
\ No newline at end of file
+cdee1b91e68daba3ef72f750f6c8074d218b18a0
\ No newline at end of file
diff --git a/bin-win/pgdiff.exe.REMOVED.git-id b/bin-win/pgdiff.exe.REMOVED.git-id
index 71d7f5b..1185649 100644
--- a/bin-win/pgdiff.exe.REMOVED.git-id
+++ b/bin-win/pgdiff.exe.REMOVED.git-id
@@ -1 +1 @@
-32fa2c8940cbed63e546ff852722035c5f317be2
\ No newline at end of file
+de7d6a4057226813eecb444a21181cee21d77ca4
\ No newline at end of file
diff --git a/build.sh b/build.sh
index 19e3919..a9a9c5f 100755
--- a/build.sh
+++ b/build.sh
@@ -1,4 +1,4 @@
-#!/bin/bash -x
+#!/bin/bash
 #
 # For OSX and Linux, this script:
 #  * builds pgdiff 
diff --git a/pgdiff.go b/pgdiff.go
index a1d821b..24314f2 100644
--- a/pgdiff.go
+++ b/pgdiff.go
@@ -25,7 +25,7 @@ type Schema interface {
 }
 
 const (
-	version = "0.9.2"
+	version = "0.9.3"
 )
 
 var (

From 95f921e3d4a66231fb5192e9b0f40d42dde45eb0 Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Fri, 3 Feb 2017 17:58:08 -0600
Subject: [PATCH 02/74] Improved trigger query for PG 9.5

Former-commit-id: 9dd548f999969a4f3bfd42d0d2c74e11f3564b4d
---
 trigger.go | 18 ++++++------------
 1 file changed, 6 insertions(+), 12 deletions(-)

diff --git a/trigger.go b/trigger.go
index aeeee6c..fe5834d 100644
--- a/trigger.go
+++ b/trigger.go
@@ -105,19 +105,13 @@ func (c TriggerSchema) Change(obj interface{}) {
 // compareTriggers outputs SQL to make the triggers match between DBs
 func compareTriggers(conn1 *sql.DB, conn2 *sql.DB) {
 	sql := `
-    SELECT tbl.nspname || '.' || tbl.relname AS table_name
-    	, t.tgname AS trigger_name
-    	, pg_catalog.pg_get_triggerdef(t.oid, true) AS definition
-    	, t.tgenabled AS enabled
+    SELECT n.nspname || '.' || c.relname AS table_name
+       , t.tgname AS trigger_name
+       , pg_catalog.pg_get_triggerdef(t.oid, true) AS definition
+       , t.tgenabled AS enabled
     FROM pg_catalog.pg_trigger t
-    INNER JOIN (
-    	SELECT c.oid, n.nspname, c.relname
-    	FROM pg_catalog.pg_class c
-    	JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace AND n.nspname NOT LIKE 'pg_%')
-    	WHERE pg_catalog.pg_table_is_visible(c.oid)) AS tbl
-    	ON (tbl.oid = t.tgrelid)
-    AND NOT t.tgisinternal
-    ORDER BY 1;
+    INNER JOIN pg_catalog.pg_class c ON (c.oid = t.tgrelid)
+    INNER JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace AND n.nspname NOT LIKE 'pg_%');
 	`
 
 	rowChan1, _ := pgutil.QueryStrings(conn1, sql)

From a8e8437db011f4f529c5455c91f1571ab976306c Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Fri, 3 Feb 2017 18:04:11 -0600
Subject: [PATCH 03/74] New 0.9.3 binaries

Former-commit-id: 4d8a54ebb03a07c094442d3048b5c65d6d7d81c2
---
 bin-linux/pgdiff.tgz.REMOVED.git-id | 2 +-
 bin-osx/pgdiff.tgz.REMOVED.git-id   | 2 +-
 bin-win/pgdiff.exe.REMOVED.git-id   | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/bin-linux/pgdiff.tgz.REMOVED.git-id b/bin-linux/pgdiff.tgz.REMOVED.git-id
index e582dfc..7856fd2 100644
--- a/bin-linux/pgdiff.tgz.REMOVED.git-id
+++ b/bin-linux/pgdiff.tgz.REMOVED.git-id
@@ -1 +1 @@
-8dd82d980d95c6f21d3d6176ba0aaf6c8e311fc9
\ No newline at end of file
+645910252828d67b3934b92640f9919910be3f72
\ No newline at end of file
diff --git a/bin-osx/pgdiff.tgz.REMOVED.git-id b/bin-osx/pgdiff.tgz.REMOVED.git-id
index 01fbca4..81df825 100644
--- a/bin-osx/pgdiff.tgz.REMOVED.git-id
+++ b/bin-osx/pgdiff.tgz.REMOVED.git-id
@@ -1 +1 @@
-cdee1b91e68daba3ef72f750f6c8074d218b18a0
\ No newline at end of file
+5e3b173cb942e7c0b98ab450835cb8cd577bffb9
\ No newline at end of file
diff --git a/bin-win/pgdiff.exe.REMOVED.git-id b/bin-win/pgdiff.exe.REMOVED.git-id
index 1185649..5503484 100644
--- a/bin-win/pgdiff.exe.REMOVED.git-id
+++ b/bin-win/pgdiff.exe.REMOVED.git-id
@@ -1 +1 @@
-de7d6a4057226813eecb444a21181cee21d77ca4
\ No newline at end of file
+546e07755e6b1683a0e70013d0d03223e7f03c3f
\ No newline at end of file

From 765fb92bf33bd3db44cf6a8381198e2229ad7d45 Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Sat, 21 Oct 2017 22:07:30 -0500
Subject: [PATCH 04/74] Work In Progress: Adding the ability to compare two
 different schemas in the same database

Former-commit-id: 07d956eb684cb1100ffed196551df3a761dae234
---
 .gitignore            |   4 ++
 README.md             |   9 +--
 column.go             | 125 +++++++++++++++++++++++++++++-------------
 flags.go              |   6 +-
 foreignkey.go         |  14 ++++-
 function.go           |  12 +++-
 grant-attribute.go    |  14 ++++-
 grant-relationship.go |  16 +++++-
 index.go              |  21 +++++--
 owner.go              |  14 ++++-
 pgdiff.go             |  35 ++++++++----
 role.go               |  16 +++++-
 schemata.go           |  16 ++++--
 schemata_test.go      |   8 +--
 sequence.go           |   4 +-
 table.go              |  84 ++++++++++++++++++++--------
 table_test.go         |   8 +--
 test/README.md        |   2 +
 test/test-column.sh   |  45 +++++++++++++++
 test/test-table.sh    |  63 +++++++++++++++++++++
 trigger.go            |  14 ++++-
 view.go               |  14 ++++-
 22 files changed, 427 insertions(+), 117 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 test/README.md
 create mode 100755 test/test-column.sh
 create mode 100755 test/test-table.sh

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1d6f4f3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+bin-linux
+bin-win
+bin-osx
+pgdiff
diff --git a/README.md b/README.md
index bd6451f..699ce3e 100644
--- a/README.md
+++ b/README.md
@@ -58,12 +58,14 @@ options           | explanation
   -u, --user2     | second postgres user
   -W, --password1 | first db password
   -w, --password2 | second db password
-  -H, --host1     | first db host. default is localhost
+  -H, --host1     | first db host.  default is localhost
   -h, --host2     | second db host. default is localhost
-  -P, --port1     | first db port number. default is 5432
+  -P, --port1     | first db port number.  default is 5432
   -p, --port2     | second db port number. default is 5432
   -D, --dbname1   | first db name
   -d, --dbname2   | second db name
+  -S, --schema1   | first schema name.  default is public
+  -s, --schema2   | second schema name. default is public
   -O, --option1   | first db options. example: sslmode=disable
   -o, --option2   | second db options. example: sslmode=disable
 
@@ -92,8 +94,7 @@ linux and osx binaries are packaged with an extra, optional bash script and pgru
 1. 0.9.0 - Implemented ROLE, SEQUENCE, TABLE, COLUMN, INDEX, FOREIGN\_KEY, OWNER, GRANT\_RELATIONSHIP, GRANT\_ATTRIBUTE
 1. 0.9.1 - Added VIEW, FUNCTION, and TRIGGER (Thank you, Shawn Carroll AKA SparkeyG)
 1. 0.9.2 - Fixed bug when using the non-default port
-1. 0.9.3 - Added support for schemas other than public. Fixed VARCHAR bug when no max length
-   specified
+1. 0.9.3 - Added support for schemas other than public. Fixed VARCHAR bug when no max length specified
 
 
 ### todo
diff --git a/column.go b/column.go
index 3bb15a9..c4a0449 100644
--- a/column.go
+++ b/column.go
@@ -6,13 +6,48 @@
 
 package main
 
-import "sort"
-import "fmt"
-import "strconv"
-import "strings"
-import "database/sql"
-import "github.com/joncrlsn/pgutil"
-import "github.com/joncrlsn/misc"
+import (
+"sort"
+"fmt"
+"strconv"
+"strings"
+"database/sql"
+"github.com/joncrlsn/pgutil"
+"github.com/joncrlsn/misc"
+"text/template"
+"bytes"
+)
+
+var (
+	columnSqlTemplate = initColumnSqlTemplate()
+)
+
+// Initializes the Sql template
+func initColumnSqlTemplate() *template.Template {
+	sql := `
+SELECT table_schema
+    , {{if eq $.DbSchema "*" }}table_schema || '.' || {{end}}table_name AS compare_name
+	, table_name
+    , column_name
+    , data_type
+    , is_nullable
+    , column_default
+    , character_maximum_length
+FROM information_schema.columns 
+WHERE is_updatable = 'YES'
+{{if eq $.DbSchema "*" }}
+AND table_schema NOT LIKE 'pg_%' 
+AND table_schema <> 'information_schema' 
+{{else}}
+AND table_schema = '{{$.DbSchema}}'
+{{end}}
+ORDER BY compare_name, column_name;
+`
+	t := template.New("ColumnSqlTmpl")
+	template.Must(t.Parse(sql))
+	return t
+}
+
 
 // ==================================
 // Column Rows definition
@@ -26,8 +61,8 @@ func (slice ColumnRows) Len() int {
 }
 
 func (slice ColumnRows) Less(i, j int) bool {
-	if slice[i]["table_name"] != slice[j]["table_name"] {
-		return slice[i]["table_name"] < slice[j]["table_name"]
+	if slice[i]["compare_name"] != slice[j]["compare_name"] {
+		return slice[i]["column_name"] < slice[j]["compare_name"]
 	}
 	return slice[i]["column_name"] < slice[j]["column_name"]
 }
@@ -73,7 +108,7 @@ func (c *ColumnSchema) Compare(obj interface{}) int {
 		fmt.Println("Error!!!, Compare needs a ColumnSchema instance", c2)
 	}
 
-	val := misc.CompareStrings(c.get("table_name"), c2.get("table_name"))
+	val := misc.CompareStrings(c.get("compare_name"), c2.get("compare_name"))
 	if val != 0 {
 		// Table name differed so return that value
 		return val
@@ -85,19 +120,24 @@ func (c *ColumnSchema) Compare(obj interface{}) int {
 }
 
 // Add prints SQL to add the column
-func (c *ColumnSchema) Add() {
+func (c *ColumnSchema) Add(obj interface{}) {
+	c2, ok := obj.(*ColumnSchema)
+	if !ok {
+		fmt.Println("Error!!!, ColumnSchema.Add(obj) needs a ColumnSchema instance", c2)
+	}
+
 	if c.get("data_type") == "character varying" {
 		maxLength, valid := getMaxLength(c.get("character_maximum_length"))
 		if !valid {
-		    fmt.Printf("ALTER TABLE %s ADD COLUMN %s character varying", c.get("table_name"), c.get("column_name"))
+			fmt.Printf("ALTER TABLE %s.%s ADD COLUMN %s character varying", c2.get("table_schema"), c.get("table_name"), c.get("column_name"))
 		} else {
-			fmt.Printf("ALTER TABLE %s ADD COLUMN %s character varying(%s)", c.get("table_name"), c.get("column_name"), maxLength)
+			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)
 		}
 	} else {
 		if c.get("data_type") == "ARRAY" {
 			fmt.Println("-- Note that adding of array data types are not yet generated properly.")
 		}
-		fmt.Printf("ALTER TABLE %s ADD COLUMN %s %s", c.get("table_name"), c.get("column_name"), c.get("data_type"))
+		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"))
 	}
 
 	if c.get("is_nullable") == "NO" {
@@ -110,9 +150,13 @@ func (c *ColumnSchema) Add() {
 }
 
 // Drop prints SQL to drop the column
-func (c *ColumnSchema) Drop() {
+func (c *ColumnSchema) Drop(obj interface{}) {
+	c2, ok := obj.(*ColumnSchema)
+	if !ok {
+		fmt.Println("Error!!!, ColumnSchema.Drop(obj) needs a ColumnSchema instance", c2)
+	}
 	// if dropping column
-	fmt.Printf("ALTER TABLE %s DROP COLUMN IF EXISTS %s;\n", c.get("table_name"), c.get("column_name"))
+	fmt.Printf("ALTER TABLE %s.%s DROP COLUMN IF EXISTS %s;\n", c2.get("table_schema"), c2.get("table_name"), c2.get("column_name"))
 }
 
 // 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{}) {
 		fmt.Println("Error!!!, ColumnSchema.Change(obj) needs a ColumnSchema instance", c2)
 	}
 
-	// Detect column type change (mostly varchar length, or number size increase) 
+	// Detect column type change (mostly varchar length, or number size increase)
 	// (integer to/from bigint is OK)
 	if c.get("data_type") == c2.get("data_type") {
 		if c.get("data_type") == "character varying" {
@@ -141,8 +185,8 @@ func (c *ColumnSchema) Change(obj interface{}) {
 				if max1Int < max2Int {
 					fmt.Println("-- WARNING: The next statement will shorten a character varying column, which may result in data loss.")
 				}
-				fmt.Printf("-- max1Valid: %v  max2Valid: %v ", max1Valid, max2Valid)
-				fmt.Printf("ALTER TABLE %s ALTER COLUMN %s TYPE character varying(%s);\n", c.get("table_name"), c.get("column_name"), max1)
+				fmt.Printf("-- max1Valid: %v  max2Valid: %v \n", max1Valid, max2Valid)
+				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)
 			}
 		}
 	}
@@ -155,27 +199,27 @@ func (c *ColumnSchema) Change(obj interface{}) {
 			if !max1Valid {
 				fmt.Println("-- WARNING: varchar column has no maximum length.  Setting to 1024")
 			}
-			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)
+			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)
 		} else {
-			fmt.Printf("ALTER TABLE %s ALTER COLUMN %s TYPE %s;\n", c.get("table_name"), c.get("column_name"), c.get("data_type"))
+			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"))
 		}
 	}
 
 	// Detect column default change (or added, dropped)
 	if c.get("column_default") == "null" {
 		if c.get("column_default") != "null" {
-			fmt.Printf("ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT;\n", c.get("table_name"), c.get("column_name"))
+			fmt.Printf("ALTER TABLE %s.%s ALTER COLUMN %s DROP DEFAULT;\n", c2.get("table_schema"), c.get("table_name"), c.get("column_name"))
 		}
 	} else if c.get("column_default") != c2.get("column_default") {
-		fmt.Printf("ALTER TABLE %s ALTER COLUMN %s SET DEFAULT %s;\n", c.get("table_name"), c.get("column_name"), c.get("column_default"))
+		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"))
 	}
 
 	// Detect not-null and nullable change
 	if c.get("is_nullable") != c2.get("is_nullable") {
 		if c.get("is_nullable") == "YES" {
-			fmt.Printf("ALTER TABLE %s ALTER COLUMN %s DROP NOT NULL;\n", c.get("table_name"), c.get("column_name"))
+			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"))
 		} else {
-			fmt.Printf("ALTER TABLE %s ALTER COLUMN %s SET NOT NULL;\n", c.get("table_name"), c.get("column_name"))
+			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"))
 		}
 	}
 }
@@ -188,21 +232,15 @@ func (c *ColumnSchema) Change(obj interface{}) {
  * Compare the columns in the two databases
  */
 func compareColumns(conn1 *sql.DB, conn2 *sql.DB) {
-	sql := `
-SELECT table_schema || '.' || table_name AS table_name
-    , column_name
-    , data_type
-    , is_nullable
-    , column_default
-    , character_maximum_length
-FROM information_schema.columns 
-WHERE table_schema NOT LIKE 'pg_%'
-  AND table_schema <> 'information_schema'
-  AND is_updatable = 'YES'
-ORDER BY table_name, column_name;`
+	
+	buf1 := new(bytes.Buffer)
+	columnSqlTemplate.Execute(buf1, dbInfo1)
 
-	rowChan1, _ := pgutil.QueryStrings(conn1, sql)
-	rowChan2, _ := pgutil.QueryStrings(conn2, sql)
+	buf2 := new(bytes.Buffer)
+	columnSqlTemplate.Execute(buf2, dbInfo2)
+
+	rowChan1, _ := pgutil.QueryStrings(conn1, buf1.String())
+	rowChan2, _ := pgutil.QueryStrings(conn2, buf2.String())
 
 	//rows1 := make([]map[string]string, 500)
 	rows1 := make(ColumnRows, 0)
@@ -218,6 +256,15 @@ ORDER BY table_name, column_name;`
 	}
 	sort.Sort(&rows2)
 
+    //for _, val := range rows1 {
+		//fmt.Println("list1: ", val["table_schema"], val["compare_name"], val["column_name"], val["character_maximum_length"] )
+	//}
+	//fmt.Println()
+
+    //for _, val := range rows2 {
+		//fmt.Println("list2: ", val["table_schema"], val["compare_name"], val["column_name"], val["character_maximum_length"])
+	//}
+
 	// We have to explicitly type this as Schema here for some unknown reason
 	var schema1 Schema = &ColumnSchema{rows: rows1, rowNum: -1}
 	var schema2 Schema = &ColumnSchema{rows: rows2, rowNum: -1}
diff --git a/flags.go b/flags.go
index 50c92a3..ecc0295 100644
--- a/flags.go
+++ b/flags.go
@@ -18,6 +18,7 @@ func parseFlags() (pgutil.DbInfo, pgutil.DbInfo) {
 	var dbHost1 = flag.StringP("host1", "H", "localhost", "db host")
 	var dbPort1 = flag.IntP("port1", "P", 5432, "db port")
 	var dbName1 = flag.StringP("dbname1", "D", "", "db name")
+	var dbSchema1 = flag.StringP("schema1", "S", "public", "schema name or * for all schemas")
 	var dbOptions1 = flag.StringP("options1", "O", "", "db options (eg. sslmode=disable)")
 
 	var dbUser2 = flag.StringP("user2", "u", "", "db user")
@@ -25,13 +26,14 @@ func parseFlags() (pgutil.DbInfo, pgutil.DbInfo) {
 	var dbHost2 = flag.StringP("host2", "h", "localhost", "db host")
 	var dbPort2 = flag.IntP("port2", "p", 5432, "db port")
 	var dbName2 = flag.StringP("dbname2", "d", "", "db name")
+	var dbSchema2 = flag.StringP("schema2", "s", "public", "schema name or * for all schemas")
 	var dbOptions2 = flag.StringP("options2", "o", "", "db options (eg. sslmode=disable)")
 
 	flag.Parse()
 
-	dbInfo1 := pgutil.DbInfo{DbName: *dbName1, DbHost: *dbHost1, DbPort: int32(*dbPort1), DbUser: *dbUser1, DbPass: *dbPass1, DbOptions: *dbOptions1}
+	dbInfo1 := pgutil.DbInfo{DbName: *dbName1, DbHost: *dbHost1, DbPort: int32(*dbPort1), DbUser: *dbUser1, DbPass: *dbPass1, DbSchema: *dbSchema1, DbOptions: *dbOptions1}
 
-	dbInfo2 := pgutil.DbInfo{DbName: *dbName2, DbHost: *dbHost2, DbPort: int32(*dbPort2), DbUser: *dbUser2, DbPass: *dbPass2, DbOptions: *dbOptions2}
+	dbInfo2 := pgutil.DbInfo{DbName: *dbName2, DbHost: *dbHost2, DbPort: int32(*dbPort2), DbUser: *dbUser2, DbPass: *dbPass2, DbSchema: *dbSchema2, DbOptions: *dbOptions2}
 
 	return dbInfo1, dbInfo2
 }
diff --git a/foreignkey.go b/foreignkey.go
index 6f9298e..048bcb2 100644
--- a/foreignkey.go
+++ b/foreignkey.go
@@ -90,12 +90,20 @@ func (c *ForeignKeySchema) Compare(obj interface{}) int {
 }
 
 // Add returns SQL to add the foreign key
-func (c *ForeignKeySchema) Add() {
+func (c *ForeignKeySchema) Add(obj interface{}) {
+	c2, ok := obj.(*ForeignKeySchema)
+	if !ok {
+		fmt.Println("Error!!!, ForeignKeySchema.Add(obj) needs a ForeignKeySchema instance", c2)
+	}
 	fmt.Printf("ALTER TABLE %s ADD CONSTRAINT %s %s;\n", c.get("table_name"), c.get("fk_name"), c.get("constraint_def"))
 }
 
 // Drop returns SQL to drop the foreign key
-func (c ForeignKeySchema) Drop() {
+func (c ForeignKeySchema) Drop(obj interface{}) {
+	c2, ok := obj.(*ForeignKeySchema)
+	if !ok {
+		fmt.Println("Error!!!, ForeignKeySchema.Drop(obj) needs a ForeignKeySchema instance", c2)
+	}
 	fmt.Printf("ALTER TABLE %s DROP CONSTRAINT %s; -- %s\n", c.get("table_name"), c.get("fk_name"), c.get("constraint_def"))
 }
 
@@ -103,7 +111,7 @@ func (c ForeignKeySchema) Drop() {
 func (c *ForeignKeySchema) Change(obj interface{}) {
 	c2, ok := obj.(*ForeignKeySchema)
 	if !ok {
-		fmt.Println("Error!!!, Change(obj) needs a ForeignKeySchema instance", c2)
+		fmt.Println("Error!!!, ForeignKeySchema.Change(obj) needs a ForeignKeySchema instance", c2)
 	}
 	// There is no "changing" a foreign key.  It either gets created or dropped (or left as-is).
 }
diff --git a/function.go b/function.go
index 6e7aa00..bc40e49 100644
--- a/function.go
+++ b/function.go
@@ -72,14 +72,22 @@ func (c *FunctionSchema) Compare(obj interface{}) int {
 }
 
 // Add returns SQL to create the function
-func (c FunctionSchema) Add() {
+func (c FunctionSchema) Add(obj interface{}) {
+	c2, ok := obj.(*FunctionSchema)
+	if !ok {
+		fmt.Println("Error!!!, FunctionSchema.Add needs a FunctionSchema instance", c2)
+	}
 	fmt.Println("-- STATEMENT-BEGIN")
 	fmt.Println(c.get("definition"))
 	fmt.Println("-- STATEMENT-END")
 }
 
 // Drop returns SQL to drop the function
-func (c FunctionSchema) Drop() {
+func (c FunctionSchema) Drop(obj interface{}) {
+	c2, ok := obj.(*FunctionSchema)
+	if !ok {
+		fmt.Println("Error!!!, FunctionSchema.Drop needs a FunctionSchema instance", c2)
+	}
 	fmt.Println("-- Note that CASCADE in the statement below will also drop any triggers depending on this function.")
 	fmt.Println("-- Also, if there are two functions with this name, you will need to add arguments to identify the correct one to drop.")
 	fmt.Println("-- (See http://www.postgresql.org/docs/9.4/interactive/sql-dropfunction.html) ")
diff --git a/grant-attribute.go b/grant-attribute.go
index f941ef9..2da886a 100644
--- a/grant-attribute.go
+++ b/grant-attribute.go
@@ -119,13 +119,23 @@ func (c *GrantAttributeSchema) Compare(obj interface{}) int {
 }
 
 // Add prints SQL to add the column
-func (c *GrantAttributeSchema) Add() {
+func (c *GrantAttributeSchema) Add(obj interface{}) {
+	c2, ok := obj.(*GrantAttributeSchema)
+	if !ok {
+		fmt.Println("-- Error!!!, Add needs a GrantAttributeSchema instance", c2)
+	}
+
 	role, grants := parseGrants(c.get("attribute_acl"))
 	fmt.Printf("GRANT %s (%s) ON %s TO %s; -- Add\n", strings.Join(grants, ", "), c.get("attribute_name"), c.get("relationship_name"), role)
 }
 
 // Drop prints SQL to drop the column
-func (c *GrantAttributeSchema) Drop() {
+func (c *GrantAttributeSchema) Drop(obj interface{}) {
+	c2, ok := obj.(*GrantAttributeSchema)
+	if !ok {
+		fmt.Println("-- Error!!!, Drop needs a GrantAttributeSchema instance", c2)
+	}
+
 	role, grants := parseGrants(c.get("attribute_acl"))
 	fmt.Printf("REVOKE %s (%s) ON %s FROM %s; -- Drop\n", strings.Join(grants, ", "), c.get("attribute_name"), c.get("relationship_name"), role)
 }
diff --git a/grant-relationship.go b/grant-relationship.go
index 8f0ac42..60e49c9 100644
--- a/grant-relationship.go
+++ b/grant-relationship.go
@@ -112,13 +112,23 @@ func (c *GrantRelationshipSchema) Compare(obj interface{}) int {
 }
 
 // Add prints SQL to add the column
-func (c *GrantRelationshipSchema) Add() {
+func (c *GrantRelationshipSchema) Add(obj interface{}) {
+	c2, ok := obj.(*GrantRelationshipSchema)
+	if !ok {
+		fmt.Println("-- Error!!!, Add needs a GrantRelationshipSchema instance", c2)
+	}
+
 	role, grants := parseGrants(c.get("relationship_acl"))
 	fmt.Printf("GRANT %s ON %s TO %s; -- Add\n", strings.Join(grants, ", "), c.get("relationship_name"), role)
 }
 
 // Drop prints SQL to drop the column
-func (c *GrantRelationshipSchema) Drop() {
+func (c *GrantRelationshipSchema) Drop(obj interface{}) {
+	c2, ok := obj.(*GrantRelationshipSchema)
+	if !ok {
+		fmt.Println("-- Error!!!, Drop needs a GrantRelationshipSchema instance", c2)
+	}
+
 	role, grants := parseGrants(c.get("relationship_acl"))
 	fmt.Printf("REVOKE %s ON %s FROM %s; -- Drop\n", strings.Join(grants, ", "), c.get("relationship_name"), role)
 }
@@ -127,7 +137,7 @@ func (c *GrantRelationshipSchema) Drop() {
 func (c *GrantRelationshipSchema) Change(obj interface{}) {
 	c2, ok := obj.(*GrantRelationshipSchema)
 	if !ok {
-		fmt.Println("-- Error!!!, change needs a GrantRelationshipSchema instance", c2)
+		fmt.Println("-- Error!!!, Change needs a GrantRelationshipSchema instance", c2)
 	}
 
 	role, grants1 := parseGrants(c.get("relationship_acl"))
diff --git a/index.go b/index.go
index 29dddcc..317128a 100644
--- a/index.go
+++ b/index.go
@@ -99,8 +99,13 @@ func (c *IndexSchema) Compare(obj interface{}) int {
 }
 
 // Add prints SQL to add the column
-func (c *IndexSchema) Add() {
+func (c *IndexSchema) Add(obj interface{}) {
 	//fmt.Println("--\n--Add\n--")
+	c2, ok := obj.(*IndexSchema)
+	if !ok {
+		fmt.Println("-- Error!!!, Add needs an IndexSchema instance", c2)
+	}
+
 
 	// Assertion
 	if c.get("index_def") == "null" || len(c.get("index_def")) == 0 {
@@ -124,8 +129,13 @@ func (c *IndexSchema) Add() {
 }
 
 // Drop prints SQL to drop the column
-func (c *IndexSchema) Drop() {
+func (c *IndexSchema) Drop(obj interface{}) {
 	//fmt.Println("--\n--Drop\n--")
+	c2, ok := obj.(*IndexSchema)
+	if !ok {
+		fmt.Println("-- Error!!!, Drop needs an IndexSchema instance", c2)
+	}
+
 	if c.get("constraint_def") != "null" {
 		fmt.Println("-- Warning, this may drop foreign keys pointing at this column.  Make sure you re-run the FOREIGN_KEY diff after running this SQL.")
 		//fmt.Printf("ALTER TABLE ONLY %s DROP CONSTRAINT IF EXISTS %s CASCADE; -- %s\n", c.get("table_name"), c.get("index_name"), c.get("constraint_def"))
@@ -140,8 +150,9 @@ func (c *IndexSchema) Drop() {
 func (c *IndexSchema) Change(obj interface{}) {
 	c2, ok := obj.(*IndexSchema)
 	if !ok {
-		fmt.Println("-- Error!!!, change needs an IndexSchema instance", c2)
+		fmt.Println("-- Error!!!, Change needs an IndexSchema instance", c2)
 	}
+
 	// Table and constraint name matches... We need to make sure the details match
 
 	// NOTE that there should always be an index_def for both c and c2 (but we're checking below anyway)
@@ -195,10 +206,10 @@ func (c *IndexSchema) Change(obj interface{}) {
 			fmt.Printf("--CHANGE: Different index defs:\n--    %s\n--    %s\n", c.get("index_def"), c2.get("index_def"))
 
 			// Drop the index (and maybe the constraint) so we can recreate the index
-			c.Drop()
+			c.Drop(c2)
 
 			// Recreate the index (and a constraint if specified)
-			c.Add()
+			c.Add(c2)
 		}
 	}
 
diff --git a/owner.go b/owner.go
index 4811a16..39733e0 100644
--- a/owner.go
+++ b/owner.go
@@ -82,12 +82,22 @@ func (c *OwnerSchema) Compare(obj interface{}) int {
 }
 
 // Add generates SQL to add the table/view owner
-func (c OwnerSchema) Add() {
+func (c OwnerSchema) Add(obj interface{}) {
+	c2, ok := obj.(*OwnerSchema)
+	if !ok {
+		fmt.Println("-- Error!!!, Add needs a OwnerSchema instance", c2)
+	}
+
 	fmt.Printf("-- Notice, db2 has no %s named %s. You probably need to run pgdiff with the %s option first.\n", c.get("type"), c.get("relationship_name"), c.get("type"))
 }
 
 // Drop generates SQL to drop the role
-func (c OwnerSchema) Drop() {
+func (c OwnerSchema) Drop(obj interface{}) {
+	c2, ok := obj.(*OwnerSchema)
+	if !ok {
+		fmt.Println("-- Error!!!, Drop needs a OwnerSchema instance", c2)
+	}
+
 	fmt.Printf("-- Notice, db2 has a %s that db1 does not: %s. Cannot compare owners.\n", c.get("type"), c.get("relationship_name"))
 }
 
diff --git a/pgdiff.go b/pgdiff.go
index 24314f2..c0cbfc8 100644
--- a/pgdiff.go
+++ b/pgdiff.go
@@ -18,8 +18,8 @@ import "github.com/joncrlsn/pgutil"
 // added, dropped, or changed to match another database.
 type Schema interface {
 	Compare(schema interface{}) int
-	Add()
-	Drop()
+	Add(schema interface{})
+	Drop(schema interface{})
 	Change(schema interface{})
 	NextRow() bool
 }
@@ -60,16 +60,24 @@ func main() {
 
 	if *versionPtr {
 		fmt.Fprintf(os.Stderr, "%s - version %s\n", os.Args[0], version)
-		fmt.Fprintln(os.Stderr, "Copyright (c) 2016 Jon Carlson.  All rights reserved.")
+		fmt.Fprintln(os.Stderr, "Copyright (c) 2017 Jon Carlson.  All rights reserved.")
 		fmt.Fprintln(os.Stderr, "Use of this source code is governed by the MIT license")
 		fmt.Fprintln(os.Stderr, "that can be found here: http://opensource.org/licenses/MIT")
 		os.Exit(1)
 	}
 
 	if len(args) == 0 {
-		fmt.Println("The required first argument is SchemaType: ROLE, SEQUENCE, TABLE, VIEW, COLUMN, INDEX, FOREIGN_KEY, OWNER, GRANT_RELATIONSHIP, GRANT_ATTRIBUTE")
+		fmt.Println("The required first argument is SchemaType: SCHEMA, ROLE, SEQUENCE, TABLE, VIEW, COLUMN, INDEX, FOREIGN_KEY, OWNER, GRANT_RELATIONSHIP, GRANT_ATTRIBUTE")
 		os.Exit(1)
 	}
+
+	// Verify schemas
+	schemas := dbInfo1.DbSchema + dbInfo2.DbSchema
+	if schemas != "**" && strings.Contains(schemas, "*") {
+		fmt.Println("If one schema is an asterisk, both must be.")
+		os.Exit(1)
+	}
+
 	schemaType = strings.ToUpper(args[0])
 	fmt.Println("-- schemaType:", schemaType)
 
@@ -87,6 +95,9 @@ func main() {
 	// of alter statements to generate.  Rather, all should be generated in the
 	// proper order.
 	if schemaType == "ALL" {
+		if dbInfo1.DbSchema == "*" {
+			compareSchematas(conn1, conn2)
+		}
 		compareRoles(conn1, conn2)
 		compareFunctions(conn1, conn2)
 		compareSchematas(conn1, conn2)
@@ -149,21 +160,21 @@ func doDiff(db1 Schema, db2 Schema) {
 		} else if compareVal < 0 {
 			// db2 is missing a value that db1 has
 			if more1 {
-				db1.Add()
+				db1.Add(db2)
 				more1 = db1.NextRow()
 			} else {
 				// db1 is at the end
-				db2.Drop()
+				db2.Drop(db2)
 				more2 = db2.NextRow()
 			}
 		} else if compareVal > 0 {
 			// db2 has an extra column that we don't want
 			if more2 {
-				db2.Drop()
+				db2.Drop(db2)
 				more2 = db2.NextRow()
 			} else {
 				// db2 is at the end
-				db1.Add()
+				db1.Add(db2)
 				more1 = db1.NextRow()
 			}
 		}
@@ -183,14 +194,16 @@ Options:
   -v, --verbose : print extra run information
   -U, --user1   : first postgres user 
   -u, --user2   : second postgres user 
-  -H, --host1   : first database host. default is localhost 
+  -H, --host1   : first database host.  default is localhost 
   -h, --host2   : second database host. default is localhost 
-  -P, --port1   : first port. default is 5432 
+  -P, --port1   : first port.  default is 5432 
   -p, --port2   : second port. default is 5432 
   -D, --dbname1 : first database name 
   -d, --dbname2 : second database name 
+  -S, --schema1 : first schema.  default is public
+  -s, --schema2 : second schema. default is public
 
-<schemaTpe> can be: ROLE, SEQUENCE, TABLE, VIEW, COLUMN, INDEX, FOREIGN_KEY, OWNER, GRANT_RELATIONSHIP, GRANT_ATTRIBUTE
+<schemaTpe> can be: SCHEMA ROLE, SEQUENCE, TABLE, VIEW, COLUMN, INDEX, FOREIGN_KEY, OWNER, GRANT_RELATIONSHIP, GRANT_ATTRIBUTE
 `)
 
 	os.Exit(2)
diff --git a/role.go b/role.go
index 3273d1c..4a1fae5 100644
--- a/role.go
+++ b/role.go
@@ -107,7 +107,12 @@ where option can be:
 */
 
 // Add generates SQL to add the constraint/index
-func (c RoleSchema) Add() {
+func (c RoleSchema) Add(obj interface{}) {
+	c2, ok := obj.(*RoleSchema)
+	if !ok {
+		fmt.Println("Error!!!, Add needs a RoleSchema instance", c2)
+	}
+
 	// We don't care about efficiency here so we just concat strings
 	options := " WITH PASSWORD 'changeme'"
 
@@ -152,7 +157,12 @@ func (c RoleSchema) Add() {
 }
 
 // Drop generates SQL to drop the role
-func (c RoleSchema) Drop() {
+func (c RoleSchema) Drop(obj interface{}) {
+	c2, ok := obj.(*RoleSchema)
+	if !ok {
+		fmt.Println("Error!!!, Drop needs a RoleSchema instance", c2)
+	}
+
 	fmt.Printf("DROP ROLE %s;\n", c.get("rolname"))
 }
 
@@ -160,7 +170,7 @@ func (c RoleSchema) Drop() {
 func (c RoleSchema) Change(obj interface{}) {
 	c2, ok := obj.(*RoleSchema)
 	if !ok {
-		fmt.Println("Error!!!, change needs a RoleSchema instance", c2)
+		fmt.Println("Error!!!, Change needs a RoleSchema instance", c2)
 	}
 
 	options := ""
diff --git a/schemata.go b/schemata.go
index cc4c684..9d07252 100644
--- a/schemata.go
+++ b/schemata.go
@@ -58,8 +58,6 @@ func (c *SchemataSchema) NextRow() bool {
 	return !c.done
 }
 
-
-
 // Compare tells you, in one pass, whether or not the first row matches, is less than, or greater than the second row
 func (c *SchemataSchema) Compare(obj interface{}) int {
 	c2, ok := obj.(*SchemataSchema)
@@ -74,15 +72,25 @@ func (c *SchemataSchema) Compare(obj interface{}) int {
 }
 
 // Add returns SQL to add the schemata
-func (c SchemataSchema) Add() {
+func (c SchemataSchema) Add(obj interface{}) {
 	// CREATE SCHEMA schema_name [ AUTHORIZATION user_name
+	c2, ok := obj.(*SchemataSchema)
+	if !ok {
+		fmt.Println("Error!!!, Add needs a SchemataSchema instance", c2)
+	}
+
 	fmt.Printf("CREATE SCHEMA %s AUTHORIZATION %s;", c.get("schema_name"), c.get("schema_owner"))
 	fmt.Println()
 }
 
 // Drop returns SQL to drop the schemata
-func (c SchemataSchema) Drop() {
+func (c SchemataSchema) Drop(obj interface{}) {
 	// DROP SCHEMA [ IF EXISTS ] name [, ...] [ CASCADE | RESTRICT ]
+	c2, ok := obj.(*SchemataSchema)
+	if !ok {
+		fmt.Println("Error!!!, Drop needs a SchemataSchema instance", c2)
+	}
+
 	fmt.Printf("DROP SCHEMA IF EXISTS %s;\n", c.get("schema_name"))
 }
 
diff --git a/schemata_test.go b/schemata_test.go
index a2be4d0..fa89ee6 100644
--- a/schemata_test.go
+++ b/schemata_test.go
@@ -16,19 +16,19 @@ SELECT schema_name
     , schema_owner
     , default_character_set_schema
 FROM information_schema.schemata
-WHERE table_schema NOT LIKE 'pg_%' 
-WHERE table_schema <> 'information_schema' 
+WHERE table_schema NOT LIKE 'pg_%'
+WHERE table_schema <> 'information_schema'
 ORDER BY schema_name;
 */
 
 // Note that these must be sorted by table name for this to work
-var testSchematas1= []map[string]string{
+var testSchematas1 = []map[string]string{
 	{"schema_name": "schema_add", "schema_owner": "noop"},
 	{"schema_name": "schema_same", "schema_owner": "noop"},
 }
 
 // Note that these must be sorted by schema_name for this to work
-var testSchematas2= []map[string]string{
+var testSchematas2 = []map[string]string{
 	{"schema_name": "schema_delete", "schema_owner": "noop"},
 	{"schema_name": "schema_same", "schema_owner": "noop"},
 }
diff --git a/sequence.go b/sequence.go
index 57db965..6323778 100644
--- a/sequence.go
+++ b/sequence.go
@@ -71,13 +71,13 @@ func (c *SequenceSchema) Compare(obj interface{}) int {
 }
 
 // Add returns SQL to add the table
-func (c SequenceSchema) Add() {
+func (c SequenceSchema) Add(obj interface{}) {
 	fmt.Printf("CREATE SEQUENCE %s INCREMENT %s MINVALUE %s MAXVALUE %s START %s;\n", c.get("sequence_name"), c.get("increment"), c.get("minimum_value"), c.get("maximum_value"), c.get("start_value"))
 
 }
 
 // Drop returns SQL to drop the table
-func (c SequenceSchema) Drop() {
+func (c SequenceSchema) Drop(obj interface{}) {
 	fmt.Printf("DROP SEQUENCE IF EXISTS %s;\n", c.get("sequence_name"))
 }
 
diff --git a/table.go b/table.go
index ccd130f..2188e99 100644
--- a/table.go
+++ b/table.go
@@ -6,11 +6,45 @@
 
 package main
 
-import "fmt"
-import "sort"
-import "database/sql"
-import "github.com/joncrlsn/pgutil"
-import "github.com/joncrlsn/misc"
+import (
+	"bytes"
+	"database/sql"
+	"fmt"
+	"github.com/joncrlsn/misc"
+	"github.com/joncrlsn/pgutil"
+	"sort"
+	"text/template"
+)
+
+var (
+	tableSqlTemplate = initTableSqlTemplate()
+)
+
+// Initializes the Sql template
+func initTableSqlTemplate() *template.Template {
+
+	sql := `
+SELECT table_schema
+    , {{if eq $.DbSchema "*" }}table_schema || '.' || {{end}}table_name AS compare_name
+	, table_name
+    , CASE table_type 
+	  WHEN 'BASE TABLE' THEN 'TABLE' 
+	  ELSE table_type END AS table_type
+    , is_insertable_into
+FROM information_schema.tables 
+WHERE table_type = 'BASE TABLE'
+{{if eq $.DbSchema "*" }}
+AND table_schema NOT LIKE 'pg_%' 
+AND table_schema <> 'information_schema' 
+{{else}}
+AND table_schema = '{{$.DbSchema}}'
+{{end}}
+ORDER BY compare_name;
+`
+	t := template.New("TableSqlTmpl")
+	template.Must(t.Parse(sql))
+	return t
+}
 
 // ==================================
 // TableRows definition
@@ -18,13 +52,12 @@ import "github.com/joncrlsn/misc"
 
 // TableRows is a sortable slice of string maps
 type TableRows []map[string]string
-
 func (slice TableRows) Len() int {
 	return len(slice)
 }
 
 func (slice TableRows) Less(i, j int) bool {
-	return slice[i]["table_name"] < slice[j]["table_name"]
+	return slice[i]["compare_name"] < slice[j]["compare_name"]
 }
 
 func (slice TableRows) Swap(i, j int) {
@@ -66,20 +99,28 @@ func (c *TableSchema) Compare(obj interface{}) int {
 		return +999
 	}
 
-	val := misc.CompareStrings(c.get("table_name"), c2.get("table_name"))
+	val := misc.CompareStrings(c.get("compare_name"), c2.get("compare_name"))
 	//fmt.Printf("-- Compared %v: %s with %s \n", val, c.get("table_name"), c2.get("table_name"))
 	return val
 }
 
 // Add returns SQL to add the table or view
-func (c TableSchema) Add() {
-	fmt.Printf("CREATE %s %s();", c.get("table_type"), c.get("table_name"))
+func (c TableSchema) Add(obj interface{}) {
+	c2, ok := obj.(*TableSchema)
+	if !ok {
+		fmt.Println("Error!!!, Add needs a TableSchema instance", c2)
+	}
+	fmt.Printf("CREATE %s %s.%s();", c.get("table_type"), c2.get("table_schema"), c.get("table_name"))
 	fmt.Println()
 }
 
 // Drop returns SQL to drop the table or view
-func (c TableSchema) Drop() {
-	fmt.Printf("DROP %s IF EXISTS %s;\n", c.get("table_type"), c.get("table_name"))
+func (c TableSchema) Drop(obj interface{}) {
+	c2, ok := obj.(*TableSchema)
+	if !ok {
+		fmt.Println("Error!!!, Drop needs a TableSchema instance", c2)
+	}
+	fmt.Printf("DROP %s IF EXISTS %s.%s;\n", c.get("table_type"), c2.get("table_schema"), c.get("table_name"))
 }
 
 // Change handles the case where the table and column match, but the details do not
@@ -93,18 +134,15 @@ func (c TableSchema) Change(obj interface{}) {
 
 // compareTables outputs SQL to make the table names match between DBs
 func compareTables(conn1 *sql.DB, conn2 *sql.DB) {
-	sql := `
-SELECT table_schema || '.' || table_name AS table_name
-    , CASE table_type WHEN 'BASE TABLE' THEN 'TABLE' ELSE table_type END AS table_type
-    , is_insertable_into
-FROM information_schema.tables 
-WHERE table_schema NOT LIKE 'pg_%' 
-  AND table_schema <> 'information_schema' 
-  AND table_type = 'BASE TABLE'
-ORDER BY table_name;`
 
-	rowChan1, _ := pgutil.QueryStrings(conn1, sql)
-	rowChan2, _ := pgutil.QueryStrings(conn2, sql)
+	buf1 := new(bytes.Buffer)
+	tableSqlTemplate.Execute(buf1, dbInfo1)
+
+	buf2 := new(bytes.Buffer)
+	tableSqlTemplate.Execute(buf2, dbInfo2)
+
+	rowChan1, _ := pgutil.QueryStrings(conn1, buf1.String())
+	rowChan2, _ := pgutil.QueryStrings(conn2, buf2.String())
 
 	rows1 := make(TableRows, 0)
 	for row := range rowChan1 {
diff --git a/table_test.go b/table_test.go
index 86a305a..7a5bb73 100644
--- a/table_test.go
+++ b/table_test.go
@@ -15,9 +15,9 @@ import (
 SELECT table_schema || '.' || table_name AS table_name
     , CASE table_type WHEN 'BASE TABLE' THEN 'TABLE' ELSE table_type END AS table_type
     , is_insertable_into
-FROM information_schema.tables 
-WHERE table_schema NOT LIKE 'pg_%' 
-WHERE table_schema <> 'information_schema' 
+FROM information_schema.tables
+WHERE table_schema NOT LIKE 'pg_%'
+WHERE table_schema <> 'information_schema'
 AND table_type = 'BASE TABLE'
 ORDER BY table_name;
 */
@@ -31,7 +31,7 @@ var testTables1a = []map[string]string{
 }
 
 // Note that these must be sorted by table_name for this to work
-var testTables1b= []map[string]string{
+var testTables1b = []map[string]string{
 	{"table_name": "schema1.delete", "table_type": "TABLE"},
 	{"table_name": "schema1.same", "table_type": "TABLE"},
 	{"table_name": "schema2.same", "table_type": "TABLE"},
diff --git a/test/README.md b/test/README.md
new file mode 100644
index 0000000..a3936ff
--- /dev/null
+++ b/test/README.md
@@ -0,0 +1,2 @@
+These are not automated tests (as much as I'd rather have automated tests), but manual tests 
+for verifying that individual schema types are working.
diff --git a/test/test-column.sh b/test/test-column.sh
new file mode 100755
index 0000000..c081701
--- /dev/null
+++ b/test/test-column.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+#
+# Useful for visually inspecting the output SQL to verify it is doing what it should
+#
+
+sudo su - postgres -- <<EOT
+psql <<'SQL'
+    DROP DATABASE IF EXISTS db1;
+    DROP DATABASE IF EXISTS db2;
+    DROP USER IF EXISTS u1;
+    CREATE USER u1 WITH SUPERUSER PASSWORD 'asdf';
+    CREATE DATABASE db1 WITH OWNER = u1 TEMPLATE = template1;
+    CREATE DATABASE db2 WITH OWNER = u1 TEMPLATE = template1;
+SQL
+EOT
+export PGPASSWORD=asdf
+
+echo
+echo "# Compare the tables in two schemas in the same database"
+
+#
+# Compare the columns in two schemas in the same database
+#
+psql -U u1 -h localhost -d db1 <<'EOS'
+    CREATE SCHEMA s1;
+    CREATE TABLE s1.table1 (
+      id integer, 
+      name varchar(24) 
+    );
+
+    CREATE SCHEMA s2;
+    CREATE TABLE s2.table1(
+                              -- id will be added to s2
+      name varchar(20),       -- name will grow to 24 in s2
+      description varchar(24) -- description will be dropped in s2
+    );
+    
+EOS
+
+
+echo
+echo "SQL to run:"
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
+          COLUMN
diff --git a/test/test-table.sh b/test/test-table.sh
new file mode 100755
index 0000000..3fe95ce
--- /dev/null
+++ b/test/test-table.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+#
+# Useful for visually inspecting the output SQL to verify it is doing what it should
+#
+
+sudo su - postgres -- <<EOT
+psql <<'SQL'
+    DROP DATABASE IF EXISTS db1;
+    DROP DATABASE IF EXISTS db2;
+    DROP USER IF EXISTS u1;
+    CREATE USER u1 WITH SUPERUSER PASSWORD 'asdf';
+    CREATE DATABASE db1 WITH OWNER = u1 TEMPLATE = template1;
+    CREATE DATABASE db2 WITH OWNER = u1 TEMPLATE = template1;
+SQL
+EOT
+export PGPASSWORD=asdf
+
+echo
+echo "# Compare the tables in two schemas in the same database"
+
+#
+# Compare the tables in two schemas in the same database
+#
+psql -U u1 -h localhost -d db1 <<'EOS'
+    CREATE SCHEMA s1;
+    CREATE TABLE s1.table9 (id integer); -- to be added to s2
+    CREATE TABLE s1.table10 (id integer);
+    
+    CREATE SCHEMA s2;
+    CREATE TABLE s2.table10 (id integer);
+    CREATE TABLE s2.table11 (id integer); -- will be dropped from s2
+EOS
+
+
+echo
+echo "SQL to run:"
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
+          TABLE
+
+echo
+echo "# Compare the tables in all schemas in two databases"
+
+
+#
+# Compare the tables in all schemas in two databases
+#
+psql -U u1 -h localhost -d db2 <<'EOS'
+    CREATE SCHEMA s1;
+    CREATE TABLE s1.table9 (id integer);
+    -- table10 will be added (in db1, but not db2) 
+
+    CREATE SCHEMA s2;
+    CREATE TABLE s2.table10 (id integer); 
+    CREATE TABLE s2.table11 (id integer);
+    CREATE TABLE s2.table12 (id integer); -- will be dropped (not in db1)
+EOS
+
+echo
+echo "SQL to run:"
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "*" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db2" -s "*" -o "sslmode=disable" \
+          TABLE
diff --git a/trigger.go b/trigger.go
index fe5834d..150c841 100644
--- a/trigger.go
+++ b/trigger.go
@@ -78,12 +78,22 @@ func (c *TriggerSchema) Compare(obj interface{}) int {
 }
 
 // Add returns SQL to create the trigger
-func (c TriggerSchema) Add() {
+func (c TriggerSchema) Add(obj interface{}) {
+	c2, ok := obj.(*TriggerSchema)
+	if !ok {
+		fmt.Println("Error!!!, Add needs a TriggerSchema instance", c2)
+	}
+
 	fmt.Printf("%s;\n", c.get("definition"))
 }
 
 // Drop returns SQL to drop the trigger
-func (c TriggerSchema) Drop() {
+func (c TriggerSchema) Drop(obj interface{}) {
+	c2, ok := obj.(*TriggerSchema)
+	if !ok {
+		fmt.Println("Error!!!, Drop needs a TriggerSchema instance", c2)
+	}
+
 	fmt.Printf("DROP TRIGGER %s ON %s;\n", c.get("trigger_name"), c.get("table_name"))
 }
 
diff --git a/view.go b/view.go
index 4ea7a07..11db676 100644
--- a/view.go
+++ b/view.go
@@ -72,12 +72,22 @@ func (c *ViewSchema) Compare(obj interface{}) int {
 }
 
 // Add returns SQL to create the view
-func (c ViewSchema) Add() {
+func (c ViewSchema) Add(obj interface{}) {
+	c2, ok := obj.(*ViewSchema)
+	if !ok {
+		fmt.Println("Error!!!, Add needs a ViewSchema instance", c2)
+	}
+
 	fmt.Printf("CREATE VIEW %s AS %s \n\n", c.get("viewname"), c.get("definition"))
 }
 
 // Drop returns SQL to drop the view
-func (c ViewSchema) Drop() {
+func (c ViewSchema) Drop(obj interface{}) {
+	c2, ok := obj.(*ViewSchema)
+	if !ok {
+		fmt.Println("Error!!!, Drop needs a ViewSchema instance", c2)
+	}
+
 	fmt.Printf("DROP VIEW IF EXISTS %s;\n\n", c.get("viewname"))
 }
 

From 6ba9ec74884df8481656cac10aa936466688a343 Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Sat, 21 Oct 2017 23:52:18 -0500
Subject: [PATCH 05/74] wip

Former-commit-id: ce17d8583bd70b73d6df7d86ec997e6bac79f4bc
---
 test/add-schema.sh  |  5 +++++
 test/start-fresh.sh | 13 +++++++++++++
 test/test-table.sh  | 27 +++++++++++++++------------
 3 files changed, 33 insertions(+), 12 deletions(-)
 create mode 100755 test/add-schema.sh
 create mode 100755 test/start-fresh.sh

diff --git a/test/add-schema.sh b/test/add-schema.sh
new file mode 100755
index 0000000..4fc420c
--- /dev/null
+++ b/test/add-schema.sh
@@ -0,0 +1,5 @@
+
+psql -U u1 -h localhost -d db1 >/dev/null <<EOS
+    $1
+EOS
+
diff --git a/test/start-fresh.sh b/test/start-fresh.sh
new file mode 100755
index 0000000..5ceedb8
--- /dev/null
+++ b/test/start-fresh.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+sudo su - postgres -- <<EOT
+psql <<'SQL'
+    DROP DATABASE IF EXISTS db1;
+    DROP DATABASE IF EXISTS db2;
+    DROP USER IF EXISTS u1;
+    CREATE USER u1 WITH SUPERUSER PASSWORD 'asdf';
+    CREATE DATABASE db1 WITH OWNER = u1 TEMPLATE = template1;
+    CREATE DATABASE db2 WITH OWNER = u1 TEMPLATE = template1;
+SQL
+EOT 
+
diff --git a/test/test-table.sh b/test/test-table.sh
index 3fe95ce..450b775 100755
--- a/test/test-table.sh
+++ b/test/test-table.sh
@@ -3,16 +3,18 @@
 # Useful for visually inspecting the output SQL to verify it is doing what it should
 #
 
-sudo su - postgres -- <<EOT
-psql <<'SQL'
-    DROP DATABASE IF EXISTS db1;
-    DROP DATABASE IF EXISTS db2;
-    DROP USER IF EXISTS u1;
-    CREATE USER u1 WITH SUPERUSER PASSWORD 'asdf';
-    CREATE DATABASE db1 WITH OWNER = u1 TEMPLATE = template1;
-    CREATE DATABASE db2 WITH OWNER = u1 TEMPLATE = template1;
-SQL
-EOT
+source ./start-fresh.sh >/dev/null 
+
+#sudo su - postgres -- <<EOT
+#psql <<'SQL'
+#    DROP DATABASE IF EXISTS db1;
+#    DROP DATABASE IF EXISTS db2;
+#    DROP USER IF EXISTS u1;
+#    CREATE USER u1 WITH SUPERUSER PASSWORD 'asdf';
+#    CREATE DATABASE db1 WITH OWNER = u1 TEMPLATE = template1;
+#    CREATE DATABASE db2 WITH OWNER = u1 TEMPLATE = template1;
+#SQL
+#EOT
 export PGPASSWORD=asdf
 
 echo
@@ -21,7 +23,8 @@ echo "# Compare the tables in two schemas in the same database"
 #
 # Compare the tables in two schemas in the same database
 #
-psql -U u1 -h localhost -d db1 <<'EOS'
+#psql -U u1 -h localhost -d db1 <<'EOS'
+./add-schema.sh "
     CREATE SCHEMA s1;
     CREATE TABLE s1.table9 (id integer); -- to be added to s2
     CREATE TABLE s1.table10 (id integer);
@@ -29,7 +32,7 @@ psql -U u1 -h localhost -d db1 <<'EOS'
     CREATE SCHEMA s2;
     CREATE TABLE s2.table10 (id integer);
     CREATE TABLE s2.table11 (id integer); -- will be dropped from s2
-EOS
+"
 
 
 echo

From f943dd3905c4ff692900a188cf57cf9677d816e1 Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Mon, 23 Oct 2017 21:46:44 -0500
Subject: [PATCH 06/74] 0.9.3 checkpoint -- bedtime

Former-commit-id: ecab74e42362eef383cf25f6cc548e9d29def1ab
---
 column.go               | 23 +++++++---------
 foreignkey.go           | 12 ++-------
 function.go             | 12 ++-------
 grant-attribute.go      | 14 ++--------
 grant-relationship.go   | 14 ++--------
 index.go                | 20 +++-----------
 owner.go                | 16 +++--------
 pgdiff.go               | 12 ++++-----
 role.go                 | 13 ++-------
 schemata.go             | 14 ++--------
 sequence.go             |  4 +--
 table.go                | 18 +++++--------
 test/add-schema.sh      |  5 ----
 test/functions.sh       | 11 ++++++++
 test/populate-db.sh     |  9 +++++++
 test/start-fresh.sh     |  8 ++++--
 test/test-column.sh     | 59 +++++++++++++++++++++++++++++------------
 test/test-foreignkey.sh | 50 ++++++++++++++++++++++++++++++++++
 test/test-table.sh      | 39 ++++++++++++---------------
 trigger.go              | 14 ++--------
 view.go                 | 14 ++--------
 21 files changed, 183 insertions(+), 198 deletions(-)
 delete mode 100755 test/add-schema.sh
 create mode 100644 test/functions.sh
 create mode 100755 test/populate-db.sh
 create mode 100755 test/test-foreignkey.sh

diff --git a/column.go b/column.go
index c4a0449..af48b01 100644
--- a/column.go
+++ b/column.go
@@ -120,24 +120,25 @@ func (c *ColumnSchema) Compare(obj interface{}) int {
 }
 
 // Add prints SQL to add the column
-func (c *ColumnSchema) Add(obj interface{}) {
-	c2, ok := obj.(*ColumnSchema)
-	if !ok {
-		fmt.Println("Error!!!, ColumnSchema.Add(obj) needs a ColumnSchema instance", c2)
+func (c *ColumnSchema) Add() {
+	
+	schema := dbInfo2.DbSchema
+	if schema == "*" {
+		schema = c.get("table_schema")
 	}
 
 	if c.get("data_type") == "character varying" {
 		maxLength, valid := getMaxLength(c.get("character_maximum_length"))
 		if !valid {
-			fmt.Printf("ALTER TABLE %s.%s ADD COLUMN %s character varying", c2.get("table_schema"), c.get("table_name"), c.get("column_name"))
+			fmt.Printf("ALTER TABLE %s.%s ADD COLUMN %s character varying", schema, c.get("table_name"), c.get("column_name"))
 		} else {
-			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)
+			fmt.Printf("ALTER TABLE %s.%s ADD COLUMN %s character varying(%s)", schema, c.get("table_name"), c.get("column_name"), maxLength)
 		}
 	} else {
 		if c.get("data_type") == "ARRAY" {
 			fmt.Println("-- Note that adding of array data types are not yet generated properly.")
 		}
-		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"))
+		fmt.Printf("ALTER TABLE %s.%s ADD COLUMN %s %s", schema, c.get("table_name"), c.get("column_name"), c.get("data_type"))
 	}
 
 	if c.get("is_nullable") == "NO" {
@@ -150,13 +151,9 @@ func (c *ColumnSchema) Add(obj interface{}) {
 }
 
 // Drop prints SQL to drop the column
-func (c *ColumnSchema) Drop(obj interface{}) {
-	c2, ok := obj.(*ColumnSchema)
-	if !ok {
-		fmt.Println("Error!!!, ColumnSchema.Drop(obj) needs a ColumnSchema instance", c2)
-	}
+func (c *ColumnSchema) Drop() {
 	// if dropping column
-	fmt.Printf("ALTER TABLE %s.%s DROP COLUMN IF EXISTS %s;\n", c2.get("table_schema"), c2.get("table_name"), c2.get("column_name"))
+	fmt.Printf("ALTER TABLE %s.%s DROP COLUMN IF EXISTS %s;\n", c.get("table_schema"), c.get("table_name"), c.get("column_name"))
 }
 
 // Change handles the case where the table and column match, but the details do not
diff --git a/foreignkey.go b/foreignkey.go
index 048bcb2..fe65ffa 100644
--- a/foreignkey.go
+++ b/foreignkey.go
@@ -90,20 +90,12 @@ func (c *ForeignKeySchema) Compare(obj interface{}) int {
 }
 
 // Add returns SQL to add the foreign key
-func (c *ForeignKeySchema) Add(obj interface{}) {
-	c2, ok := obj.(*ForeignKeySchema)
-	if !ok {
-		fmt.Println("Error!!!, ForeignKeySchema.Add(obj) needs a ForeignKeySchema instance", c2)
-	}
+func (c *ForeignKeySchema) Add() {
 	fmt.Printf("ALTER TABLE %s ADD CONSTRAINT %s %s;\n", c.get("table_name"), c.get("fk_name"), c.get("constraint_def"))
 }
 
 // Drop returns SQL to drop the foreign key
-func (c ForeignKeySchema) Drop(obj interface{}) {
-	c2, ok := obj.(*ForeignKeySchema)
-	if !ok {
-		fmt.Println("Error!!!, ForeignKeySchema.Drop(obj) needs a ForeignKeySchema instance", c2)
-	}
+func (c ForeignKeySchema) Drop() {
 	fmt.Printf("ALTER TABLE %s DROP CONSTRAINT %s; -- %s\n", c.get("table_name"), c.get("fk_name"), c.get("constraint_def"))
 }
 
diff --git a/function.go b/function.go
index bc40e49..6e7aa00 100644
--- a/function.go
+++ b/function.go
@@ -72,22 +72,14 @@ func (c *FunctionSchema) Compare(obj interface{}) int {
 }
 
 // Add returns SQL to create the function
-func (c FunctionSchema) Add(obj interface{}) {
-	c2, ok := obj.(*FunctionSchema)
-	if !ok {
-		fmt.Println("Error!!!, FunctionSchema.Add needs a FunctionSchema instance", c2)
-	}
+func (c FunctionSchema) Add() {
 	fmt.Println("-- STATEMENT-BEGIN")
 	fmt.Println(c.get("definition"))
 	fmt.Println("-- STATEMENT-END")
 }
 
 // Drop returns SQL to drop the function
-func (c FunctionSchema) Drop(obj interface{}) {
-	c2, ok := obj.(*FunctionSchema)
-	if !ok {
-		fmt.Println("Error!!!, FunctionSchema.Drop needs a FunctionSchema instance", c2)
-	}
+func (c FunctionSchema) Drop() {
 	fmt.Println("-- Note that CASCADE in the statement below will also drop any triggers depending on this function.")
 	fmt.Println("-- Also, if there are two functions with this name, you will need to add arguments to identify the correct one to drop.")
 	fmt.Println("-- (See http://www.postgresql.org/docs/9.4/interactive/sql-dropfunction.html) ")
diff --git a/grant-attribute.go b/grant-attribute.go
index 2da886a..f941ef9 100644
--- a/grant-attribute.go
+++ b/grant-attribute.go
@@ -119,23 +119,13 @@ func (c *GrantAttributeSchema) Compare(obj interface{}) int {
 }
 
 // Add prints SQL to add the column
-func (c *GrantAttributeSchema) Add(obj interface{}) {
-	c2, ok := obj.(*GrantAttributeSchema)
-	if !ok {
-		fmt.Println("-- Error!!!, Add needs a GrantAttributeSchema instance", c2)
-	}
-
+func (c *GrantAttributeSchema) Add() {
 	role, grants := parseGrants(c.get("attribute_acl"))
 	fmt.Printf("GRANT %s (%s) ON %s TO %s; -- Add\n", strings.Join(grants, ", "), c.get("attribute_name"), c.get("relationship_name"), role)
 }
 
 // Drop prints SQL to drop the column
-func (c *GrantAttributeSchema) Drop(obj interface{}) {
-	c2, ok := obj.(*GrantAttributeSchema)
-	if !ok {
-		fmt.Println("-- Error!!!, Drop needs a GrantAttributeSchema instance", c2)
-	}
-
+func (c *GrantAttributeSchema) Drop() {
 	role, grants := parseGrants(c.get("attribute_acl"))
 	fmt.Printf("REVOKE %s (%s) ON %s FROM %s; -- Drop\n", strings.Join(grants, ", "), c.get("attribute_name"), c.get("relationship_name"), role)
 }
diff --git a/grant-relationship.go b/grant-relationship.go
index 60e49c9..e974a83 100644
--- a/grant-relationship.go
+++ b/grant-relationship.go
@@ -112,23 +112,13 @@ func (c *GrantRelationshipSchema) Compare(obj interface{}) int {
 }
 
 // Add prints SQL to add the column
-func (c *GrantRelationshipSchema) Add(obj interface{}) {
-	c2, ok := obj.(*GrantRelationshipSchema)
-	if !ok {
-		fmt.Println("-- Error!!!, Add needs a GrantRelationshipSchema instance", c2)
-	}
-
+func (c *GrantRelationshipSchema) Add() {
 	role, grants := parseGrants(c.get("relationship_acl"))
 	fmt.Printf("GRANT %s ON %s TO %s; -- Add\n", strings.Join(grants, ", "), c.get("relationship_name"), role)
 }
 
 // Drop prints SQL to drop the column
-func (c *GrantRelationshipSchema) Drop(obj interface{}) {
-	c2, ok := obj.(*GrantRelationshipSchema)
-	if !ok {
-		fmt.Println("-- Error!!!, Drop needs a GrantRelationshipSchema instance", c2)
-	}
-
+func (c *GrantRelationshipSchema) Drop() {
 	role, grants := parseGrants(c.get("relationship_acl"))
 	fmt.Printf("REVOKE %s ON %s FROM %s; -- Drop\n", strings.Join(grants, ", "), c.get("relationship_name"), role)
 }
diff --git a/index.go b/index.go
index 317128a..eeb5743 100644
--- a/index.go
+++ b/index.go
@@ -99,13 +99,7 @@ func (c *IndexSchema) Compare(obj interface{}) int {
 }
 
 // Add prints SQL to add the column
-func (c *IndexSchema) Add(obj interface{}) {
-	//fmt.Println("--\n--Add\n--")
-	c2, ok := obj.(*IndexSchema)
-	if !ok {
-		fmt.Println("-- Error!!!, Add needs an IndexSchema instance", c2)
-	}
-
+func (c *IndexSchema) Add() {
 
 	// Assertion
 	if c.get("index_def") == "null" || len(c.get("index_def")) == 0 {
@@ -129,13 +123,7 @@ func (c *IndexSchema) Add(obj interface{}) {
 }
 
 // Drop prints SQL to drop the column
-func (c *IndexSchema) Drop(obj interface{}) {
-	//fmt.Println("--\n--Drop\n--")
-	c2, ok := obj.(*IndexSchema)
-	if !ok {
-		fmt.Println("-- Error!!!, Drop needs an IndexSchema instance", c2)
-	}
-
+func (c *IndexSchema) Drop() {
 	if c.get("constraint_def") != "null" {
 		fmt.Println("-- Warning, this may drop foreign keys pointing at this column.  Make sure you re-run the FOREIGN_KEY diff after running this SQL.")
 		//fmt.Printf("ALTER TABLE ONLY %s DROP CONSTRAINT IF EXISTS %s CASCADE; -- %s\n", c.get("table_name"), c.get("index_name"), c.get("constraint_def"))
@@ -206,10 +194,10 @@ func (c *IndexSchema) Change(obj interface{}) {
 			fmt.Printf("--CHANGE: Different index defs:\n--    %s\n--    %s\n", c.get("index_def"), c2.get("index_def"))
 
 			// Drop the index (and maybe the constraint) so we can recreate the index
-			c.Drop(c2)
+			c.Drop()
 
 			// Recreate the index (and a constraint if specified)
-			c.Add(c2)
+			c.Add()
 		}
 	}
 
diff --git a/owner.go b/owner.go
index 39733e0..954ace2 100644
--- a/owner.go
+++ b/owner.go
@@ -82,22 +82,12 @@ func (c *OwnerSchema) Compare(obj interface{}) int {
 }
 
 // Add generates SQL to add the table/view owner
-func (c OwnerSchema) Add(obj interface{}) {
-	c2, ok := obj.(*OwnerSchema)
-	if !ok {
-		fmt.Println("-- Error!!!, Add needs a OwnerSchema instance", c2)
-	}
-
+func (c OwnerSchema) Add() {
 	fmt.Printf("-- Notice, db2 has no %s named %s. You probably need to run pgdiff with the %s option first.\n", c.get("type"), c.get("relationship_name"), c.get("type"))
 }
 
-// Drop generates SQL to drop the role
-func (c OwnerSchema) Drop(obj interface{}) {
-	c2, ok := obj.(*OwnerSchema)
-	if !ok {
-		fmt.Println("-- Error!!!, Drop needs a OwnerSchema instance", c2)
-	}
-
+// Drop generates SQL to drop the owner
+func (c OwnerSchema) Drop() {
 	fmt.Printf("-- Notice, db2 has a %s that db1 does not: %s. Cannot compare owners.\n", c.get("type"), c.get("relationship_name"))
 }
 
diff --git a/pgdiff.go b/pgdiff.go
index c0cbfc8..1ad458c 100644
--- a/pgdiff.go
+++ b/pgdiff.go
@@ -18,8 +18,8 @@ import "github.com/joncrlsn/pgutil"
 // added, dropped, or changed to match another database.
 type Schema interface {
 	Compare(schema interface{}) int
-	Add(schema interface{})
-	Drop(schema interface{})
+	Add()
+	Drop()
 	Change(schema interface{})
 	NextRow() bool
 }
@@ -160,21 +160,21 @@ func doDiff(db1 Schema, db2 Schema) {
 		} else if compareVal < 0 {
 			// db2 is missing a value that db1 has
 			if more1 {
-				db1.Add(db2)
+				db1.Add()
 				more1 = db1.NextRow()
 			} else {
 				// db1 is at the end
-				db2.Drop(db2)
+				db2.Drop()
 				more2 = db2.NextRow()
 			}
 		} else if compareVal > 0 {
 			// db2 has an extra column that we don't want
 			if more2 {
-				db2.Drop(db2)
+				db2.Drop()
 				more2 = db2.NextRow()
 			} else {
 				// db2 is at the end
-				db1.Add(db2)
+				db1.Add()
 				more1 = db1.NextRow()
 			}
 		}
diff --git a/role.go b/role.go
index 4a1fae5..b7a43c9 100644
--- a/role.go
+++ b/role.go
@@ -107,11 +107,7 @@ where option can be:
 */
 
 // Add generates SQL to add the constraint/index
-func (c RoleSchema) Add(obj interface{}) {
-	c2, ok := obj.(*RoleSchema)
-	if !ok {
-		fmt.Println("Error!!!, Add needs a RoleSchema instance", c2)
-	}
+func (c RoleSchema) Add() {
 
 	// We don't care about efficiency here so we just concat strings
 	options := " WITH PASSWORD 'changeme'"
@@ -157,12 +153,7 @@ func (c RoleSchema) Add(obj interface{}) {
 }
 
 // Drop generates SQL to drop the role
-func (c RoleSchema) Drop(obj interface{}) {
-	c2, ok := obj.(*RoleSchema)
-	if !ok {
-		fmt.Println("Error!!!, Drop needs a RoleSchema instance", c2)
-	}
-
+func (c RoleSchema) Drop() {
 	fmt.Printf("DROP ROLE %s;\n", c.get("rolname"))
 }
 
diff --git a/schemata.go b/schemata.go
index 9d07252..ffe9086 100644
--- a/schemata.go
+++ b/schemata.go
@@ -72,25 +72,15 @@ func (c *SchemataSchema) Compare(obj interface{}) int {
 }
 
 // Add returns SQL to add the schemata
-func (c SchemataSchema) Add(obj interface{}) {
+func (c SchemataSchema) Add() {
 	// CREATE SCHEMA schema_name [ AUTHORIZATION user_name
-	c2, ok := obj.(*SchemataSchema)
-	if !ok {
-		fmt.Println("Error!!!, Add needs a SchemataSchema instance", c2)
-	}
-
 	fmt.Printf("CREATE SCHEMA %s AUTHORIZATION %s;", c.get("schema_name"), c.get("schema_owner"))
 	fmt.Println()
 }
 
 // Drop returns SQL to drop the schemata
-func (c SchemataSchema) Drop(obj interface{}) {
+func (c SchemataSchema) Drop() {
 	// DROP SCHEMA [ IF EXISTS ] name [, ...] [ CASCADE | RESTRICT ]
-	c2, ok := obj.(*SchemataSchema)
-	if !ok {
-		fmt.Println("Error!!!, Drop needs a SchemataSchema instance", c2)
-	}
-
 	fmt.Printf("DROP SCHEMA IF EXISTS %s;\n", c.get("schema_name"))
 }
 
diff --git a/sequence.go b/sequence.go
index 6323778..57db965 100644
--- a/sequence.go
+++ b/sequence.go
@@ -71,13 +71,13 @@ func (c *SequenceSchema) Compare(obj interface{}) int {
 }
 
 // Add returns SQL to add the table
-func (c SequenceSchema) Add(obj interface{}) {
+func (c SequenceSchema) Add() {
 	fmt.Printf("CREATE SEQUENCE %s INCREMENT %s MINVALUE %s MAXVALUE %s START %s;\n", c.get("sequence_name"), c.get("increment"), c.get("minimum_value"), c.get("maximum_value"), c.get("start_value"))
 
 }
 
 // Drop returns SQL to drop the table
-func (c SequenceSchema) Drop(obj interface{}) {
+func (c SequenceSchema) Drop() {
 	fmt.Printf("DROP SEQUENCE IF EXISTS %s;\n", c.get("sequence_name"))
 }
 
diff --git a/table.go b/table.go
index 2188e99..30992c4 100644
--- a/table.go
+++ b/table.go
@@ -105,22 +105,18 @@ func (c *TableSchema) Compare(obj interface{}) int {
 }
 
 // Add returns SQL to add the table or view
-func (c TableSchema) Add(obj interface{}) {
-	c2, ok := obj.(*TableSchema)
-	if !ok {
-		fmt.Println("Error!!!, Add needs a TableSchema instance", c2)
+func (c TableSchema) Add() {
+	schema := dbInfo2.DbSchema
+	if schema == "*" {
+		schema = c.get("table_schema")
 	}
-	fmt.Printf("CREATE %s %s.%s();", c.get("table_type"), c2.get("table_schema"), c.get("table_name"))
+	fmt.Printf("CREATE %s %s.%s();", c.get("table_type"), schema, c.get("table_name"))
 	fmt.Println()
 }
 
 // Drop returns SQL to drop the table or view
-func (c TableSchema) Drop(obj interface{}) {
-	c2, ok := obj.(*TableSchema)
-	if !ok {
-		fmt.Println("Error!!!, Drop needs a TableSchema instance", c2)
-	}
-	fmt.Printf("DROP %s IF EXISTS %s.%s;\n", c.get("table_type"), c2.get("table_schema"), c.get("table_name"))
+func (c TableSchema) Drop() {
+	fmt.Printf("DROP %s IF EXISTS %s.%s;\n", c.get("table_type"), c.get("table_schema"), c.get("table_name"))
 }
 
 // Change handles the case where the table and column match, but the details do not
diff --git a/test/add-schema.sh b/test/add-schema.sh
deleted file mode 100755
index 4fc420c..0000000
--- a/test/add-schema.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-
-psql -U u1 -h localhost -d db1 >/dev/null <<EOS
-    $1
-EOS
-
diff --git a/test/functions.sh b/test/functions.sh
new file mode 100644
index 0000000..8b11ea2
--- /dev/null
+++ b/test/functions.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+sudo su - postgres -- <<EOT
+psql <<'SQL'
+    DROP DATABASE IF EXISTS db1;
+    DROP DATABASE IF EXISTS db2;
+    DROP USER IF EXISTS u1;
+    CREATE USER u1 WITH SUPERUSER PASSWORD 'asdf';
+    CREATE DATABASE db1 WITH OWNER = u1 TEMPLATE = template1;
+    CREATE DATABASE db2 WITH OWNER = u1 TEMPLATE = template1;
+SQL
+EOT
diff --git a/test/populate-db.sh b/test/populate-db.sh
new file mode 100755
index 0000000..c1eb0fa
--- /dev/null
+++ b/test/populate-db.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+#
+
+db=$1
+
+PGPASSWORD=asdf psql -U u1 -h localhost -d $db >/dev/null <<EOS
+    $2
+EOS
+
diff --git a/test/start-fresh.sh b/test/start-fresh.sh
index 5ceedb8..34cb0a0 100755
--- a/test/start-fresh.sh
+++ b/test/start-fresh.sh
@@ -1,4 +1,8 @@
 #!/bin/bash
+#
+# Wipe out and recreate a testing user u1
+# Wipe out and recreate 2 known databases (db1, db2) used for testing 
+#
 
 sudo su - postgres -- <<EOT
 psql <<'SQL'
@@ -6,8 +10,8 @@ psql <<'SQL'
     DROP DATABASE IF EXISTS db2;
     DROP USER IF EXISTS u1;
     CREATE USER u1 WITH SUPERUSER PASSWORD 'asdf';
-    CREATE DATABASE db1 WITH OWNER = u1 TEMPLATE = template1;
-    CREATE DATABASE db2 WITH OWNER = u1 TEMPLATE = template1;
+    CREATE DATABASE db1 WITH OWNER = u1 TEMPLATE = template0;
+    CREATE DATABASE db2 WITH OWNER = u1 TEMPLATE = template0;
 SQL
 EOT 
 
diff --git a/test/test-column.sh b/test/test-column.sh
index c081701..bde1877 100755
--- a/test/test-column.sh
+++ b/test/test-column.sh
@@ -3,25 +3,19 @@
 # Useful for visually inspecting the output SQL to verify it is doing what it should
 #
 
-sudo su - postgres -- <<EOT
-psql <<'SQL'
-    DROP DATABASE IF EXISTS db1;
-    DROP DATABASE IF EXISTS db2;
-    DROP USER IF EXISTS u1;
-    CREATE USER u1 WITH SUPERUSER PASSWORD 'asdf';
-    CREATE DATABASE db1 WITH OWNER = u1 TEMPLATE = template1;
-    CREATE DATABASE db2 WITH OWNER = u1 TEMPLATE = template1;
-SQL
-EOT
-export PGPASSWORD=asdf
+source ./start-fresh.sh >/dev/null
 
 echo
 echo "# Compare the tables in two schemas in the same database"
+echo "# Expect SQL:"
+echo "#   Add s2.table1.id"
+echo "#   Drop s2.table1.description"
+echo "#   Alter s2.table1.name to varchar(24)"
 
 #
 # Compare the columns in two schemas in the same database
 #
-psql -U u1 -h localhost -d db1 <<'EOS'
+./populate-db.sh db1 "
     CREATE SCHEMA s1;
     CREATE TABLE s1.table1 (
       id integer, 
@@ -31,15 +25,46 @@ psql -U u1 -h localhost -d db1 <<'EOS'
     CREATE SCHEMA s2;
     CREATE TABLE s2.table1(
                               -- id will be added to s2
-      name varchar(20),       -- name will grow to 24 in s2
+      name varchar(12),       -- name will grow to 24 in s2
       description varchar(24) -- description will be dropped in s2
     );
-    
-EOS
-
+" 
 
 echo
 echo "SQL to run:"
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
-          COLUMN
+          COLUMN | grep -v '^-- '
+
+echo
+echo ==============================================================
+echo
+
+echo "# Compare the columns in all schemas in two databases"
+echo "# Expect SQL:"
+echo "#   Drop column_to_delete from s1.table1 (in db2)"
+echo "#   Add s1.table1.name  (in db2) "
+echo "#   Alter s2.table1.name to varchar(24)"
+#
+# Compare the columns in all schemas in two database
+#
+./populate-db.sh db2 "
+    CREATE SCHEMA s1;
+    CREATE TABLE s1.table1 (
+      id integer, 
+                                -- Name will be added
+      column_to_delete integer  -- This will be deleted
+    );
+
+    CREATE SCHEMA s2;
+    CREATE TABLE s2.table1(
+      name varchar(24),       -- name will change to varchar(12)
+      description varchar(24) -- description will be dropped in s2
+    );
+"
+
+echo
+echo "SQL to run:"
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "*" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db2" -s "*" -o "sslmode=disable" \
+          COLUMN | grep -v '^-- '
diff --git a/test/test-foreignkey.sh b/test/test-foreignkey.sh
new file mode 100755
index 0000000..fd54f89
--- /dev/null
+++ b/test/test-foreignkey.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+#
+# Useful for visually inspecting the output SQL to verify it is doing what it should
+#
+
+source ./start-fresh.sh >/dev/null
+
+echo
+echo "# Compare the columns in two schemas in the same database"
+echo "# Expect SQL:"
+echo "#   Add foreign key on s2.table2.table1_id"
+echo "#   Drop foreign key from s2.table3.table2_id"
+
+#
+# Compare the columns in two schemas in the same database
+#
+./populate-db.sh db1 "
+    CREATE SCHEMA s1;
+    CREATE TABLE s1.table1 (
+      id integer PRIMARY KEY
+    );
+    CREATE TABLE s1.table2 (
+      id integer PRIMARY KEY,
+      table1_id integer REFERENCES s1.table1(id)
+    );
+    CREATE TABLE s1.table3 (
+      id integer, 
+      table2_id integer
+    );
+
+    CREATE SCHEMA s2;
+    CREATE TABLE s2.table1 (
+      id integer PRIMARY KEY 
+    );
+    CREATE TABLE s2.table2 (
+      id integer PRIMARY KEY, 
+      table1_id integer 
+    );
+    CREATE TABLE s2.table3 (
+      id integer, 
+      table2_id integer REFERENCES s2.table2(id) -- This will be deleted
+    );
+"
+
+echo
+echo "SQL to run:"
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
+          FOREIGN_KEY | grep -v '^-- '
+
diff --git a/test/test-table.sh b/test/test-table.sh
index 450b775..95089f9 100755
--- a/test/test-table.sh
+++ b/test/test-table.sh
@@ -5,26 +5,17 @@
 
 source ./start-fresh.sh >/dev/null 
 
-#sudo su - postgres -- <<EOT
-#psql <<'SQL'
-#    DROP DATABASE IF EXISTS db1;
-#    DROP DATABASE IF EXISTS db2;
-#    DROP USER IF EXISTS u1;
-#    CREATE USER u1 WITH SUPERUSER PASSWORD 'asdf';
-#    CREATE DATABASE db1 WITH OWNER = u1 TEMPLATE = template1;
-#    CREATE DATABASE db2 WITH OWNER = u1 TEMPLATE = template1;
-#SQL
-#EOT
-export PGPASSWORD=asdf
-
 echo
 echo "# Compare the tables in two schemas in the same database"
+echo "# Expect SQL:"
+echo "#   Add table9 to schema s2"
+echo "#   Drop table11 from schema s2"
 
 #
 # Compare the tables in two schemas in the same database
 #
 #psql -U u1 -h localhost -d db1 <<'EOS'
-./add-schema.sh "
+./populate-db.sh db1 "
     CREATE SCHEMA s1;
     CREATE TABLE s1.table9 (id integer); -- to be added to s2
     CREATE TABLE s1.table10 (id integer);
@@ -34,33 +25,37 @@ echo "# Compare the tables in two schemas in the same database"
     CREATE TABLE s2.table11 (id integer); -- will be dropped from s2
 "
 
-
 echo
 echo "SQL to run:"
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
-          TABLE
+          TABLE | grep -v '^-- '
+
+echo
+echo ==============================================================
 
 echo
 echo "# Compare the tables in all schemas in two databases"
+echo "# Expect:"
+echo "#   Add s1.table10 to db2"
+echo "#   Drop s2.table12 from db2"
 
 
-#
-# Compare the tables in all schemas in two databases
-#
-psql -U u1 -h localhost -d db2 <<'EOS'
+./populate-db.sh db2 "
     CREATE SCHEMA s1;
     CREATE TABLE s1.table9 (id integer);
-    -- table10 will be added (in db1, but not db2) 
+    -- table10 will be added in db2
 
     CREATE SCHEMA s2;
     CREATE TABLE s2.table10 (id integer); 
     CREATE TABLE s2.table11 (id integer);
     CREATE TABLE s2.table12 (id integer); -- will be dropped (not in db1)
-EOS
+
+    CREATE SCHEMA s3;
+"
 
 echo
 echo "SQL to run:"
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "*" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db2" -s "*" -o "sslmode=disable" \
-          TABLE
+          TABLE | grep -v '^-- '
diff --git a/trigger.go b/trigger.go
index 150c841..fe5834d 100644
--- a/trigger.go
+++ b/trigger.go
@@ -78,22 +78,12 @@ func (c *TriggerSchema) Compare(obj interface{}) int {
 }
 
 // Add returns SQL to create the trigger
-func (c TriggerSchema) Add(obj interface{}) {
-	c2, ok := obj.(*TriggerSchema)
-	if !ok {
-		fmt.Println("Error!!!, Add needs a TriggerSchema instance", c2)
-	}
-
+func (c TriggerSchema) Add() {
 	fmt.Printf("%s;\n", c.get("definition"))
 }
 
 // Drop returns SQL to drop the trigger
-func (c TriggerSchema) Drop(obj interface{}) {
-	c2, ok := obj.(*TriggerSchema)
-	if !ok {
-		fmt.Println("Error!!!, Drop needs a TriggerSchema instance", c2)
-	}
-
+func (c TriggerSchema) Drop() {
 	fmt.Printf("DROP TRIGGER %s ON %s;\n", c.get("trigger_name"), c.get("table_name"))
 }
 
diff --git a/view.go b/view.go
index 11db676..4ea7a07 100644
--- a/view.go
+++ b/view.go
@@ -72,22 +72,12 @@ func (c *ViewSchema) Compare(obj interface{}) int {
 }
 
 // Add returns SQL to create the view
-func (c ViewSchema) Add(obj interface{}) {
-	c2, ok := obj.(*ViewSchema)
-	if !ok {
-		fmt.Println("Error!!!, Add needs a ViewSchema instance", c2)
-	}
-
+func (c ViewSchema) Add() {
 	fmt.Printf("CREATE VIEW %s AS %s \n\n", c.get("viewname"), c.get("definition"))
 }
 
 // Drop returns SQL to drop the view
-func (c ViewSchema) Drop(obj interface{}) {
-	c2, ok := obj.(*ViewSchema)
-	if !ok {
-		fmt.Println("Error!!!, Drop needs a ViewSchema instance", c2)
-	}
-
+func (c ViewSchema) Drop() {
 	fmt.Printf("DROP VIEW IF EXISTS %s;\n\n", c.get("viewname"))
 }
 

From 9f79da8a2f9dbb599e5498d5330cc4fc1ccae75b Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Tue, 24 Oct 2017 22:29:11 -0500
Subject: [PATCH 07/74] WIP foreignkey.go

Former-commit-id: fa4bc3872fabe887d0f3d9a921e0da9a683fbb19
---
 foreignkey.go        |  90 +++++++++++++++++++++++++++++--------------
 test/example.dump    | Bin 0 -> 46429 bytes
 test/load-example.sh |  11 ++++++
 test/mypsql          |   1 +
 test/populate-db.sh  |   2 +-
 5 files changed, 75 insertions(+), 29 deletions(-)
 create mode 100644 test/example.dump
 create mode 100644 test/load-example.sh
 create mode 100755 test/mypsql

diff --git a/foreignkey.go b/foreignkey.go
index fe65ffa..695b0a5 100644
--- a/foreignkey.go
+++ b/foreignkey.go
@@ -6,11 +6,49 @@
 
 package main
 
-import "sort"
-import "fmt"
-import "database/sql"
-import "github.com/joncrlsn/pgutil"
-import "github.com/joncrlsn/misc"
+import (
+	"sort"
+ "fmt"
+ "database/sql"
+ "github.com/joncrlsn/pgutil"
+ "github.com/joncrlsn/misc"
+ )
+
+
+var (
+	foreignKeySqlTemplate = initForeignKeySqlTemplate()
+)
+
+
+// Initializes the Sql template
+func initForeignKeySqlTemplate() *template.Template {
+ 	sql := `
+SELECT {{if eq $.DbSchema "*" }}ns.nspname || '.' || {{end}}cl.relname || '.' || c.conname AS compare_name
+    , ns.nspname AS schema_name
+	, cl.relname AS table_name
+    , c.conname AS fk_name
+	, pg_catalog.pg_get_constraintdef(c.oid, true) as constraint_def
+FROM pg_catalog.pg_constraint c
+INNER JOIN pg_class AS cl ON (c.conrelid = cl.oid)
+INNER JOIN pg_namespace AS ns ON (ns.oid = c.connamespace)
+WHERE c.contype = 'f'
+{{if eq $.DbSchema "*"}}
+AND ns.nspname NOT LIKE 'pg_%' 
+AND ns.nspname <> 'information_schema' 
+{{else}}
+AND ns.nspname = '{{$.DbSchema}}'
+{{end}}
+`
+	t := template.New("ForeignKeySqlTmpl")
+	template.Must(t.Parse(sql))
+	return t
+}
+
+
+// ==================================
+// ForeignKeyRows definition
+// ==================================
+
 
 // ForeignKeyRows is a sortable string map
 type ForeignKeyRows []map[string]string
@@ -20,13 +58,10 @@ func (slice ForeignKeyRows) Len() int {
 }
 
 func (slice ForeignKeyRows) Less(i, j int) bool {
-	if slice[i]["table_name"] != slice[j]["table_name"] {
-		return slice[i]["table_name"] < slice[j]["table_name"]
+	if slice[i]["compare_name"] != slice[j]["compare_name"] {
+	    return slice[i]["compare_name"] < slice[j]["compare_name"]
 	}
-	if slice[i]["constraint_def"] != slice[j]["constraint_def"] {
-		return slice[i]["constraint_def"] < slice[j]["constraint_def"]
-	}
-	return slice[i]["table_name"] < slice[j]["table_name"]
+	return slice[i]["constraint_def"] < slice[j]["constraint_def"]
 }
 
 func (slice ForeignKeyRows) Swap(i, j int) {
@@ -80,7 +115,7 @@ func (c *ForeignKeySchema) Compare(obj interface{}) int {
 	}
 
 	//fmt.Printf("Comparing %s with %s", c.get("table_name"), c2.get("table_name"))
-	val := misc.CompareStrings(c.get("table_name"), c2.get("table_name"))
+	val := misc.CompareStrings(c.get("compare_name"), c2.get("compare_name"))
 	if val != 0 {
 		return val
 	}
@@ -91,37 +126,36 @@ func (c *ForeignKeySchema) Compare(obj interface{}) int {
 
 // Add returns SQL to add the foreign key
 func (c *ForeignKeySchema) Add() {
-	fmt.Printf("ALTER TABLE %s ADD CONSTRAINT %s %s;\n", c.get("table_name"), c.get("fk_name"), c.get("constraint_def"))
+	fmt.Printf("ALTER TABLE %s.%s ADD CONSTRAINT %s %s;\n", c.get("schema_name"), c.get("table_name"), c.get("fk_name"), c.get("constraint_def"))
 }
 
 // Drop returns SQL to drop the foreign key
-func (c ForeignKeySchema) Drop() {
-	fmt.Printf("ALTER TABLE %s DROP CONSTRAINT %s; -- %s\n", c.get("table_name"), c.get("fk_name"), c.get("constraint_def"))
+func (c ForeignKeySchema) Drop() 
+	fmt.Printf("ALTER TABLE %s.%s DROP CONSTRAINT %s; -- %s\n", c.get("schema_name"), c.get("table_name"), c.get("fk_name"), c.get("constraint_def"))
 }
 
 // Change handles the case where the table and foreign key name, but the details do not
 func (c *ForeignKeySchema) Change(obj interface{}) {
 	c2, ok := obj.(*ForeignKeySchema)
 	if !ok {
-		fmt.Println("Error!!!, ForeignKeySchema.Change(obj) needs a ForeignKeySchema instance", c2)
+	    fmt.Println("Error!!!, ForeignKeySchema.Change(obj) needs a ForeignKeySchema instance", c2)
 	}
 	// There is no "changing" a foreign key.  It either gets created or dropped (or left as-is).
 }
 
 /*
- * Compare the foreign keys in the two databases.  We do not recreate foreign keys if just the name is different.
+ * Compare the foreign keys in the two databases. 
  */
-func compareForeignKeys(conn1 *sql.DB, conn2 *sql.DB) {
-	sql := `
-SELECT c.conname AS fk_name
-	, cl.relname AS table_name
-	, pg_catalog.pg_get_constraintdef(c.oid, true) as constraint_def
-FROM pg_catalog.pg_constraint c
-INNER JOIN pg_class AS cl ON (c.conrelid = cl.oid)
-WHERE c.contype = 'f';
-`
-	rowChan1, _ := pgutil.QueryStrings(conn1, sql)
-	rowChan2, _ := pgutil.QueryStrings(conn2, sql)
+func compareForeignKeys(conn1 *sql.DB, conn2 *sql.DB) {	
+	
+	buf1 := new(bytes.Buffer)
+	foreignKeySqlTemplate.Execute(buf1, dbInfo1)
+
+	buf2 := new(bytes.Buffer)
+	foreignKeySqlTemplate.Execute(buf2, dbInfo2)
+
+	rowChan1, _ := pgutil.QueryStrings(conn1, buf1.String())
+	rowChan2, _ := pgutil.QueryStrings(conn2, buf2.String())
 
 	rows1 := make(ForeignKeyRows, 0)
 	for row := range rowChan1 {
diff --git a/test/example.dump b/test/example.dump
new file mode 100644
index 0000000000000000000000000000000000000000..fc33498850cbeee16c651e23f894831f20d9f6b6
GIT binary patch
literal 46429
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

literal 0
HcmV?d00001

diff --git a/test/load-example.sh b/test/load-example.sh
new file mode 100644
index 0000000..68da63a
--- /dev/null
+++ b/test/load-example.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# Load example dump found here:
+# http://postgresguide.com/setup/example.htmlhttp://postgresguide.com/setup/example.html
+
+#curl -L -O http://cl.ly/173L141n3402/download/example.dump
+sudo su - postgres -- -c "
+    createdb pgguide
+    pg_restore --no-owner --dbname pgguide example.dump
+    psql --dbname pgguide
+"
diff --git a/test/mypsql b/test/mypsql
new file mode 100755
index 0000000..75601c6
--- /dev/null
+++ b/test/mypsql
@@ -0,0 +1 @@
+sudo su - postgres -c "psql -d db1"
diff --git a/test/populate-db.sh b/test/populate-db.sh
index c1eb0fa..1ebab6e 100755
--- a/test/populate-db.sh
+++ b/test/populate-db.sh
@@ -3,7 +3,7 @@
 
 db=$1
 
-PGPASSWORD=asdf psql -U u1 -h localhost -d $db >/dev/null <<EOS
+PGPASSWORD=asdf psql -U u1 -h localhost -d $db <<EOS
     $2
 EOS
 

From ab12ee21f2c78aaedc40b805dfe4e52c10d794a6 Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Wed, 25 Oct 2017 18:13:37 -0500
Subject: [PATCH 08/74] foreign key is 90%, started on index

Former-commit-id: 9d30f5c26c3074e750f8d9d0ae07bfd60bdd8daa
---
 column.go     |  31 ++++++++-------
 foreignkey.go |  38 ++++++++++---------
 index.go      | 103 ++++++++++++++++++++++++++++++++------------------
 table.go      |   1 +
 4 files changed, 102 insertions(+), 71 deletions(-)

diff --git a/column.go b/column.go
index af48b01..5a0503c 100644
--- a/column.go
+++ b/column.go
@@ -7,15 +7,15 @@
 package main
 
 import (
-"sort"
-"fmt"
-"strconv"
-"strings"
-"database/sql"
-"github.com/joncrlsn/pgutil"
-"github.com/joncrlsn/misc"
-"text/template"
-"bytes"
+	"bytes"
+	"database/sql"
+	"fmt"
+	"github.com/joncrlsn/misc"
+	"github.com/joncrlsn/pgutil"
+	"sort"
+	"strconv"
+	"strings"
+	"text/template"
 )
 
 var (
@@ -48,7 +48,6 @@ ORDER BY compare_name, column_name;
 	return t
 }
 
-
 // ==================================
 // Column Rows definition
 // ==================================
@@ -121,7 +120,7 @@ func (c *ColumnSchema) Compare(obj interface{}) int {
 
 // Add prints SQL to add the column
 func (c *ColumnSchema) Add() {
-	
+
 	schema := dbInfo2.DbSchema
 	if schema == "*" {
 		schema = c.get("table_schema")
@@ -229,7 +228,7 @@ func (c *ColumnSchema) Change(obj interface{}) {
  * Compare the columns in the two databases
  */
 func compareColumns(conn1 *sql.DB, conn2 *sql.DB) {
-	
+
 	buf1 := new(bytes.Buffer)
 	columnSqlTemplate.Execute(buf1, dbInfo1)
 
@@ -253,13 +252,13 @@ func compareColumns(conn1 *sql.DB, conn2 *sql.DB) {
 	}
 	sort.Sort(&rows2)
 
-    //for _, val := range rows1 {
-		//fmt.Println("list1: ", val["table_schema"], val["compare_name"], val["column_name"], val["character_maximum_length"] )
+	//for _, val := range rows1 {
+	//fmt.Println("list1: ", val["table_schema"], val["compare_name"], val["column_name"], val["character_maximum_length"] )
 	//}
 	//fmt.Println()
 
-    //for _, val := range rows2 {
-		//fmt.Println("list2: ", val["table_schema"], val["compare_name"], val["column_name"], val["character_maximum_length"])
+	//for _, val := range rows2 {
+	//fmt.Println("list2: ", val["table_schema"], val["compare_name"], val["column_name"], val["character_maximum_length"])
 	//}
 
 	// We have to explicitly type this as Schema here for some unknown reason
diff --git a/foreignkey.go b/foreignkey.go
index 695b0a5..d18a074 100644
--- a/foreignkey.go
+++ b/foreignkey.go
@@ -1,5 +1,5 @@
 //
-// Copyright (c) 2014 Jon Carlson.  All rights reserved.
+// Copyright (c) 2017 Jon Carlson.  All rights reserved.
 // Use of this source code is governed by an MIT-style
 // license that can be found in the LICENSE file.
 //
@@ -7,22 +7,22 @@
 package main
 
 import (
+	"bytes"
+	"database/sql"
+	"fmt"
+	"github.com/joncrlsn/misc"
+	"github.com/joncrlsn/pgutil"
 	"sort"
- "fmt"
- "database/sql"
- "github.com/joncrlsn/pgutil"
- "github.com/joncrlsn/misc"
- )
-
+	"text/template"
+)
 
 var (
 	foreignKeySqlTemplate = initForeignKeySqlTemplate()
 )
 
-
 // Initializes the Sql template
 func initForeignKeySqlTemplate() *template.Template {
- 	sql := `
+	sql := `
 SELECT {{if eq $.DbSchema "*" }}ns.nspname || '.' || {{end}}cl.relname || '.' || c.conname AS compare_name
     , ns.nspname AS schema_name
 	, cl.relname AS table_name
@@ -44,12 +44,10 @@ AND ns.nspname = '{{$.DbSchema}}'
 	return t
 }
 
-
 // ==================================
 // ForeignKeyRows definition
 // ==================================
 
-
 // ForeignKeyRows is a sortable string map
 type ForeignKeyRows []map[string]string
 
@@ -59,7 +57,7 @@ func (slice ForeignKeyRows) Len() int {
 
 func (slice ForeignKeyRows) Less(i, j int) bool {
 	if slice[i]["compare_name"] != slice[j]["compare_name"] {
-	    return slice[i]["compare_name"] < slice[j]["compare_name"]
+		return slice[i]["compare_name"] < slice[j]["compare_name"]
 	}
 	return slice[i]["constraint_def"] < slice[j]["constraint_def"]
 }
@@ -126,11 +124,15 @@ func (c *ForeignKeySchema) Compare(obj interface{}) int {
 
 // Add returns SQL to add the foreign key
 func (c *ForeignKeySchema) Add() {
-	fmt.Printf("ALTER TABLE %s.%s ADD CONSTRAINT %s %s;\n", c.get("schema_name"), c.get("table_name"), c.get("fk_name"), c.get("constraint_def"))
+	schema := dbInfo2.DbSchema
+	if schema == "*" {
+		schema = c.get("schema_name")
+	}
+	fmt.Printf("ALTER TABLE %s.%s ADD CONSTRAINT %s %s;\n", schema, c.get("table_name"), c.get("fk_name"), c.get("constraint_def"))
 }
 
 // Drop returns SQL to drop the foreign key
-func (c ForeignKeySchema) Drop() 
+func (c ForeignKeySchema) Drop() {
 	fmt.Printf("ALTER TABLE %s.%s DROP CONSTRAINT %s; -- %s\n", c.get("schema_name"), c.get("table_name"), c.get("fk_name"), c.get("constraint_def"))
 }
 
@@ -138,16 +140,16 @@ func (c ForeignKeySchema) Drop()
 func (c *ForeignKeySchema) Change(obj interface{}) {
 	c2, ok := obj.(*ForeignKeySchema)
 	if !ok {
-	    fmt.Println("Error!!!, ForeignKeySchema.Change(obj) needs a ForeignKeySchema instance", c2)
+		fmt.Println("Error!!!, ForeignKeySchema.Change(obj) needs a ForeignKeySchema instance", c2)
 	}
 	// There is no "changing" a foreign key.  It either gets created or dropped (or left as-is).
 }
 
 /*
- * Compare the foreign keys in the two databases. 
+ * Compare the foreign keys in the two databases.
  */
-func compareForeignKeys(conn1 *sql.DB, conn2 *sql.DB) {	
-	
+func compareForeignKeys(conn1 *sql.DB, conn2 *sql.DB) {
+
 	buf1 := new(bytes.Buffer)
 	foreignKeySqlTemplate.Execute(buf1, dbInfo1)
 
diff --git a/index.go b/index.go
index eeb5743..c105535 100644
--- a/index.go
+++ b/index.go
@@ -1,17 +1,56 @@
 //
-// Copyright (c) 2014 Jon Carlson.  All rights reserved.
+// Copyright (c) 2017 Jon Carlson.  All rights reserved.
 // Use of this source code is governed by an MIT-style
 // license that can be found in the LICENSE file.
 //
 
 package main
 
-import "sort"
-import "fmt"
-import "strings"
-import "database/sql"
-import "github.com/joncrlsn/pgutil"
-import "github.com/joncrlsn/misc"
+import (
+	"bytes"
+	"database/sql"
+	"fmt"
+	"github.com/joncrlsn/misc"
+	"github.com/joncrlsn/pgutil"
+	"sort"
+	"strings"
+	"text/template"
+)
+
+var (
+	indexSqlTemplate = initIndexSqlTemplate()
+)
+
+// Initializes the Sql template
+func initIndexSqlTemplate() *template.Template {
+	sql := `
+SELECT {{if eq $.DbSchema "*" }}n.nspname || '.' || {{end}}c.relname || '.' || c2.relname AS compare_name
+    , n.nspname AS schema_name
+    , c.relname AS table_name
+    , c2.relname AS index_name
+    , i.indisprimary AS pk
+    , i.indisunique AS uq
+    , pg_catalog.pg_get_indexdef(i.indexrelid, 0, true) AS index_def
+    , pg_catalog.pg_get_constraintdef(con.oid, true) AS constraint_def
+    , con.contype AS typ
+FROM pg_catalog.pg_index AS i
+INNER JOIN pg_catalog.pg_class AS c ON (c.oid = i.indrelid)
+INNER JOIN pg_catalog.pg_class AS c2 ON (c2.oid = i.indexrelid)
+LEFT JOIN pg_catalog.pg_constraint con
+    ON (con.conrelid = i.indrelid AND con.conindid = i.indexrelid AND con.contype IN ('p','u','x'))
+INNER JOIN pg_catalog.pg_namespace AS n ON (c2.relnamespace = n.oid)
+WHERE true
+{{if eq $.DbSchema "*"}}
+AND n.nspname NOT LIKE 'pg_%' 
+AND n.nspname <> 'information_schema' 
+{{else}}
+AND n.nspname = '{{$.DbSchema}}'
+{{end}}
+`
+	t := template.New("IndexSqlTmpl")
+	template.Must(t.Parse(sql))
+	return t
+}
 
 // ==================================
 // IndexRows definition
@@ -26,10 +65,7 @@ func (slice IndexRows) Len() int {
 
 func (slice IndexRows) Less(i, j int) bool {
 	//fmt.Printf("--Less %s:%s with %s:%s", slice[i]["table_name"], slice[i]["column_name"], slice[j]["table_name"], slice[j]["column_name"])
-	if slice[i]["table_name"] == slice[j]["table_name"] {
-		return slice[i]["index_name"] < slice[j]["index_name"]
-	}
-	return slice[i]["table_name"] < slice[j]["table_name"]
+	return slice[i]["compare_name"] < slice[j]["compare_name"]
 }
 
 func (slice IndexRows) Swap(i, j int) {
@@ -101,9 +137,14 @@ func (c *IndexSchema) Compare(obj interface{}) int {
 // Add prints SQL to add the column
 func (c *IndexSchema) Add() {
 
+	schema := dbInfo2.DbSchema
+	if schema == "*" {
+		schema = c.get("schema_name")
+	}
+
 	// Assertion
 	if c.get("index_def") == "null" || len(c.get("index_def")) == 0 {
-		fmt.Printf("-- Add Unexpected situation in index.go: there is no index_def for %s %s\n", c.get("table_name"), c.get("index_name"))
+		fmt.Printf("-- Add Unexpected situation in index.go: there is no index_def for %s.%s %s\n", schema, c.get("table_name"), c.get("index_name"))
 		return
 	}
 
@@ -114,10 +155,10 @@ func (c *IndexSchema) Add() {
 		// Create the constraint using the index we just created
 		if c.get("pk") == "true" {
 			// Add primary key using the index
-			fmt.Printf("ALTER TABLE ONLY %s ADD CONSTRAINT %s PRIMARY KEY USING INDEX %s;\n", c.get("table_name"), c.get("index_name"), c.get("index_name"))
+			fmt.Printf("ALTER TABLE ONLY %s.%s ADD CONSTRAINT %s PRIMARY KEY USING INDEX %s;\n", schema, c.get("table_name"), c.get("index_name"), c.get("index_name"))
 		} else if c.get("uq") == "true" {
 			// Add unique constraint using the index
-			fmt.Printf("ALTER TABLE ONLY %s ADD CONSTRAINT %s UNIQUE USING INDEX %s;\n", c.get("table_name"), c.get("index_name"), c.get("index_name"))
+			fmt.Printf("ALTER TABLE ONLY %s.%s ADD CONSTRAINT %s UNIQUE USING INDEX %s;\n", schema, c.get("table_name"), c.get("index_name"), c.get("index_name"))
 		}
 	}
 }
@@ -127,11 +168,11 @@ func (c *IndexSchema) Drop() {
 	if c.get("constraint_def") != "null" {
 		fmt.Println("-- Warning, this may drop foreign keys pointing at this column.  Make sure you re-run the FOREIGN_KEY diff after running this SQL.")
 		//fmt.Printf("ALTER TABLE ONLY %s DROP CONSTRAINT IF EXISTS %s CASCADE; -- %s\n", c.get("table_name"), c.get("index_name"), c.get("constraint_def"))
-		fmt.Printf("ALTER TABLE ONLY %s DROP CONSTRAINT IF EXISTS %s CASCADE;\n", c.get("table_name"), c.get("index_name"))
+		fmt.Printf("ALTER TABLE ONLY %s.%s DROP CONSTRAINT IF EXISTS %s CASCADE;\n", c.get("schema_name"), c.get("table_name"), c.get("index_name"))
 	}
 	// The second line has no index_def
 	//fmt.Printf("DROP INDEX IF EXISTS %s; -- %s \n", c.get("index_name"), c.get("index_def"))
-	fmt.Printf("DROP INDEX IF EXISTS %s;\n", c.get("index_name"))
+	fmt.Printf("DROP INDEX IF EXISTS %s.%s;\n", c.get("schema_name"), c.get("index_name"))
 }
 
 // Change handles the case where the table and column match, but the details do not
@@ -211,27 +252,15 @@ func (c *IndexSchema) Change(obj interface{}) {
  * Compare the columns in the two databases
  */
 func compareIndexes(conn1 *sql.DB, conn2 *sql.DB) {
-	// This SQL was generated with psql -E -c "\d t_org"
-	// The "magic" is in pg_get_indexdef and pg_get_constraint
-	sql := `
-SELECT n.nspname || '.' || c.relname AS table_name
-    , c2.relname AS index_name
-    , i.indisprimary AS pk
-    , i.indisunique AS uq
-    , pg_catalog.pg_get_indexdef(i.indexrelid, 0, true) AS index_def
-    , pg_catalog.pg_get_constraintdef(con.oid, true) AS constraint_def
-    , con.contype AS typ
-FROM pg_catalog.pg_index AS i
-INNER JOIN pg_catalog.pg_class AS c ON (c.oid = i.indrelid)
-INNER JOIN pg_catalog.pg_class AS c2 ON (c2.oid = i.indexrelid)
-LEFT JOIN pg_catalog.pg_constraint con
-    ON (con.conrelid = i.indrelid AND con.conindid = i.indexrelid AND con.contype IN ('p','u','x'))
-INNER JOIN pg_catalog.pg_namespace AS n ON (c2.relnamespace = n.oid)
-WHERE c.relname NOT LIKE 'pg_%'
-AND n.nspname NOT LIKE 'pg_%';
-`
-	rowChan1, _ := pgutil.QueryStrings(conn1, sql)
-	rowChan2, _ := pgutil.QueryStrings(conn2, sql)
+
+	buf1 := new(bytes.Buffer)
+	indexSqlTemplate.Execute(buf1, dbInfo1)
+
+	buf2 := new(bytes.Buffer)
+	indexSqlTemplate.Execute(buf2, dbInfo2)
+
+	rowChan1, _ := pgutil.QueryStrings(conn1, buf1.String())
+	rowChan2, _ := pgutil.QueryStrings(conn2, buf2.String())
 
 	rows1 := make(IndexRows, 0)
 	for row := range rowChan1 {
diff --git a/table.go b/table.go
index 30992c4..afb9081 100644
--- a/table.go
+++ b/table.go
@@ -52,6 +52,7 @@ ORDER BY compare_name;
 
 // TableRows is a sortable slice of string maps
 type TableRows []map[string]string
+
 func (slice TableRows) Len() int {
 	return len(slice)
 }

From 86fdecb8eddb41e5eb36887c7082c4e6cdb9a037 Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Thu, 26 Oct 2017 18:01:23 -0500
Subject: [PATCH 09/74] ForeignKey tests out now.

Former-commit-id: f75b26a1132fa4777799b2a8293b8096ea3e3962
---
 column.go               |  6 ++---
 test/populate-db.sh     |  2 +-
 test/test-foreignkey.sh | 59 ++++++++++++++++++++++++++++++++++++-----
 3 files changed, 55 insertions(+), 12 deletions(-)

diff --git a/column.go b/column.go
index 5a0503c..581c28f 100644
--- a/column.go
+++ b/column.go
@@ -221,12 +221,10 @@ func (c *ColumnSchema) Change(obj interface{}) {
 }
 
 // ==================================
-// Functions
+// Standalone Functions
 // ==================================
 
-/*
- * Compare the columns in the two databases
- */
+// compareColumns outputs SQL to make the columns match between two databases or schemas
 func compareColumns(conn1 *sql.DB, conn2 *sql.DB) {
 
 	buf1 := new(bytes.Buffer)
diff --git a/test/populate-db.sh b/test/populate-db.sh
index 1ebab6e..c1eb0fa 100755
--- a/test/populate-db.sh
+++ b/test/populate-db.sh
@@ -3,7 +3,7 @@
 
 db=$1
 
-PGPASSWORD=asdf psql -U u1 -h localhost -d $db <<EOS
+PGPASSWORD=asdf psql -U u1 -h localhost -d $db >/dev/null <<EOS
     $2
 EOS
 
diff --git a/test/test-foreignkey.sh b/test/test-foreignkey.sh
index fd54f89..7be0374 100755
--- a/test/test-foreignkey.sh
+++ b/test/test-foreignkey.sh
@@ -5,15 +5,10 @@
 
 source ./start-fresh.sh >/dev/null
 
-echo
-echo "# Compare the columns in two schemas in the same database"
-echo "# Expect SQL:"
-echo "#   Add foreign key on s2.table2.table1_id"
-echo "#   Drop foreign key from s2.table3.table2_id"
-
 #
-# Compare the columns in two schemas in the same database
+# Compare the foreign keys in two schemas in the same database
 #
+
 ./populate-db.sh db1 "
     CREATE SCHEMA s1;
     CREATE TABLE s1.table1 (
@@ -42,9 +37,59 @@ echo "#   Drop foreign key from s2.table3.table2_id"
     );
 "
 
+echo
+echo "# Compare the foreign keys in two schemas in the same database"
+echo "# Expect SQL:"
+echo "#   Add foreign key on s2.table2.table1_id"
+echo "#   Drop foreign key from s2.table3.table2_id"
+
 echo
 echo "SQL to run:"
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
           FOREIGN_KEY | grep -v '^-- '
 
+
+#
+# Compare the foreign keys in all schemas in two databases
+#
+./populate-db.sh db2 "
+    CREATE SCHEMA s1;
+    CREATE TABLE s1.table1 (
+      id integer PRIMARY KEY
+    );
+    CREATE TABLE s1.table2 (
+      id integer PRIMARY KEY,
+      table1_id integer      -- a foreign key will be added
+    );
+    CREATE TABLE s1.table3 (
+      id integer, 
+      table2_id integer
+    );
+
+    CREATE SCHEMA s2;
+    CREATE TABLE s2.table1 (
+      id integer PRIMARY KEY 
+    );
+    CREATE TABLE s2.table2 (
+      id integer PRIMARY KEY, 
+      table1_id integer REFERENCES s2.table1(id) -- This will be deleted
+
+    );
+    CREATE TABLE s2.table3 (
+      id integer, 
+      table2_id integer REFERENCES s2.table2(id)    
+    );
+"
+
+echo
+echo "# Compare the foreign keys in all schemas between two databases"
+echo "# Expect SQL:"
+echo "#   Add foreign key on db2.s1.table2.table1_id"
+echo "#   Drop foreign key on db2.s2.table2.table1_id"
+
+echo
+echo "SQL to run:"
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "*" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db2" -s "*" -o "sslmode=disable" \
+          FOREIGN_KEY | grep -v '^-- '

From 667f205d4f11505294945023f3592670a91e3894 Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Thu, 26 Oct 2017 18:02:13 -0500
Subject: [PATCH 10/74] WIP: index.go comparing between schemas

Former-commit-id: b47ba75e9c2244b4a539fe203f55f535d727afd2
---
 index.go           |  9 +----
 test/test-index.sh | 86 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 87 insertions(+), 8 deletions(-)
 create mode 100755 test/test-index.sh

diff --git a/index.go b/index.go
index c105535..b168484 100644
--- a/index.go
+++ b/index.go
@@ -136,7 +136,6 @@ func (c *IndexSchema) Compare(obj interface{}) int {
 
 // Add prints SQL to add the column
 func (c *IndexSchema) Add() {
-
 	schema := dbInfo2.DbSchema
 	if schema == "*" {
 		schema = c.get("schema_name")
@@ -244,13 +243,7 @@ func (c *IndexSchema) Change(obj interface{}) {
 
 }
 
-// ==================================
-// Functions
-// ==================================
-
-/*
- * Compare the columns in the two databases
- */
+// compareIndexes outputs Sql to make the indexes match between to DBs or schemas
 func compareIndexes(conn1 *sql.DB, conn2 *sql.DB) {
 
 	buf1 := new(bytes.Buffer)
diff --git a/test/test-index.sh b/test/test-index.sh
new file mode 100755
index 0000000..e23d6fb
--- /dev/null
+++ b/test/test-index.sh
@@ -0,0 +1,86 @@
+#!/bin/bash
+#
+# Useful for visually inspecting the output SQL to verify it is doing what it should
+#
+
+source ./start-fresh.sh >/dev/null
+
+#
+# Compare the indexes in two schemas in the same database
+#
+
+./populate-db.sh db1 "
+    CREATE SCHEMA s1;
+    CREATE TABLE s1.table1 (
+      id integer PRIMARY KEY,
+      name varchar(32),
+      url varchar(200)
+    );
+    CREATE INDEX ON s1.table1(name);
+
+    CREATE SCHEMA s2;
+    CREATE TABLE s2.table1 (
+      id integer PRIMARY KEY, 
+      name varchar(32),
+      url varchar(200)
+    );
+    CREATE INDEX ON s2.table1(url);
+"
+
+echo
+echo "# Compare the indexes in two schemas in the same database"
+echo "# Expect SQL:"
+echo "#   Add index on 2.table1.name"
+echo "#   Drop index on s2.table1.url"
+
+echo
+echo "SQL to run:"
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
+          INDEX | grep -v '^-- '
+
+exit 1
+
+#
+# Compare the foreign keys in all schemas in two databases
+#
+./populate-db.sh db2 "
+    CREATE SCHEMA s1;
+    CREATE TABLE s1.table1 (
+      id integer PRIMARY KEY
+    );
+    CREATE TABLE s1.table2 (
+      id integer PRIMARY KEY,
+      table1_id integer      -- a foreign key will be added
+    );
+    CREATE TABLE s1.table3 (
+      id integer, 
+      table2_id integer
+    );
+
+    CREATE SCHEMA s2;
+    CREATE TABLE s2.table1 (
+      id integer PRIMARY KEY 
+    );
+    CREATE TABLE s2.table2 (
+      id integer PRIMARY KEY, 
+      table1_id integer REFERENCES s2.table1(id) -- This will be deleted
+
+    );
+    CREATE TABLE s2.table3 (
+      id integer, 
+      table2_id integer REFERENCES s2.table2(id)    
+    );
+"
+
+echo
+echo "# Compare the foreign keys in all schemas between two databases"
+echo "# Expect SQL:"
+echo "#   Add foreign key on db2.s1.table2.table1_id"
+echo "#   Drop foreign key on db2.s2.table2.table1_id"
+
+echo
+echo "SQL to run:"
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "*" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db2" -s "*" -o "sslmode=disable" \
+          FOREIGN_KEY | grep -v '^-- '

From 0b1b3dfad2743b61f2e2e20e3e48d7fe55700242 Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Thu, 26 Oct 2017 21:05:19 -0500
Subject: [PATCH 11/74] index WIP

Former-commit-id: 729d3bf298eb844444d2c03714c33a920260de2e
---
 index.go           | 36 +++++++++++++-----------------------
 test/test-index.sh |  4 ++--
 2 files changed, 15 insertions(+), 25 deletions(-)

diff --git a/index.go b/index.go
index b168484..6b5e720 100644
--- a/index.go
+++ b/index.go
@@ -36,7 +36,7 @@ SELECT {{if eq $.DbSchema "*" }}n.nspname || '.' || {{end}}c.relname || '.' || c
 FROM pg_catalog.pg_index AS i
 INNER JOIN pg_catalog.pg_class AS c ON (c.oid = i.indrelid)
 INNER JOIN pg_catalog.pg_class AS c2 ON (c2.oid = i.indexrelid)
-LEFT JOIN pg_catalog.pg_constraint con
+LEFT OUTER JOIN pg_catalog.pg_constraint con
     ON (con.conrelid = i.indrelid AND con.conindid = i.indexrelid AND con.contype IN ('p','u','x'))
 INNER JOIN pg_catalog.pg_namespace AS n ON (c2.relnamespace = n.oid)
 WHERE true
@@ -123,18 +123,11 @@ func (c *IndexSchema) Compare(obj interface{}) int {
 		fmt.Printf("--Comparing (table_name or index_name is empty): %v\n--           %v\n", c.getRow(), c2.getRow())
 	}
 
-	val := misc.CompareStrings(c.get("table_name"), c2.get("table_name"))
-	if val != 0 {
-		// Table name differed so return that value
-		return val
-	}
-
-	// Table name was the same so compare index name
-	val = misc.CompareStrings(c.get("index_name"), c2.get("index_name"))
+	val := misc.CompareStrings(c.get("compare_name"), c2.get("compare_name"))
 	return val
 }
 
-// Add prints SQL to add the column
+// Add prints SQL to add the index
 func (c *IndexSchema) Add() {
 	schema := dbInfo2.DbSchema
 	if schema == "*" {
@@ -154,24 +147,21 @@ func (c *IndexSchema) Add() {
 		// Create the constraint using the index we just created
 		if c.get("pk") == "true" {
 			// Add primary key using the index
-			fmt.Printf("ALTER TABLE ONLY %s.%s ADD CONSTRAINT %s PRIMARY KEY USING INDEX %s;\n", schema, c.get("table_name"), c.get("index_name"), c.get("index_name"))
+			fmt.Printf("ALTER TABLE %s.%s ADD CONSTRAINT %s PRIMARY KEY USING INDEX %s; -- (1)\n", schema, c.get("table_name"), c.get("index_name"), c.get("index_name"))
 		} else if c.get("uq") == "true" {
 			// Add unique constraint using the index
-			fmt.Printf("ALTER TABLE ONLY %s.%s ADD CONSTRAINT %s UNIQUE USING INDEX %s;\n", schema, c.get("table_name"), c.get("index_name"), c.get("index_name"))
+			fmt.Printf("ALTER TABLE %s.%s ADD CONSTRAINT %s UNIQUE USING INDEX %s; -- (2)\n", schema, c.get("table_name"), c.get("index_name"), c.get("index_name"))
 		}
 	}
 }
 
-// Drop prints SQL to drop the column
+// Drop prints SQL to drop the index
 func (c *IndexSchema) Drop() {
 	if c.get("constraint_def") != "null" {
 		fmt.Println("-- Warning, this may drop foreign keys pointing at this column.  Make sure you re-run the FOREIGN_KEY diff after running this SQL.")
-		//fmt.Printf("ALTER TABLE ONLY %s DROP CONSTRAINT IF EXISTS %s CASCADE; -- %s\n", c.get("table_name"), c.get("index_name"), c.get("constraint_def"))
-		fmt.Printf("ALTER TABLE ONLY %s.%s DROP CONSTRAINT IF EXISTS %s CASCADE;\n", c.get("schema_name"), c.get("table_name"), c.get("index_name"))
+		fmt.Printf("ALTER TABLE %s.%s DROP CONSTRAINT %s CASCADE; -- %s\n", c.get("schema_name"), c.get("table_name"), c.get("index_name"), c.get("constraint_def"))
 	}
-	// The second line has no index_def
-	//fmt.Printf("DROP INDEX IF EXISTS %s; -- %s \n", c.get("index_name"), c.get("index_def"))
-	fmt.Printf("DROP INDEX IF EXISTS %s.%s;\n", c.get("schema_name"), c.get("index_name"))
+	fmt.Printf("DROP INDEX %s.%s;\n", c.get("schema_name"), c.get("index_name"))
 }
 
 // Change handles the case where the table and column match, but the details do not
@@ -199,7 +189,7 @@ func (c *IndexSchema) Change(obj interface{}) {
 		if c.get("constraint_def") == "null" {
 			// c1.constraint does not exist, c2.constraint does, so
 			// Drop constraint
-			fmt.Printf("DROP INDEX IF EXISTS %s; -- %s \n", c2.get("index_name"), c2.get("index_def"))
+			fmt.Printf("DROP INDEX %s; -- %s \n", c2.get("index_name"), c2.get("index_def"))
 		} else if c2.get("constraint_def") == "null" {
 			// c1.constraint exists, c2.constraint does not, so
 			// Add constraint
@@ -208,16 +198,16 @@ func (c *IndexSchema) Change(obj interface{}) {
 				// Add constraint using the index
 				if c.get("pk") == "true" {
 					// Add primary key using the index
-					fmt.Printf("ALTER TABLE %s ADD CONSTRAINT %s PRIMARY KEY USING INDEX %s;\n", c.get("table_name"), c.get("index_name"), c.get("index_name"))
+					fmt.Printf("ALTER TABLE %s ADD CONSTRAINT %s PRIMARY KEY USING INDEX %s; -- (3)\n", c.get("table_name"), c.get("index_name"), c.get("index_name"))
 				} else if c.get("uq") == "true" {
 					// Add unique constraint using the index
-					fmt.Printf("ALTER TABLE %s ADD CONSTRAINT %s UNIQUE USING INDEX %s;\n", c.get("table_name"), c.get("index_name"), c.get("index_name"))
+					fmt.Printf("ALTER TABLE %s ADD CONSTRAINT %s UNIQUE USING INDEX %s; -- (4)\n", c.get("table_name"), c.get("index_name"), c.get("index_name"))
 				} else {
 
 				}
 			} else {
 				// Drop the c2 index, create a copy of the c1 index
-				fmt.Printf("DROP INDEX IF EXISTS %s; -- %s \n", c2.get("index_name"), c2.get("index_def"))
+				fmt.Printf("DROP INDEX %s; -- %s \n", c2.get("index_name"), c2.get("index_def"))
 			}
 			// WIP
 			//fmt.Printf("ALTER TABLE %s ADD CONSTRAINT %s %s;\n", c.get("table_name"), c.get("index_name"), c.get("constraint_def"))
@@ -228,7 +218,7 @@ func (c *IndexSchema) Change(obj interface{}) {
 	} else if c.get("index_def") != c2.get("index_def") {
 		if !strings.HasPrefix(c.get("index_def"), c2.get("index_def")) &&
 			!strings.HasPrefix(c2.get("index_def"), c.get("index_def")) {
-			fmt.Println("--\n--Change index defs different\n--")
+			fmt.Println("--\n--Change index defs different:")
 			// Remember, if we are here, then the two constraint_defs match (both may be empty)
 			// The indexes do not match, but the constraints do
 			fmt.Printf("--CHANGE: Different index defs:\n--    %s\n--    %s\n", c.get("index_def"), c2.get("index_def"))
diff --git a/test/test-index.sh b/test/test-index.sh
index e23d6fb..23fe62a 100755
--- a/test/test-index.sh
+++ b/test/test-index.sh
@@ -37,12 +37,12 @@ echo
 echo "SQL to run:"
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
-          INDEX | grep -v '^-- '
+          INDEX # | grep -v '^-- '
 
 exit 1
 
 #
-# Compare the foreign keys in all schemas in two databases
+# Compare the indexes in all schemas in two databases
 #
 ./populate-db.sh db2 "
     CREATE SCHEMA s1;

From 6d292fbefc1bb7499d2280526da55705437cdafb Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Fri, 27 Oct 2017 19:28:37 -0500
Subject: [PATCH 12/74] Index comparison works across schemas

Former-commit-id: 4e33da1b497fc0ffabfc02d07ae411b21c3bd541
---
 index.go           | 47 +++++++++++++++++++++++++++++++++++--------
 test/test-index.sh | 50 +++++++++++++++++++---------------------------
 2 files changed, 59 insertions(+), 38 deletions(-)

diff --git a/index.go b/index.go
index 6b5e720..222b3e0 100644
--- a/index.go
+++ b/index.go
@@ -120,7 +120,8 @@ func (c *IndexSchema) Compare(obj interface{}) int {
 	}
 
 	if len(c.get("table_name")) == 0 || len(c.get("index_name")) == 0 {
-		fmt.Printf("--Comparing (table_name or index_name is empty): %v\n--           %v\n", c.getRow(), c2.getRow())
+		fmt.Printf("--Comparing (table_name and/or index_name is empty): %v\n", c.getRow())
+		fmt.Printf("--           %v\n", c2.getRow())
 	}
 
 	val := misc.CompareStrings(c.get("compare_name"), c2.get("compare_name"))
@@ -140,8 +141,18 @@ func (c *IndexSchema) Add() {
 		return
 	}
 
-	// Create the index first
-	fmt.Printf("%s;\n", c.get("index_def"))
+	// If we are comparing two different schemas against each other, we need to do some
+	// modification of the first index_def so we create the index in the write schema
+	indexDef := c.get("index_def")
+	if dbInfo1.DbSchema != dbInfo2.DbSchema {
+		indexDef = strings.Replace(
+			indexDef,
+			fmt.Sprintf(" %s.%s ", c.get("schema_name"), c.get("table_name")),
+			fmt.Sprintf(" %s.%s ", dbInfo2.DbSchema, c.get("table_name")),
+			-1)
+	}
+
+	fmt.Println(indexDef)
 
 	if c.get("constraint_def") != "null" {
 		// Create the constraint using the index we just created
@@ -215,13 +226,33 @@ func (c *IndexSchema) Change(obj interface{}) {
 		} else if c.get("index_def") != c2.get("index_def") {
 			// The constraints match
 		}
-	} else if c.get("index_def") != c2.get("index_def") {
+
+		return
+	}
+
+	// At this point, we know that the constraint_def matches.  Compare the index_def
+
+	indexDef1 := c.get("index_def")
+	indexDef2 := c2.get("index_def")
+
+	// If we are comparing two different schemas against each other, we need to do
+	// some modification of the first index_def so it looks more like the second
+	if dbInfo1.DbSchema != dbInfo2.DbSchema {
+		indexDef1 = strings.Replace(
+			indexDef1,
+			fmt.Sprintf(" %s.%s ", c.get("schema_name"), c.get("table_name")),
+			fmt.Sprintf(" %s.%s ", c2.get("schema_name"), c2.get("table_name")),
+			-1,
+		)
+	}
+
+	if indexDef1 != indexDef2 {
+		// Notice that, if we are here, then the two constraint_defs match (both may be empty)
+		// The indexes do not match, but the constraints do
 		if !strings.HasPrefix(c.get("index_def"), c2.get("index_def")) &&
 			!strings.HasPrefix(c2.get("index_def"), c.get("index_def")) {
-			fmt.Println("--\n--Change index defs different:")
-			// Remember, if we are here, then the two constraint_defs match (both may be empty)
-			// The indexes do not match, but the constraints do
-			fmt.Printf("--CHANGE: Different index defs:\n--    %s\n--    %s\n", c.get("index_def"), c2.get("index_def"))
+			fmt.Println("--\n--CHANGE: index defs are different for identical constraint defs:")
+			fmt.Printf("--    %s\n--    %s\n", c.get("index_def"), c2.get("index_def"))
 
 			// Drop the index (and maybe the constraint) so we can recreate the index
 			c.Drop()
diff --git a/test/test-index.sh b/test/test-index.sh
index 23fe62a..974c8e2 100755
--- a/test/test-index.sh
+++ b/test/test-index.sh
@@ -29,17 +29,17 @@ source ./start-fresh.sh >/dev/null
 
 echo
 echo "# Compare the indexes in two schemas in the same database"
-echo "# Expect SQL:"
-echo "#   Add index on 2.table1.name"
+echo "# Expect SQL (pseudocode):"
+echo "#   Add index on s2.table1.name"
 echo "#   Drop index on s2.table1.url"
-
 echo
-echo "SQL to run:"
+
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
-          INDEX # | grep -v '^-- '
+          INDEX | grep -v '^-- '
+echo
+echo
 
-exit 1
 
 #
 # Compare the indexes in all schemas in two databases
@@ -47,40 +47,30 @@ exit 1
 ./populate-db.sh db2 "
     CREATE SCHEMA s1;
     CREATE TABLE s1.table1 (
-      id integer PRIMARY KEY
-    );
-    CREATE TABLE s1.table2 (
       id integer PRIMARY KEY,
-      table1_id integer      -- a foreign key will be added
-    );
-    CREATE TABLE s1.table3 (
-      id integer, 
-      table2_id integer
+      name varchar(32),
+      url varchar(200)
     );
+    CREATE INDEX ON s1.table1(name);
+    CREATE INDEX ON s1.table1(url);
 
     CREATE SCHEMA s2;
     CREATE TABLE s2.table1 (
-      id integer PRIMARY KEY 
-    );
-    CREATE TABLE s2.table2 (
       id integer PRIMARY KEY, 
-      table1_id integer REFERENCES s2.table1(id) -- This will be deleted
-
-    );
-    CREATE TABLE s2.table3 (
-      id integer, 
-      table2_id integer REFERENCES s2.table2(id)    
+      name varchar(32),
+      url varchar(200)
     );
 "
 
 echo
-echo "# Compare the foreign keys in all schemas between two databases"
-echo "# Expect SQL:"
-echo "#   Add foreign key on db2.s1.table2.table1_id"
-echo "#   Drop foreign key on db2.s2.table2.table1_id"
-
+echo "# Compare the indexes in all schemas between two databases"
+echo "# Expect SQL (pseudocode):"
+echo "#   Drop index on db2 s1.table1.url"
+echo "#   Add index on db2 s2.table1.url"
 echo
-echo "SQL to run:"
+
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "*" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db2" -s "*" -o "sslmode=disable" \
-          FOREIGN_KEY | grep -v '^-- '
+          INDEX | grep -v '^-- '
+echo
+echo

From d6838a04df147a7edc8009c6648312f88c12f1e9 Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Fri, 27 Oct 2017 19:32:12 -0500
Subject: [PATCH 13/74] Minor formatting improvments

Former-commit-id: 91e032194b3ff4f494a84f6105715750309d561a
---
 test/README.md      | 4 ++++
 test/populate-db.sh | 2 +-
 test/test-index.sh  | 4 ++++
 3 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/test/README.md b/test/README.md
index a3936ff..7ad1851 100644
--- a/test/README.md
+++ b/test/README.md
@@ -1,2 +1,6 @@
 These are not automated tests (as much as I'd rather have automated tests), but manual tests 
 for verifying that individual schema types are working.
+
+
+Connect to the database:
+  sudo su - postgres -- -c "psql -d db1"
diff --git a/test/populate-db.sh b/test/populate-db.sh
index c1eb0fa..1ebab6e 100755
--- a/test/populate-db.sh
+++ b/test/populate-db.sh
@@ -3,7 +3,7 @@
 
 db=$1
 
-PGPASSWORD=asdf psql -U u1 -h localhost -d $db >/dev/null <<EOS
+PGPASSWORD=asdf psql -U u1 -h localhost -d $db <<EOS
     $2
 EOS
 
diff --git a/test/test-index.sh b/test/test-index.sh
index 974c8e2..6d1a0d9 100755
--- a/test/test-index.sh
+++ b/test/test-index.sh
@@ -4,6 +4,9 @@
 #
 
 source ./start-fresh.sh >/dev/null
+echo
+echo ==========================================================
+echo
 
 #
 # Compare the indexes in two schemas in the same database
@@ -38,6 +41,7 @@ echo
           -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
           INDEX | grep -v '^-- '
 echo
+echo ==========================================================
 echo
 
 

From 644a675e3fbf32b6a3e8ec659ecf3e8162bd9b1c Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Sat, 28 Oct 2017 14:52:51 -0500
Subject: [PATCH 14/74] sequences work

Former-commit-id: 1842453172bf70a40ed7bd19cdafc07e23adeafa
---
 column.go     |  9 ------
 sequence.go   | 89 +++++++++++++++++++++++++++++++++++----------------
 table.go      |  2 +-
 table_test.go | 68 +++++++++++++++++++++++++++++----------
 view.go       |  2 +-
 5 files changed, 114 insertions(+), 56 deletions(-)

diff --git a/column.go b/column.go
index 581c28f..011e841 100644
--- a/column.go
+++ b/column.go
@@ -250,15 +250,6 @@ func compareColumns(conn1 *sql.DB, conn2 *sql.DB) {
 	}
 	sort.Sort(&rows2)
 
-	//for _, val := range rows1 {
-	//fmt.Println("list1: ", val["table_schema"], val["compare_name"], val["column_name"], val["character_maximum_length"] )
-	//}
-	//fmt.Println()
-
-	//for _, val := range rows2 {
-	//fmt.Println("list2: ", val["table_schema"], val["compare_name"], val["column_name"], val["character_maximum_length"])
-	//}
-
 	// We have to explicitly type this as Schema here for some unknown reason
 	var schema1 Schema = &ColumnSchema{rows: rows1, rowNum: -1}
 	var schema2 Schema = &ColumnSchema{rows: rows2, rowNum: -1}
diff --git a/sequence.go b/sequence.go
index 57db965..924c5ec 100644
--- a/sequence.go
+++ b/sequence.go
@@ -6,11 +6,46 @@
 
 package main
 
-import "sort"
-import "fmt"
-import "database/sql"
-import "github.com/joncrlsn/pgutil"
-import "github.com/joncrlsn/misc"
+import (
+	"bytes"
+	"database/sql"
+	"fmt"
+	"github.com/joncrlsn/misc"
+	"github.com/joncrlsn/pgutil"
+	"sort"
+	"text/template"
+)
+
+var (
+	sequenceSqlTemplate = initSequenceSqlTemplate()
+)
+
+// Initializes the Sql template
+func initSequenceSqlTemplate() *template.Template {
+	sql := `
+SELECT sequence_schema,
+    , {{if eq $.DbSchema "*" }}sequence_schema || '.' || {{end}}sequence_name AS compare_name
+    ,  sequence_name AS sequence_name
+	, data_type
+	, start_value
+	, minimum_value
+	, maximum_value
+	, increment
+	, cycle_option 
+FROM information_schema.sequences
+WHERE true
+{{if eq $.DbSchema "*" }}
+AND sequence_schema NOT LIKE 'pg_%' 
+AND sequence_schema <> 'information_schema' 
+{{else}}
+AND sequence_schema = '{{$.DbSchema}}'
+{{end}}
+`
+
+	t := template.New("SequenceSqlTmpl")
+	template.Must(t.Parse(sql))
+	return t
+}
 
 // ==================================
 // SequenceRows definition
@@ -24,7 +59,7 @@ func (slice SequenceRows) Len() int {
 }
 
 func (slice SequenceRows) Less(i, j int) bool {
-	return slice[i]["sequence_name"] < slice[j]["sequence_name"]
+	return slice[i]["compare_name"] < slice[j]["compare_name"]
 }
 
 func (slice SequenceRows) Swap(i, j int) {
@@ -66,22 +101,25 @@ func (c *SequenceSchema) Compare(obj interface{}) int {
 		return +999
 	}
 
-	val := misc.CompareStrings(c.get("sequence_name"), c2.get("sequence_name"))
+	val := misc.CompareStrings(c.get("compare_name"), c2.get("compare_name"))
 	return val
 }
 
-// Add returns SQL to add the table
+// Add returns SQL to add the sequence
 func (c SequenceSchema) Add() {
-	fmt.Printf("CREATE SEQUENCE %s INCREMENT %s MINVALUE %s MAXVALUE %s START %s;\n", c.get("sequence_name"), c.get("increment"), c.get("minimum_value"), c.get("maximum_value"), c.get("start_value"))
-
+	schema := dbInfo2.DbSchema
+	if schema == "*" {
+		schema = c.get("sequence_schema")
+	}
+	fmt.Printf("CREATE SEQUENCE %s.%s INCREMENT %s MINVALUE %s MAXVALUE %s START %s;\n", schema, c.get("sequence_name"), c.get("increment"), c.get("minimum_value"), c.get("maximum_value"), c.get("start_value"))
 }
 
-// Drop returns SQL to drop the table
+// Drop returns SQL to drop the sequence
 func (c SequenceSchema) Drop() {
-	fmt.Printf("DROP SEQUENCE IF EXISTS %s;\n", c.get("sequence_name"))
+	fmt.Printf("DROP SEQUENCE %s.%s;\n", c.get("sequence_schema"), c.get("sequence_name"))
 }
 
-// Change handles the case where the table and column match, but the details do not
+// Change doesn't do anything right now.
 func (c SequenceSchema) Change(obj interface{}) {
 	c2, ok := obj.(*SequenceSchema)
 	if !ok {
@@ -92,20 +130,15 @@ func (c SequenceSchema) Change(obj interface{}) {
 
 // compareSequences outputs SQL to make the sequences match between DBs
 func compareSequences(conn1 *sql.DB, conn2 *sql.DB) {
-	sql := `
-SELECT sequence_schema || '.' || sequence_name AS sequence_name
-	, data_type
-	, start_value
-	, minimum_value
-	, maximum_value
-	, increment
-	, cycle_option 
-FROM information_schema.sequences
-WHERE sequence_schema NOT LIKE 'pg_%';
-`
 
-	rowChan1, _ := pgutil.QueryStrings(conn1, sql)
-	rowChan2, _ := pgutil.QueryStrings(conn2, sql)
+	buf1 := new(bytes.Buffer)
+	sequenceSqlTemplate.Execute(buf1, dbInfo1)
+
+	buf2 := new(bytes.Buffer)
+	sequenceSqlTemplate.Execute(buf2, dbInfo2)
+
+	rowChan1, _ := pgutil.QueryStrings(conn1, buf1.String())
+	rowChan2, _ := pgutil.QueryStrings(conn2, buf2.String())
 
 	rows1 := make(SequenceRows, 0)
 	for row := range rowChan1 {
@@ -119,10 +152,10 @@ WHERE sequence_schema NOT LIKE 'pg_%';
 	}
 	sort.Sort(rows2)
 
-	// We have to explicitly type this as Schema here for some unknown reason
+	// We have to explicitly type this as Schema here for some unknown (to me) reason
 	var schema1 Schema = &SequenceSchema{rows: rows1, rowNum: -1}
 	var schema2 Schema = &SequenceSchema{rows: rows2, rowNum: -1}
 
-	// Compare the tables
+	// Compare the sequences
 	doDiff(schema1, schema2)
 }
diff --git a/table.go b/table.go
index afb9081..a7f2cc8 100644
--- a/table.go
+++ b/table.go
@@ -117,7 +117,7 @@ func (c TableSchema) Add() {
 
 // Drop returns SQL to drop the table or view
 func (c TableSchema) Drop() {
-	fmt.Printf("DROP %s IF EXISTS %s.%s;\n", c.get("table_type"), c.get("table_schema"), c.get("table_name"))
+	fmt.Printf("DROP %s %s.%s;\n", c.get("table_type"), c.get("table_schema"), c.get("table_name"))
 }
 
 // Change handles the case where the table and column match, but the details do not
diff --git a/table_test.go b/table_test.go
index 7a5bb73..b544032 100644
--- a/table_test.go
+++ b/table_test.go
@@ -8,38 +8,72 @@ package main
 
 import (
 	"fmt"
+	"github.com/joncrlsn/pgutil"
 	"testing"
 )
 
 /*
-SELECT table_schema || '.' || table_name AS table_name
-    , CASE table_type WHEN 'BASE TABLE' THEN 'TABLE' ELSE table_type END AS table_type
+SELECT table_schema
+    , {{if eq $.DbSchema "*" }}table_schema || '.' || {{end}}table_name AS compare_name
+	, table_name
+    , CASE table_type
+	  WHEN 'BASE TABLE' THEN 'TABLE'
+	  ELSE table_type END AS table_type
     , is_insertable_into
 FROM information_schema.tables
-WHERE table_schema NOT LIKE 'pg_%'
-WHERE table_schema <> 'information_schema'
-AND table_type = 'BASE TABLE'
-ORDER BY table_name;
+WHERE table_type = 'BASE TABLE'
+{{if eq $.DbSchema "*" }}
+AND table_schema NOT LIKE 'pg_%'
+AND table_schema <> 'information_schema'
+{{else}}
+AND table_schema = '{{$.DbSchema}}'
+{{end}}
+ORDER BY compare_name;
 */
 
-// Note that these must be sorted by table name for this to work
+// Note that these must be sorted by schema and table name for this to work
 var testTables1a = []map[string]string{
-	{"table_name": "schema1.add", "table_type": "TABLE"},
-	{"table_name": "schema1.same", "table_type": "TABLE"},
-	{"table_name": "schema2.add", "table_type": "TABLE"},
-	{"table_name": "schema2.same", "table_type": "TABLE"},
+	{"compare_name": "s1.add", "table_schema": "s1", "table_name": "s1_add", "table_type": "TABLE"},
+	{"compare_name": "s1.same", "table_schema": "s1", "table_name": "same", "table_type": "TABLE"},
+	{"compare_name": "s2.add", "table_schema": "s2", "table_name": "s2_add", "table_type": "TABLE"},
+	{"compare_name": "s2.same", "table_schema": "s2", "table_name": "same", "table_type": "TABLE"},
 }
 
-// Note that these must be sorted by table_name for this to work
+// Note that these must be sorted by schema and table name for this to work
 var testTables1b = []map[string]string{
-	{"table_name": "schema1.delete", "table_type": "TABLE"},
-	{"table_name": "schema1.same", "table_type": "TABLE"},
-	{"table_name": "schema2.same", "table_type": "TABLE"},
+	{"compare_name": "s1.delete", "table_schema": "s1", "table_name": "delete", "table_type": "TABLE"},
+	{"compare_name": "s1.same", "table_schema": "s1", "table_name": "same", "table_type": "TABLE"},
+	{"compare_name": "s2.same", "table_schema": "s2", "table_name": "same", "table_type": "TABLE"},
 }
 
-func Test_diffTables(t *testing.T) {
-	fmt.Println("-- ==========\n-- Tables\n-- ==========")
+func Test_diffTablesAllSchemas(t *testing.T) {
+	fmt.Println("-- ==========\n-- Tables all schemas \n-- ==========")
+	dbInfo1 = pgutil.DbInfo{DbSchema: "*"}
+	dbInfo2 = pgutil.DbInfo{DbSchema: "*"}
 	var schema1 Schema = &TableSchema{rows: testTables1a, rowNum: -1}
 	var schema2 Schema = &TableSchema{rows: testTables1b, rowNum: -1}
 	doDiff(schema1, schema2)
 }
+
+// =================================================================================================
+
+// Note that these must be sorted by compare_name (witout schema) for this to work
+var testTables2a = []map[string]string{
+	{"compare_name": "add", "table_schema": "s1", "table_name": "add", "table_type": "TABLE"},
+	{"compare_name": "same", "table_schema": "s1", "table_name": "same", "table_type": "TABLE"},
+}
+
+// Note that these must be sorted by compare_name (witout schema) for this to work
+var testTables2b = []map[string]string{
+	{"compare_name": "delete", "table_schema": "s2", "table_name": "delete", "table_type": "TABLE"},
+	{"compare_name": "same", "table_schema": "s2", "table_name": "same", "table_type": "TABLE"},
+}
+
+func Test_diffTablesBetweenSchemas(t *testing.T) {
+	fmt.Println("-- ==========\n-- Tables between schemas \n-- ==========")
+	dbInfo1 = pgutil.DbInfo{DbSchema: "s1"}
+	dbInfo2 = pgutil.DbInfo{DbSchema: "s2"}
+	var schema1 Schema = &TableSchema{rows: testTables2a, rowNum: -1}
+	var schema2 Schema = &TableSchema{rows: testTables2b, rowNum: -1}
+	doDiff(schema1, schema2)
+}
diff --git a/view.go b/view.go
index 4ea7a07..e2f39b9 100644
--- a/view.go
+++ b/view.go
@@ -78,7 +78,7 @@ func (c ViewSchema) Add() {
 
 // Drop returns SQL to drop the view
 func (c ViewSchema) Drop() {
-	fmt.Printf("DROP VIEW IF EXISTS %s;\n\n", c.get("viewname"))
+	fmt.Printf("DROP VIEW %s;\n\n", c.get("viewname"))
 }
 
 // Change handles the case where the names match, but the definition does not

From e47b1aa0124984dc4d4edee14c0f9f92880faabe Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Sat, 28 Oct 2017 14:54:19 -0500
Subject: [PATCH 15/74] Added sequence test

Former-commit-id: 0b4ae7fb0312e4fb121ba8b5459a277a1e75cf41
---
 sequence_test.go | 80 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 80 insertions(+)
 create mode 100644 sequence_test.go

diff --git a/sequence_test.go b/sequence_test.go
new file mode 100644
index 0000000..4ded876
--- /dev/null
+++ b/sequence_test.go
@@ -0,0 +1,80 @@
+//
+// Copyright (c) 2017 Jon Carlson.  All rights reserved.
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file.
+//
+// sequence_test.go
+package main
+
+import (
+	"fmt"
+	"github.com/joncrlsn/pgutil"
+	"testing"
+)
+
+/*
+SELECT sequence_schema,
+    , {{if eq $.DbSchema "*" }}sequence_schema || '.' || {{end}}sequence_name AS compare_name
+    ,  sequence_name AS sequence_name
+	, data_type
+	, start_value
+	, minimum_value
+	, maximum_value
+	, increment
+	, cycle_option 
+FROM information_schema.sequences
+WHERE true
+{{if eq $.DbSchema "*" }}
+AND sequence_schema NOT LIKE 'pg_%' 
+AND sequence_schema <> 'information_schema' 
+{{else}}
+AND sequence_schema = '{{$.DbSchema}}'
+{{end}}
+*/
+
+// Note that these must be sorted by schema and sequence name for this to work
+var testSequences1a = []map[string]string{
+	{"compare_name":"s1.add",  "sequence_schema":"s1", "sequence_name":"s1_add"},
+	{"compare_name":"s1.same", "sequence_schema":"s1", "sequence_name":"same"},
+	{"compare_name":"s2.add",  "sequence_schema":"s2", "sequence_name":"s2_add"},
+	{"compare_name":"s2.same", "sequence_schema":"s2", "sequence_name":"same"},
+}
+
+// Note that these must be sorted by schema and sequence name for this to work
+var testSequences1b = []map[string]string{
+	{"compare_name": "s1.delete", "sequence_schema": "s1", "sequence_name": "delete"},
+	{"compare_name": "s1.same",   "sequence_schema": "s1", "sequence_name": "same"},
+	{"compare_name": "s2.same",   "sequence_schema": "s2", "sequence_name": "same"},
+}
+
+func Test_diffSequencesAllSchemas(t *testing.T) {
+	fmt.Println("-- ==========\n-- Sequences all schemas \n-- ==========")
+	dbInfo1 = pgutil.DbInfo{DbSchema: "*"}
+	dbInfo2 = pgutil.DbInfo{DbSchema: "*"}
+	var schema1 Schema = &SequenceSchema{rows: testSequences1a, rowNum: -1}
+	var schema2 Schema = &SequenceSchema{rows: testSequences1b, rowNum: -1}
+	doDiff(schema1, schema2)
+}
+
+// =================================================================================================
+
+// Note that these must be sorted by compare_name (witout schema) for this to work
+var testSequences2a = []map[string]string{
+	{"compare_name": "add",  "sequence_schema": "s1", "sequence_name": "add"},
+	{"compare_name": "same", "sequence_schema": "s1", "sequence_name": "same"},
+}
+
+// Note that these must be sorted by compare_name (witout schema) for this to work
+var testSequences2b = []map[string]string{
+	{"compare_name": "delete", "sequence_schema": "s2", "sequence_name": "delete"},
+	{"compare_name": "same",   "sequence_schema": "s2", "sequence_name": "same"},
+}
+
+func Test_diffSequencesBetweenSchemas(t *testing.T) {
+	fmt.Println("-- ==========\n-- Sequences between schemas \n-- ==========")
+	dbInfo1 = pgutil.DbInfo{DbSchema: "s1"}
+	dbInfo2 = pgutil.DbInfo{DbSchema: "s2"}
+	var schema1 Schema = &SequenceSchema{rows: testSequences2a, rowNum: -1}
+	var schema2 Schema = &SequenceSchema{rows: testSequences2b, rowNum: -1}
+	doDiff(schema1, schema2)
+}

From 458c51ed7b588b8cf09148c08210895ccaa0472b Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Wed, 1 Nov 2017 19:01:34 -0500
Subject: [PATCH 16/74] Better testing of sequences

Former-commit-id: 7b26430f8a069b069148750f0a273097db378f7f
---
 foreignkey.go      |  2 +-
 function.go        | 67 ++++++++++++++++++++++++++++-----------
 index.go           |  2 +-
 role.go            |  1 +
 schemata.go        |  9 +++++-
 sequence.go        | 12 +++----
 sequence_test.go   | 22 ++++++-------
 test/README.md     |  6 ++--
 test/test-schemata | 34 ++++++++++++++++++++
 test/test-sequence | 72 ++++++++++++++++++++++++++++++++++++++++++
 trigger.go         | 78 ++++++++++++++++++++++++++++++++++------------
 11 files changed, 243 insertions(+), 62 deletions(-)
 create mode 100755 test/test-schemata
 create mode 100755 test/test-sequence

diff --git a/foreignkey.go b/foreignkey.go
index d18a074..bccd770 100644
--- a/foreignkey.go
+++ b/foreignkey.go
@@ -175,6 +175,6 @@ func compareForeignKeys(conn1 *sql.DB, conn2 *sql.DB) {
 	var schema1 Schema = &ForeignKeySchema{rows: rows1, rowNum: -1}
 	var schema2 Schema = &ForeignKeySchema{rows: rows2, rowNum: -1}
 
-	// Compare the columns
+	// Compare the foreign keys
 	doDiff(schema1, schema2)
 }
diff --git a/function.go b/function.go
index 6e7aa00..638edee 100644
--- a/function.go
+++ b/function.go
@@ -6,11 +6,44 @@
 
 package main
 
-import "fmt"
-import "sort"
-import "database/sql"
-import "github.com/joncrlsn/pgutil"
-import "github.com/joncrlsn/misc"
+import (
+ "fmt"
+ "sort"
+ "database/sql"
+ "github.com/joncrlsn/pgutil"
+ "github.com/joncrlsn/misc"
+ "text/template"
+ "bytes"
+)
+
+var (
+	functionSqlTemplate = initFunctionSqlTemplate()
+)
+
+// Initializes the Sql template
+func initFunctionSqlTemplate() *template.Template {
+	sql := `
+    SELECT n.nspname                 AS schema_name
+        , {{if eq $.DbSchema "*" }}n.nspname || '.' || {{end}}p.oid::regprocedure AS compare_name
+        , p.oid::regprocedure        AS function_name
+        , t.typname                  AS return_type
+        , pg_get_functiondef(p.oid)  AS definition
+    FROM pg_proc AS p
+    JOIN pg_type t ON (p.prorettype = t.oid)
+    JOIN pg_namespace n ON (n.oid = p.pronamespace)
+    JOIN pg_language l ON (p.prolang = l.oid AND l.lanname IN ('c','plpgsql', 'sql'))
+    WHERE true
+	{{if eq $.DbSchema "*" }}
+    AND n.nspname NOT LIKE 'pg_%' 
+    AND n.nspname <> 'information_schema' 
+    {{else}}
+    AND n.nspname = '{{$.DbSchema}}'
+    {{end}};
+	`
+	t := template.New("FunctionSqlTmpl")
+	template.Must(t.Parse(sql))
+	return t
+}
 
 // ==================================
 // FunctionRows definition
@@ -74,7 +107,7 @@ func (c *FunctionSchema) Compare(obj interface{}) int {
 // Add returns SQL to create the function
 func (c FunctionSchema) Add() {
 	fmt.Println("-- STATEMENT-BEGIN")
-	fmt.Println(c.get("definition"))
+	fmt.Printf("%s;\n", c.get("definition"))
 	fmt.Println("-- STATEMENT-END")
 }
 
@@ -96,26 +129,22 @@ func (c FunctionSchema) Change(obj interface{}) {
 		fmt.Println("-- This function is different so we'll recreate it:")
 		// The definition column has everything needed to rebuild the function
 		fmt.Println("-- STATEMENT-BEGIN")
-		fmt.Println(c.get("definition"))
+		fmt.Printf("%s;\n", c.get("definition"))
 		fmt.Println("-- STATEMENT-END")
 	}
 }
 
 // compareFunctions outputs SQL to make the functions match between DBs
 func compareFunctions(conn1 *sql.DB, conn2 *sql.DB) {
-	sql := `
-    SELECT n.nspname || '.' || p.oid::regprocedure   AS function_name
-        , t.typname                                  AS return_type
-        , pg_get_functiondef(p.oid)                  AS definition
-    FROM pg_proc AS p
-    JOIN pg_type t ON (p.prorettype = t.oid)
-    JOIN pg_namespace n ON (n.oid = p.pronamespace)
-    JOIN pg_language l ON (p.prolang = l.oid AND l.lanname IN ('c','plpgsql', 'sql'))
-    WHERE n.nspname NOT LIKE 'pg_%';
-	`
 
-	rowChan1, _ := pgutil.QueryStrings(conn1, sql)
-	rowChan2, _ := pgutil.QueryStrings(conn2, sql)
+	buf1 := new(bytes.Buffer)
+	functionSqlTemplate.Execute(buf1, dbInfo1)
+
+	buf2 := new(bytes.Buffer)
+	functionSqlTemplate.Execute(buf2, dbInfo2)
+
+	rowChan1, _ := pgutil.QueryStrings(conn1, buf1.String())
+	rowChan2, _ := pgutil.QueryStrings(conn2, buf2.String())
 
 	rows1 := make(FunctionRows, 0)
 	for row := range rowChan1 {
diff --git a/index.go b/index.go
index 222b3e0..59e1334 100644
--- a/index.go
+++ b/index.go
@@ -292,6 +292,6 @@ func compareIndexes(conn1 *sql.DB, conn2 *sql.DB) {
 	var schema1 Schema = &IndexSchema{rows: rows1, rowNum: -1}
 	var schema2 Schema = &IndexSchema{rows: rows2, rowNum: -1}
 
-	// Compare the columns
+	// Compare the indexes
 	doDiff(schema1, schema2)
 }
diff --git a/role.go b/role.go
index b7a43c9..9749d64 100644
--- a/role.go
+++ b/role.go
@@ -305,5 +305,6 @@ ORDER BY r.rolname;
 	var schema1 Schema = &RoleSchema{rows: rows1, rowNum: -1}
 	var schema2 Schema = &RoleSchema{rows: rows2, rowNum: -1}
 
+	// Compare the roles
 	doDiff(schema1, schema2)
 }
diff --git a/schemata.go b/schemata.go
index ffe9086..5c28708 100644
--- a/schemata.go
+++ b/schemata.go
@@ -95,6 +95,13 @@ func (c SchemataSchema) Change(obj interface{}) {
 
 // compareSchematas outputs SQL to make the schema names match between DBs
 func compareSchematas(conn1 *sql.DB, conn2 *sql.DB) {
+
+	// if we are comparing two schemas against each other, then
+	// we won't compare to ensure they are created, although maybe we should.
+	if dbInfo1.DbSchema != dbInfo2.DbSchema {
+		return
+	}
+
 	sql := `
 SELECT schema_name
     , schema_owner
@@ -123,6 +130,6 @@ ORDER BY schema_name;`
 	var schema1 Schema = &SchemataSchema{rows: rows1, rowNum: -1}
 	var schema2 Schema = &SchemataSchema{rows: rows2, rowNum: -1}
 
-	// Compare the tables
+	// Compare the schematas
 	doDiff(schema1, schema2)
 }
diff --git a/sequence.go b/sequence.go
index 924c5ec..566ae3e 100644
--- a/sequence.go
+++ b/sequence.go
@@ -23,9 +23,9 @@ var (
 // Initializes the Sql template
 func initSequenceSqlTemplate() *template.Template {
 	sql := `
-SELECT sequence_schema,
+SELECT sequence_schema AS schema_name
     , {{if eq $.DbSchema "*" }}sequence_schema || '.' || {{end}}sequence_name AS compare_name
-    ,  sequence_name AS sequence_name
+    , sequence_name 
 	, data_type
 	, start_value
 	, minimum_value
@@ -66,7 +66,7 @@ func (slice SequenceRows) Swap(i, j int) {
 	slice[i], slice[j] = slice[j], slice[i]
 }
 
-// SequenceSchema holds a channel streaming table information from one of the databases as well as
+// SequenceSchema holds a channel streaming sequence information from one of the databases as well as
 // a reference to the current row of data we're viewing.
 //
 // SequenceSchema implements the Schema interface defined in pgdiff.go
@@ -109,14 +109,14 @@ func (c *SequenceSchema) Compare(obj interface{}) int {
 func (c SequenceSchema) Add() {
 	schema := dbInfo2.DbSchema
 	if schema == "*" {
-		schema = c.get("sequence_schema")
+		schema = c.get("schema_name")
 	}
 	fmt.Printf("CREATE SEQUENCE %s.%s INCREMENT %s MINVALUE %s MAXVALUE %s START %s;\n", schema, c.get("sequence_name"), c.get("increment"), c.get("minimum_value"), c.get("maximum_value"), c.get("start_value"))
 }
 
 // Drop returns SQL to drop the sequence
 func (c SequenceSchema) Drop() {
-	fmt.Printf("DROP SEQUENCE %s.%s;\n", c.get("sequence_schema"), c.get("sequence_name"))
+	fmt.Printf("DROP SEQUENCE %s.%s;\n", c.get("schema_name"), c.get("sequence_name"))
 }
 
 // Change doesn't do anything right now.
@@ -128,7 +128,7 @@ func (c SequenceSchema) Change(obj interface{}) {
 	// Don't know of anything helpful we should do here
 }
 
-// compareSequences outputs SQL to make the sequences match between DBs
+// compareSequences outputs SQL to make the sequences match between DBs or schemas
 func compareSequences(conn1 *sql.DB, conn2 *sql.DB) {
 
 	buf1 := new(bytes.Buffer)
diff --git a/sequence_test.go b/sequence_test.go
index 4ded876..87eb652 100644
--- a/sequence_test.go
+++ b/sequence_test.go
@@ -21,12 +21,12 @@ SELECT sequence_schema,
 	, minimum_value
 	, maximum_value
 	, increment
-	, cycle_option 
+	, cycle_option
 FROM information_schema.sequences
 WHERE true
 {{if eq $.DbSchema "*" }}
-AND sequence_schema NOT LIKE 'pg_%' 
-AND sequence_schema <> 'information_schema' 
+AND sequence_schema NOT LIKE 'pg_%'
+AND sequence_schema <> 'information_schema'
 {{else}}
 AND sequence_schema = '{{$.DbSchema}}'
 {{end}}
@@ -34,17 +34,17 @@ AND sequence_schema = '{{$.DbSchema}}'
 
 // Note that these must be sorted by schema and sequence name for this to work
 var testSequences1a = []map[string]string{
-	{"compare_name":"s1.add",  "sequence_schema":"s1", "sequence_name":"s1_add"},
-	{"compare_name":"s1.same", "sequence_schema":"s1", "sequence_name":"same"},
-	{"compare_name":"s2.add",  "sequence_schema":"s2", "sequence_name":"s2_add"},
-	{"compare_name":"s2.same", "sequence_schema":"s2", "sequence_name":"same"},
+	{"compare_name": "s1.add", "sequence_schema": "s1", "sequence_name": "s1_add"},
+	{"compare_name": "s1.same", "sequence_schema": "s1", "sequence_name": "same"},
+	{"compare_name": "s2.add", "sequence_schema": "s2", "sequence_name": "s2_add"},
+	{"compare_name": "s2.same", "sequence_schema": "s2", "sequence_name": "same"},
 }
 
 // Note that these must be sorted by schema and sequence name for this to work
 var testSequences1b = []map[string]string{
 	{"compare_name": "s1.delete", "sequence_schema": "s1", "sequence_name": "delete"},
-	{"compare_name": "s1.same",   "sequence_schema": "s1", "sequence_name": "same"},
-	{"compare_name": "s2.same",   "sequence_schema": "s2", "sequence_name": "same"},
+	{"compare_name": "s1.same", "sequence_schema": "s1", "sequence_name": "same"},
+	{"compare_name": "s2.same", "sequence_schema": "s2", "sequence_name": "same"},
 }
 
 func Test_diffSequencesAllSchemas(t *testing.T) {
@@ -60,14 +60,14 @@ func Test_diffSequencesAllSchemas(t *testing.T) {
 
 // Note that these must be sorted by compare_name (witout schema) for this to work
 var testSequences2a = []map[string]string{
-	{"compare_name": "add",  "sequence_schema": "s1", "sequence_name": "add"},
+	{"compare_name": "add", "sequence_schema": "s1", "sequence_name": "add"},
 	{"compare_name": "same", "sequence_schema": "s1", "sequence_name": "same"},
 }
 
 // Note that these must be sorted by compare_name (witout schema) for this to work
 var testSequences2b = []map[string]string{
 	{"compare_name": "delete", "sequence_schema": "s2", "sequence_name": "delete"},
-	{"compare_name": "same",   "sequence_schema": "s2", "sequence_name": "same"},
+	{"compare_name": "same", "sequence_schema": "s2", "sequence_name": "same"},
 }
 
 func Test_diffSequencesBetweenSchemas(t *testing.T) {
diff --git a/test/README.md b/test/README.md
index 7ad1851..72ac18e 100644
--- a/test/README.md
+++ b/test/README.md
@@ -1,6 +1,6 @@
-These are not automated tests (as much as I'd rather have automated tests), but manual tests 
-for verifying that individual schema types are working.
+These are not automated tests (as much as I'd rather have automated tests), but manual
+integration tests for verifying that individual schema types are working.
 
 
-Connect to the database:
+Connect to the database manually:
   sudo su - postgres -- -c "psql -d db1"
diff --git a/test/test-schemata b/test/test-schemata
new file mode 100755
index 0000000..0ab2080
--- /dev/null
+++ b/test/test-schemata
@@ -0,0 +1,34 @@
+#!/bin/bash
+#
+# Useful for visually inspecting the output SQL to verify it is doing what it should
+#
+
+source ./start-fresh.sh >/dev/null
+echo
+echo ==========================================================
+echo
+
+#
+# Compare the schemas (aka schematas) between two databases
+#
+./populate-db.sh db1 "
+    CREATE SCHEMA s1;  -- matches db2
+    CREATE SCHEMA s2;  -- to be added to db2
+"
+./populate-db.sh db2 "
+    CREATE SCHEMA s1;  -- matches db1
+    CREATE SCHEMA s3;  -- to be removed from this db
+"
+
+echo
+echo "# Compare the indexes in all schemas between two databases"
+echo "# Expect SQL (pseudocode):"
+echo "#   Add schema on db2:     s2 "
+echo "#   Drop schema from db2:  s3 "
+echo
+
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "*" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db2" -s "*" -o "sslmode=disable" \
+          SCHEMA | grep -v '^-- '
+echo
+echo
diff --git a/test/test-sequence b/test/test-sequence
new file mode 100755
index 0000000..1d51257
--- /dev/null
+++ b/test/test-sequence
@@ -0,0 +1,72 @@
+#!/bin/bash
+#
+# Useful for visually inspecting the output SQL to verify it is doing what it should
+#
+
+source ./start-fresh.sh >/dev/null
+echo
+echo ==========================================================
+echo
+
+#
+# Compare the sequences in two schemas in the same database
+#
+
+./populate-db.sh db1 "
+    CREATE SCHEMA s1;
+    CREATE TABLE s1.table1 (id integer PRIMARY KEY); -- just for kicks
+    CREATE SEQUENCE s1.sequence_1 
+       INCREMENT BY 2
+       MINVALUE 1024 
+       MAXVALUE 99998
+       START WITH 2048
+       NO CYCLE 
+       OWNED BY s1.table1.id;
+    CREATE SEQUENCE s1.sequence_2;
+
+    CREATE SCHEMA s2;
+    CREATE SEQUENCE s2.sequence_2; 
+    CREATE SEQUENCE s2.sequence_3; 
+"
+
+echo
+echo "# Compare the sequences in two schemas in the same database"
+echo "# Expect SQL (pseudocode):"
+echo "#   Add s2.sequence_1"
+echo "#   Drop s2.sequence_3"
+echo
+
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
+          SEQUENCE | grep -v '^-- '
+
+
+echo
+echo ==========================================================
+echo
+
+#
+# Compare the sequences in all schemas in two databases
+#
+./populate-db.sh db2 "
+    CREATE SCHEMA s1;
+    CREATE SEQUENCE s1.sequence_2;
+
+    CREATE SCHEMA s2;
+    CREATE SEQUENCE s2.sequence_2; 
+    CREATE SEQUENCE s2.sequence_3; 
+    CREATE SEQUENCE s2.sequence_4; 
+"
+
+echo
+echo "# Compare the sequences in all schemas between two databases"
+echo "# Expect SQL (pseudocode):"
+echo "#   Add sequence s1.sequence_1"
+echo "#   Drop sequence s2.sequence_4"
+echo
+
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "*" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db2" -s "*" -o "sslmode=disable" \
+          SEQUENCE | grep -v '^-- '
+echo
+echo
diff --git a/trigger.go b/trigger.go
index fe5834d..cf100dd 100644
--- a/trigger.go
+++ b/trigger.go
@@ -6,11 +6,44 @@
 
 package main
 
-import "fmt"
-import "sort"
-import "database/sql"
-import "github.com/joncrlsn/pgutil"
-import "github.com/joncrlsn/misc"
+import (
+	"database/sql"
+	"fmt"
+	"github.com/joncrlsn/misc"
+	"github.com/joncrlsn/pgutil"
+	"text/template"
+	"bytes"
+	"sort"
+)
+
+var (
+	triggerSqlTemplate = initTriggerSqlTemplate()
+)
+
+// Initializes the Sql template
+func initTriggerSqlTemplate() *template.Template {
+	sql := `
+    SELECT n.nspname AS schema_name
+       , {{if eq $.DbSchema "*" }}n.nspname || '.' || {{end}}c.relname AS compare_name
+       , c.relname AS table_name
+       , t.tgname AS trigger_name
+       , pg_catalog.pg_get_triggerdef(t.oid, true) AS definition
+       , t.tgenabled AS enabled
+    FROM pg_catalog.pg_trigger t
+    INNER JOIN pg_catalog.pg_class c ON (c.oid = t.tgrelid)
+    INNER JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
+	WHERE true
+    {{if eq $.DbSchema "*" }}
+    AND n.nspname NOT LIKE 'pg_%' 
+    AND n.nspname <> 'information_schema' 
+    {{else}}
+    AND n.nspname = '{{$.DbSchema}}'
+    {{end}}
+	`
+	t := template.New("TriggerSqlTmpl")
+	template.Must(t.Parse(sql))
+	return t
+}
 
 // ==================================
 // TriggerRows definition
@@ -24,8 +57,8 @@ func (slice TriggerRows) Len() int {
 }
 
 func (slice TriggerRows) Less(i, j int) bool {
-	if slice[i]["table_name"] != slice[j]["table_name"] {
-		return slice[i]["table_name"] < slice[j]["table_name"]
+	if slice[i]["compare_name"] != slice[j]["compare_name"] {
+		return slice[i]["compare_name"] < slice[j]["compare_name"]
 	}
 	return slice[i]["trigger_name"] < slice[j]["trigger_name"]
 }
@@ -69,7 +102,7 @@ func (c *TriggerSchema) Compare(obj interface{}) int {
 		return +999
 	}
 
-	val := misc.CompareStrings(c.get("table_name"), c2.get("table_name"))
+	val := misc.CompareStrings(c.get("compare_name"), c2.get("compare_name"))
 	if val != 0 {
 		return val
 	}
@@ -79,12 +112,20 @@ func (c *TriggerSchema) Compare(obj interface{}) int {
 
 // Add returns SQL to create the trigger
 func (c TriggerSchema) Add() {
+	schema := dbInfo2.DbSchema
+	if schema == "*" {
+		schema = c.get("schema_name")
+	}
+
+	// NOTE: we may need to do some tweaking of the definition here to replace the old schema
+	// name with the new schema name.
+
 	fmt.Printf("%s;\n", c.get("definition"))
 }
 
 // Drop returns SQL to drop the trigger
 func (c TriggerSchema) Drop() {
-	fmt.Printf("DROP TRIGGER %s ON %s;\n", c.get("trigger_name"), c.get("table_name"))
+	fmt.Printf("DROP TRIGGER %s ON %s.%s;\n", c.get("trigger_name"), c.get("schema_name"), c.get("table_name"))
 }
 
 // Change handles the case where the trigger names match, but the definition does not
@@ -104,18 +145,15 @@ func (c TriggerSchema) Change(obj interface{}) {
 
 // compareTriggers outputs SQL to make the triggers match between DBs
 func compareTriggers(conn1 *sql.DB, conn2 *sql.DB) {
-	sql := `
-    SELECT n.nspname || '.' || c.relname AS table_name
-       , t.tgname AS trigger_name
-       , pg_catalog.pg_get_triggerdef(t.oid, true) AS definition
-       , t.tgenabled AS enabled
-    FROM pg_catalog.pg_trigger t
-    INNER JOIN pg_catalog.pg_class c ON (c.oid = t.tgrelid)
-    INNER JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace AND n.nspname NOT LIKE 'pg_%');
-	`
 
-	rowChan1, _ := pgutil.QueryStrings(conn1, sql)
-	rowChan2, _ := pgutil.QueryStrings(conn2, sql)
+	buf1 := new(bytes.Buffer)
+	triggerSqlTemplate.Execute(buf1, dbInfo1)
+
+	buf2 := new(bytes.Buffer)
+	triggerSqlTemplate.Execute(buf2, dbInfo2)
+
+	rowChan1, _ := pgutil.QueryStrings(conn1, buf1.String())
+	rowChan2, _ := pgutil.QueryStrings(conn2, buf2.String())
 
 	rows1 := make(TriggerRows, 0)
 	for row := range rowChan1 {

From c290ea05aba228b1b9ead295d29e761bdba648a4 Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Wed, 1 Nov 2017 19:04:36 -0500
Subject: [PATCH 17/74] Removed .sh extension from test scripts

Former-commit-id: 05530d134754ee9de27dee4d98f6832a8479e893
---
 test/{test-column.sh => test-column}         | 0
 test/{test-foreignkey.sh => test-foreignkey} | 0
 test/{test-index.sh => test-index}           | 0
 test/{test-table.sh => test-table}           | 0
 4 files changed, 0 insertions(+), 0 deletions(-)
 rename test/{test-column.sh => test-column} (100%)
 rename test/{test-foreignkey.sh => test-foreignkey} (100%)
 rename test/{test-index.sh => test-index} (100%)
 rename test/{test-table.sh => test-table} (100%)

diff --git a/test/test-column.sh b/test/test-column
similarity index 100%
rename from test/test-column.sh
rename to test/test-column
diff --git a/test/test-foreignkey.sh b/test/test-foreignkey
similarity index 100%
rename from test/test-foreignkey.sh
rename to test/test-foreignkey
diff --git a/test/test-index.sh b/test/test-index
similarity index 100%
rename from test/test-index.sh
rename to test/test-index
diff --git a/test/test-table.sh b/test/test-table
similarity index 100%
rename from test/test-table.sh
rename to test/test-table

From d5b0ca2e29eb11ed89cc7690a44ec8a82a3a4c1d Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Wed, 1 Nov 2017 19:11:02 -0500
Subject: [PATCH 18/74] Improved formatting of test output

Former-commit-id: 0b8c7d1a096d765ba657752980f75003be28417e
---
 test/test-column     | 21 +++++++++++++--------
 test/test-foreignkey | 13 ++++++++++---
 test/test-table      |  4 ++++
 3 files changed, 27 insertions(+), 11 deletions(-)

diff --git a/test/test-column b/test/test-column
index bde1877..424ee80 100755
--- a/test/test-column
+++ b/test/test-column
@@ -5,16 +5,21 @@
 
 source ./start-fresh.sh >/dev/null
 
+echo
+echo ==============================================================
+
+#
+# Compare the columns in two schemas in the same database
+#
+
 echo
 echo "# Compare the tables in two schemas in the same database"
 echo "# Expect SQL:"
 echo "#   Add s2.table1.id"
 echo "#   Drop s2.table1.description"
 echo "#   Alter s2.table1.name to varchar(24)"
+echo
 
-#
-# Compare the columns in two schemas in the same database
-#
 ./populate-db.sh db1 "
     CREATE SCHEMA s1;
     CREATE TABLE s1.table1 (
@@ -31,7 +36,6 @@ echo "#   Alter s2.table1.name to varchar(24)"
 " 
 
 echo
-echo "SQL to run:"
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
           COLUMN | grep -v '^-- '
@@ -40,14 +44,15 @@ echo
 echo ==============================================================
 echo
 
+#
+# Compare the columns in all schemas between two database
+#
+
 echo "# Compare the columns in all schemas in two databases"
 echo "# Expect SQL:"
 echo "#   Drop column_to_delete from s1.table1 (in db2)"
 echo "#   Add s1.table1.name  (in db2) "
 echo "#   Alter s2.table1.name to varchar(24)"
-#
-# Compare the columns in all schemas in two database
-#
 ./populate-db.sh db2 "
     CREATE SCHEMA s1;
     CREATE TABLE s1.table1 (
@@ -64,7 +69,7 @@ echo "#   Alter s2.table1.name to varchar(24)"
 "
 
 echo
-echo "SQL to run:"
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "*" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db2" -s "*" -o "sslmode=disable" \
           COLUMN | grep -v '^-- '
+echo
diff --git a/test/test-foreignkey b/test/test-foreignkey
index 7be0374..7e12558 100755
--- a/test/test-foreignkey
+++ b/test/test-foreignkey
@@ -5,6 +5,10 @@
 
 source ./start-fresh.sh >/dev/null
 
+echo
+echo ====================================================
+echo
+
 #
 # Compare the foreign keys in two schemas in the same database
 #
@@ -42,13 +46,16 @@ echo "# Compare the foreign keys in two schemas in the same database"
 echo "# Expect SQL:"
 echo "#   Add foreign key on s2.table2.table1_id"
 echo "#   Drop foreign key from s2.table3.table2_id"
-
 echo
-echo "SQL to run:"
+
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
           FOREIGN_KEY | grep -v '^-- '
 
+echo
+echo ====================================================
+echo
+
 
 #
 # Compare the foreign keys in all schemas in two databases
@@ -89,7 +96,7 @@ echo "#   Add foreign key on db2.s1.table2.table1_id"
 echo "#   Drop foreign key on db2.s2.table2.table1_id"
 
 echo
-echo "SQL to run:"
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "*" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db2" -s "*" -o "sslmode=disable" \
           FOREIGN_KEY | grep -v '^-- '
+echo
diff --git a/test/test-table b/test/test-table
index 95089f9..3d389bb 100755
--- a/test/test-table
+++ b/test/test-table
@@ -5,6 +5,9 @@
 
 source ./start-fresh.sh >/dev/null 
 
+echo
+echo ==============================================================
+
 echo
 echo "# Compare the tables in two schemas in the same database"
 echo "# Expect SQL:"
@@ -59,3 +62,4 @@ echo "SQL to run:"
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "*" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db2" -s "*" -o "sslmode=disable" \
           TABLE | grep -v '^-- '
+echo

From 1c377dbbd5c901cea5f7f05741f53a2a55eca26c Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Wed, 1 Nov 2017 19:43:29 -0500
Subject: [PATCH 19/74] WIP function comparisons

Former-commit-id: e254463a90f29a15e7571865ff4796974f9891d7
---
 function.go        |  8 ++--
 test/test-function | 95 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 99 insertions(+), 4 deletions(-)
 create mode 100755 test/test-function

diff --git a/function.go b/function.go
index 638edee..705c448 100644
--- a/function.go
+++ b/function.go
@@ -1,5 +1,5 @@
 //
-// Copyright (c) 2016 Jon Carlson.  All rights reserved.
+// Copyright (c) 2017 Jon Carlson.  All rights reserved.
 // Use of this source code is governed by an MIT-style
 // license that can be found in the LICENSE file.
 //
@@ -57,7 +57,7 @@ func (slice FunctionRows) Len() int {
 }
 
 func (slice FunctionRows) Less(i, j int) bool {
-	return slice[i]["function_name"] < slice[j]["function_name"]
+	return slice[i]["compare_name"] < slice[j]["compare_name"]
 }
 
 func (slice FunctionRows) Swap(i, j int) {
@@ -99,7 +99,7 @@ func (c *FunctionSchema) Compare(obj interface{}) int {
 		return +999
 	}
 
-	val := misc.CompareStrings(c.get("function_name"), c2.get("function_name"))
+	val := misc.CompareStrings(c.get("compare_name"), c2.get("compare_name"))
 	//fmt.Printf("-- Compared %v: %s with %s \n", val, c.get("function_name"), c2.get("function_name"))
 	return val
 }
@@ -116,7 +116,7 @@ func (c FunctionSchema) Drop() {
 	fmt.Println("-- Note that CASCADE in the statement below will also drop any triggers depending on this function.")
 	fmt.Println("-- Also, if there are two functions with this name, you will need to add arguments to identify the correct one to drop.")
 	fmt.Println("-- (See http://www.postgresql.org/docs/9.4/interactive/sql-dropfunction.html) ")
-	fmt.Printf("DROP FUNCTION %s CASCADE;\n", c.get("function_name"))
+	fmt.Printf("DROP FUNCTION %s.%s CASCADE;\n", c.get("schema_name"), c.get("function_name"))
 }
 
 // Change handles the case where the function names match, but the definition does not
diff --git a/test/test-function b/test/test-function
new file mode 100755
index 0000000..1c6e53c
--- /dev/null
+++ b/test/test-function
@@ -0,0 +1,95 @@
+#!/bin/bash
+#
+# Useful for visually inspecting the output SQL to verify it is doing what it should
+#
+
+source ./start-fresh.sh >/dev/null
+echo
+echo ==========================================================
+echo
+
+#
+# Compare the functions between two schemas in the same database
+#
+
+./populate-db.sh db1 "$(cat << 'EOF' 
+CREATE SCHEMA s1;
+CREATE OR REPLACE FUNCTION s1.increment(i integer) RETURNS integer AS $$
+    BEGIN
+            RETURN i + 1;
+    END;
+$$ LANGUAGE plpgsql;
+CREATE FUNCTION s1.add(integer, integer) RETURNS integer
+    AS 'select $1 + $2;'
+    LANGUAGE SQL
+    IMMUTABLE
+    RETURNS NULL ON NULL INPUT;
+
+
+CREATE SCHEMA s2;
+CREATE OR REPLACE FUNCTION s2.add(integer, integer) RETURNS integer
+    AS 'select $1 + $2;'
+    LANGUAGE SQL
+    IMMUTABLE
+    RETURNS NULL ON NULL INPUT;
+CREATE FUNCTION s2.minus(integer, integer) RETURNS integer
+    AS 'select $1 - $2;'
+    LANGUAGE SQL
+    IMMUTABLE
+    RETURNS NULL ON NULL INPUT;
+
+EOF
+)"
+
+echo
+echo "# Compare the functions between two schemas in the same database"
+echo "# Expect SQL (pseudocode):"
+echo "#   Add function s2.increment"
+echo "#   Replace function s2.add"
+echo "#   Drop function s2.minus"
+echo
+
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
+          FUNCTION | grep -v '^-- '
+
+exit 0
+
+echo
+echo ==========================================================
+echo
+
+
+#
+# Compare the functions in all schemas in two databases
+#
+./populate-db.sh db2 "
+    CREATE SCHEMA s1;
+    CREATE TABLE s1.table1 (
+      id integer PRIMARY KEY,
+      name varchar(32),
+      url varchar(200)
+    );
+    CREATE INDEX ON s1.table1(name);
+    CREATE INDEX ON s1.table1(url);
+
+    CREATE SCHEMA s2;
+    CREATE TABLE s2.table1 (
+      id integer PRIMARY KEY, 
+      name varchar(32),
+      url varchar(200)
+    );
+"
+
+echo
+echo "# Compare the functions in all schemas between two databases"
+echo "# Expect SQL (pseudocode):"
+echo "#   Drop index on db2 s1.table1.url"
+echo "#   Add index on db2 s2.table1.url"
+echo
+
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "*" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db2" -s "*" -o "sslmode=disable" \
+          FUNCTION | grep -v '^-- '
+echo
+echo

From 8f89b1bfe40bb239a2fee0424b0d02e0d7f29028 Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Fri, 3 Nov 2017 14:30:04 -0500
Subject: [PATCH 20/74] Fix wording in test script

Former-commit-id: f61f49dabf7eb94cc9e3f8c71b634ac901940a57
---
 test/test-column     | 8 ++++----
 test/test-foreignkey | 6 +++---
 test/test-function   | 2 +-
 test/test-index      | 6 +++---
 test/test-sequence   | 6 +++---
 test/test-table      | 6 +++---
 6 files changed, 17 insertions(+), 17 deletions(-)

diff --git a/test/test-column b/test/test-column
index 424ee80..13335ef 100755
--- a/test/test-column
+++ b/test/test-column
@@ -9,11 +9,11 @@ echo
 echo ==============================================================
 
 #
-# Compare the columns in two schemas in the same database
+# Compare the columns between two schemas in the same database
 #
 
 echo
-echo "# Compare the tables in two schemas in the same database"
+echo "# Compare the tables between two schemas in the same database"
 echo "# Expect SQL:"
 echo "#   Add s2.table1.id"
 echo "#   Drop s2.table1.description"
@@ -45,10 +45,10 @@ echo ==============================================================
 echo
 
 #
-# Compare the columns in all schemas between two database
+# Compare the columns in all schemas between two databases
 #
 
-echo "# Compare the columns in all schemas in two databases"
+echo "# Compare the columns in all schemas between two databases"
 echo "# Expect SQL:"
 echo "#   Drop column_to_delete from s1.table1 (in db2)"
 echo "#   Add s1.table1.name  (in db2) "
diff --git a/test/test-foreignkey b/test/test-foreignkey
index 7e12558..d25b6a0 100755
--- a/test/test-foreignkey
+++ b/test/test-foreignkey
@@ -10,7 +10,7 @@ echo ====================================================
 echo
 
 #
-# Compare the foreign keys in two schemas in the same database
+# Compare the foreign keys between two schemas in the same database
 #
 
 ./populate-db.sh db1 "
@@ -42,7 +42,7 @@ echo
 "
 
 echo
-echo "# Compare the foreign keys in two schemas in the same database"
+echo "# Compare the foreign keys between two schemas in the same database"
 echo "# Expect SQL:"
 echo "#   Add foreign key on s2.table2.table1_id"
 echo "#   Drop foreign key from s2.table3.table2_id"
@@ -58,7 +58,7 @@ echo
 
 
 #
-# Compare the foreign keys in all schemas in two databases
+# Compare the foreign keys in all schemas between two databases
 #
 ./populate-db.sh db2 "
     CREATE SCHEMA s1;
diff --git a/test/test-function b/test/test-function
index 1c6e53c..98b7d06 100755
--- a/test/test-function
+++ b/test/test-function
@@ -61,7 +61,7 @@ echo
 
 
 #
-# Compare the functions in all schemas in two databases
+# Compare the functions in all schemas between two databases
 #
 ./populate-db.sh db2 "
     CREATE SCHEMA s1;
diff --git a/test/test-index b/test/test-index
index 6d1a0d9..463f80b 100755
--- a/test/test-index
+++ b/test/test-index
@@ -9,7 +9,7 @@ echo ==========================================================
 echo
 
 #
-# Compare the indexes in two schemas in the same database
+# Compare the indexes between two schemas in the same database
 #
 
 ./populate-db.sh db1 "
@@ -31,7 +31,7 @@ echo
 "
 
 echo
-echo "# Compare the indexes in two schemas in the same database"
+echo "# Compare the indexes between two schemas in the same database"
 echo "# Expect SQL (pseudocode):"
 echo "#   Add index on s2.table1.name"
 echo "#   Drop index on s2.table1.url"
@@ -46,7 +46,7 @@ echo
 
 
 #
-# Compare the indexes in all schemas in two databases
+# Compare the indexes in all schemas between two databases
 #
 ./populate-db.sh db2 "
     CREATE SCHEMA s1;
diff --git a/test/test-sequence b/test/test-sequence
index 1d51257..c13d26f 100755
--- a/test/test-sequence
+++ b/test/test-sequence
@@ -9,7 +9,7 @@ echo ==========================================================
 echo
 
 #
-# Compare the sequences in two schemas in the same database
+# Compare the sequences between two schemas in the same database
 #
 
 ./populate-db.sh db1 "
@@ -30,7 +30,7 @@ echo
 "
 
 echo
-echo "# Compare the sequences in two schemas in the same database"
+echo "# Compare the sequences between two schemas in the same database"
 echo "# Expect SQL (pseudocode):"
 echo "#   Add s2.sequence_1"
 echo "#   Drop s2.sequence_3"
@@ -46,7 +46,7 @@ echo ==========================================================
 echo
 
 #
-# Compare the sequences in all schemas in two databases
+# Compare the sequences in all schemas between two databases
 #
 ./populate-db.sh db2 "
     CREATE SCHEMA s1;
diff --git a/test/test-table b/test/test-table
index 3d389bb..6b435ab 100755
--- a/test/test-table
+++ b/test/test-table
@@ -9,13 +9,13 @@ echo
 echo ==============================================================
 
 echo
-echo "# Compare the tables in two schemas in the same database"
+echo "# Compare the tables between two schemas in the same database"
 echo "# Expect SQL:"
 echo "#   Add table9 to schema s2"
 echo "#   Drop table11 from schema s2"
 
 #
-# Compare the tables in two schemas in the same database
+# Compare the tables between two schemas in the same database
 #
 #psql -U u1 -h localhost -d db1 <<'EOS'
 ./populate-db.sh db1 "
@@ -38,7 +38,7 @@ echo
 echo ==============================================================
 
 echo
-echo "# Compare the tables in all schemas in two databases"
+echo "# Compare the tables in all schemas between two databases"
 echo "# Expect:"
 echo "#   Add s1.table10 to db2"
 echo "#   Drop s2.table12 from db2"

From 1daf397da601e600bf19efb096a0cff11d716ca9 Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Sat, 4 Nov 2017 13:20:18 -0500
Subject: [PATCH 21/74] owner works now across schemas

Former-commit-id: 1f7ee463a32437893138e6cf5e4d7d989ad6b3d9
---
 function.go         | 14 +++----
 owner.go            | 85 ++++++++++++++++++++++++++++--------------
 role.go             |  2 +-
 test/start-fresh.sh |  2 +
 test/test-owner     | 90 +++++++++++++++++++++++++++++++++++++++++++++
 trigger.go          |  4 +-
 6 files changed, 159 insertions(+), 38 deletions(-)
 create mode 100755 test/test-owner

diff --git a/function.go b/function.go
index 705c448..33e3e97 100644
--- a/function.go
+++ b/function.go
@@ -7,13 +7,13 @@
 package main
 
 import (
- "fmt"
- "sort"
- "database/sql"
- "github.com/joncrlsn/pgutil"
- "github.com/joncrlsn/misc"
- "text/template"
- "bytes"
+	"bytes"
+	"database/sql"
+	"fmt"
+	"github.com/joncrlsn/misc"
+	"github.com/joncrlsn/pgutil"
+	"sort"
+	"text/template"
 )
 
 var (
diff --git a/owner.go b/owner.go
index 954ace2..5139754 100644
--- a/owner.go
+++ b/owner.go
@@ -6,11 +6,47 @@
 
 package main
 
-import "fmt"
-import "database/sql"
-import "sort"
-import "github.com/joncrlsn/pgutil"
-import "github.com/joncrlsn/misc"
+import (
+	"bytes"
+	"database/sql"
+	"fmt"
+	"github.com/joncrlsn/misc"
+	"github.com/joncrlsn/pgutil"
+	"sort"
+	"text/template"
+)
+
+var (
+	ownerSqlTemplate = initOwnerSqlTemplate()
+)
+
+// Initializes the Sql template
+func initOwnerSqlTemplate() *template.Template {
+	sql := `
+SELECT n.nspname AS schema_name
+    , {{if eq $.DbSchema "*" }}n.nspname || '.' || {{end}}c.relname || '.' || c.relname AS compare_name
+    , c.relname AS relationship_name
+    , a.rolname AS owner
+    , CASE WHEN c.relkind = 'r' THEN 'TABLE' 
+        WHEN c.relkind = 'S' THEN 'SEQUENCE' 
+        WHEN c.relkind = 'v' THEN 'VIEW' 
+        ELSE c.relkind::varchar END AS type
+FROM pg_class AS c
+INNER JOIN pg_authid AS a ON (a.oid = c.relowner)
+INNER JOIN pg_namespace AS n ON (n.oid = c.relnamespace)
+WHERE c.relkind IN ('r', 'S', 'v')
+{{if eq $.DbSchema "*"}}
+AND n.nspname NOT LIKE 'pg_%' 
+AND n.nspname <> 'information_schema'
+{{else}}
+AND n.nspname = '{{$.DbSchema}}'
+{{end}}
+;`
+
+	t := template.New("OwnerSqlTmpl")
+	template.Must(t.Parse(sql))
+	return t
+}
 
 // ==================================
 // OwnerRows definition
@@ -24,7 +60,7 @@ func (slice OwnerRows) Len() int {
 }
 
 func (slice OwnerRows) Less(i, j int) bool {
-	return slice[i]["relationship_name"] < slice[j]["relationship_name"]
+	return slice[i]["compare_name"] < slice[j]["compare_name"]
 }
 
 func (slice OwnerRows) Swap(i, j int) {
@@ -77,21 +113,21 @@ func (c *OwnerSchema) Compare(obj interface{}) int {
 		return +999
 	}
 
-	val := misc.CompareStrings(c.get("relationship_name"), c2.get("relationship_name"))
+	val := misc.CompareStrings(c.get("compare_name"), c2.get("compare_name"))
 	return val
 }
 
 // Add generates SQL to add the table/view owner
 func (c OwnerSchema) Add() {
-	fmt.Printf("-- Notice, db2 has no %s named %s. You probably need to run pgdiff with the %s option first.\n", c.get("type"), c.get("relationship_name"), c.get("type"))
+	fmt.Printf("-- Notice!, db2 has no %s named %s.  First, run pgdiff with the %s option.\n", c.get("type"), c.get("relationship_name"), c.get("type"))
 }
 
 // Drop generates SQL to drop the owner
 func (c OwnerSchema) Drop() {
-	fmt.Printf("-- Notice, db2 has a %s that db1 does not: %s. Cannot compare owners.\n", c.get("type"), c.get("relationship_name"))
+	fmt.Printf("-- Notice!, db2 has a %s that db1 does not: %s.   First, run pgdiff with the %s option.\n", c.get("type"), c.get("relationship_name"), c.get("type"))
 }
 
-// Change handles the case where the role name matches, but the details do not
+// Change handles the case where the relationship name matches, but the owner does not
 func (c OwnerSchema) Change(obj interface{}) {
 	c2, ok := obj.(*OwnerSchema)
 	if !ok {
@@ -99,30 +135,23 @@ func (c OwnerSchema) Change(obj interface{}) {
 	}
 
 	if c.get("owner") != c2.get("owner") {
-		fmt.Printf("ALTER %s %s OWNER TO %s; \n", c.get("type"), c.get("relationship_name"), c.get("owner"))
+		fmt.Printf("ALTER %s %s.%s OWNER TO %s; \n", c.get("type"), c2.get("schema_name"), c.get("relationship_name"), c.get("owner"))
 	}
 }
 
 /*
- * Compare the ownership of tables, sequences, and views in the two databases
+ * Compare the ownership of tables, sequences, and views between two databases or schemas
  */
 func compareOwners(conn1 *sql.DB, conn2 *sql.DB) {
-	sql := `
-SELECT n.nspname AS schema
-    , c.relname AS relationship_name
-    , a.rolname AS owner
-    , CASE WHEN c.relkind = 'r' THEN 'TABLE' 
-        WHEN c.relkind = 'S' THEN 'SEQUENCE' 
-        WHEN c.relkind = 'v' THEN 'VIEW' 
-        ELSE c.relkind::varchar END AS type
-FROM pg_class AS c
-INNER JOIN pg_authid AS a ON (a.oid = c.relowner)
-INNER JOIN pg_namespace AS n ON (n.oid = c.relnamespace)
-WHERE n.nspname = 'public' 
-AND c.relkind IN ('r', 'S', 'v');
-`
-	rowChan1, _ := pgutil.QueryStrings(conn1, sql)
-	rowChan2, _ := pgutil.QueryStrings(conn2, sql)
+
+	buf1 := new(bytes.Buffer)
+	ownerSqlTemplate.Execute(buf1, dbInfo1)
+
+	buf2 := new(bytes.Buffer)
+	ownerSqlTemplate.Execute(buf2, dbInfo2)
+
+	rowChan1, _ := pgutil.QueryStrings(conn1, buf1.String())
+	rowChan2, _ := pgutil.QueryStrings(conn2, buf2.String())
 
 	rows1 := make(OwnerRows, 0)
 	for row := range rowChan1 {
diff --git a/role.go b/role.go
index 9749d64..34ed1e8 100644
--- a/role.go
+++ b/role.go
@@ -266,7 +266,7 @@ func (c RoleSchema) Change(obj interface{}) {
 }
 
 /*
- * Compare the roles in the two databases
+ * Compare the roles between two databases or schemas
  */
 func compareRoles(conn1 *sql.DB, conn2 *sql.DB) {
 	sql := `
diff --git a/test/start-fresh.sh b/test/start-fresh.sh
index 34cb0a0..8c45de8 100755
--- a/test/start-fresh.sh
+++ b/test/start-fresh.sh
@@ -8,8 +8,10 @@ sudo su - postgres -- <<EOT
 psql <<'SQL'
     DROP DATABASE IF EXISTS db1;
     DROP DATABASE IF EXISTS db2;
+
     DROP USER IF EXISTS u1;
     CREATE USER u1 WITH SUPERUSER PASSWORD 'asdf';
+
     CREATE DATABASE db1 WITH OWNER = u1 TEMPLATE = template0;
     CREATE DATABASE db2 WITH OWNER = u1 TEMPLATE = template0;
 SQL
diff --git a/test/test-owner b/test/test-owner
new file mode 100755
index 0000000..f32723e
--- /dev/null
+++ b/test/test-owner
@@ -0,0 +1,90 @@
+#!/bin/bash
+#
+# Useful for visually inspecting the output SQL to verify it is doing what it should
+#
+
+# Rebuild the basic databases
+source ./start-fresh.sh >/dev/null
+
+# An additional user/role is needed for this test
+sudo su - postgres -- -c " 
+  psql -c \" 
+    DROP USER IF EXISTS u2; CREATE USER u2 WITH SUPERUSER PASSWORD 'asdf'; 
+  \"
+"
+
+
+echo
+echo ==========================================================
+echo
+
+#
+# Compare the table, etc owners between two schemas in the same database
+#
+
+./populate-db.sh db1 "
+    -- schema s1
+    CREATE SCHEMA s1;
+    CREATE TABLE s1.table1(); 
+    ALTER TABLE s1.table1 OWNER TO u2;
+    CREATE TABLE s1.table2(); 
+    CREATE TABLE s1.table3(); 
+    CREATE TABLE s1.table4(); 
+
+    -- schema s2
+    CREATE SCHEMA s2;
+    CREATE TABLE s2.table1();
+    CREATE TABLE s2.table2();
+    ALTER TABLE s2.table2 OWNER TO u2;
+    CREATE TABLE s2.table3();
+    CREATE TABLE s2.table5(); 
+"
+
+echo
+echo "# Compare the table, etc. owners between two schemas in the same database"
+echo "# Expect SQL (pseudocode):"
+echo "#   Change s2.table1 owner to u2"
+echo "#   Change s2.table2 owner to u1"
+echo "#   No changes to ownership of s2.table3"
+echo "#   Messages about table4 and table5 not being in both schemas"
+echo
+
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
+          OWNER #| grep -v '^-- '
+echo
+echo ==========================================================
+echo
+
+
+#
+# Compare the table, etc. owners in all schemas between two databases
+#
+./populate-db.sh db2 "
+    -- schema s1
+    CREATE SCHEMA s1;
+    CREATE TABLE s1.table1(); 
+    ALTER TABLE s1.table1 OWNER TO u2;
+    CREATE TABLE s1.table2(); 
+    CREATE TABLE s1.table3(); 
+    ALTER TABLE s1.table3 OWNER TO u2;
+
+    -- schema s2
+    CREATE SCHEMA s2;
+    CREATE TABLE s2.table1();
+    CREATE TABLE s2.table2();
+    CREATE TABLE s2.table3();
+"
+
+echo
+echo "# Compare the table, etc owners in all schemas between two databases"
+echo "# Expect SQL (pseudocode):"
+echo "#   Change s1.table3 owner to u1 "
+echo "#   Change s2.table2 owner to u2 "
+echo
+
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "*" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db2" -s "*" -o "sslmode=disable" \
+          OWNER | grep -v '^-- '
+echo
+echo
diff --git a/trigger.go b/trigger.go
index cf100dd..eea69c5 100644
--- a/trigger.go
+++ b/trigger.go
@@ -7,13 +7,13 @@
 package main
 
 import (
+	"bytes"
 	"database/sql"
 	"fmt"
 	"github.com/joncrlsn/misc"
 	"github.com/joncrlsn/pgutil"
-	"text/template"
-	"bytes"
 	"sort"
+	"text/template"
 )
 
 var (

From 1ca4b0370d618a137e53e1fcf6a537b5c3e4662d Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Sat, 4 Nov 2017 13:20:41 -0500
Subject: [PATCH 22/74] Updated copyright date on LICENSE

Former-commit-id: 4a339cdc35e78c821ce5fbefa2b2a09dfbb1c8f1
---
 LICENSE | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/LICENSE b/LICENSE
index d772589..9235b22 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright (c) 2016 Jon Carlson
+Copyright (c) 2017 Jon Carlson
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

From c666033ce497921256c980f029d3958ea227c74f Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Sat, 4 Nov 2017 13:47:49 -0500
Subject: [PATCH 23/74] Added test-column script

Former-commit-id: c59c9b40b8061627824f04c2c08ea96bf3bbaeaa
---
 column.go        | 16 ++--------
 test/test-column | 79 ++++++++++++++++++++++++------------------------
 2 files changed, 43 insertions(+), 52 deletions(-)

diff --git a/column.go b/column.go
index 011e841..48a0f49 100644
--- a/column.go
+++ b/column.go
@@ -26,7 +26,7 @@ var (
 func initColumnSqlTemplate() *template.Template {
 	sql := `
 SELECT table_schema
-    , {{if eq $.DbSchema "*" }}table_schema || '.' || {{end}}table_name AS compare_name
+    , {{if eq $.DbSchema "*" }}table_schema || '.' || {{end}}table_name || '.' || column_name  AS compare_name
 	, table_name
     , column_name
     , data_type
@@ -41,7 +41,7 @@ AND table_schema <> 'information_schema'
 {{else}}
 AND table_schema = '{{$.DbSchema}}'
 {{end}}
-ORDER BY compare_name, column_name;
+ORDER BY compare_name ASC;
 `
 	t := template.New("ColumnSqlTmpl")
 	template.Must(t.Parse(sql))
@@ -60,10 +60,7 @@ func (slice ColumnRows) Len() int {
 }
 
 func (slice ColumnRows) Less(i, j int) bool {
-	if slice[i]["compare_name"] != slice[j]["compare_name"] {
-		return slice[i]["column_name"] < slice[j]["compare_name"]
-	}
-	return slice[i]["column_name"] < slice[j]["column_name"]
+	return slice[i]["compare_name"] < slice[j]["compare_name"]
 }
 
 func (slice ColumnRows) Swap(i, j int) {
@@ -108,13 +105,6 @@ func (c *ColumnSchema) Compare(obj interface{}) int {
 	}
 
 	val := misc.CompareStrings(c.get("compare_name"), c2.get("compare_name"))
-	if val != 0 {
-		// Table name differed so return that value
-		return val
-	}
-
-	// Table name was the same so compare column name
-	val = misc.CompareStrings(c.get("column_name"), c2.get("column_name"))
 	return val
 }
 
diff --git a/test/test-column b/test/test-column
index 13335ef..c4cca1a 100755
--- a/test/test-column
+++ b/test/test-column
@@ -3,7 +3,7 @@
 # Useful for visually inspecting the output SQL to verify it is doing what it should
 #
 
-source ./start-fresh.sh >/dev/null
+source ./start-fresh.sh >/dev/null 
 
 echo
 echo ==============================================================
@@ -11,64 +11,65 @@ echo ==============================================================
 #
 # Compare the columns between two schemas in the same database
 #
-
-echo
-echo "# Compare the tables between two schemas in the same database"
-echo "# Expect SQL:"
-echo "#   Add s2.table1.id"
-echo "#   Drop s2.table1.description"
-echo "#   Alter s2.table1.name to varchar(24)"
-echo
-
+#psql -U u1 -h localhost -d db1 <<'EOS'
 ./populate-db.sh db1 "
     CREATE SCHEMA s1;
-    CREATE TABLE s1.table1 (
-      id integer, 
-      name varchar(24) 
+    CREATE TABLE s1.table9 (
+        id integer,
+        name varchar(50)
     );
-
+    CREATE TABLE s1.table10 (id bigint);
+    CREATE TABLE s1.table11 ();
+    
     CREATE SCHEMA s2;
-    CREATE TABLE s2.table1(
-                              -- id will be added to s2
-      name varchar(12),       -- name will grow to 24 in s2
-      description varchar(24) -- description will be dropped in s2
-    );
-" 
+    CREATE TABLE s2.table9 (  -- Add name column 
+        id integer
+    ); 
+    CREATE TABLE s2.table10 (id integer); -- change id to bigint
+    CREATE TABLE s2.table11 (id integer); -- drop id column
+"
+
+echo
+echo "# Compare the columns between two schemas in the same database"
+echo "# Expect SQL:"
+echo "#   Add s2.table9.name"
+echo "#   Change s2.table10.id to bigint"
+echo "#   Drop s2.table11.id"
 
 echo
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
           COLUMN | grep -v '^-- '
 
+
 echo
 echo ==============================================================
-echo
-
-#
-# Compare the columns in all schemas between two databases
-#
 
-echo "# Compare the columns in all schemas between two databases"
-echo "# Expect SQL:"
-echo "#   Drop column_to_delete from s1.table1 (in db2)"
-echo "#   Add s1.table1.name  (in db2) "
-echo "#   Alter s2.table1.name to varchar(24)"
 ./populate-db.sh db2 "
     CREATE SCHEMA s1;
-    CREATE TABLE s1.table1 (
-      id integer, 
-                                -- Name will be added
-      column_to_delete integer  -- This will be deleted
+    CREATE TABLE s1.table9 (
+        id integer,
+        name varchar(40)
     );
-
+    CREATE TABLE s1.table10 ();
+    CREATE TABLE s1.table11 (dropme integer);
+    
     CREATE SCHEMA s2;
-    CREATE TABLE s2.table1(
-      name varchar(24),       -- name will change to varchar(12)
-      description varchar(24) -- description will be dropped in s2
-    );
+    CREATE TABLE s2.table9 (  -- Add name column 
+        id integer
+    ); 
+    CREATE TABLE s2.table10 (id integer); -- change id to bigint
+    CREATE TABLE s2.table11 (id integer); -- drop id column
 "
 
 echo
+echo "# Compare the columns in all schemas between two databases"
+echo "# Expect:"
+echo "#   Change s1.table9.name to varchar(50) "
+echo "#   Add s1.table10.id"
+echo "#   Drop s1.table11.dropme"
+echo
+
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "*" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db2" -s "*" -o "sslmode=disable" \
           COLUMN | grep -v '^-- '

From 24b217bfa5ebf30311e3d319c0665db2b519eabd Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Sat, 4 Nov 2017 15:02:39 -0500
Subject: [PATCH 24/74] WIP functions and triggers

Former-commit-id: 77782600e37f629599e3344cc9cba7b53b8a8254
---
 README.md          | 11 +++---
 flags.go           |  4 +--
 pgdiff.go          | 24 ++++++-------
 pgdiff.sh          |  2 +-
 test/functions.sh  | 11 ------
 test/test-function | 18 ++--------
 test/test-table    | 25 ++++++--------
 test/test-trigger  | 85 ++++++++++++++++++++++++++++++++++++++++++++++
 trigger.go         |  2 +-
 9 files changed, 118 insertions(+), 64 deletions(-)
 delete mode 100644 test/functions.sh
 create mode 100755 test/test-trigger

diff --git a/README.md b/README.md
index 699ce3e..17739b0 100644
--- a/README.md
+++ b/README.md
@@ -16,26 +16,25 @@ pgdiff is written to be easy to expand and improve the accuracy of the diff.
 
 (where options and &lt;schemaType&gt; are listed below)
 
-I have found that there is an ideal order for running the different schema types.  This order should minimize the problems you encounter.  For example, you will always want to add new tables before you add new columns.  This is the order that has worked for me, however "your mileage may vary".
+I have found that there is an ideal order for running the different schema types.  This order should minimize the problems you encounter.  For example, you will always want to add new tables before you add new columns.  This is the order that has worked for me.
 
 In addition, some types can have dependencies which are not in the right order.  A classic case is views which depend on other views.  The missing view SQL is generated in alphabetical order so if a view create fails due to a missing view, just run the views SQL file over again. The pgdiff.sh script will prompt you about running it again.
  
 Schema type ordering:
 
-1. ROLE
-1. FUNCTION
-1. SEQUENCE
 1. SCHEMA
+1. ROLE
 1. SEQUENCE
 1. TABLE
 1. COLUMN
 1. INDEX
 1. VIEW
-1. OWNER
 1. FOREIGN\_KEY
+1. FUNCTION
+1. TRIGGER
+1. OWNER
 1. GRANT\_RELATIONSHIP
 1. GRANT\_ATTRIBUTE
-1. TRIGGER
 
 
 ### example
diff --git a/flags.go b/flags.go
index ecc0295..c5c067e 100644
--- a/flags.go
+++ b/flags.go
@@ -18,7 +18,7 @@ func parseFlags() (pgutil.DbInfo, pgutil.DbInfo) {
 	var dbHost1 = flag.StringP("host1", "H", "localhost", "db host")
 	var dbPort1 = flag.IntP("port1", "P", 5432, "db port")
 	var dbName1 = flag.StringP("dbname1", "D", "", "db name")
-	var dbSchema1 = flag.StringP("schema1", "S", "public", "schema name or * for all schemas")
+	var dbSchema1 = flag.StringP("schema1", "S", "*", "schema name or * for all schemas")
 	var dbOptions1 = flag.StringP("options1", "O", "", "db options (eg. sslmode=disable)")
 
 	var dbUser2 = flag.StringP("user2", "u", "", "db user")
@@ -26,7 +26,7 @@ func parseFlags() (pgutil.DbInfo, pgutil.DbInfo) {
 	var dbHost2 = flag.StringP("host2", "h", "localhost", "db host")
 	var dbPort2 = flag.IntP("port2", "p", 5432, "db port")
 	var dbName2 = flag.StringP("dbname2", "d", "", "db name")
-	var dbSchema2 = flag.StringP("schema2", "s", "public", "schema name or * for all schemas")
+	var dbSchema2 = flag.StringP("schema2", "s", "*", "schema name or * for all schemas")
 	var dbOptions2 = flag.StringP("options2", "o", "", "db options (eg. sslmode=disable)")
 
 	flag.Parse()
diff --git a/pgdiff.go b/pgdiff.go
index 1ad458c..0bb9ab6 100644
--- a/pgdiff.go
+++ b/pgdiff.go
@@ -98,19 +98,19 @@ func main() {
 		if dbInfo1.DbSchema == "*" {
 			compareSchematas(conn1, conn2)
 		}
-		compareRoles(conn1, conn2)
-		compareFunctions(conn1, conn2)
 		compareSchematas(conn1, conn2)
+		compareRoles(conn1, conn2)
 		compareSequences(conn1, conn2)
 		compareTables(conn1, conn2)
 		compareColumns(conn1, conn2)
 		compareIndexes(conn1, conn2) // includes PK and Unique constraints
 		compareViews(conn1, conn2)
-		compareOwners(conn1, conn2)
 		compareForeignKeys(conn1, conn2)
+		compareFunctions(conn1, conn2)
+		compareTriggers(conn1, conn2)
+		compareOwners(conn1, conn2)
 		compareGrantRelationships(conn1, conn2)
 		compareGrantAttributes(conn1, conn2)
-		compareTriggers(conn1, conn2)
 	} else if schemaType == "SCHEMA" {
 		compareSchematas(conn1, conn2)
 	} else if schemaType == "ROLE" {
@@ -121,22 +121,22 @@ func main() {
 		compareTables(conn1, conn2)
 	} else if schemaType == "COLUMN" {
 		compareColumns(conn1, conn2)
-	} else if schemaType == "FUNCTION" {
-		compareFunctions(conn1, conn2)
-	} else if schemaType == "VIEW" {
-		compareViews(conn1, conn2)
 	} else if schemaType == "INDEX" {
 		compareIndexes(conn1, conn2)
+	} else if schemaType == "VIEW" {
+		compareViews(conn1, conn2)
 	} else if schemaType == "FOREIGN_KEY" {
 		compareForeignKeys(conn1, conn2)
+	} else if schemaType == "FUNCTION" {
+		compareFunctions(conn1, conn2)
+	} else if schemaType == "TRIGGER" {
+		compareTriggers(conn1, conn2)
 	} else if schemaType == "OWNER" {
 		compareOwners(conn1, conn2)
 	} else if schemaType == "GRANT_RELATIONSHIP" {
 		compareGrantRelationships(conn1, conn2)
 	} else if schemaType == "GRANT_ATTRIBUTE" {
 		compareGrantAttributes(conn1, conn2)
-	} else if schemaType == "TRIGGER" {
-		compareTriggers(conn1, conn2)
 	} else {
 		fmt.Println("Not yet handled:", schemaType)
 	}
@@ -200,8 +200,8 @@ Options:
   -p, --port2   : second port. default is 5432 
   -D, --dbname1 : first database name 
   -d, --dbname2 : second database name 
-  -S, --schema1 : first schema.  default is public
-  -s, --schema2 : second schema. default is public
+  -S, --schema1 : first schema.  default is all schemas
+  -s, --schema2 : second schema. default is all schemas
 
 <schemaTpe> can be: SCHEMA ROLE, SEQUENCE, TABLE, VIEW, COLUMN, INDEX, FOREIGN_KEY, OWNER, GRANT_RELATIONSHIP, GRANT_ATTRIBUTE
 `)
diff --git a/pgdiff.sh b/pgdiff.sh
index a75aadf..8effe41 100755
--- a/pgdiff.sh
+++ b/pgdiff.sh
@@ -77,11 +77,11 @@ rundiff TABLE
 rundiff COLUMN
 rundiff INDEX
 rundiff VIEW
+rundiff TRIGGER
 rundiff OWNER
 rundiff FOREIGN_KEY
 rundiff GRANT_RELATIONSHIP
 rundiff GRANT_ATTRIBUTE
-rundiff TRIGGER
 
 echo
 echo "Done!"
diff --git a/test/functions.sh b/test/functions.sh
deleted file mode 100644
index 8b11ea2..0000000
--- a/test/functions.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/bash
-sudo su - postgres -- <<EOT
-psql <<'SQL'
-    DROP DATABASE IF EXISTS db1;
-    DROP DATABASE IF EXISTS db2;
-    DROP USER IF EXISTS u1;
-    CREATE USER u1 WITH SUPERUSER PASSWORD 'asdf';
-    CREATE DATABASE db1 WITH OWNER = u1 TEMPLATE = template1;
-    CREATE DATABASE db2 WITH OWNER = u1 TEMPLATE = template1;
-SQL
-EOT
diff --git a/test/test-function b/test/test-function
index 98b7d06..dc5a738 100755
--- a/test/test-function
+++ b/test/test-function
@@ -52,9 +52,7 @@ echo
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
           FUNCTION | grep -v '^-- '
-
 exit 0
-
 echo
 echo ==========================================================
 echo
@@ -64,21 +62,9 @@ echo
 # Compare the functions in all schemas between two databases
 #
 ./populate-db.sh db2 "
-    CREATE SCHEMA s1;
-    CREATE TABLE s1.table1 (
-      id integer PRIMARY KEY,
-      name varchar(32),
-      url varchar(200)
-    );
-    CREATE INDEX ON s1.table1(name);
-    CREATE INDEX ON s1.table1(url);
 
-    CREATE SCHEMA s2;
-    CREATE TABLE s2.table1 (
-      id integer PRIMARY KEY, 
-      name varchar(32),
-      url varchar(200)
-    );
+
+
 "
 
 echo
diff --git a/test/test-table b/test/test-table
index 6b435ab..7530cf3 100755
--- a/test/test-table
+++ b/test/test-table
@@ -8,12 +8,6 @@ source ./start-fresh.sh >/dev/null
 echo
 echo ==============================================================
 
-echo
-echo "# Compare the tables between two schemas in the same database"
-echo "# Expect SQL:"
-echo "#   Add table9 to schema s2"
-echo "#   Drop table11 from schema s2"
-
 #
 # Compare the tables between two schemas in the same database
 #
@@ -29,7 +23,11 @@ echo "#   Drop table11 from schema s2"
 "
 
 echo
-echo "SQL to run:"
+echo "# Compare the tables between two schemas in the same database"
+echo "# Expect SQL:"
+echo "#   Add table9 to schema s2"
+echo "#   Drop table11 from schema s2"
+echo
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
           TABLE | grep -v '^-- '
@@ -37,13 +35,6 @@ echo "SQL to run:"
 echo
 echo ==============================================================
 
-echo
-echo "# Compare the tables in all schemas between two databases"
-echo "# Expect:"
-echo "#   Add s1.table10 to db2"
-echo "#   Drop s2.table12 from db2"
-
-
 ./populate-db.sh db2 "
     CREATE SCHEMA s1;
     CREATE TABLE s1.table9 (id integer);
@@ -58,7 +49,11 @@ echo "#   Drop s2.table12 from db2"
 "
 
 echo
-echo "SQL to run:"
+echo "# Compare the tables in all schemas between two databases"
+echo "# Expect:"
+echo "#   Add s1.table10 to db2"
+echo "#   Drop s2.table12 from db2"
+echo
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "*" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db2" -s "*" -o "sslmode=disable" \
           TABLE | grep -v '^-- '
diff --git a/test/test-trigger b/test/test-trigger
new file mode 100755
index 0000000..f5f56e2
--- /dev/null
+++ b/test/test-trigger
@@ -0,0 +1,85 @@
+#!/bin/bash
+#
+# Useful for visually inspecting the output SQL to verify it is doing what it should
+#
+
+source ./start-fresh.sh >/dev/null
+echo
+echo ==========================================================
+echo
+
+#
+# Compare the triggers between two schemas in the same database
+#
+
+#CREATE [ CONSTRAINT ] TRIGGER name { BEFORE | AFTER | INSTEAD OF } { event [ OR ... ] }
+#    ON table_name
+#    [ FROM referenced_table_name ]
+#    [ NOT DEFERRABLE | [ DEFERRABLE ] { INITIALLY IMMEDIATE | INITIALLY DEFERRED } ]
+#    [ FOR [ EACH ] { ROW | STATEMENT } ]
+#    [ WHEN ( condition ) ]
+#    EXECUTE PROCEDURE function_name ( arguments )
+
+./populate-db.sh db1 "$(cat << 'EOF'
+-- Schema s1
+CREATE SCHEMA s1;
+CREATE TABLE s1.table1 (
+  id integer,
+  name varchar(32)
+);
+CREATE OR REPLACE FUNCTION s1.validate1() RETURNS TRIGGER AS $$
+    BEGIN
+            SELECT 1; -- look like we are doing something ;^>
+    END;
+$$ LANGUAGE plpgsql;
+CREATE TRIGGER trigger1 AFTER INSERT ON s1.table1 FOR EACH ROW EXECUTE PROCEDURE s1.validate1();
+
+
+-- Schema s2
+CREATE SCHEMA s2;
+CREATE TABLE s2.table1 (
+    id integer,
+    name varchar(32)
+);
+CREATE TRIGGER trigger2 AFTER INSERT ON s2.table1 FOR EACH ROW EXECUTE PROCEDURE s1.validate1();
+
+EOF
+)"
+
+echo
+echo "# Compare the triggers between two schemas in the same database"
+echo "# Expect SQL (pseudocode):"
+echo "#   Create trigger s2.trigger1"
+echo "#   Drop trigger   s2.trigger2"
+echo
+
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
+          TRIGGER | grep -v '^-- '
+exit 0
+
+
+echo
+echo ==========================================================
+echo
+
+#
+# Compare the triggers in all schemas between two databases
+#
+./populate-db.sh db2 "
+
+
+"
+
+echo
+echo "# Compare the triggers in all schemas between two databases"
+echo "# Expect SQL (pseudocode):"
+echo "#   Drop index on db2 s1.table1.url"
+echo "#   Add index on db2 s2.table1.url"
+echo
+
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "*" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db2" -s "*" -o "sslmode=disable" \
+          TRIGGER | grep -v '^-- '
+echo
+echo
diff --git a/trigger.go b/trigger.go
index eea69c5..8173661 100644
--- a/trigger.go
+++ b/trigger.go
@@ -1,5 +1,5 @@
 //
-// Copyright (c) 2016 Jon Carlson.  All rights reserved.
+// Copyright (c) 2017 Jon Carlson.  All rights reserved.
 // Use of this source code is governed by an MIT-style
 // license that can be found in the LICENSE file.
 //

From a78d9c436c5459c9ec5e1c569a426c536fa72f6c Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Mon, 6 Nov 2017 22:00:19 -0600
Subject: [PATCH 25/74] Function comparison works between schemas

Former-commit-id: 2da622c09e27575c212105cdcf33244aecaaccf3
---
 function.go        | 39 ++++++++++++++++++++++++++++++++++-----
 test/test-function |  4 ++--
 2 files changed, 36 insertions(+), 7 deletions(-)

diff --git a/function.go b/function.go
index 33e3e97..4693a10 100644
--- a/function.go
+++ b/function.go
@@ -13,6 +13,7 @@ import (
 	"github.com/joncrlsn/misc"
 	"github.com/joncrlsn/pgutil"
 	"sort"
+	"strings"
 	"text/template"
 )
 
@@ -24,8 +25,9 @@ var (
 func initFunctionSqlTemplate() *template.Template {
 	sql := `
     SELECT n.nspname                 AS schema_name
-        , {{if eq $.DbSchema "*" }}n.nspname || '.' || {{end}}p.oid::regprocedure AS compare_name
-        , p.oid::regprocedure        AS function_name
+        , {{if eq $.DbSchema "*" }}n.nspname || '.' || {{end}}p.proname AS compare_name
+        , p.proname                  AS function_name
+        , p.oid::regprocedure        AS fancy
         , t.typname                  AS return_type
         , pg_get_functiondef(p.oid)  AS definition
     FROM pg_proc AS p
@@ -106,30 +108,57 @@ func (c *FunctionSchema) Compare(obj interface{}) int {
 
 // Add returns SQL to create the function
 func (c FunctionSchema) Add() {
+	fmt.Println("-- Add")
+
+	// If we are comparing two different schemas against each other, we need to do some
+	// modification of the first function definition so we create it in the right schema
+	functionDef := c.get("definition")
+	if dbInfo1.DbSchema != dbInfo2.DbSchema {
+		functionDef = strings.Replace(
+			functionDef,
+			fmt.Sprintf("FUNCTION %s.%s(", c.get("schema_name"), c.get("function_name")),
+			fmt.Sprintf("FUNCTION %s.%s(", dbInfo2.DbSchema, c.get("function_name")),
+			-1)
+	}
+
 	fmt.Println("-- STATEMENT-BEGIN")
-	fmt.Printf("%s;\n", c.get("definition"))
+	fmt.Println(functionDef, ";")
 	fmt.Println("-- STATEMENT-END")
 }
 
 // Drop returns SQL to drop the function
 func (c FunctionSchema) Drop() {
+	fmt.Println("-- Drop")
 	fmt.Println("-- Note that CASCADE in the statement below will also drop any triggers depending on this function.")
-	fmt.Println("-- Also, if there are two functions with this name, you will need to add arguments to identify the correct one to drop.")
+	fmt.Println("-- Also, if there are two functions with this name, you will want to add arguments to identify the correct one to drop.")
 	fmt.Println("-- (See http://www.postgresql.org/docs/9.4/interactive/sql-dropfunction.html) ")
 	fmt.Printf("DROP FUNCTION %s.%s CASCADE;\n", c.get("schema_name"), c.get("function_name"))
 }
 
 // Change handles the case where the function names match, but the definition does not
 func (c FunctionSchema) Change(obj interface{}) {
+	fmt.Println("-- Change")
 	c2, ok := obj.(*FunctionSchema)
 	if !ok {
 		fmt.Println("Error!!!, Change needs a FunctionSchema instance", c2)
 	}
 	if c.get("definition") != c2.get("definition") {
 		fmt.Println("-- This function is different so we'll recreate it:")
+
+		// If we are comparing two different schemas against each other, we need to do some
+		// modification of the first function definition so we create it in the right schema
+		functionDef := c.get("definition")
+		if dbInfo1.DbSchema != dbInfo2.DbSchema {
+			functionDef = strings.Replace(
+				functionDef,
+				fmt.Sprintf("FUNCTION %s.%s(", c.get("schema_name"), c.get("function_name")),
+				fmt.Sprintf("FUNCTION %s.%s(", dbInfo2.DbSchema, c.get("function_name")),
+				-1)
+		}
+
 		// The definition column has everything needed to rebuild the function
 		fmt.Println("-- STATEMENT-BEGIN")
-		fmt.Printf("%s;\n", c.get("definition"))
+		fmt.Printf("%s;\n", functionDef)
 		fmt.Println("-- STATEMENT-END")
 	}
 }
diff --git a/test/test-function b/test/test-function
index dc5a738..a895777 100755
--- a/test/test-function
+++ b/test/test-function
@@ -27,7 +27,7 @@ CREATE FUNCTION s1.add(integer, integer) RETURNS integer
 
 
 CREATE SCHEMA s2;
-CREATE OR REPLACE FUNCTION s2.add(integer, integer) RETURNS integer
+CREATE OR REPLACE FUNCTION s2.add(bigint, bigint) RETURNS bigint
     AS 'select $1 + $2;'
     LANGUAGE SQL
     IMMUTABLE
@@ -51,7 +51,7 @@ echo
 
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
-          FUNCTION | grep -v '^-- '
+          FUNCTION #| grep -v '^-- '
 exit 0
 echo
 echo ==========================================================

From 25ba44cff29e87435dbe3d70d2aad5dfc063154b Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Tue, 7 Nov 2017 17:16:51 -0600
Subject: [PATCH 26/74] test-function works correctly now

Former-commit-id: c19a82bc39bad734daae1df776b3626c5b7471dc
---
 test/test-function | 36 ++++++++++++++++++++++++++++++------
 1 file changed, 30 insertions(+), 6 deletions(-)

diff --git a/test/test-function b/test/test-function
index a895777..27f8cba 100755
--- a/test/test-function
+++ b/test/test-function
@@ -52,7 +52,6 @@ echo
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
           FUNCTION #| grep -v '^-- '
-exit 0
 echo
 echo ==========================================================
 echo
@@ -61,21 +60,46 @@ echo
 #
 # Compare the functions in all schemas between two databases
 #
-./populate-db.sh db2 "
+./populate-db.sh db2 "$(cat << 'EOF' 
+CREATE SCHEMA s1;
+CREATE OR REPLACE FUNCTION s1.increment(i integer) RETURNS integer AS $$
+    BEGIN
+            RETURN i + 1;
+    END;
+$$ LANGUAGE plpgsql;
+CREATE FUNCTION s1.addition(integer, integer) RETURNS integer
+    AS 'select $1 + $2;'
+    LANGUAGE SQL
+    IMMUTABLE
+    RETURNS NULL ON NULL INPUT;
+
 
+CREATE SCHEMA s2;
+CREATE OR REPLACE FUNCTION s2.add(integer, integer) RETURNS integer
+    AS 'select $1 + $2;'
+    LANGUAGE SQL
+    IMMUTABLE
+    RETURNS NULL ON NULL INPUT;
+CREATE FUNCTION s2.minus(integer, integer) RETURNS integer
+    AS 'select $1 - $2;'
+    LANGUAGE SQL
+    IMMUTABLE
+    RETURNS NULL ON NULL INPUT;
 
+EOF
+)"
 
-"
 
 echo
 echo "# Compare the functions in all schemas between two databases"
 echo "# Expect SQL (pseudocode):"
-echo "#   Drop index on db2 s1.table1.url"
-echo "#   Add index on db2 s2.table1.url"
+echo "#   Add function s1.add"
+echo "#   Change/Replace function s2.add"
+echo "#   Drop function s1.addition"
 echo
 
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "*" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db2" -s "*" -o "sslmode=disable" \
-          FUNCTION | grep -v '^-- '
+          FUNCTION #| grep -v '^-- '
 echo
 echo

From 6918bc1bd3f894b0fb1d60b3cee64c5dc5d8494e Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Tue, 7 Nov 2017 20:54:35 -0600
Subject: [PATCH 27/74] trigger tests OK now

Former-commit-id: 9b8b695c75a20066503714013d6feb97d6fd88c1
---
 test/test-trigger | 48 ++++++++++++++++++++++++--------------
 trigger.go        | 59 ++++++++++++++++++++++++++++++-----------------
 2 files changed, 69 insertions(+), 38 deletions(-)

diff --git a/test/test-trigger b/test/test-trigger
index f5f56e2..8ee17c2 100755
--- a/test/test-trigger
+++ b/test/test-trigger
@@ -23,25 +23,21 @@ echo
 ./populate-db.sh db1 "$(cat << 'EOF'
 -- Schema s1
 CREATE SCHEMA s1;
-CREATE TABLE s1.table1 (
-  id integer,
-  name varchar(32)
-);
+CREATE TABLE s1.table1 (id integer);
 CREATE OR REPLACE FUNCTION s1.validate1() RETURNS TRIGGER AS $$
     BEGIN
             SELECT 1; -- look like we are doing something ;^>
     END;
 $$ LANGUAGE plpgsql;
 CREATE TRIGGER trigger1 AFTER INSERT ON s1.table1 FOR EACH ROW EXECUTE PROCEDURE s1.validate1();
+CREATE TRIGGER trigger2 AFTER INSERT ON s1.table1 FOR EACH ROW EXECUTE PROCEDURE s1.validate1();
 
 
 -- Schema s2
 CREATE SCHEMA s2;
-CREATE TABLE s2.table1 (
-    id integer,
-    name varchar(32)
-);
-CREATE TRIGGER trigger2 AFTER INSERT ON s2.table1 FOR EACH ROW EXECUTE PROCEDURE s1.validate1();
+CREATE TABLE s2.table1 (id integer);
+CREATE TRIGGER trigger2 BEFORE INSERT ON s2.table1 FOR EACH ROW EXECUTE PROCEDURE s1.validate1();
+CREATE TRIGGER trigger3 AFTER INSERT ON s2.table1 FOR EACH ROW EXECUTE PROCEDURE s1.validate1();
 
 EOF
 )"
@@ -49,15 +45,14 @@ EOF
 echo
 echo "# Compare the triggers between two schemas in the same database"
 echo "# Expect SQL (pseudocode):"
-echo "#   Create trigger s2.trigger1"
-echo "#   Drop trigger   s2.trigger2"
+echo "#   Create   trigger1 on s2.table1"
+echo "#   Recreate trigger2 on s2.table1"
+echo "#   Drop     trigger3 on s2.table1"
 echo
 
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
           TRIGGER | grep -v '^-- '
-exit 0
-
 
 echo
 echo ==========================================================
@@ -66,16 +61,35 @@ echo
 #
 # Compare the triggers in all schemas between two databases
 #
-./populate-db.sh db2 "
+./populate-db.sh db2 "$(cat << 'EOF'
 
+-- Schema s1
+CREATE SCHEMA s1;
+CREATE TABLE s1.table1 (id integer);
+CREATE OR REPLACE FUNCTION s1.validate1() RETURNS TRIGGER AS $$
+    BEGIN
+            SELECT 1; -- look like we are doing something :^>
+    END;
+$$ LANGUAGE plpgsql;
+CREATE TRIGGER trigger2 BEFORE INSERT ON s1.table1 FOR EACH ROW EXECUTE PROCEDURE s1.validate1();
 
-"
+
+-- Schema s2
+CREATE SCHEMA s2;
+CREATE TABLE s2.table1 (id integer);
+CREATE TRIGGER trigger2 BEFORE INSERT ON s2.table1 FOR EACH ROW EXECUTE PROCEDURE s1.validate1();
+CREATE TRIGGER trigger3 AFTER INSERT ON s2.table1 FOR EACH ROW EXECUTE PROCEDURE s1.validate1();
+CREATE TRIGGER trigger4 AFTER INSERT ON s2.table1 FOR EACH ROW EXECUTE PROCEDURE s1.validate1();
+
+EOF
+)"
 
 echo
 echo "# Compare the triggers in all schemas between two databases"
 echo "# Expect SQL (pseudocode):"
-echo "#   Drop index on db2 s1.table1.url"
-echo "#   Add index on db2 s2.table1.url"
+echo "#   Create   trigger1 on s1.table1"
+echo "#   Recreate trigger2 on s1.table1"
+echo "#   Drop     trigger4 on s2.table1"
 echo
 
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "*" -O "sslmode=disable" \
diff --git a/trigger.go b/trigger.go
index 8173661..d69c35f 100644
--- a/trigger.go
+++ b/trigger.go
@@ -13,6 +13,7 @@ import (
 	"github.com/joncrlsn/misc"
 	"github.com/joncrlsn/pgutil"
 	"sort"
+	"strings"
 	"text/template"
 )
 
@@ -24,10 +25,10 @@ var (
 func initTriggerSqlTemplate() *template.Template {
 	sql := `
     SELECT n.nspname AS schema_name
-       , {{if eq $.DbSchema "*" }}n.nspname || '.' || {{end}}c.relname AS compare_name
+       , {{if eq $.DbSchema "*" }}n.nspname || '.' || {{end}}c.relname || '.' || t.tgname AS compare_name
        , c.relname AS table_name
        , t.tgname AS trigger_name
-       , pg_catalog.pg_get_triggerdef(t.oid, true) AS definition
+       , pg_catalog.pg_get_triggerdef(t.oid, true) AS trigger_def
        , t.tgenabled AS enabled
     FROM pg_catalog.pg_trigger t
     INNER JOIN pg_catalog.pg_class c ON (c.oid = t.tgrelid)
@@ -57,10 +58,7 @@ func (slice TriggerRows) Len() int {
 }
 
 func (slice TriggerRows) Less(i, j int) bool {
-	if slice[i]["compare_name"] != slice[j]["compare_name"] {
-		return slice[i]["compare_name"] < slice[j]["compare_name"]
-	}
-	return slice[i]["trigger_name"] < slice[j]["trigger_name"]
+	return slice[i]["compare_name"] < slice[j]["compare_name"]
 }
 
 func (slice TriggerRows) Swap(i, j int) {
@@ -103,24 +101,27 @@ func (c *TriggerSchema) Compare(obj interface{}) int {
 	}
 
 	val := misc.CompareStrings(c.get("compare_name"), c2.get("compare_name"))
-	if val != 0 {
-		return val
-	}
-	val = misc.CompareStrings(c.get("trigger_name"), c2.get("trigger_name"))
 	return val
 }
 
 // Add returns SQL to create the trigger
 func (c TriggerSchema) Add() {
-	schema := dbInfo2.DbSchema
-	if schema == "*" {
-		schema = c.get("schema_name")
+	fmt.Println("-- Add")
+
+	// If we are comparing two different schemas against each other, we need to do some
+	// modification of the first trigger definition so we create it in the right schema
+	triggerDef := c.get("trigger_def")
+	schemaName := c.get("schema_name")
+	if dbInfo1.DbSchema != dbInfo2.DbSchema {
+		schemaName = dbInfo2.DbSchema
+		triggerDef = strings.Replace(
+			triggerDef,
+			fmt.Sprintf(" %s.%s ", c.get("schema_name"), c.get("table_name")),
+			fmt.Sprintf(" %s.%s ", schemaName, c.get("table_name")),
+			-1)
 	}
 
-	// NOTE: we may need to do some tweaking of the definition here to replace the old schema
-	// name with the new schema name.
-
-	fmt.Printf("%s;\n", c.get("definition"))
+	fmt.Printf("%s;\n", triggerDef)
 }
 
 // Drop returns SQL to drop the trigger
@@ -130,15 +131,31 @@ func (c TriggerSchema) Drop() {
 
 // Change handles the case where the trigger names match, but the definition does not
 func (c TriggerSchema) Change(obj interface{}) {
+	fmt.Println("-- Change")
 	c2, ok := obj.(*TriggerSchema)
 	if !ok {
 		fmt.Println("Error!!!, Change needs a TriggerSchema instance", c2)
 	}
-	if c.get("definition") != c2.get("definition") {
-		fmt.Println("-- This function looks different so we'll recreate it:")
-		// The definition column has everything needed to rebuild the function
+	if c.get("trigger_def") != c2.get("trigger_def") {
+		fmt.Println("-- This function looks different so we'll drop and recreate it:")
+
+		// If we are comparing two different schemas against each other, we need to do some
+		// modification of the first trigger definition so we create it in the right schema
+		triggerDef := c.get("trigger_def")
+		schemaName := c.get("schema_name")
+		if dbInfo1.DbSchema != dbInfo2.DbSchema {
+			schemaName = dbInfo2.DbSchema
+			triggerDef = strings.Replace(
+				triggerDef,
+				fmt.Sprintf(" %s.%s ", c.get("schema_name"), c.get("table_name")),
+				fmt.Sprintf(" %s.%s ", schemaName, c.get("table_name")),
+				-1)
+		}
+
+		// The trigger_def column has everything needed to rebuild the function
+		fmt.Printf("DROP TRIGGER %s ON %s.%s;\n", c.get("trigger_name"), schemaName, c.get("table_name"))
 		fmt.Println("-- STATEMENT-BEGIN")
-		fmt.Println(c.get("definition"))
+		fmt.Printf("%s;\n", triggerDef)
 		fmt.Println("-- STATEMENT-END")
 	}
 }

From 93c3654f38db9f7610439399b85ea04e4ee61244 Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Tue, 7 Nov 2017 21:52:16 -0600
Subject: [PATCH 28/74] WIP grant-*.go

Former-commit-id: 65f953b8e61fba3c168439c467439fbf9d4e4ff3
---
 README.md             |   6 +--
 function.go           |   4 ++
 grant-attribute.go    | 109 +++++++++++++++++++++++++++-------------
 grant-relationship.go | 101 +++++++++++++++++++++----------------
 notes.md              | 112 ------------------------------------------
 owner.go              |   4 +-
 test/test-owner       |   2 +-
 7 files changed, 143 insertions(+), 195 deletions(-)
 delete mode 100644 notes.md

diff --git a/README.md b/README.md
index 17739b0..f99b68c 100644
--- a/README.md
+++ b/README.md
@@ -41,8 +41,8 @@ Schema type ordering:
 I have found it helpful to take ```--schema-only``` dumps of the databases in question, load them into a local postgres, then do my sql generation and testing there before running the SQL against a more official database. Your local postgres instance will need the correct users/roles populated because db dumps do not copy that information.
 
 ```
-pgdiff -U dbuser -H localhost -D refDB  -O "sslmode=disable" \
-       -u dbuser -h localhost -d compDB -o "sslmode=disable" \
+pgdiff -U dbuser -H localhost -D refDB  -O "sslmode=disable" -S "public" \
+       -u dbuser -h localhost -d compDB -o "sslmode=disable" -s "public" \
        TABLE 
 ```
 
@@ -93,7 +93,7 @@ linux and osx binaries are packaged with an extra, optional bash script and pgru
 1. 0.9.0 - Implemented ROLE, SEQUENCE, TABLE, COLUMN, INDEX, FOREIGN\_KEY, OWNER, GRANT\_RELATIONSHIP, GRANT\_ATTRIBUTE
 1. 0.9.1 - Added VIEW, FUNCTION, and TRIGGER (Thank you, Shawn Carroll AKA SparkeyG)
 1. 0.9.2 - Fixed bug when using the non-default port
-1. 0.9.3 - Added support for schemas other than public. Fixed VARCHAR bug when no max length specified
+1. 0.9.3 - Added support for comparing two different schemas, one schema between databases, or all schemas.  Fixed VARCHAR bug when no max length specified
 
 
 ### todo
diff --git a/function.go b/function.go
index 4693a10..e0cc197 100644
--- a/function.go
+++ b/function.go
@@ -163,6 +163,10 @@ func (c FunctionSchema) Change(obj interface{}) {
 	}
 }
 
+// ==================================
+// Functions
+// ==================================
+
 // compareFunctions outputs SQL to make the functions match between DBs
 func compareFunctions(conn1 *sql.DB, conn2 *sql.DB) {
 
diff --git a/grant-attribute.go b/grant-attribute.go
index f941ef9..48678fc 100644
--- a/grant-attribute.go
+++ b/grant-attribute.go
@@ -1,17 +1,75 @@
 //
-// Copyright (c) 2014 Jon Carlson.  All rights reserved.
+// Copyright (c) 2017 Jon Carlson.  All rights reserved.
 // Use of this source code is governed by an MIT-style
 // license that can be found in the LICENSE file.
 //
 
 package main
 
-import "sort"
-import "fmt"
-import "strings"
-import "database/sql"
-import "github.com/joncrlsn/pgutil"
-import "github.com/joncrlsn/misc"
+import (
+	"text/template"
+	"bytes"
+	"database/sql"
+	"fmt"
+	"github.com/joncrlsn/misc"
+	"github.com/joncrlsn/pgutil"
+	"sort"
+	"strings"
+)
+
+var (
+	grantAttributeSqlTemplate = initGrantAttributeSqlTemplate()
+)
+
+// Initializes the Sql template
+func initGrantAttributeSqlTemplate() *template.Template {
+	sql := `
+-- Attribute/Column ACL only
+SELECT
+  n.nspname AS schema
+  , CASE c.relkind
+    WHEN 'r' THEN 'TABLE'
+    WHEN 'v' THEN 'VIEW'
+    WHEN 'f' THEN 'FOREIGN TABLE'
+    END as type
+  , c.relname AS relationship_name
+  , a.attname AS attribute_name
+  , a.attacl AS attribute_acl
+FROM pg_catalog.pg_class c
+LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
+INNER JOIN (SELECT attname, unnest(attacl) AS attacl, attrelid
+           FROM pg_catalog.pg_attribute
+           WHERE NOT attisdropped AND attacl IS NOT NULL)
+      AS a ON (a.attrelid = c.oid)
+WHERE c.relkind IN ('r', 'v', 'f')
+AND n.nspname !~ '^pg_' 
+AND pg_catalog.pg_table_is_visible(c.oid)
+ORDER BY n.nspname, c.relname, a.attname;
+`
+
+	//sql := `
+	//SELECT n.nspname                 AS schema_name
+	//, {{if eq $.DbSchema "*" }}n.nspname || '.' || {{end}}p.proname AS compare_name
+	//, p.proname                  AS function_name
+	//, p.oid::regprocedure        AS fancy
+	//, t.typname                  AS return_type
+	//, pg_get_functiondef(p.oid)  AS definition
+	//FROM pg_proc AS p
+	//JOIN pg_type t ON (p.prorettype = t.oid)
+	//JOIN pg_namespace n ON (n.oid = p.pronamespace)
+	//JOIN pg_language l ON (p.prolang = l.oid AND l.lanname IN ('c','plpgsql', 'sql'))
+	//WHERE true
+	//{{if eq $.DbSchema "*" }}
+	//AND n.nspname NOT LIKE 'pg_%'
+	//AND n.nspname <> 'information_schema'
+	//{{else}}
+	//AND n.nspname = '{{$.DbSchema}}'
+	//{{end}};
+	//`
+	t := template.New("GrantAttributeSqlTmpl")
+	template.Must(t.Parse(sql))
+	return t
+}
 
 // ==================================
 // GrantAttributeRows definition
@@ -172,36 +230,17 @@ func (c *GrantAttributeSchema) Change(obj interface{}) {
 // Functions
 // ==================================
 
-/*
- * Compare the columns in the two databases
- */
+// compareGrantAttributes outputs SQL to make the granted permissions match between DBs or schemas
 func compareGrantAttributes(conn1 *sql.DB, conn2 *sql.DB) {
-	sql := `
--- Attribute/Column ACL only
-SELECT
-  n.nspname AS schema
-  , CASE c.relkind
-    WHEN 'r' THEN 'TABLE'
-    WHEN 'v' THEN 'VIEW'
-    WHEN 'f' THEN 'FOREIGN TABLE'
-    END as type
-  , c.relname AS relationship_name
-  , a.attname AS attribute_name
-  , a.attacl AS attribute_acl
-FROM pg_catalog.pg_class c
-LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
-INNER JOIN (SELECT attname, unnest(attacl) AS attacl, attrelid
-           FROM pg_catalog.pg_attribute
-           WHERE NOT attisdropped AND attacl IS NOT NULL)
-      AS a ON (a.attrelid = c.oid)
-WHERE c.relkind IN ('r', 'v', 'f')
-AND n.nspname !~ '^pg_' 
-AND pg_catalog.pg_table_is_visible(c.oid)
-ORDER BY n.nspname, c.relname, a.attname;
-`
 
-	rowChan1, _ := pgutil.QueryStrings(conn1, sql)
-	rowChan2, _ := pgutil.QueryStrings(conn2, sql)
+	buf1 := new(bytes.Buffer)
+	grantAttributeSqlTemplate.Execute(buf1, dbInfo1)
+
+	buf2 := new(bytes.Buffer)
+	grantAttributeSqlTemplate.Execute(buf2, dbInfo2)
+
+	rowChan1, _ := pgutil.QueryStrings(conn1, buf1.String())
+	rowChan2, _ := pgutil.QueryStrings(conn2, buf2.String())
 
 	rows1 := make(GrantAttributeRows, 0)
 	for row := range rowChan1 {
diff --git a/grant-relationship.go b/grant-relationship.go
index e974a83..7d33e88 100644
--- a/grant-relationship.go
+++ b/grant-relationship.go
@@ -1,17 +1,56 @@
 //
-// Copyright (c) 2014 Jon Carlson.  All rights reserved.
+// Copyright (c) 2017 Jon Carlson.  All rights reserved.
 // Use of this source code is governed by an MIT-style
 // license that can be found in the LICENSE file.
 //
 
 package main
 
-import "sort"
-import "fmt"
-import "strings"
-import "database/sql"
-import "github.com/joncrlsn/pgutil"
-import "github.com/joncrlsn/misc"
+import (
+	"bytes"
+	"database/sql"
+	"fmt"
+	"github.com/joncrlsn/misc"
+	"github.com/joncrlsn/pgutil"
+	"sort"
+	"strings"
+	"text/template"
+)
+
+var (
+	grantRelationshipSqlTemplate = initGrantRelationshipSqlTemplate()
+)
+
+// Initializes the Sql template
+func initGrantRelationshipSqlTemplate() *template.Template {
+	sql := `
+SELECT n.nspname AS schema_name
+  , {{ if eq $.DbSchema "*" }}n.nspname || '.' || {{ end }}c.relkind || '.' || c.relname AS compare_name
+  , CASE c.relkind
+    WHEN 'r' THEN 'TABLE'
+    WHEN 'v' THEN 'VIEW'
+    WHEN 'S' THEN 'SEQUENCE'
+    WHEN 'f' THEN 'FOREIGN TABLE'
+    END as type
+  , c.relname AS relationship_name
+  , unnest(c.relacl) AS relationship_acl
+FROM pg_catalog.pg_class c
+LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
+WHERE c.relkind IN ('r', 'v', 'S', 'f')
+AND pg_catalog.pg_table_is_visible(c.oid)
+{{ if eq $.DbSchema "*" }}
+AND n.nspname NOT LIKE 'pg_%'
+AND n.nspname <> 'information_schema'
+{{ else }}
+AND n.nspname = '{{ $.DbSchema }}'
+{{ end }};
+ORDER BY n.nspname, c.relname;
+`
+
+	t := template.New("GrantAttributeSqlTmpl")
+	template.Must(t.Parse(sql))
+	return t
+}
 
 // ==================================
 // GrantRelationshipRows definition
@@ -25,11 +64,8 @@ func (slice GrantRelationshipRows) Len() int {
 }
 
 func (slice GrantRelationshipRows) Less(i, j int) bool {
-	if slice[i]["schema"] != slice[j]["schema"] {
-		return slice[i]["schema"] < slice[j]["schema"]
-	}
-	if slice[i]["relationship_name"] != slice[j]["relationship_name"] {
-		return slice[i]["relationship_name"] < slice[j]["relationship_name"]
+	if slice[i]["compare_name"] != slice[j]["compare_name"] {
+		return slice[i]["compare_name"] < slice[j]["compare_name"]
 	}
 
 	// Only compare the role part of the ACL
@@ -94,12 +130,7 @@ func (c *GrantRelationshipSchema) Compare(obj interface{}) int {
 		return +999
 	}
 
-	val := misc.CompareStrings(c.get("schema"), c2.get("schema"))
-	if val != 0 {
-		return val
-	}
-
-	val = misc.CompareStrings(c.get("relationship_name"), c2.get("relationship_name"))
+	val := misc.CompareStrings(c.get("compare_name"), c2.get("compare_name"))
 	if val != 0 {
 		return val
 	}
@@ -165,31 +196,17 @@ func (c *GrantRelationshipSchema) Change(obj interface{}) {
 // Functions
 // ==================================
 
-/*
- * Compare the columns in the two databases
- */
+// compareGrantRelationships outputs SQL to make the granted permissions match between DBs or schemas
 func compareGrantRelationships(conn1 *sql.DB, conn2 *sql.DB) {
-	sql := `
-SELECT
-  n.nspname AS schema
-  , CASE c.relkind
-    WHEN 'r' THEN 'TABLE'
-    WHEN 'v' THEN 'VIEW'
-    WHEN 'S' THEN 'SEQUENCE'
-    WHEN 'f' THEN 'FOREIGN TABLE'
-    END as type
-  , c.relname AS relationship_name
-  , unnest(c.relacl) AS relationship_acl
-FROM pg_catalog.pg_class c
-LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
-WHERE c.relkind IN ('r', 'v', 'S', 'f')
-  AND n.nspname NOT LIKE 'pg_%' 
-  AND pg_catalog.pg_table_is_visible(c.oid)
-ORDER BY n.nspname, c.relname;
-`
 
-	rowChan1, _ := pgutil.QueryStrings(conn1, sql)
-	rowChan2, _ := pgutil.QueryStrings(conn2, sql)
+	buf1 := new(bytes.Buffer)
+	grantRelationshipSqlTemplate.Execute(buf1, dbInfo1)
+
+	buf2 := new(bytes.Buffer)
+	grantRelationshipSqlTemplate.Execute(buf2, dbInfo2)
+
+	rowChan1, _ := pgutil.QueryStrings(conn1, buf1.String())
+	rowChan2, _ := pgutil.QueryStrings(conn2, buf2.String())
 
 	rows1 := make(GrantRelationshipRows, 0)
 	for row := range rowChan1 {
@@ -203,7 +220,7 @@ ORDER BY n.nspname, c.relname;
 	}
 	sort.Sort(rows2)
 
-	// We have to explicitly type this as Schema here for some unknown reason
+	// We have to explicitly type this as Schema here for some unknown (to me) reason
 	var schema1 Schema = &GrantRelationshipSchema{rows: rows1, rowNum: -1}
 	var schema2 Schema = &GrantRelationshipSchema{rows: rows2, rowNum: -1}
 
diff --git a/notes.md b/notes.md
deleted file mode 100644
index e3a112a..0000000
--- a/notes.md
+++ /dev/null
@@ -1,112 +0,0 @@
- ==== Constraints
-
-SELECT c.conname AS constraint_name,
-          CASE c.contype
-            WHEN 'c' THEN 'CHECK'
-            WHEN 'f' THEN 'FOREIGN KEY'
-            WHEN 'p' THEN 'PRIMARY KEY'
-            WHEN 'u' THEN 'UNIQUE'
-          END AS "constraint_type",
-          CASE WHEN c.condeferrable = 'f' THEN 0 ELSE 1 END AS is_deferrable,
-          CASE WHEN c.condeferred = 'f' THEN 0 ELSE 1 END AS is_deferred,
-          t.relname AS table_name,
-          array_to_string(c.conkey, ' ') AS constraint_key,
-          CASE confupdtype
-            WHEN 'a' THEN 'NO ACTION'
-            WHEN 'r' THEN 'RESTRICT'
-            WHEN 'c' THEN 'CASCADE'
-            WHEN 'n' THEN 'SET NULL'
-            WHEN 'd' THEN 'SET DEFAULT'
-          END AS on_update,
-          CASE confdeltype
-            WHEN 'a' THEN 'NO ACTION'
-            WHEN 'r' THEN 'RESTRICT'
-            WHEN 'c' THEN 'CASCADE'
-            WHEN 'n' THEN 'SET NULL'
-            WHEN 'd' THEN 'SET DEFAULT'
-          END AS on_delete,
-          CASE confmatchtype
-            WHEN 'u' THEN 'UNSPECIFIED'
-            WHEN 'f' THEN 'FULL'
-            WHEN 'p' THEN 'PARTIAL'
-          END AS match_type,
-          t2.relname AS references_table,
-          array_to_string(c.confkey, ' ') AS fk_constraint_key
-     FROM pg_constraint c
-LEFT JOIN pg_class t  ON c.conrelid  = t.oid
-LEFT JOIN pg_class t2 ON c.confrelid = t2.oid;
-
-
-   SELECT tc.constraint_name
-          , tc.constraint_type
-          , tc.table_name
-          , kcu.column_name
-          , tc.is_deferrable
-          , tc.initially_deferred
-          , rc.match_option AS match_type
-          , rc.update_rule AS on_update
-          , rc.delete_rule AS on_delete
-          , ccu.table_name AS references_table
-          , ccu.column_name AS references_field
-     FROM information_schema.table_constraints tc
-LEFT JOIN information_schema.key_column_usage kcu
-       ON tc.constraint_catalog = kcu.constraint_catalog
-      AND tc.constraint_schema = kcu.constraint_schema
-      AND tc.constraint_name = kcu.constraint_name
-LEFT JOIN information_schema.referential_constraints rc
-       ON tc.constraint_catalog = rc.constraint_catalog
-      AND tc.constraint_schema = rc.constraint_schema
-      AND tc.constraint_name = rc.constraint_name
-LEFT JOIN information_schema.constraint_column_usage ccu
-       ON rc.unique_constraint_catalog = ccu.constraint_catalog
-      AND rc.unique_constraint_schema = ccu.constraint_schema
-      AND rc.unique_constraint_name = ccu.constraint_name
-WHERE tc.constraint_schema = 'public'
-AND tc.constraint_type = 'UNIQUE';
-
-
- ==== Trigger and Function
-
-SELECT *
-  FROM information_schema.triggers
-   WHERE trigger_schema NOT IN
-          ('pg_catalog', 'information_schema');
-
-
-CREATE TABLE emp (
-    empname text,
-    salary integer,
-    last_date timestamp,
-    last_user text
-);
-
-CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
-    BEGIN
-        -- Check that empname and salary are given
-        IF NEW.empname IS NULL THEN
-            RAISE EXCEPTION 'empname cannot be null';
-        END IF;
-        IF NEW.salary IS NULL THEN
-            RAISE EXCEPTION '% cannot have null salary', NEW.empname;
-        END IF;
-
-        -- Who works for us when she must pay for it?
-        IF NEW.salary < 0 THEN
-            RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;
-        END IF;
-
-        -- Remember who changed the payroll when
-        NEW.last_date := current_timestamp;
-        NEW.last_user := current_user;
-        RETURN NEW;
-    END;
-$emp_stamp$ LANGUAGE plpgsql;
-
-CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp
-    FOR EACH ROW EXECUTE PROCEDURE emp_stamp();
-
-CREATE TRIGGER check_update
-    BEFORE UPDATE ON emp
-    FOR EACH ROW
-    WHEN (OLD.empname IS DISTINCT FROM NEW.empname)
-    EXECUTE PROCEDURE emp_stamp();
diff --git a/owner.go b/owner.go
index 5139754..b804e01 100644
--- a/owner.go
+++ b/owner.go
@@ -1,5 +1,5 @@
 //
-// Copyright (c) 2014 Jon Carlson.  All rights reserved.
+// Copyright (c) 2017 Jon Carlson.  All rights reserved.
 // Use of this source code is governed by an MIT-style
 // license that can be found in the LICENSE file.
 //
@@ -35,7 +35,7 @@ FROM pg_class AS c
 INNER JOIN pg_authid AS a ON (a.oid = c.relowner)
 INNER JOIN pg_namespace AS n ON (n.oid = c.relnamespace)
 WHERE c.relkind IN ('r', 'S', 'v')
-{{if eq $.DbSchema "*"}}
+{{if eq $.DbSchema "*" }}
 AND n.nspname NOT LIKE 'pg_%' 
 AND n.nspname <> 'information_schema'
 {{else}}
diff --git a/test/test-owner b/test/test-owner
index f32723e..d255ffb 100755
--- a/test/test-owner
+++ b/test/test-owner
@@ -51,7 +51,7 @@ echo
 
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
-          OWNER #| grep -v '^-- '
+          OWNER | grep -v '^-- '
 echo
 echo ==========================================================
 echo

From 305cf7423002b4ca7d7802bb03727d673a31eec1 Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Wed, 8 Nov 2017 17:56:49 -0600
Subject: [PATCH 29/74] OWNER works as expected now

Former-commit-id: f14ad63291c8e3260206adf47ac4a9acef82d8a7
---
 owner.go        | 4 +---
 test/test-owner | 6 ++++--
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/owner.go b/owner.go
index b804e01..2f0f7d7 100644
--- a/owner.go
+++ b/owner.go
@@ -139,9 +139,7 @@ func (c OwnerSchema) Change(obj interface{}) {
 	}
 }
 
-/*
- * Compare the ownership of tables, sequences, and views between two databases or schemas
- */
+// compareOwners compares the ownership of tables, sequences, and views between two databases or schemas
 func compareOwners(conn1 *sql.DB, conn2 *sql.DB) {
 
 	buf1 := new(bytes.Buffer)
diff --git a/test/test-owner b/test/test-owner
index d255ffb..670fe5d 100755
--- a/test/test-owner
+++ b/test/test-owner
@@ -51,7 +51,9 @@ echo
 
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
-          OWNER | grep -v '^-- '
+          OWNER #| grep -v '^-- '
+
+
 echo
 echo ==========================================================
 echo
@@ -85,6 +87,6 @@ echo
 
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "*" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db2" -s "*" -o "sslmode=disable" \
-          OWNER | grep -v '^-- '
+          OWNER #| grep -v '^-- '
 echo
 echo

From 5e3d44c18deff6da38b793a6b56c9e0b1a494178 Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Wed, 8 Nov 2017 21:07:54 -0600
Subject: [PATCH 30/74] WIP grant-relationship

Former-commit-id: 348aeb3addc6123b8afc18adca702d5f791b45b9
---
 grant-relationship.go        | 17 +++++---
 test/start-fresh.sh          |  3 ++
 test/test-grant-relationship | 83 ++++++++++++++++++++++++++++++++++++
 3 files changed, 97 insertions(+), 6 deletions(-)
 create mode 100755 test/test-grant-relationship

diff --git a/grant-relationship.go b/grant-relationship.go
index 7d33e88..e4442e7 100644
--- a/grant-relationship.go
+++ b/grant-relationship.go
@@ -37,13 +37,13 @@ SELECT n.nspname AS schema_name
 FROM pg_catalog.pg_class c
 LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
 WHERE c.relkind IN ('r', 'v', 'S', 'f')
-AND pg_catalog.pg_table_is_visible(c.oid)
+--AND pg_catalog.pg_table_is_visible(c.oid)
 {{ if eq $.DbSchema "*" }}
 AND n.nspname NOT LIKE 'pg_%'
 AND n.nspname <> 'information_schema'
 {{ else }}
 AND n.nspname = '{{ $.DbSchema }}'
-{{ end }};
+{{ end }}
 ORDER BY n.nspname, c.relname;
 `
 
@@ -144,14 +144,19 @@ func (c *GrantRelationshipSchema) Compare(obj interface{}) int {
 
 // Add prints SQL to add the column
 func (c *GrantRelationshipSchema) Add() {
+	schema := dbInfo2.DbSchema
+	if schema == "*" {
+		schema = c.get("schema_name")
+	}
+
 	role, grants := parseGrants(c.get("relationship_acl"))
-	fmt.Printf("GRANT %s ON %s TO %s; -- Add\n", strings.Join(grants, ", "), c.get("relationship_name"), role)
+	fmt.Printf("GRANT %s ON %s.%s TO %s; -- Add\n", strings.Join(grants, ", "), schema, c.get("relationship_name"), role)
 }
 
 // Drop prints SQL to drop the column
 func (c *GrantRelationshipSchema) Drop() {
 	role, grants := parseGrants(c.get("relationship_acl"))
-	fmt.Printf("REVOKE %s ON %s FROM %s; -- Drop\n", strings.Join(grants, ", "), c.get("relationship_name"), role)
+	fmt.Printf("REVOKE %s ON %s.%s FROM %s; -- Drop\n", strings.Join(grants, ", "), c.get("schema_name"), c.get("relationship_name"), role)
 }
 
 // Change handles the case where the relationship and column match, but the details do not
@@ -173,7 +178,7 @@ func (c *GrantRelationshipSchema) Change(obj interface{}) {
 		}
 	}
 	if len(grantList) > 0 {
-		fmt.Printf("GRANT %s ON %s TO %s; -- Change\n", strings.Join(grantList, ", "), c.get("relationship_name"), role)
+		fmt.Printf("GRANT %s ON %s.%s TO %s; -- Change\n", strings.Join(grantList, ", "), c2.get("schema_name"), c.get("relationship_name"), role)
 	}
 
 	// Find grants in the second db that are not in the first
@@ -185,7 +190,7 @@ func (c *GrantRelationshipSchema) Change(obj interface{}) {
 		}
 	}
 	if len(revokeList) > 0 {
-		fmt.Printf("REVOKE %s ON %s FROM %s; -- Change\n", strings.Join(revokeList, ", "), c.get("relationship_name"), role)
+		fmt.Printf("REVOKE %s ON %s.%s FROM %s; -- Change\n", strings.Join(revokeList, ", "), c2.get("schema_name"), c.get("relationship_name"), role)
 	}
 
 	//	fmt.Printf("--1 rel:%s, relAcl:%s, col:%s, colAcl:%s\n", c.get("relationship_name"), c.get("relationship_acl"), c.get("column_name"), c.get("column_acl"))
diff --git a/test/start-fresh.sh b/test/start-fresh.sh
index 8c45de8..df3c580 100755
--- a/test/start-fresh.sh
+++ b/test/start-fresh.sh
@@ -14,6 +14,9 @@ psql <<'SQL'
 
     CREATE DATABASE db1 WITH OWNER = u1 TEMPLATE = template0;
     CREATE DATABASE db2 WITH OWNER = u1 TEMPLATE = template0;
+
+    DROP USER IF EXISTS u2;
+    CREATE USER u2 PASSWORD 'asdf';
 SQL
 EOT 
 
diff --git a/test/test-grant-relationship b/test/test-grant-relationship
new file mode 100755
index 0000000..1047909
--- /dev/null
+++ b/test/test-grant-relationship
@@ -0,0 +1,83 @@
+#!/bin/bash
+#
+# Useful for visually inspecting the output SQL to verify it is doing what it should
+#
+
+source ./start-fresh.sh >/dev/null
+
+
+echo
+echo ==========================================================
+echo
+
+#
+# Compare the grants between two schemas in the same database
+#
+
+./populate-db.sh db1 "
+    CREATE SCHEMA s1;
+    CREATE TABLE s1.table1 (id integer);
+    GRANT INSERT, UPDATE ON s1.table1 TO u2;
+    CREATE TABLE s1.table2 (id integer);
+    GRANT SELECT ON s1.table2 TO u2;
+    CREATE TABLE s1.table3 (id integer); 
+    GRANT SELECT ON s1.table3 TO u2;  
+
+    CREATE SCHEMA s2;
+    CREATE TABLE s2.table1 (id integer);
+    GRANT SELECT ON s2.table1 TO u2;     -- add INSERT, UPDATE
+    CREATE TABLE s2.table2 (id integer); 
+    GRANT SELECT ON s2.table2 TO u2;     -- no change
+    CREATE TABLE s2.table3 (id integer); -- add SELECT
+"
+
+echo
+echo "# Compare the grants between two schemas in the same database"
+echo "# Expect SQL (pseudocode):"
+echo "#   Revoke SELECT on s2.table1 for u2"
+echo "#   Grant INSERT, UPDATE on s2.table1 for u2"
+echo "#   Grant SELECT on s2.table3 for u2"
+echo
+
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
+          GRANT_RELATIONSHIP #| grep -v '^-- '
+
+exit 0
+
+
+echo
+echo ==========================================================
+echo
+
+
+#
+# Compare the grants in all schemas between two databases
+#
+./populate-db.sh db2 "
+    CREATE SCHEMA s1;
+    CREATE TABLE s1.table1 (id integer);
+    GRANT INSERT, UPDATE ON s1.table1 TO u2;
+    CREATE TABLE s1.table2 (id integer);
+    GRANT SELECT ON s1.table2 TO u2;
+    CREATE TABLE s1.table3 (id integer); 
+
+    CREATE SCHEMA s2;
+    CREATE TABLE s2.table1 (id integer);
+    GRANT SELECT ON s2.table1 TO u2;     -- add INSERT, UPDATE
+    CREATE TABLE s2.table2 (id integer); 
+    CREATE TABLE s2.table3 (id integer); -- no grants, so no change
+"
+
+echo
+echo "# Compare the grants in all schemas between two databases"
+echo "# Expect SQL (pseudocode):"
+echo "#   Revoke SELECT on s1.table3 for u2"
+echo "#   Grant SELECT on s2.table2 for u2"
+echo
+
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "*" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db2" -s "*" -o "sslmode=disable" \
+          GRANT_RELATIONSHIP #| grep -v '^-- '
+echo
+echo

From a1219f93da689640f5683abe724668060459876d Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Thu, 9 Nov 2017 17:44:42 -0600
Subject: [PATCH 31/74] test-grant-relationship passes

Former-commit-id: 25917659f467a946912937620fdfb0d52496e1c5
---
 test/test-grant-relationship | 19 +++++++++++--------
 1 file changed, 11 insertions(+), 8 deletions(-)

diff --git a/test/test-grant-relationship b/test/test-grant-relationship
index 1047909..1f6583d 100755
--- a/test/test-grant-relationship
+++ b/test/test-grant-relationship
@@ -29,6 +29,7 @@ echo
     CREATE TABLE s2.table2 (id integer); 
     GRANT SELECT ON s2.table2 TO u2;     -- no change
     CREATE TABLE s2.table3 (id integer); -- add SELECT
+    GRANT SELECT ON s2.table3 TO u1;  
 "
 
 echo
@@ -43,8 +44,6 @@ echo
           -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
           GRANT_RELATIONSHIP #| grep -v '^-- '
 
-exit 0
-
 
 echo
 echo ==========================================================
@@ -57,23 +56,27 @@ echo
 ./populate-db.sh db2 "
     CREATE SCHEMA s1;
     CREATE TABLE s1.table1 (id integer);
-    GRANT INSERT, UPDATE ON s1.table1 TO u2;
+    GRANT SELECT ON s1.table1 TO u2;
     CREATE TABLE s1.table2 (id integer);
     GRANT SELECT ON s1.table2 TO u2;
     CREATE TABLE s1.table3 (id integer); 
+    GRANT SELECT ON s1.table3 TO u2;  
 
     CREATE SCHEMA s2;
     CREATE TABLE s2.table1 (id integer);
-    GRANT SELECT ON s2.table1 TO u2;     -- add INSERT, UPDATE
-    CREATE TABLE s2.table2 (id integer); 
-    CREATE TABLE s2.table3 (id integer); -- no grants, so no change
+    GRANT SELECT ON s2.table1 TO u2;
+    CREATE TABLE s2.table2 (id integer);
+    GRANT SELECT ON s2.table2 TO u2;
+    CREATE TABLE s2.table3 (id integer);
+    GRANT UPDATE ON s2.table3 TO u2;  -- revoke
 "
 
 echo
 echo "# Compare the grants in all schemas between two databases"
 echo "# Expect SQL (pseudocode):"
-echo "#   Revoke SELECT on s1.table3 for u2"
-echo "#   Grant SELECT on s2.table2 for u2"
+echo "#   Revoke UPDATE        on s2.table3 for u2"
+echo "#   Grant  INSERT,UPDATE on s1.table1 for u2"
+echo "#   Revoke SELECT        on s1.table1 for u2"
 echo
 
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "*" -O "sslmode=disable" \

From e70922d16f4435123bedcc31e5803ac93f2f5cc4 Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Mon, 13 Nov 2017 19:16:49 -0600
Subject: [PATCH 32/74] WIP test-grant-attribute

Former-commit-id: 53ce60cf238088daa48b74656662a81c1f033209
---
 grant-attribute.go        |  91 ++++++++++++----------------
 grant-relationship.go     |  12 ++--
 test/test-grant-attribute | 123 ++++++++++++++++++++++++++++++++++++++
 test/test-owner           |   7 ---
 4 files changed, 165 insertions(+), 68 deletions(-)
 create mode 100755 test/test-grant-attribute

diff --git a/grant-attribute.go b/grant-attribute.go
index 48678fc..0367390 100644
--- a/grant-attribute.go
+++ b/grant-attribute.go
@@ -7,7 +7,6 @@
 package main
 
 import (
-	"text/template"
 	"bytes"
 	"database/sql"
 	"fmt"
@@ -15,6 +14,7 @@ import (
 	"github.com/joncrlsn/pgutil"
 	"sort"
 	"strings"
+	"text/template"
 )
 
 var (
@@ -26,7 +26,8 @@ func initGrantAttributeSqlTemplate() *template.Template {
 	sql := `
 -- Attribute/Column ACL only
 SELECT
-  n.nspname AS schema
+  n.nspname AS schema_name
+  , {{ if eq $.DbSchema "*" }}n.nspname || '.' || {{ end }}c.relkind || '.' || c.relname AS compare_name
   , CASE c.relkind
     WHEN 'r' THEN 'TABLE'
     WHEN 'v' THEN 'VIEW'
@@ -34,7 +35,7 @@ SELECT
     END as type
   , c.relname AS relationship_name
   , a.attname AS attribute_name
-  , a.attacl AS attribute_acl
+  , a.attacl  AS attribute_acl
 FROM pg_catalog.pg_class c
 LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
 INNER JOIN (SELECT attname, unnest(attacl) AS attacl, attrelid
@@ -42,30 +43,15 @@ INNER JOIN (SELECT attname, unnest(attacl) AS attacl, attrelid
            WHERE NOT attisdropped AND attacl IS NOT NULL)
       AS a ON (a.attrelid = c.oid)
 WHERE c.relkind IN ('r', 'v', 'f')
-AND n.nspname !~ '^pg_' 
-AND pg_catalog.pg_table_is_visible(c.oid)
-ORDER BY n.nspname, c.relname, a.attname;
+--AND pg_catalog.pg_table_is_visible(c.oid)
+{{ if eq $.DbSchema "*" }}
+AND n.nspname NOT LIKE 'pg_%'
+AND n.nspname <> 'information_schema'
+{{ else }}
+AND n.nspname = '{{ $.DbSchema }}'
+{{ end }};
 `
 
-	//sql := `
-	//SELECT n.nspname                 AS schema_name
-	//, {{if eq $.DbSchema "*" }}n.nspname || '.' || {{end}}p.proname AS compare_name
-	//, p.proname                  AS function_name
-	//, p.oid::regprocedure        AS fancy
-	//, t.typname                  AS return_type
-	//, pg_get_functiondef(p.oid)  AS definition
-	//FROM pg_proc AS p
-	//JOIN pg_type t ON (p.prorettype = t.oid)
-	//JOIN pg_namespace n ON (n.oid = p.pronamespace)
-	//JOIN pg_language l ON (p.prolang = l.oid AND l.lanname IN ('c','plpgsql', 'sql'))
-	//WHERE true
-	//{{if eq $.DbSchema "*" }}
-	//AND n.nspname NOT LIKE 'pg_%'
-	//AND n.nspname <> 'information_schema'
-	//{{else}}
-	//AND n.nspname = '{{$.DbSchema}}'
-	//{{end}};
-	//`
 	t := template.New("GrantAttributeSqlTmpl")
 	template.Must(t.Parse(sql))
 	return t
@@ -83,14 +69,8 @@ func (slice GrantAttributeRows) Len() int {
 }
 
 func (slice GrantAttributeRows) Less(i, j int) bool {
-	if slice[i]["schema"] != slice[j]["schema"] {
-		return slice[i]["schema"] < slice[j]["schema"]
-	}
-	if slice[i]["relationship_name"] != slice[j]["relationship_name"] {
-		return slice[i]["relationship_name"] < slice[j]["relationship_name"]
-	}
-	if slice[i]["attribute_name"] != slice[j]["attribute_name"] {
-		return slice[i]["attribute_name"] < slice[j]["attribute_name"]
+	if slice[i]["compare_name"] != slice[j]["compare_name"] {
+		return slice[i]["compare_name"] < slice[j]["compare_name"]
 	}
 
 	// Only compare the role part of the ACL
@@ -147,7 +127,8 @@ func (c *GrantAttributeSchema) NextRow() bool {
 	return !c.done
 }
 
-// Compare tells you, in one pass, whether or not the first row matches, is less than, or greater than the second row
+// Compare tells you, in one pass, whether or not the first row matches, is less than,
+// or greater than the second row.
 func (c *GrantAttributeSchema) Compare(obj interface{}) int {
 	c2, ok := obj.(*GrantAttributeSchema)
 	if !ok {
@@ -155,17 +136,7 @@ func (c *GrantAttributeSchema) Compare(obj interface{}) int {
 		return +999
 	}
 
-	val := misc.CompareStrings(c.get("schema"), c2.get("schema"))
-	if val != 0 {
-		return val
-	}
-
-	val = misc.CompareStrings(c.get("relationship_name"), c2.get("relationship_name"))
-	if val != 0 {
-		return val
-	}
-
-	val = misc.CompareStrings(c.get("attribute_name"), c2.get("attribute_name"))
+	val := misc.CompareStrings(c.get("compare_name"), c2.get("compare_name"))
 	if val != 0 {
 		return val
 	}
@@ -176,19 +147,24 @@ func (c *GrantAttributeSchema) Compare(obj interface{}) int {
 	return val
 }
 
-// Add prints SQL to add the column
+// Add prints SQL to add the grant
 func (c *GrantAttributeSchema) Add() {
+	schema := dbInfo2.DbSchema
+	if schema == "*" {
+		schema = c.get("schema_name")
+	}
+
 	role, grants := parseGrants(c.get("attribute_acl"))
-	fmt.Printf("GRANT %s (%s) ON %s TO %s; -- Add\n", strings.Join(grants, ", "), c.get("attribute_name"), c.get("relationship_name"), role)
+	fmt.Printf("GRANT %s (%s) ON %s.%s TO %s; -- Add\n", strings.Join(grants, ", "), c.get("attribute_name"), schema, c.get("relationship_name"), role)
 }
 
-// Drop prints SQL to drop the column
+// Drop prints SQL to drop the grant
 func (c *GrantAttributeSchema) Drop() {
 	role, grants := parseGrants(c.get("attribute_acl"))
-	fmt.Printf("REVOKE %s (%s) ON %s FROM %s; -- Drop\n", strings.Join(grants, ", "), c.get("attribute_name"), c.get("relationship_name"), role)
+	fmt.Printf("REVOKE %s (%s) ON %s.%s FROM %s; -- Drop\n", strings.Join(grants, ", "), c.get("attribute_name"), c.get("schema_name"), c.get("relationship_name"), role)
 }
 
-// Change handles the case where the relationship and column match, but the details do not
+// Change handles the case where the relationship and column match, but the grant does not
 func (c *GrantAttributeSchema) Change(obj interface{}) {
 	c2, ok := obj.(*GrantAttributeSchema)
 	if !ok {
@@ -207,7 +183,8 @@ func (c *GrantAttributeSchema) Change(obj interface{}) {
 		}
 	}
 	if len(grantList) > 0 {
-		fmt.Printf("GRANT %s (%s) ON %s TO %s; -- Change\n", strings.Join(grantList, ", "), c.get("attribute_name"), c.get("relationship_name"), role)
+		fmt.Printf("GRANT %s (%s) ON %s.%s TO %s; -- Change\n", strings.Join(grantList, ", "),
+			c.get("attribute_name"), c2.get("schema_name"), c.get("relationship_name"), role)
 	}
 
 	// Find grants in the second db that are not in the first
@@ -219,11 +196,11 @@ func (c *GrantAttributeSchema) Change(obj interface{}) {
 		}
 	}
 	if len(revokeList) > 0 {
-		fmt.Printf("REVOKE %s (%s) ON %s FROM %s; -- Change\n", strings.Join(grantList, ", "), c.get("attribute_name"), c.get("relationship_name"), role)
+		fmt.Printf("REVOKE %s (%s) ON %s.%s FROM %s; -- Change\n", strings.Join(revokeList, ", "), c.get("attribute_name"), c2.get("schema_name"), c.get("relationship_name"), role)
 	}
 
-	//	fmt.Printf("--1 rel:%s, relAcl:%s, col:%s, colAcl:%s\n", c.get("attribute_name"), c.get("attribute_acl"), c.get("attribute_name"), c.get("attribute_acl"))
-	//	fmt.Printf("--2 rel:%s, relAcl:%s, col:%s, colAcl:%s\n", c2.get("attribute_name"), c2.get("attribute_acl"), c2.get("attribute_name"), c2.get("attribute_acl"))
+	//fmt.Printf("--1 rel:%s, relAcl:%s, col:%s, colAcl:%s\n", c.get("attribute_name"), c.get("attribute_acl"), c.get("attribute_name"), c.get("attribute_acl"))
+	//fmt.Printf("--2 rel:%s, relAcl:%s, col:%s, colAcl:%s\n", c2.get("attribute_name"), c2.get("attribute_acl"), c2.get("attribute_name"), c2.get("attribute_acl"))
 }
 
 // ==================================
@@ -247,12 +224,18 @@ func compareGrantAttributes(conn1 *sql.DB, conn2 *sql.DB) {
 		rows1 = append(rows1, row)
 	}
 	sort.Sort(rows1)
+		for _, row := range rows1 {
+			fmt.Printf("--1b compare:%s, col:%s, colAcl:%s\n", row["compare_name"], row["attribute_name"], row["attribute_acl"])
+		}
 
 	rows2 := make(GrantAttributeRows, 0)
 	for row := range rowChan2 {
 		rows2 = append(rows2, row)
 	}
 	sort.Sort(rows2)
+	for _, row := range rows2 {
+	fmt.Printf("--2b compare:%s, col:%s, colAcl:%s\n", row["compare_name"], row["attribute_name"], row["attribute_acl"])
+	}
 
 	// We have to explicitly type this as Schema here for some unknown reason
 	var schema1 Schema = &GrantAttributeSchema{rows: rows1, rowNum: -1}
diff --git a/grant-relationship.go b/grant-relationship.go
index e4442e7..c4c12cd 100644
--- a/grant-relationship.go
+++ b/grant-relationship.go
@@ -43,11 +43,10 @@ AND n.nspname NOT LIKE 'pg_%'
 AND n.nspname <> 'information_schema'
 {{ else }}
 AND n.nspname = '{{ $.DbSchema }}'
-{{ end }}
-ORDER BY n.nspname, c.relname;
+{{ end }};
 `
 
-	t := template.New("GrantAttributeSqlTmpl")
+	t := template.New("GrantRelationshipSqlTmpl")
 	template.Must(t.Parse(sql))
 	return t
 }
@@ -139,10 +138,9 @@ func (c *GrantRelationshipSchema) Compare(obj interface{}) int {
 	relRole2, _ := parseAcl(c2.get("relationship_acl"))
 	val = misc.CompareStrings(relRole1, relRole2)
 	return val
-
 }
 
-// Add prints SQL to add the column
+// Add prints SQL to add the grant
 func (c *GrantRelationshipSchema) Add() {
 	schema := dbInfo2.DbSchema
 	if schema == "*" {
@@ -153,13 +151,13 @@ func (c *GrantRelationshipSchema) Add() {
 	fmt.Printf("GRANT %s ON %s.%s TO %s; -- Add\n", strings.Join(grants, ", "), schema, c.get("relationship_name"), role)
 }
 
-// Drop prints SQL to drop the column
+// Drop prints SQL to drop the grant
 func (c *GrantRelationshipSchema) Drop() {
 	role, grants := parseGrants(c.get("relationship_acl"))
 	fmt.Printf("REVOKE %s ON %s.%s FROM %s; -- Drop\n", strings.Join(grants, ", "), c.get("schema_name"), c.get("relationship_name"), role)
 }
 
-// Change handles the case where the relationship and column match, but the details do not
+// Change handles the case where the relationship and column match, but the grant does not
 func (c *GrantRelationshipSchema) Change(obj interface{}) {
 	c2, ok := obj.(*GrantRelationshipSchema)
 	if !ok {
diff --git a/test/test-grant-attribute b/test/test-grant-attribute
new file mode 100755
index 0000000..eed0565
--- /dev/null
+++ b/test/test-grant-attribute
@@ -0,0 +1,123 @@
+#!/bin/bash
+#
+# Useful for visually inspecting the output SQL to verify it is doing what it should
+#
+
+source ./start-fresh.sh >/dev/null
+
+
+echo
+echo ==========================================================
+echo
+
+#
+# Compare the grants between two schemas in the same database
+#
+
+./populate-db.sh db1 "
+    -- Available Column Privileges: SELECT, INSERT, UPDATE, REFERENCES
+
+    CREATE SCHEMA s1;
+    CREATE SCHEMA s2;
+
+    ---------
+
+    CREATE TABLE s1.table1 (id integer, name varchar(30));
+    GRANT SELECT, UPDATE (name) ON s1.table1 TO u2;
+
+    -- Drop REFERENCES, Add UPDATE
+    CREATE TABLE s2.table1 (id integer, name varchar(30));
+    GRANT SELECT, REFERENCES (name) ON s2.table1 TO u2;   
+
+    ---------
+
+    CREATE TABLE s1.table2 (id integer, name varchar(30));
+    -- u2 has no privileges
+
+    -- Drop SELECT on s1.table2
+    CREATE TABLE s2.table2 (id integer, name varchar(30));
+    GRANT SELECT (name) ON s2.table2 TO u2;
+
+    ---------
+
+    CREATE TABLE s1.table3 (id integer, name varchar(30));
+    GRANT SELECT (name) ON s1.table3 TO u2;
+
+    -- Add SELECT on s1.table3
+    CREATE TABLE s2.table3 (id integer, name varchar(30));
+    -- u2 has no privileges
+"
+
+echo
+echo "# Compare the grants between two schemas in the same database"
+echo "# Expect SQL (pseudocode):"
+echo "#   Grant  UPDATE     on s2.table1 (name) for u2"
+echo "#   Revoke REFERENCES on s2.table1 (name) for u2"
+echo "#   Revoke SELECT     on s2.table2 (name) for u2"
+echo "#   Grant  SELECT     on s2.table3 (name) for u2"
+echo
+
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
+          GRANT_ATTRIBUTE | grep -v '^-- '
+
+echo
+echo ==========================================================
+echo
+
+
+source ./start-fresh.sh >/dev/null
+
+#
+# Compare the grants in all schemas between two databases
+#
+
+./populate-db.sh db1 "
+    CREATE SCHEMA s1;
+    CREATE SCHEMA s2;
+    ---------
+    CREATE TABLE s1.table1 (id integer, name varchar(30));
+    GRANT SELECT, UPDATE (name) ON s1.table1 TO u2;
+
+    CREATE TABLE s2.table1 (id integer, name varchar(30));
+    GRANT SELECT, UPDATE (name) ON s2.table1 TO u2;
+
+    CREATE TABLE s2.table3 (id integer, name varchar(30));
+"
+
+./populate-db.sh db2 "
+    CREATE SCHEMA s1;
+    CREATE SCHEMA s2;
+    ---------
+    CREATE TABLE s1.table1 (id integer, name varchar(30));
+    GRANT SELECT (id) ON s1.table1 TO u2;
+    GRANT SELECT, UPDATE (name) ON s1.table1 TO u2;
+
+    CREATE TABLE s2.table1 (id integer, name varchar(30));
+
+    CREATE TABLE s2.table3 (id integer, name varchar(30));
+    GRANT SELECT, UPDATE (name) ON s2.table3 TO u2;
+"
+
+# GRANT UPDATE (name) ON s1.table1 TO u2; -- Change
+# REVOKE SELECT (name) ON s1.table1 FROM u2; -- Change
+# REVOKE UPDATE (name) ON s1.table1 FROM u2; -- Drop
+# GRANT UPDATE (name) ON s2.table1 TO u2; -- Add
+# REVOKE UPDATE (name) ON s2.table3 FROM u2; -- Drop
+
+echo
+echo "# Compare the grants in all schemas between two databases"
+echo "# Expect SQL (pseudocode):"
+echo "#   Grant  UPDATE (name)         on s1.table1 for u2"
+echo "#   Revoke SELECT (id)           on s1.table1 for u2"
+
+echo "#   Revoke SELECT (id)           on s1.table1 for u2"
+echo "#   Grant  SELECT, UPDATE (name) on s2.table2 for u2"
+echo "#   Revoke SELECT, UPDATE (name) on s2.table3 for u2"
+echo
+
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "*" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db2" -s "*" -o "sslmode=disable" \
+          GRANT_ATTRIBUTE #| grep -v '^-- '
+echo
+echo
diff --git a/test/test-owner b/test/test-owner
index 670fe5d..6368b45 100755
--- a/test/test-owner
+++ b/test/test-owner
@@ -6,13 +6,6 @@
 # Rebuild the basic databases
 source ./start-fresh.sh >/dev/null
 
-# An additional user/role is needed for this test
-sudo su - postgres -- -c " 
-  psql -c \" 
-    DROP USER IF EXISTS u2; CREATE USER u2 WITH SUPERUSER PASSWORD 'asdf'; 
-  \"
-"
-
 
 echo
 echo ==========================================================

From 8ccbc305fe8964eb242adb498e0076242865e0f4 Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Tue, 14 Nov 2017 17:01:41 -0600
Subject: [PATCH 33/74] Fixed error with the heredoc I was using

Former-commit-id: 5e1120855a58b4471c6b56e75fc8b922e2bb9873
---
 test/start-fresh.sh | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/test/start-fresh.sh b/test/start-fresh.sh
index df3c580..f8207af 100755
--- a/test/start-fresh.sh
+++ b/test/start-fresh.sh
@@ -1,11 +1,10 @@
 #!/bin/bash
 #
-# Wipe out and recreate a testing user u1
-# Wipe out and recreate 2 known databases (db1, db2) used for testing 
+# Drop and recreate 2 testing users (u1, u2)
+# Drop and recreate 2 known databases (db1, db2) used for testing 
 #
 
-sudo su - postgres -- <<EOT
-psql <<'SQL'
+sql="
     DROP DATABASE IF EXISTS db1;
     DROP DATABASE IF EXISTS db2;
 
@@ -17,6 +16,6 @@ psql <<'SQL'
 
     DROP USER IF EXISTS u2;
     CREATE USER u2 PASSWORD 'asdf';
-SQL
-EOT 
+"
 
+sudo su - postgres -- -c "psql <<< \"$sql\""

From 158758414f23e49d1ed50044e953a96e841c7fae Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Tue, 14 Nov 2017 20:54:55 -0600
Subject: [PATCH 34/74] test-grant-attribute passes

Former-commit-id: ad48ea19330a84f086284c5e1fc38576ce567963
---
 grant-attribute.go        | 14 +++++++-------
 test/test-grant-attribute | 39 +++++++++++++++++++--------------------
 2 files changed, 26 insertions(+), 27 deletions(-)

diff --git a/grant-attribute.go b/grant-attribute.go
index 0367390..69d076a 100644
--- a/grant-attribute.go
+++ b/grant-attribute.go
@@ -27,7 +27,7 @@ func initGrantAttributeSqlTemplate() *template.Template {
 -- Attribute/Column ACL only
 SELECT
   n.nspname AS schema_name
-  , {{ if eq $.DbSchema "*" }}n.nspname || '.' || {{ end }}c.relkind || '.' || c.relname AS compare_name
+  , {{ if eq $.DbSchema "*" }}n.nspname || '.' || {{ end }}c.relkind || '.' || c.relname || '.' || a.attname AS compare_name
   , CASE c.relkind
     WHEN 'r' THEN 'TABLE'
     WHEN 'v' THEN 'VIEW'
@@ -224,18 +224,18 @@ func compareGrantAttributes(conn1 *sql.DB, conn2 *sql.DB) {
 		rows1 = append(rows1, row)
 	}
 	sort.Sort(rows1)
-		for _, row := range rows1 {
-			fmt.Printf("--1b compare:%s, col:%s, colAcl:%s\n", row["compare_name"], row["attribute_name"], row["attribute_acl"])
-		}
+	//for _, row := range rows1 {
+		//fmt.Printf("--1b compare:%s, col:%s, colAcl:%s\n", row["compare_name"], row["attribute_name"], row["attribute_acl"])
+	//}
 
 	rows2 := make(GrantAttributeRows, 0)
 	for row := range rowChan2 {
 		rows2 = append(rows2, row)
 	}
 	sort.Sort(rows2)
-	for _, row := range rows2 {
-	fmt.Printf("--2b compare:%s, col:%s, colAcl:%s\n", row["compare_name"], row["attribute_name"], row["attribute_acl"])
-	}
+	//for _, row := range rows2 {
+		//fmt.Printf("--2b compare:%s, col:%s, colAcl:%s\n", row["compare_name"], row["attribute_name"], row["attribute_acl"])
+	//}
 
 	// We have to explicitly type this as Schema here for some unknown reason
 	var schema1 Schema = &GrantAttributeSchema{rows: rows1, rowNum: -1}
diff --git a/test/test-grant-attribute b/test/test-grant-attribute
index eed0565..bb63bbc 100755
--- a/test/test-grant-attribute
+++ b/test/test-grant-attribute
@@ -51,10 +51,10 @@ echo
 echo
 echo "# Compare the grants between two schemas in the same database"
 echo "# Expect SQL (pseudocode):"
-echo "#   Grant  UPDATE     on s2.table1 (name) for u2"
-echo "#   Revoke REFERENCES on s2.table1 (name) for u2"
-echo "#   Revoke SELECT     on s2.table2 (name) for u2"
-echo "#   Grant  SELECT     on s2.table3 (name) for u2"
+echo "#   Grant  UPDATE     (name) on s2.table1 for u2"
+echo "#   Revoke REFERENCES (name) on s2.table1 for u2"
+echo "#   Revoke SELECT     (name) on s2.table2 for u2"
+echo "#   Grant  SELECT     (name) on s2.table3 for u2"
 echo
 
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
@@ -80,9 +80,12 @@ source ./start-fresh.sh >/dev/null
     GRANT SELECT, UPDATE (name) ON s1.table1 TO u2;
 
     CREATE TABLE s2.table1 (id integer, name varchar(30));
-    GRANT SELECT, UPDATE (name) ON s2.table1 TO u2;
+    GRANT UPDATE (name) ON s2.table1 TO u2;
 
     CREATE TABLE s2.table3 (id integer, name varchar(30));
+
+    CREATE TABLE s2.table4 (id integer, name varchar(30));
+    GRANT SELECT, UPDATE (name) ON s2.table4 TO u2;
 "
 
 ./populate-db.sh db2 "
@@ -90,34 +93,30 @@ source ./start-fresh.sh >/dev/null
     CREATE SCHEMA s2;
     ---------
     CREATE TABLE s1.table1 (id integer, name varchar(30));
-    GRANT SELECT (id) ON s1.table1 TO u2;
     GRANT SELECT, UPDATE (name) ON s1.table1 TO u2;
+    GRANT SELECT (id) ON s1.table1 TO u2;
 
     CREATE TABLE s2.table1 (id integer, name varchar(30));
+    GRANT REFERENCES (name) ON s2.table1 TO u2;
 
     CREATE TABLE s2.table3 (id integer, name varchar(30));
-    GRANT SELECT, UPDATE (name) ON s2.table3 TO u2;
-"
+    GRANT UPDATE (name) ON s2.table3 TO u2;
 
-# GRANT UPDATE (name) ON s1.table1 TO u2; -- Change
-# REVOKE SELECT (name) ON s1.table1 FROM u2; -- Change
-# REVOKE UPDATE (name) ON s1.table1 FROM u2; -- Drop
-# GRANT UPDATE (name) ON s2.table1 TO u2; -- Add
-# REVOKE UPDATE (name) ON s2.table3 FROM u2; -- Drop
+    CREATE TABLE s2.table4 (id integer, name varchar(30));
+"
 
 echo
 echo "# Compare the grants in all schemas between two databases"
 echo "# Expect SQL (pseudocode):"
-echo "#   Grant  UPDATE (name)         on s1.table1 for u2"
-echo "#   Revoke SELECT (id)           on s1.table1 for u2"
-
-echo "#   Revoke SELECT (id)           on s1.table1 for u2"
-echo "#   Grant  SELECT, UPDATE (name) on s2.table2 for u2"
-echo "#   Revoke SELECT, UPDATE (name) on s2.table3 for u2"
+echo "#   Revoke SELECT     (id)   on s1.table1 for u2"
+echo "#   Grant  UPDATE     (name) on s2.table1 for u2"
+echo "#   Revoke REFERENCES (name) on s2.table1 for u2"
+echo "#   Revoke UPDATE     (name) on s2.table3 for u2"
+echo "#   Grant  UPDATE     (name) on s2.table4 for u2"
 echo
 
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "*" -O "sslmode=disable" \
           -u "u1" -w "asdf" -h "localhost" -d "db2" -s "*" -o "sslmode=disable" \
-          GRANT_ATTRIBUTE #| grep -v '^-- '
+          GRANT_ATTRIBUTE | grep -v '^-- '
 echo
 echo

From 9481bbfee5e7c009754db43f4b9c8e0cec068297 Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Tue, 14 Nov 2017 21:01:35 -0600
Subject: [PATCH 35/74] cleaned up grant_test.go

Former-commit-id: 034bdf643b6d716a58e027b4f1612fdac081e708
---
 grant_test.go | 54 ++-------------------------------------------------
 1 file changed, 2 insertions(+), 52 deletions(-)

diff --git a/grant_test.go b/grant_test.go
index 6a5a651..b807435 100644
--- a/grant_test.go
+++ b/grant_test.go
@@ -6,63 +6,13 @@ import (
 )
 
 func Test_parseAcls(t *testing.T) {
-	doParseAcls(t, "c42ro=rwa/c42", "c42ro", 3)
+	doParseAcls(t, "user1=rwa/c42", "user1", 3)
 	doParseAcls(t, "=arwdDxt/c42", "public", 7)   // first of two lines
-	doParseAcls(t, "c42=rwad/postgres", "c42", 4) // second of two lines
+	doParseAcls(t, "u3=rwad/postgres", "u3", 4) // second of two lines
 	doParseAcls(t, "user2=arwxt/postgres", "user2", 5)
 	doParseAcls(t, "", "", 0)
 }
 
-/*
- schema |   type   |               relationship_name               |   relationship_acl   |         column_name          | column_acl
---------+----------+-----------------------------------------------+----------------------+------------------------------+-------------
- public | TABLE    | t_brand                                       | c42ro=r/c42          |                              |
- public | TABLE    | t_brand                                       | office42=arwdDxt/c42 |                              |
- public | TABLE    | t_brand                                       | c42=arwdDxt/c42      |                              |
- public | TABLE    | t_computer                                    | c42=arwdDxt/c42      | active                       | c42ro=r/c42
- public | TABLE    | t_computer                                    | office42=arwdDxt/c42 | active                       | c42ro=r/c42
- public | TABLE    | t_computer                                    | c42=arwdDxt/c42      | address                      | c42ro=r/c42
- public | TABLE    | t_computer                                    | office42=arwdDxt/c42 | address                      | c42ro=r/c42
-*/
-
-// Note that these must be sorted for this to work
-var relationship1 = []map[string]string{
-	{"schema": "public", "type": "TABLE", "relationship_name": "table1", "relationship_acl": "c42=rwa/postgres"},
-	{"schema": "public", "type": "TABLE", "relationship_name": "table1", "relationship_acl": "o42=xdwra/postgres"},
-	{"schema": "public", "type": "TABLE", "relationship_name": "table2", "relationship_acl": "c42=rwa/postgres"},
-}
-
-// Note that these must be sorted for this to work
-var relationship2 = []map[string]string{
-	{"schema": "public", "relationship_name": "table1", "type": "TABLE", "relationship_acl": "c42=r/postgres"},
-	{"schema": "public", "relationship_name": "table2", "type": "TABLE", "relationship_acl": "c42=rwad/postgres"},
-}
-
-// Note that these must be sorted for this to work
-var attribute1 = []map[string]string{
-	{"schema": "public", "type": "TABLE", "relationship_name": "table1", "attribute_name": "column1", "attribute_acl": "c42ro=r/postgres"},
-	{"schema": "public", "type": "TABLE", "relationship_name": "table1", "attribute_name": "column1", "attribute_acl": "o42ro=rwa/postgres"},
-	{"schema": "public", "type": "TABLE", "relationship_name": "table2", "attribute_name": "column2", "attribute_acl": "c42ro=r/postgres"},
-}
-
-// Note that these must be sorted for this to work
-var attribute2 = []map[string]string{
-	{"schema": "public", "type": "TABLE", "relationship_name": "table1", "attribute_name": "column1", "attribute_acl": "c42ro=r/postgres"},
-	{"schema": "public", "type": "TABLE", "relationship_name": "table1", "attribute_name": "column1", "attribute_acl": "o42ro=r/postgres"},
-}
-
-func Test_diffGrants(t *testing.T) {
-	fmt.Println("-- ==========\n-- Grants - Relationships \n-- ==========")
-	var relSchema1 Schema = &GrantRelationshipSchema{rows: relationship1, rowNum: -1}
-	var relSchema2 Schema = &GrantRelationshipSchema{rows: relationship2, rowNum: -1}
-	doDiff(relSchema1, relSchema2)
-
-	fmt.Println("-- ==========\n-- Grants - Attributes \n-- ==========")
-	var attSchema1 Schema = &GrantAttributeSchema{rows: attribute1, rowNum: -1}
-	var attSchema2 Schema = &GrantAttributeSchema{rows: attribute2, rowNum: -1}
-	doDiff(attSchema1, attSchema2)
-}
-
 func doParseAcls(t *testing.T, acl string, expectedRole string, expectedPermCount int) {
 	fmt.Println("Testing", acl)
 	role, perms := parseAcl(acl)

From 38733e24d82a0f5da5ac10ccd4a884c50cb58906 Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Tue, 14 Nov 2017 21:18:29 -0600
Subject: [PATCH 36/74] Removed a few redundant tests

Former-commit-id: 19c4078ac2144eddda26af974b4887026c0fc36b
---
 role_test.go     | 48 -----------------------------
 schemata_test.go | 41 -------------------------
 sequence_test.go | 80 ------------------------------------------------
 table_test.go    | 79 -----------------------------------------------
 4 files changed, 248 deletions(-)
 delete mode 100644 role_test.go
 delete mode 100644 schemata_test.go
 delete mode 100644 sequence_test.go
 delete mode 100644 table_test.go

diff --git a/role_test.go b/role_test.go
deleted file mode 100644
index a1eab97..0000000
--- a/role_test.go
+++ /dev/null
@@ -1,48 +0,0 @@
-//
-// Copyright (c) 2014 Jon Carlson.  All rights reserved.
-// Use of this source code is governed by an MIT-style
-// license that can be found in the LICENSE file.
-//
-// grant_test.go
-package main
-
-import (
-	"fmt"
-	"testing"
-)
-
-/*
-SELECT r.rolname
-    , r.rolsuper
-    , r.rolinherit
-    , r.rolcreaterole
-    , r.rolcreatedb
-    , r.rolcanlogin
-    , r.rolconnlimit
-    , r.rolvaliduntil
-    , r.rolreplication
-FROM pg_catalog.pg_roles AS r
-ORDER BY r.rolname;
-*/
-
-// Note that these must be sorted by rolname for this to work
-var testRoles1a = []map[string]string{
-	{"rolname": "addme2", "rolsuper": "false", "rolinherit": "false", "rolcreaterole": "true", "rolcreatedb": "true", "rolcanlogin": "true", "rolconnlimit": "100", "rolvaliduntil": "null"},
-	{"rolname": "changeme", "rolsuper": "false", "rolinherit": "false", "rolcreaterole": "true", "rolcreatedb": "true", "rolcanlogin": "true", "rolconnlimit": "100", "rolvaliduntil": "null"},
-	{"rolname": "matchme", "rolsuper": "false", "rolinherit": "false", "rolcreaterole": "true", "rolcreatedb": "true", "rolcanlogin": "true", "rolconnlimit": "100", "rolvaliduntil": "null"},
-	{"rolname": "x-addme1", "rolsuper": "true", "rolinherit": "false", "rolcreaterole": "true", "rolcreatedb": "true", "rolcanlogin": "true", "rolconnlimit": "-1", "rolvaliduntil": "null"},
-}
-
-// Note that these must be sorted by rolname for this to work
-var testRoles1b = []map[string]string{
-	{"rolname": "changeme", "rolsuper": "false", "rolinherit": "false", "rolcreaterole": "false", "rolcreatedb": "false", "rolcanlogin": "true", "rolconnlimit": "10", "rolvaliduntil": "null"},
-	{"rolname": "deleteme"},
-	{"rolname": "matchme", "rolsuper": "false", "rolinherit": "false", "rolcreaterole": "true", "rolcreatedb": "true", "rolcanlogin": "true", "rolconnlimit": "100", "rolvaliduntil": "null"},
-}
-
-func Test_diffRoles(t *testing.T) {
-	fmt.Println("-- ==========\n-- Roles\n-- ==========")
-	var schema1 Schema = &RoleSchema{rows: testRoles1a, rowNum: -1}
-	var schema2 Schema = &RoleSchema{rows: testRoles1b, rowNum: -1}
-	doDiff(schema1, schema2)
-}
diff --git a/schemata_test.go b/schemata_test.go
deleted file mode 100644
index fa89ee6..0000000
--- a/schemata_test.go
+++ /dev/null
@@ -1,41 +0,0 @@
-//
-// Copyright (c) 2017 Jon Carlson.  All rights reserved.
-// Use of this source code is governed by an MIT-style
-// license that can be found in the LICENSE file.
-//
-// schemata_test.go
-package main
-
-import (
-	"fmt"
-	"testing"
-)
-
-/*
-SELECT schema_name
-    , schema_owner
-    , default_character_set_schema
-FROM information_schema.schemata
-WHERE table_schema NOT LIKE 'pg_%'
-WHERE table_schema <> 'information_schema'
-ORDER BY schema_name;
-*/
-
-// Note that these must be sorted by table name for this to work
-var testSchematas1 = []map[string]string{
-	{"schema_name": "schema_add", "schema_owner": "noop"},
-	{"schema_name": "schema_same", "schema_owner": "noop"},
-}
-
-// Note that these must be sorted by schema_name for this to work
-var testSchematas2 = []map[string]string{
-	{"schema_name": "schema_delete", "schema_owner": "noop"},
-	{"schema_name": "schema_same", "schema_owner": "noop"},
-}
-
-func Test_diffSchematas(t *testing.T) {
-	fmt.Println("-- ==========\n-- Schematas\n-- ==========")
-	var schema1 Schema = &SchemataSchema{rows: testSchematas1, rowNum: -1}
-	var schema2 Schema = &SchemataSchema{rows: testSchematas2, rowNum: -1}
-	doDiff(schema1, schema2)
-}
diff --git a/sequence_test.go b/sequence_test.go
deleted file mode 100644
index 87eb652..0000000
--- a/sequence_test.go
+++ /dev/null
@@ -1,80 +0,0 @@
-//
-// Copyright (c) 2017 Jon Carlson.  All rights reserved.
-// Use of this source code is governed by an MIT-style
-// license that can be found in the LICENSE file.
-//
-// sequence_test.go
-package main
-
-import (
-	"fmt"
-	"github.com/joncrlsn/pgutil"
-	"testing"
-)
-
-/*
-SELECT sequence_schema,
-    , {{if eq $.DbSchema "*" }}sequence_schema || '.' || {{end}}sequence_name AS compare_name
-    ,  sequence_name AS sequence_name
-	, data_type
-	, start_value
-	, minimum_value
-	, maximum_value
-	, increment
-	, cycle_option
-FROM information_schema.sequences
-WHERE true
-{{if eq $.DbSchema "*" }}
-AND sequence_schema NOT LIKE 'pg_%'
-AND sequence_schema <> 'information_schema'
-{{else}}
-AND sequence_schema = '{{$.DbSchema}}'
-{{end}}
-*/
-
-// Note that these must be sorted by schema and sequence name for this to work
-var testSequences1a = []map[string]string{
-	{"compare_name": "s1.add", "sequence_schema": "s1", "sequence_name": "s1_add"},
-	{"compare_name": "s1.same", "sequence_schema": "s1", "sequence_name": "same"},
-	{"compare_name": "s2.add", "sequence_schema": "s2", "sequence_name": "s2_add"},
-	{"compare_name": "s2.same", "sequence_schema": "s2", "sequence_name": "same"},
-}
-
-// Note that these must be sorted by schema and sequence name for this to work
-var testSequences1b = []map[string]string{
-	{"compare_name": "s1.delete", "sequence_schema": "s1", "sequence_name": "delete"},
-	{"compare_name": "s1.same", "sequence_schema": "s1", "sequence_name": "same"},
-	{"compare_name": "s2.same", "sequence_schema": "s2", "sequence_name": "same"},
-}
-
-func Test_diffSequencesAllSchemas(t *testing.T) {
-	fmt.Println("-- ==========\n-- Sequences all schemas \n-- ==========")
-	dbInfo1 = pgutil.DbInfo{DbSchema: "*"}
-	dbInfo2 = pgutil.DbInfo{DbSchema: "*"}
-	var schema1 Schema = &SequenceSchema{rows: testSequences1a, rowNum: -1}
-	var schema2 Schema = &SequenceSchema{rows: testSequences1b, rowNum: -1}
-	doDiff(schema1, schema2)
-}
-
-// =================================================================================================
-
-// Note that these must be sorted by compare_name (witout schema) for this to work
-var testSequences2a = []map[string]string{
-	{"compare_name": "add", "sequence_schema": "s1", "sequence_name": "add"},
-	{"compare_name": "same", "sequence_schema": "s1", "sequence_name": "same"},
-}
-
-// Note that these must be sorted by compare_name (witout schema) for this to work
-var testSequences2b = []map[string]string{
-	{"compare_name": "delete", "sequence_schema": "s2", "sequence_name": "delete"},
-	{"compare_name": "same", "sequence_schema": "s2", "sequence_name": "same"},
-}
-
-func Test_diffSequencesBetweenSchemas(t *testing.T) {
-	fmt.Println("-- ==========\n-- Sequences between schemas \n-- ==========")
-	dbInfo1 = pgutil.DbInfo{DbSchema: "s1"}
-	dbInfo2 = pgutil.DbInfo{DbSchema: "s2"}
-	var schema1 Schema = &SequenceSchema{rows: testSequences2a, rowNum: -1}
-	var schema2 Schema = &SequenceSchema{rows: testSequences2b, rowNum: -1}
-	doDiff(schema1, schema2)
-}
diff --git a/table_test.go b/table_test.go
deleted file mode 100644
index b544032..0000000
--- a/table_test.go
+++ /dev/null
@@ -1,79 +0,0 @@
-//
-// Copyright (c) 2017 Jon Carlson.  All rights reserved.
-// Use of this source code is governed by an MIT-style
-// license that can be found in the LICENSE file.
-//
-// table_test.go
-package main
-
-import (
-	"fmt"
-	"github.com/joncrlsn/pgutil"
-	"testing"
-)
-
-/*
-SELECT table_schema
-    , {{if eq $.DbSchema "*" }}table_schema || '.' || {{end}}table_name AS compare_name
-	, table_name
-    , CASE table_type
-	  WHEN 'BASE TABLE' THEN 'TABLE'
-	  ELSE table_type END AS table_type
-    , is_insertable_into
-FROM information_schema.tables
-WHERE table_type = 'BASE TABLE'
-{{if eq $.DbSchema "*" }}
-AND table_schema NOT LIKE 'pg_%'
-AND table_schema <> 'information_schema'
-{{else}}
-AND table_schema = '{{$.DbSchema}}'
-{{end}}
-ORDER BY compare_name;
-*/
-
-// Note that these must be sorted by schema and table name for this to work
-var testTables1a = []map[string]string{
-	{"compare_name": "s1.add", "table_schema": "s1", "table_name": "s1_add", "table_type": "TABLE"},
-	{"compare_name": "s1.same", "table_schema": "s1", "table_name": "same", "table_type": "TABLE"},
-	{"compare_name": "s2.add", "table_schema": "s2", "table_name": "s2_add", "table_type": "TABLE"},
-	{"compare_name": "s2.same", "table_schema": "s2", "table_name": "same", "table_type": "TABLE"},
-}
-
-// Note that these must be sorted by schema and table name for this to work
-var testTables1b = []map[string]string{
-	{"compare_name": "s1.delete", "table_schema": "s1", "table_name": "delete", "table_type": "TABLE"},
-	{"compare_name": "s1.same", "table_schema": "s1", "table_name": "same", "table_type": "TABLE"},
-	{"compare_name": "s2.same", "table_schema": "s2", "table_name": "same", "table_type": "TABLE"},
-}
-
-func Test_diffTablesAllSchemas(t *testing.T) {
-	fmt.Println("-- ==========\n-- Tables all schemas \n-- ==========")
-	dbInfo1 = pgutil.DbInfo{DbSchema: "*"}
-	dbInfo2 = pgutil.DbInfo{DbSchema: "*"}
-	var schema1 Schema = &TableSchema{rows: testTables1a, rowNum: -1}
-	var schema2 Schema = &TableSchema{rows: testTables1b, rowNum: -1}
-	doDiff(schema1, schema2)
-}
-
-// =================================================================================================
-
-// Note that these must be sorted by compare_name (witout schema) for this to work
-var testTables2a = []map[string]string{
-	{"compare_name": "add", "table_schema": "s1", "table_name": "add", "table_type": "TABLE"},
-	{"compare_name": "same", "table_schema": "s1", "table_name": "same", "table_type": "TABLE"},
-}
-
-// Note that these must be sorted by compare_name (witout schema) for this to work
-var testTables2b = []map[string]string{
-	{"compare_name": "delete", "table_schema": "s2", "table_name": "delete", "table_type": "TABLE"},
-	{"compare_name": "same", "table_schema": "s2", "table_name": "same", "table_type": "TABLE"},
-}
-
-func Test_diffTablesBetweenSchemas(t *testing.T) {
-	fmt.Println("-- ==========\n-- Tables between schemas \n-- ==========")
-	dbInfo1 = pgutil.DbInfo{DbSchema: "s1"}
-	dbInfo2 = pgutil.DbInfo{DbSchema: "s2"}
-	var schema1 Schema = &TableSchema{rows: testTables2a, rowNum: -1}
-	var schema2 Schema = &TableSchema{rows: testTables2b, rowNum: -1}
-	doDiff(schema1, schema2)
-}

From d1864a023d1b81db821316e699fd227b6c18c38c Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Thu, 16 Nov 2017 17:33:15 -0600
Subject: [PATCH 37/74] Updated the README

Former-commit-id: eca13f0a561c023a0d7eca7d10d0b3e95c5de315
---
 README.md | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

diff --git a/README.md b/README.md
index f99b68c..7b6ba6c 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ pgdiff is written to be easy to expand and improve the accuracy of the diff.
 
 (where options and &lt;schemaType&gt; are listed below)
 
-I have found that there is an ideal order for running the different schema types.  This order should minimize the problems you encounter.  For example, you will always want to add new tables before you add new columns.  This is the order that has worked for me.
+There seems to be an ideal order for running the different schema types.  This order should minimize the problems you encounter.  For example, you will always want to add new tables before you add new columns.
 
 In addition, some types can have dependencies which are not in the right order.  A classic case is views which depend on other views.  The missing view SQL is generated in alphabetical order so if a view create fails due to a missing view, just run the views SQL file over again. The pgdiff.sh script will prompt you about running it again.
  
@@ -63,8 +63,8 @@ options           | explanation
   -p, --port2     | second db port number. default is 5432
   -D, --dbname1   | first db name
   -d, --dbname2   | second db name
-  -S, --schema1   | first schema name.  default is public
-  -s, --schema2   | second schema name. default is public
+  -S, --schema1   | first schema name.  default is * (all non-system schemas)
+  -s, --schema2   | second schema name. default is * (all non-system schemas)
   -O, --option1   | first db options. example: sslmode=disable
   -o, --option2   | second db options. example: sslmode=disable
 
@@ -84,17 +84,21 @@ linux and osx binaries are packaged with an extra, optional bash script and pgru
 ### getting started on windows
 
 1. download pgdiff.exe from the bin-win directory on github
-1. edit the db connection defaults in pgdiff.sh or...
-1. manually run pgdiff for each schema type listed in the usage section above
-1. review the SQL output and, if you want to make them match, run it against db2 
+1. either install cygwin so you can run pgdiff.sh or...
+1. manually run pgdiff.exe for each schema type listed in the usage section above
+1. review the SQL output and, if you want to make them match, run it against the second db
+
+Unfortunately this project does not have as much support for Windows as it does for Linux and Mac.  If you are inclined to write a Windows complement to the pgdiff.sh script, feel free to contribute it or we can link to it.  Even better would be a replacement written in Go.
 
 
 ### version history
 1. 0.9.0 - Implemented ROLE, SEQUENCE, TABLE, COLUMN, INDEX, FOREIGN\_KEY, OWNER, GRANT\_RELATIONSHIP, GRANT\_ATTRIBUTE
 1. 0.9.1 - Added VIEW, FUNCTION, and TRIGGER (Thank you, Shawn Carroll AKA SparkeyG)
 1. 0.9.2 - Fixed bug when using the non-default port
-1. 0.9.3 - Added support for comparing two different schemas, one schema between databases, or all schemas.  Fixed VARCHAR bug when no max length specified
+1. 0.9.3 - Added support for comparing two different schemas (same or different db), one schema between databases, or all schemas between databases.  Fixed VARCHAR bug when no max length specified.
 
+### getting help
+If you think you found a bug, find the appropriate test script (in the test directory) and modify it to show the problem.  Attach the script to an Issue request.
 
 ### todo
 1. fix SQL for adding an array column

From e8737703b8d61b76f8aa26d93eaaf14336a07333 Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Thu, 16 Nov 2017 17:38:32 -0600
Subject: [PATCH 38/74] Updated the testing README

Former-commit-id: 7c07b451ae7c63c4a7ac87063961f854b3dc828a
---
 test/README.md | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/test/README.md b/test/README.md
index 72ac18e..59dc1ea 100644
--- a/test/README.md
+++ b/test/README.md
@@ -1,6 +1,8 @@
-These are not automated tests (as much as I'd rather have automated tests), but manual
+These are not automated tests (I'd rather have automated tests), but manual
 integration tests for verifying that individual schema types are working.
 
+These can be good templates for isolating bugs in the different data diffs.
 
 Connect to the database manually:
   sudo su - postgres -- -c "psql -d db1"
+

From ff1ee4a082abd8812748ccd85447a7078712b6e9 Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Thu, 16 Nov 2017 20:07:23 -0600
Subject: [PATCH 39/74] Improved build.sh, replaced old binary links in the
 README

Former-commit-id: 6f517ad24e46a93385734eacbb0a8249e9e60dce
---
 README.md                           |  4 ++--
 bin-linux/pgdiff.tgz.REMOVED.git-id |  1 -
 bin-osx/pgdiff.tgz.REMOVED.git-id   |  1 -
 bin-win/pgdiff.exe.REMOVED.git-id   |  1 -
 bin-win/pgrun.exe.REMOVED.git-id    |  1 -
 build.sh                            | 30 ++++++++++++++++++++++++-----
 6 files changed, 27 insertions(+), 11 deletions(-)
 delete mode 100644 bin-linux/pgdiff.tgz.REMOVED.git-id
 delete mode 100644 bin-osx/pgdiff.tgz.REMOVED.git-id
 delete mode 100644 bin-win/pgdiff.exe.REMOVED.git-id
 delete mode 100644 bin-win/pgrun.exe.REMOVED.git-id

diff --git a/README.md b/README.md
index 7b6ba6c..3b8ba45 100644
--- a/README.md
+++ b/README.md
@@ -7,8 +7,8 @@ pgdiff is transparent in what it does, so it never modifies a database directly.
 pgdiff is written to be easy to expand and improve the accuracy of the diff.
 
 
-### download
-[osx](https://github.com/joncrlsn/pgdiff/raw/master/bin-osx/pgdiff.tgz "OSX version") &nbsp; [linux](https://github.com/joncrlsn/pgdiff/raw/master/bin-linux/pgdiff.tgz "Linux version") &nbsp; [windows](https://github.com/joncrlsn/pgdiff/raw/master/bin-win/pgdiff.exe "Windows version")
+### download 1.0 beta 1
+[osx](https://github.com/joncrlsn/pgdiff/releases/download/v1.0-beta.1/pgdiff-osx-1.0b1.tar.gz "OSX version") &nbsp; [linux](https://github.com/joncrlsn/pgdiff/files/1480823/pgdiff-linux-1.0b1.tar.gz  "Linux version") &nbsp; [windows](https://github.com/joncrlsn/pgdiff/releases/download/v1.0-beta.1/pgdiff-win-1.0b1.zip "Windows version")
 
 
 ### usage
diff --git a/bin-linux/pgdiff.tgz.REMOVED.git-id b/bin-linux/pgdiff.tgz.REMOVED.git-id
deleted file mode 100644
index 7856fd2..0000000
--- a/bin-linux/pgdiff.tgz.REMOVED.git-id
+++ /dev/null
@@ -1 +0,0 @@
-645910252828d67b3934b92640f9919910be3f72
\ No newline at end of file
diff --git a/bin-osx/pgdiff.tgz.REMOVED.git-id b/bin-osx/pgdiff.tgz.REMOVED.git-id
deleted file mode 100644
index 81df825..0000000
--- a/bin-osx/pgdiff.tgz.REMOVED.git-id
+++ /dev/null
@@ -1 +0,0 @@
-5e3b173cb942e7c0b98ab450835cb8cd577bffb9
\ No newline at end of file
diff --git a/bin-win/pgdiff.exe.REMOVED.git-id b/bin-win/pgdiff.exe.REMOVED.git-id
deleted file mode 100644
index 5503484..0000000
--- a/bin-win/pgdiff.exe.REMOVED.git-id
+++ /dev/null
@@ -1 +0,0 @@
-546e07755e6b1683a0e70013d0d03223e7f03c3f
\ No newline at end of file
diff --git a/bin-win/pgrun.exe.REMOVED.git-id b/bin-win/pgrun.exe.REMOVED.git-id
deleted file mode 100644
index 1cc8f19..0000000
--- a/bin-win/pgrun.exe.REMOVED.git-id
+++ /dev/null
@@ -1 +0,0 @@
-1675e9f7afb3e2aa2a11db7aa34ccc08f7637a94
\ No newline at end of file
diff --git a/build.sh b/build.sh
index a9a9c5f..4304246 100755
--- a/build.sh
+++ b/build.sh
@@ -9,6 +9,7 @@
 SCRIPT_DIR="$(dirname `ls -l $0 | awk '{ print $NF }'`)"
 
 [[ -z $APPNAME ]] && APPNAME=pgdiff
+[[ -z $VERSION ]] && read -p "Enter version number: " VERSION
 
 if [[ -d bin-linux ]]; then
     echo "  ==== Building Linux ===="
@@ -22,12 +23,14 @@ if [[ -d bin-linux ]]; then
     wget -O "$workdir/pgrun" "https://github.com/joncrlsn/pgrun/raw/master/bin-linux/pgrun"
     # Copy the bash runtime script to the temp directory
     cp pgdiff.sh "$workdir/"
+    cp "${SCRIPT_DIR}/bin-linux/README.md" "$workdir/"
     cd "$tempdir"
     # Make everything executable
     chmod -v ugo+x $APPNAME/*
-    COPYFILE_DISABLE=true tar -cvzf "${APPNAME}.tgz" $APPNAME
+    tarName="${tempdir}/${APPNAME}-linux-${VERSION}.tar.gz"
+    COPYFILE_DISABLE=true tar -cvzf "$tarName" $APPNAME
     cd -
-    mv "${tempdir}/${APPNAME}.tgz" "${SCRIPT_DIR}/bin-linux/"
+    mv "$tarName" "${SCRIPT_DIR}/bin-linux/"
     echo "Built linux."
 else
     echo "Skipping linux.  No bin-linux directory."
@@ -45,12 +48,14 @@ if [[ -d bin-osx ]]; then
     wget -O "$workdir/pgrun" "https://github.com/joncrlsn/pgrun/raw/master/bin-osx/pgrun"
     # Copy the bash runtime script to the temp directory
     cp pgdiff.sh "$workdir/"
+    cp "${SCRIPT_DIR}/bin-osx/README.md" "$workdir/"
     cd "$tempdir"
     # Make everything executable
     chmod -v ugo+x $APPNAME/*
-    COPYFILE_DISABLE=true tar -cvzf "${APPNAME}.tgz" $APPNAME
+    tarName="${tempdir}/${APPNAME}-osx-${VERSION}.tar.gz"
+    COPYFILE_DISABLE=true tar -cvzf "$tarName" $APPNAME
     cd -
-    mv "${tempdir}/${APPNAME}.tgz" "${SCRIPT_DIR}/bin-osx/"
+    mv "$tarName" "${SCRIPT_DIR}/bin-osx/"
     echo "Built osx."
 else
     echo "Skipping osx.  No bin-osx directory."
@@ -58,7 +63,22 @@ fi
 
 if [[ -d bin-win ]]; then
     echo "  ==== Building Windows ===="
-    GOOS=windows GOARCH=386 go build -o bin-win/${APPNAME}.exe
+    tempdir="$(mktemp -d)"
+    workdir="$tempdir/$APPNAME"
+    echo $workdir
+    mkdir -p $workdir
+    GOOS=windows GOARCH=386 go build -o "${workdir}/${APPNAME}.exe"
+    # Download pgrun to the work directory 
+    # Copy the bash runtime script to the temp directory
+    cp "${SCRIPT_DIR}/bin-win/README.md" "$workdir/"
+    cd "$tempdir"
+    # Make everything executable
+    chmod -v ugo+x $APPNAME/*
+    wget -O "${workdir}/pgrun.exe" "https://github.com/joncrlsn/pgrun/raw/master/bin-win/pgrun.exe"
+    zipName="${tempdir}/${APPNAME}-win-${VERSION}.zip"
+    zip -r "$zipName" $APPNAME
+    cd -
+    mv "$zipName" "${SCRIPT_DIR}/bin-win/"
     echo "Built win."
 else
     echo "Skipping win.  No bin-win directory."

From 8b9e1adb2a87ffff9769fd021ad1ae405c848d4f Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Thu, 16 Nov 2017 20:24:36 -0600
Subject: [PATCH 40/74] Updated version information in README

Former-commit-id: 0a6b3a40fd66c36b28c36905c029024105d15c8b
---
 README.md | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 3b8ba45..411ddd7 100644
--- a/README.md
+++ b/README.md
@@ -88,17 +88,18 @@ linux and osx binaries are packaged with an extra, optional bash script and pgru
 1. manually run pgdiff.exe for each schema type listed in the usage section above
 1. review the SQL output and, if you want to make them match, run it against the second db
 
-Unfortunately this project does not have as much support for Windows as it does for Linux and Mac.  If you are inclined to write a Windows complement to the pgdiff.sh script, feel free to contribute it or we can link to it.  Even better would be a replacement written in Go.
+This project works on Windows, just not as nicely as it does for Linux and Mac.  If you are inclined to write a Windows complement to the pgdiff.sh script, feel free to contribute it or we can link to it.  Even better would be a replacement written in Go.
 
 
 ### version history
 1. 0.9.0 - Implemented ROLE, SEQUENCE, TABLE, COLUMN, INDEX, FOREIGN\_KEY, OWNER, GRANT\_RELATIONSHIP, GRANT\_ATTRIBUTE
 1. 0.9.1 - Added VIEW, FUNCTION, and TRIGGER (Thank you, Shawn Carroll AKA SparkeyG)
 1. 0.9.2 - Fixed bug when using the non-default port
-1. 0.9.3 - Added support for comparing two different schemas (same or different db), one schema between databases, or all schemas between databases.  Fixed VARCHAR bug when no max length specified.
+1. 0.9.3 - Fixed VARCHAR bug when no max length specified
+1. 1.0.0 - Adding support for comparing two different schemas (same or different db), one schema between databases, or all schemas between databases.
 
 ### getting help
-If you think you found a bug, find the appropriate test script (in the test directory) and modify it to show the problem.  Attach the script to an Issue request.
+If you think you found a bug, it might help me replicate it if you find the appropriate test script (in the test directory) and modify it to show the problem.  Attach the script to an Issue request.
 
 ### todo
 1. fix SQL for adding an array column

From e0ec46f01d75823fcc1e4d3d131ea32412787f28 Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Thu, 16 Nov 2017 21:53:38 -0600
Subject: [PATCH 41/74] Minor improvements to the READMEs

---
 .gitignore          |  3 ---
 README.md           | 18 +++++++++---------
 bin-linux/README.md |  6 +++---
 bin-osx/README.md   |  8 ++++----
 bin-win/README.md   |  9 +++++++++
 5 files changed, 25 insertions(+), 19 deletions(-)
 create mode 100644 bin-win/README.md

diff --git a/.gitignore b/.gitignore
index 1d6f4f3..8ae2e97 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1 @@
-bin-linux
-bin-win
-bin-osx
 pgdiff
diff --git a/README.md b/README.md
index 411ddd7..7b3e2f7 100644
--- a/README.md
+++ b/README.md
@@ -92,17 +92,17 @@ This project works on Windows, just not as nicely as it does for Linux and Mac.
 
 
 ### version history
-1. 0.9.0 - Implemented ROLE, SEQUENCE, TABLE, COLUMN, INDEX, FOREIGN\_KEY, OWNER, GRANT\_RELATIONSHIP, GRANT\_ATTRIBUTE
-1. 0.9.1 - Added VIEW, FUNCTION, and TRIGGER (Thank you, Shawn Carroll AKA SparkeyG)
-1. 0.9.2 - Fixed bug when using the non-default port
-1. 0.9.3 - Fixed VARCHAR bug when no max length specified
-1. 1.0.0 - Adding support for comparing two different schemas (same or different db), one schema between databases, or all schemas between databases.
+* 0.9.0 - Implemented ROLE, SEQUENCE, TABLE, COLUMN, INDEX, FOREIGN\_KEY, OWNER, GRANT\_RELATIONSHIP, GRANT\_ATTRIBUTE
+* 0.9.1 - Added VIEW, FUNCTION, and TRIGGER (Thank you, Shawn Carroll AKA SparkeyG)
+* 0.9.2 - Fixed bug when using the non-default port
+* 0.9.3 - Fixed VARCHAR bug when no max length specified
+* 1.0.0 - Adding support for comparing two different schemas (same or different db), one schema between databases, or all schemas between databases. (Also removed binaries from git repository)
 
 ### getting help
 If you think you found a bug, it might help me replicate it if you find the appropriate test script (in the test directory) and modify it to show the problem.  Attach the script to an Issue request.
 
 ### todo
-1. fix SQL for adding an array column
-1. create windows version of pgdiff.sh (or even better: re-write it all in Go)
-1. allow editing of individual SQL lines after failure (this would probably be done in the script pgdiff.sh)
-1. store failed SQL statements in an error file for later fixing and rerunning?
+* fix SQL for adding an array column
+* create windows version of pgdiff.sh (or even better: re-write it all in Go)
+* allow editing of individual SQL lines after failure (this would probably be done in the script pgdiff.sh)
+* store failed SQL statements in an error file for later fixing and rerunning?
diff --git a/bin-linux/README.md b/bin-linux/README.md
index 967bc0d..e6e3e41 100644
--- a/bin-linux/README.md
+++ b/bin-linux/README.md
@@ -2,10 +2,10 @@
 
 These instructions will guide you through the process of generating SQL, reviewing it, and optionally running it on the target database. It requires a familiarity with a Linux command-line shell.
 
-1. download pgdiff.tgz to your machine
-1. untar pgdiff.tgz (a new directory will be created: called pgdiff)
+1. download pgdiff-linux-\<version\>.tar.gz to your machine
+1. untar it (a new directory will be created: called pgdiff)
 1. cd into the new pgdiff directory
-1. optionally edit pgdiff.sh to change the db access values... or set them at runtime (i.e. USER1=joe NAME1=mydb USER2=joe NAME2=myotherdb ./pgdiff.sh)
+1. edit pgdiff.sh to change the db access values... or set them at runtime (i.e. USER1=joe NAME1=mydb USER2=joe NAME2=myotherdb ./pgdiff.sh)
 1. run pgdiff.sh
 
 ## tar contents
diff --git a/bin-osx/README.md b/bin-osx/README.md
index fa51472..9611c66 100644
--- a/bin-osx/README.md
+++ b/bin-osx/README.md
@@ -2,10 +2,10 @@
 
 These instructions will guide you through the process of generating SQL, reviewing it, and optionally running it on the target database.  It requires a familiarity with the bash shell in OSX.
 
-1. download pgdiff.tgz to your machine
-1. untar pgdiff.tgz (a new directory will be created: called pgdiff)
+1. download pgdiff-mac-\<version\>.tar.gz to your machine
+1. untar it (a new directory will be created: called pgdiff)
 1. cd into the new pgdiff directory
-1. optionally edit pgdiff.sh to change the db access values... or set them at runtime (i.e. USER1=joe NAME1=mydb USER2=joe NAME2=myotherdb ./pgdiff.sh)
+1. edit pgdiff.sh to change the db access values... or set them at runtime (i.e. USER1=joe NAME1=mydb USER2=joe NAME2=myotherdb ./pgdiff.sh)
 1. run pgdiff.sh
 
 ## tar contents
@@ -13,4 +13,4 @@ These instructions will guide you through the process of generating SQL, reviewi
 * pgrun - an OSX executable for running SQL
 * pgdiff.sh - a bash shell script to coordinate your interaction with pgdiff and pgrun
 
-If you write a Go version of pgdiff.sh, please share it and I'll include it for others to use (with your copyright information intact). 
+If you write a Go version of pgdiff.sh, please share it so we can include it or link to it for others to use.
diff --git a/bin-win/README.md b/bin-win/README.md
new file mode 100644
index 0000000..ab9f95c
--- /dev/null
+++ b/bin-win/README.md
@@ -0,0 +1,9 @@
+### getting started on windows
+
+1. download pgdiff.exe from the release page on github
+1. either install cygwin so you can run pgdiff.sh or...
+1. manually run pgdiff.exe for each schema type listed in the usage section above
+1. review the SQL output and, if you want to make them match, run it against the second db
+
+This project works on Windows, just not as nicely as it does for Linux and Mac.  If you are inclined to write a Windows complement to the pgdiff.sh script, feel free to contribute it or we can link to it.  Even better would be a replacement written in Go.
+

From 67762b839acac6fc141d1672f57b754472c502d6 Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Fri, 17 Nov 2017 17:33:31 -0600
Subject: [PATCH 42/74] Moved platform-specific READMEs

---
 bin-linux/README.md => README-linux.md | 0
 bin-osx/README.md => README-osx.md     | 0
 bin-win/README.md => README-win.md     | 0
 3 files changed, 0 insertions(+), 0 deletions(-)
 rename bin-linux/README.md => README-linux.md (100%)
 rename bin-osx/README.md => README-osx.md (100%)
 rename bin-win/README.md => README-win.md (100%)

diff --git a/bin-linux/README.md b/README-linux.md
similarity index 100%
rename from bin-linux/README.md
rename to README-linux.md
diff --git a/bin-osx/README.md b/README-osx.md
similarity index 100%
rename from bin-osx/README.md
rename to README-osx.md
diff --git a/bin-win/README.md b/README-win.md
similarity index 100%
rename from bin-win/README.md
rename to README-win.md

From 15d34316aabb03626dc2fadb69fd646e0e516730 Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Fri, 17 Nov 2017 17:42:53 -0600
Subject: [PATCH 43/74] Refactored to remove bin directories

---
 build.sh | 50 +++++++++++++++++++++++++++++++++-----------------
 1 file changed, 33 insertions(+), 17 deletions(-)

diff --git a/build.sh b/build.sh
index 4304246..20de750 100755
--- a/build.sh
+++ b/build.sh
@@ -1,9 +1,16 @@
 #!/bin/bash
 #
-# For OSX and Linux, this script:
+# Builds executable and downloadable bundle for 3 platforms
+#
+# For OSX and Linux:
 #  * builds pgdiff 
 #  * downloads pgrun 
-#  * combines them and pgdiff.sh into a tgz file
+#  * combines them, a README, and pgdiff.sh into a tgz file
+#
+# For Windows:
+#  * builds pgdiff.exe
+#  * downloads pgrun.exe
+#  * combines them, a README, and pgdiff.sh into a zip file
 #
 
 SCRIPT_DIR="$(dirname `ls -l $0 | awk '{ print $NF }'`)"
@@ -11,7 +18,16 @@ SCRIPT_DIR="$(dirname `ls -l $0 | awk '{ print $NF }'`)"
 [[ -z $APPNAME ]] && APPNAME=pgdiff
 [[ -z $VERSION ]] && read -p "Enter version number: " VERSION
 
-if [[ -d bin-linux ]]; then
+LINUX_README=README-linux.md
+LINUX_FILE="${APPNAME}-linux-${VERSION}.tar.gz"
+
+OSX_README=README-osx.md
+OSX_FILE="${APPNAME}-osx-${VERSION}.tar.gz"
+
+WIN_README=README-win.md
+WIN_FILE="${APPNAME}-win-${VERSION}.zip"
+
+if [[ -f $LINUX_README ]]; then
     echo "  ==== Building Linux ===="
     tempdir="$(mktemp -d)"
     workdir="$tempdir/$APPNAME"
@@ -23,20 +39,20 @@ if [[ -d bin-linux ]]; then
     wget -O "$workdir/pgrun" "https://github.com/joncrlsn/pgrun/raw/master/bin-linux/pgrun"
     # Copy the bash runtime script to the temp directory
     cp pgdiff.sh "$workdir/"
-    cp "${SCRIPT_DIR}/bin-linux/README.md" "$workdir/"
+    cp "${SCRIPT_DIR}/${LINUX_README}" "$workdir/README.md"
     cd "$tempdir"
     # Make everything executable
     chmod -v ugo+x $APPNAME/*
-    tarName="${tempdir}/${APPNAME}-linux-${VERSION}.tar.gz"
+    tarName="${tempdir}/${LINUX_FILE}"
     COPYFILE_DISABLE=true tar -cvzf "$tarName" $APPNAME
     cd -
-    mv "$tarName" "${SCRIPT_DIR}/bin-linux/"
+    mv "$tarName" "${SCRIPT_DIR}/"
     echo "Built linux."
 else
-    echo "Skipping linux.  No bin-linux directory."
+    echo "Skipping linux.  No $LINUX_README file."
 fi
 
-if [[ -d bin-osx ]]; then
+if [[ -f $OSX_README ]]; then
     echo "  ==== Building OSX ===="
     tempdir="$(mktemp -d)"
     workdir="$tempdir/$APPNAME"
@@ -48,20 +64,20 @@ if [[ -d bin-osx ]]; then
     wget -O "$workdir/pgrun" "https://github.com/joncrlsn/pgrun/raw/master/bin-osx/pgrun"
     # Copy the bash runtime script to the temp directory
     cp pgdiff.sh "$workdir/"
-    cp "${SCRIPT_DIR}/bin-osx/README.md" "$workdir/"
+    cp "${SCRIPT_DIR}/${OSX_README}" "$workdir/README.md"
     cd "$tempdir"
     # Make everything executable
     chmod -v ugo+x $APPNAME/*
-    tarName="${tempdir}/${APPNAME}-osx-${VERSION}.tar.gz"
+    tarName="${tempdir}/${OSX_FILE}"
     COPYFILE_DISABLE=true tar -cvzf "$tarName" $APPNAME
     cd -
-    mv "$tarName" "${SCRIPT_DIR}/bin-osx/"
+    mv "$tarName" "${SCRIPT_DIR}/"
     echo "Built osx."
 else
-    echo "Skipping osx.  No bin-osx directory."
+    echo "Skipping osx.  No $OSX_README file."
 fi
 
-if [[ -d bin-win ]]; then
+if [[ -f $WIN_README ]]; then
     echo "  ==== Building Windows ===="
     tempdir="$(mktemp -d)"
     workdir="$tempdir/$APPNAME"
@@ -70,16 +86,16 @@ if [[ -d bin-win ]]; then
     GOOS=windows GOARCH=386 go build -o "${workdir}/${APPNAME}.exe"
     # Download pgrun to the work directory 
     # Copy the bash runtime script to the temp directory
-    cp "${SCRIPT_DIR}/bin-win/README.md" "$workdir/"
+    cp "${SCRIPT_DIR}/${WIN_README}" "$workdir/README.md"
     cd "$tempdir"
     # Make everything executable
     chmod -v ugo+x $APPNAME/*
     wget -O "${workdir}/pgrun.exe" "https://github.com/joncrlsn/pgrun/raw/master/bin-win/pgrun.exe"
-    zipName="${tempdir}/${APPNAME}-win-${VERSION}.zip"
+    zipName="${tempdir}/${WIN_FILE}"
     zip -r "$zipName" $APPNAME
     cd -
-    mv "$zipName" "${SCRIPT_DIR}/bin-win/"
+    mv "$zipName" "${SCRIPT_DIR}/"
     echo "Built win."
 else
-    echo "Skipping win.  No bin-win directory."
+    echo "Skipping win.  No $WIN_README file."
 fi

From 20eba81657ff2a475a2f56321ca4e65ad31c16df Mon Sep 17 00:00:00 2001
From: Jon Carlson <joncrlsn@gmail.com>
Date: Fri, 17 Nov 2017 19:49:50 -0600
Subject: [PATCH 44/74] Minor fixes to README

---
 README.md | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md
index 7b3e2f7..24e5058 100644
--- a/README.md
+++ b/README.md
@@ -41,8 +41,8 @@ Schema type ordering:
 I have found it helpful to take ```--schema-only``` dumps of the databases in question, load them into a local postgres, then do my sql generation and testing there before running the SQL against a more official database. Your local postgres instance will need the correct users/roles populated because db dumps do not copy that information.
 
 ```
-pgdiff -U dbuser -H localhost -D refDB  -O "sslmode=disable" -S "public" \
-       -u dbuser -h localhost -d compDB -o "sslmode=disable" -s "public" \
+pgdiff -U dbuser -H localhost -D refDB  -O "sslmode=disable" -S public \
+       -u dbuser -h localhost -d compDB -o "sslmode=disable" -s public \
        TABLE 
 ```
 
@@ -78,7 +78,7 @@ linux and osx binaries are packaged with an extra, optional bash script and pgru
 1. cd to the new pgdiff directory
 1. edit the db connection defaults in pgdiff.sh 
 1. ...or manually run pgdiff for each schema type listed in the usage section above
-1. review the SQL output for each schema type and, if you want to make them match, run it against db2 (Function SQL requires the use of pgrun instead of psql)
+1. review the SQL output for each schema type and, if you want to make them match, run it against the second db
 
 
 ### getting started on windows
@@ -99,7 +99,7 @@ This project works on Windows, just not as nicely as it does for Linux and Mac.
 * 1.0.0 - Adding support for comparing two different schemas (same or different db), one schema between databases, or all schemas between databases. (Also removed binaries from git repository)
 
 ### getting help
-If you think you found a bug, it might help me replicate it if you find the appropriate test script (in the test directory) and modify it to show the problem.  Attach the script to an Issue request.
+If you think you found a bug, it might help replicate it if you find the appropriate test script (in the test directory) and modify it to show the problem.  Attach the script to an Issue request.
 
 ### todo
 * fix SQL for adding an array column

From b3eda33185ed8d9175870e67ddcc57baac67f2e4 Mon Sep 17 00:00:00 2001
From: Dubreil Romain <rdl@provexi.fr>
Date: Wed, 13 Dec 2017 16:53:49 +0100
Subject: [PATCH 45/74] Add option TABLE_COLUMN

Only table columns are compared without taking view columns into account
---
 column.go              | 61 ++++++++++++++++++++++++++++---
 pgdiff.go              |  4 ++-
 test/test-table-column | 82 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 141 insertions(+), 6 deletions(-)
 create mode 100755 test/test-table-column

diff --git a/column.go b/column.go
index 48a0f49..6177a8f 100644
--- a/column.go
+++ b/column.go
@@ -48,6 +48,43 @@ ORDER BY compare_name ASC;
 	return t
 }
 
+var (
+	tableColumnSqlTemplate = initTableColumnSqlTemplate()
+)
+
+// Initializes the Sql template
+func initTableColumnSqlTemplate() *template.Template {
+	sql := `
+SELECT a.table_schema
+    , {{if eq $.DbSchema "*" }}a.table_schema || '.' || {{end}}a.table_name || '.' || column_name  AS compare_name
+	, a.table_name
+    , column_name
+    , data_type
+    , is_nullable
+    , column_default
+    , character_maximum_length
+FROM information_schema.columns a
+INNER JOIN information_schema.tables b
+    ON a.table_schema = b.table_schema AND
+       a.table_name = b.table_name AND
+       b.table_type = 'BASE TABLE'
+WHERE is_updatable = 'YES'
+{{if eq $.DbSchema "*" }}
+AND a.table_schema NOT LIKE 'pg_%' 
+AND a.table_schema <> 'information_schema' 
+{{else}}
+AND a.table_schema = '{{$.DbSchema}}'
+{{end}}
+{{ if $.TableType }}
+AND b.table_type = '{{ $.TableType }}'
+{{ end }}
+ORDER BY compare_name ASC;
+`
+	t := template.New("ColumnSqlTmpl")
+	template.Must(t.Parse(sql))
+	return t
+}
+
 // ==================================
 // Column Rows definition
 // ==================================
@@ -214,14 +251,13 @@ func (c *ColumnSchema) Change(obj interface{}) {
 // Standalone Functions
 // ==================================
 
-// compareColumns outputs SQL to make the columns match between two databases or schemas
-func compareColumns(conn1 *sql.DB, conn2 *sql.DB) {
-
+// compare outputs SQL to make the columns match between two databases or schemas
+func compare(conn1 *sql.DB, conn2 *sql.DB, tpl *template.Template) {
 	buf1 := new(bytes.Buffer)
-	columnSqlTemplate.Execute(buf1, dbInfo1)
+	tpl.Execute(buf1, dbInfo1)
 
 	buf2 := new(bytes.Buffer)
-	columnSqlTemplate.Execute(buf2, dbInfo2)
+	tpl.Execute(buf2, dbInfo2)
 
 	rowChan1, _ := pgutil.QueryStrings(conn1, buf1.String())
 	rowChan2, _ := pgutil.QueryStrings(conn2, buf2.String())
@@ -246,6 +282,21 @@ func compareColumns(conn1 *sql.DB, conn2 *sql.DB) {
 
 	// Compare the columns
 	doDiff(schema1, schema2)
+
+}
+
+// compareColumns outputs SQL to make the columns match between two databases or schemas
+func compareColumns(conn1 *sql.DB, conn2 *sql.DB) {
+
+    compare(conn1, conn2, columnSqlTemplate)
+
+}
+
+// compareColumns outputs SQL to make the tables columns (without views columns) match between two databases or schemas
+func compareTableColumns(conn1 *sql.DB, conn2 *sql.DB) {
+
+    compare(conn1, conn2, tableColumnSqlTemplate)
+
 }
 
 // getMaxLength returns the maximum length and whether or not it is valid
diff --git a/pgdiff.go b/pgdiff.go
index 0bb9ab6..43e16df 100644
--- a/pgdiff.go
+++ b/pgdiff.go
@@ -121,6 +121,8 @@ func main() {
 		compareTables(conn1, conn2)
 	} else if schemaType == "COLUMN" {
 		compareColumns(conn1, conn2)
+	} else if schemaType == "TABLE_COLUMN" {
+		compareTableColumns(conn1, conn2)
 	} else if schemaType == "INDEX" {
 		compareIndexes(conn1, conn2)
 	} else if schemaType == "VIEW" {
@@ -203,7 +205,7 @@ Options:
   -S, --schema1 : first schema.  default is all schemas
   -s, --schema2 : second schema. default is all schemas
 
-<schemaTpe> can be: SCHEMA ROLE, SEQUENCE, TABLE, VIEW, COLUMN, INDEX, FOREIGN_KEY, OWNER, GRANT_RELATIONSHIP, GRANT_ATTRIBUTE
+<schemaTpe> can be: SCHEMA ROLE, SEQUENCE, TABLE, VIEW, COLUMN, TABLE_COLUMN, INDEX, FOREIGN_KEY, OWNER, GRANT_RELATIONSHIP, GRANT_ATTRIBUTE
 `)
 
 	os.Exit(2)
diff --git a/test/test-table-column b/test/test-table-column
new file mode 100755
index 0000000..baae26a
--- /dev/null
+++ b/test/test-table-column
@@ -0,0 +1,82 @@
+#!/bin/bash
+#
+# Useful for visually inspecting the output SQL to verify it is doing what it should
+#
+
+source ./start-fresh.sh >/dev/null
+
+echo
+echo ==============================================================
+
+#
+# Compare the table columns between two schemas in the same database
+#
+#psql -U u1 -h localhost -d db1 <<'EOS'
+./populate-db.sh db1 "
+    CREATE SCHEMA s1;
+    CREATE TABLE s1.table9 (
+        id integer,
+        name varchar(50)
+    );
+    CREATE TABLE s1.table10 (id bigint);
+    CREATE TABLE s1.table11 ();
+
+    CREATE SCHEMA s2;
+    CREATE TABLE s2.table9 (  -- Add name column
+        id integer
+    );
+    CREATE TABLE s2.table10 (id integer); -- change id to bigint
+    CREATE TABLE s2.table11 (id integer); -- drop id column
+    CREATE OR REPLACE VIEW s1.view1 AS
+        SELECT *
+        FROM s1.table10;
+"
+
+echo
+echo "# Compare the columns between two schemas in the same database"
+echo "# Expect SQL:"
+echo "#   Add s2.table9.name"
+echo "#   Change s2.table10.id to bigint"
+echo "#   Drop s2.table11.id"
+
+echo
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
+          TABLE_COLUMN | grep -v '^-- '
+
+
+echo
+echo ==============================================================
+
+./populate-db.sh db2 "
+    CREATE SCHEMA s1;
+    CREATE TABLE s1.table9 (
+        id integer,
+        name varchar(40)
+    );
+    CREATE TABLE s1.table10 ();
+    CREATE TABLE s1.table11 (dropme integer);
+
+    CREATE SCHEMA s2;
+    CREATE TABLE s2.table9 (  -- Add name column
+        id integer
+    );
+    CREATE TABLE s2.table10 (id integer); -- change id to bigint
+    CREATE TABLE s2.table11 (id integer); -- drop id column
+    CREATE OR REPLACE VIEW s1.view1 AS
+        SELECT *
+        FROM s1.table10;
+"
+
+echo
+echo "# Compare the table columns in all schemas between two databases"
+echo "# Expect:"
+echo "#   Change s1.table9.name to varchar(50) "
+echo "#   Add s1.table10.id"
+echo "#   Drop s1.table11.dropme"
+echo
+
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "*" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db2" -s "*" -o "sslmode=disable" \
+          TABLE_COLUMN | grep -v '^-- '
+echo

From fa1d9c4813bc5de8ca80737a09026c2cda77c5a3 Mon Sep 17 00:00:00 2001
From: Daniel Woznicki <danwoz@docspot.com>
Date: Sat, 6 Jan 2018 00:49:06 -0800
Subject: [PATCH 46/74] Add basic support for PostgreSQL 10 identity column

---
 column.go                 | 40 +++++++++++++++++++++++++++++++++++-
 test/test-identity-column | 43 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 82 insertions(+), 1 deletion(-)
 create mode 100755 test/test-identity-column

diff --git a/column.go b/column.go
index 48a0f49..341f088 100644
--- a/column.go
+++ b/column.go
@@ -33,6 +33,8 @@ SELECT table_schema
     , is_nullable
     , column_default
     , character_maximum_length
+    , is_identity
+    , identity_generation
 FROM information_schema.columns 
 WHERE is_updatable = 'YES'
 {{if eq $.DbSchema "*" }}
@@ -116,6 +118,12 @@ func (c *ColumnSchema) Add() {
 		schema = c.get("table_schema")
 	}
 
+    // Knowing the version of db2 would eliminate the need for this warning
+    if c.get("is_identity") == "YES" {
+        fmt.Println("-- WARNING: identity columns are not supported in PostgreSQL versions < 10.")
+        fmt.Println("-- Attempting to create identity columns in earlier versions will probably result in errors.")
+    }
+
 	if c.get("data_type") == "character varying" {
 		maxLength, valid := getMaxLength(c.get("character_maximum_length"))
 		if !valid {
@@ -136,6 +144,11 @@ func (c *ColumnSchema) Add() {
 	if c.get("column_default") != "null" {
 		fmt.Printf(" DEFAULT %s", c.get("column_default"))
 	}
+    // NOTE: there are more identity column sequence options according to the PostgreSQL 
+    // CREATE TABLE docs, but these do not appear to be available as of version 10.1
+    if c.get("is_identity") == "YES" {
+        fmt.Printf(" GENERATED %s AS IDENTITY", c.get("identity_generation"))
+    }
 	fmt.Printf(";\n")
 }
 
@@ -200,14 +213,39 @@ func (c *ColumnSchema) Change(obj interface{}) {
 		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"))
 	}
 
+    // Detect identity column change
+    // Save result to variable instead of printing because order for adding/removing
+    // is_nullable affects identity columns
+    var identitySql string
+    if c.get("is_identity") != c2.get("is_identity") {
+        // Knowing the version of db2 would eliminate the need for this warning
+        fmt.Println("-- WARNING: identity columns are not supported in PostgreSQL versions < 10.")
+        fmt.Println("-- Attempting to create identity columns in earlier versions will probably result in errors.")
+        if c.get("is_identity") == "YES" {
+            identitySql = fmt.Sprintf("ALTER TABLE \"%s\".\"%s\" ALTER COLUMN \"%s\" ADD GENERATED %s AS IDENTITY;\n", c2.get("table_schema"), c.get("table_name"), c.get("column_name"), c.get("identity_generation"))
+        } else {
+            identitySql = fmt.Sprintf("ALTER TABLE \"%s\".\"%s\" ALTER COLUMN \"%s\" DROP IDENTITY;\n", c2.get("table_schema"), c.get("table_name"), c.get("column_name"))
+        }
+    }
+
 	// Detect not-null and nullable change
 	if c.get("is_nullable") != c2.get("is_nullable") {
 		if c.get("is_nullable") == "YES" {
+            if identitySql != "" {
+                fmt.Printf(identitySql)
+            }
 			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"))
 		} else {
 			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"))
+            if identitySql != "" {
+                fmt.Printf(identitySql)
+            }
 		}
-	}
+	} else {
+        if identitySql != "" {
+            fmt.Printf(identitySql)
+        }
+    }
 }
 
 // ==================================
diff --git a/test/test-identity-column b/test/test-identity-column
new file mode 100755
index 0000000..ded8bb1
--- /dev/null
+++ b/test/test-identity-column
@@ -0,0 +1,43 @@
+#!/bin/bash
+#
+# Useful for visually inspecting the output SQL to verify it is doing what it should
+#
+
+source ./start-fresh.sh >/dev/null 
+
+echo
+echo ==============================================================
+
+#
+# Compare the columns between two schemas in the same database
+#
+#psql -U u1 -h localhost -d db1 <<'EOS'
+./populate-db.sh db1 "
+    CREATE SCHEMA s1;
+    CREATE TABLE s1.table13 (
+        id1 integer,
+        id2 bigint generated by default as identity,
+        id3 integer generated by default as identity
+    );
+    
+    CREATE SCHEMA s2;
+    CREATE TABLE s2.table13 (
+        id1 integer generated by default as identity, -- drop identity
+        id2 integer -- int to bigint, add identity
+        -- add identity column
+    );
+"
+
+echo
+echo "# Compare differences in identity columns between two tables"
+echo "# Expect SQL:"
+echo "#   Change s2.table13.id1 drop identity"
+echo "#   Change s2.table13.id2 to bigint, add identity"
+echo "#   Add s2.table13.id3"
+
+echo
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
+          COLUMN | grep -v '^-- '
+echo
+

From fce3d152aa6db0326065e8255ef5bbe4686debbf Mon Sep 17 00:00:00 2001
From: Daniel Woznicki <danwoz@docspot.com>
Date: Sat, 6 Jan 2018 01:04:11 -0800
Subject: [PATCH 47/74] Fix indentation issues

---
 column.go | 68 +++++++++++++++++++++++++++----------------------------
 1 file changed, 34 insertions(+), 34 deletions(-)

diff --git a/column.go b/column.go
index 341f088..ed4258a 100644
--- a/column.go
+++ b/column.go
@@ -118,11 +118,11 @@ func (c *ColumnSchema) Add() {
 		schema = c.get("table_schema")
 	}
 
-    // Knowing the version of db2 would eliminate the need for this warning
-    if c.get("is_identity") == "YES" {
-        fmt.Println("-- WARNING: identity columns are not supported in PostgreSQL versions < 10.")
-        fmt.Println("-- Attempting to create identity columns in earlier versions will probably result in errors.")
-    }
+	// Knowing the version of db2 would eliminate the need for this warning
+	if c.get("is_identity") == "YES" {
+		fmt.Println("-- WARNING: identity columns are not supported in PostgreSQL versions < 10.")
+		fmt.Println("-- Attempting to create identity columns in earlier versions will probably result in errors.")
+	}
 
 	if c.get("data_type") == "character varying" {
 		maxLength, valid := getMaxLength(c.get("character_maximum_length"))
@@ -144,11 +144,11 @@ func (c *ColumnSchema) Add() {
 	if c.get("column_default") != "null" {
 		fmt.Printf(" DEFAULT %s", c.get("column_default"))
 	}
-    // NOTE: there are more identity column sequence options according to the PostgreSQL 
-    // CREATE TABLE docs, but these do not appear to be available as of version 10.1
-    if c.get("is_identity") == "YES" {
-        fmt.Printf(" GENERATED %s AS IDENTITY", c.get("identity_generation"))
-    }
+	// NOTE: there are more identity column sequence options according to the PostgreSQL 
+	// CREATE TABLE docs, but these do not appear to be available as of version 10.1
+	if c.get("is_identity") == "YES" {
+		fmt.Printf(" GENERATED %s AS IDENTITY", c.get("identity_generation"))
+	}
 	fmt.Printf(";\n")
 }
 
@@ -213,39 +213,39 @@ func (c *ColumnSchema) Change(obj interface{}) {
 		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"))
 	}
 
-    // Detect identity column change
-    // Save result to variable instead of printing because order for adding/removing
-    // is_nullable affects identity columns
-    var identitySql string
-    if c.get("is_identity") != c2.get("is_identity") {
-        // Knowing the version of db2 would eliminate the need for this warning
-        fmt.Println("-- WARNING: identity columns are not supported in PostgreSQL versions < 10.")
-        fmt.Println("-- Attempting to create identity columns in earlier versions will probably result in errors.")
-        if c.get("is_identity") == "YES" {
-            identitySql = fmt.Sprintf("ALTER TABLE \"%s\".\"%s\" ALTER COLUMN \"%s\" ADD GENERATED %s AS IDENTITY;\n", c2.get("table_schema"), c.get("table_name"), c.get("column_name"), c.get("identity_generation"))
-        } else {
-            identitySql = fmt.Sprintf("ALTER TABLE \"%s\".\"%s\" ALTER COLUMN \"%s\" DROP IDENTITY;\n", c2.get("table_schema"), c.get("table_name"), c.get("column_name"))
-        }
-    }
+	// Detect identity column change
+	// Save result to variable instead of printing because order for adding/removing
+	// is_nullable affects identity columns
+	var identitySql string
+	if c.get("is_identity") != c2.get("is_identity") {
+		// Knowing the version of db2 would eliminate the need for this warning
+		fmt.Println("-- WARNING: identity columns are not supported in PostgreSQL versions < 10.")
+		fmt.Println("-- Attempting to create identity columns in earlier versions will probably result in errors.")
+		if c.get("is_identity") == "YES" {
+			identitySql = fmt.Sprintf("ALTER TABLE \"%s\".\"%s\" ALTER COLUMN \"%s\" ADD GENERATED %s AS IDENTITY;\n", c2.get("table_schema"), c.get("table_name"), c.get("column_name"), c.get("identity_generation"))
+		} else {
+			identitySql = fmt.Sprintf("ALTER TABLE \"%s\".\"%s\" ALTER COLUMN \"%s\" DROP IDENTITY;\n", c2.get("table_schema"), c.get("table_name"), c.get("column_name"))
+		}
+	}
 
 	// Detect not-null and nullable change
 	if c.get("is_nullable") != c2.get("is_nullable") {
 		if c.get("is_nullable") == "YES" {
-            if identitySql != "" {
-                fmt.Printf(identitySql)
-            }
+			if identitySql != "" {
+				fmt.Printf(identitySql)
+			}
 			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"))
 		} else {
 			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"))
-            if identitySql != "" {
-                fmt.Printf(identitySql)
-            }
+			if identitySql != "" {
+				fmt.Printf(identitySql)
+			}
 		}
 	} else {
-        if identitySql != "" {
-            fmt.Printf(identitySql)
-        }
-    }
+		if identitySql != "" {
+			fmt.Printf(identitySql)
+		}
+	}
 }
 
 // ==================================

From 0d54bf87c253b08f7510454ff662b231407b142a Mon Sep 17 00:00:00 2001
From: Daniel Woznicki <danwoz@docspot.com>
Date: Fri, 9 Feb 2018 00:17:13 -0800
Subject: [PATCH 48/74] Modify the initial template SQL to include array data
 type and array dimensions Add support for array type to Add()

---
 column.go | 33 +++++++++++++++++++++++++++++----
 1 file changed, 29 insertions(+), 4 deletions(-)

diff --git a/column.go b/column.go
index ed4258a..637c9e6 100644
--- a/column.go
+++ b/column.go
@@ -35,7 +35,21 @@ SELECT table_schema
     , character_maximum_length
     , is_identity
     , identity_generation
-FROM information_schema.columns 
+	, substring(udt_name from 2) AS array_type
+	, attndims AS array_dimensions
+FROM information_schema.columns
+JOIN (SELECT attname
+		, attndims
+		, relname
+		, nspname
+	FROM pg_attribute
+	JOIN pg_class
+	ON attrelid = pg_class.oid
+	JOIN pg_namespace
+	ON pg_class.relnamespace = pg_namespace.oid) s
+ON (table_name = s.relname
+	AND column_name = s.attname
+	AND table_schema = s.nspname)
 WHERE is_updatable = 'YES'
 {{if eq $.DbSchema "*" }}
 AND table_schema NOT LIKE 'pg_%' 
@@ -132,10 +146,17 @@ func (c *ColumnSchema) Add() {
 			fmt.Printf("ALTER TABLE %s.%s ADD COLUMN %s character varying(%s)", schema, c.get("table_name"), c.get("column_name"), maxLength)
 		}
 	} else {
-		if c.get("data_type") == "ARRAY" {
-			fmt.Println("-- Note that adding of array data types are not yet generated properly.")
+		dataType := c.get("data_type")
+		//if c.get("data_type") == "ARRAY" {
+			//fmt.Println("-- Note that adding of array data types are not yet generated properly.")
+		//}
+		if dataType == "ARRAY" {
+			dimensions, err := strconv.Atoi(c.get("array_dimensions"))
+			check("converting string to int", err)
+			dataType = getArrayDefinition(c.get("array_type"), dimensions)
 		}
-		fmt.Printf("ALTER TABLE %s.%s ADD COLUMN %s %s", schema, c.get("table_name"), c.get("column_name"), c.get("data_type"))
+		//fmt.Printf("ALTER TABLE %s.%s ADD COLUMN %s %s", schema, c.get("table_name"), c.get("column_name"), c.get("data_type"))
+		fmt.Printf("ALTER TABLE %s.%s ADD COLUMN %s %s", schema, c.get("table_name"), c.get("column_name"), dataType)
 	}
 
 	if c.get("is_nullable") == "NO" {
@@ -295,3 +316,7 @@ func getMaxLength(maxLength string) (string, bool) {
 	}
 	return maxLength, true
 }
+
+func getArrayDefinition(arrayType string, dimensions int) string {
+	return arrayType + strings.Repeat("[]", dimensions)
+}

From c6cccc6258ecf795f47b7096a901a201106bde7d Mon Sep 17 00:00:00 2001
From: Daniel Woznicki <danwoz@docspot.com>
Date: Thu, 22 Feb 2018 23:33:33 -0800
Subject: [PATCH 49/74] Remove unnecessary check for array dimensions 	Oddly,
 PostgreSQL does not seem to strictly enforce dimensions in array columns Add
 a simple array column test to test-column script

---
 column.go        | 41 ++++++++++++++++++-----------------------
 test/test-column | 29 +++++++++++++++++++++++++++++
 2 files changed, 47 insertions(+), 23 deletions(-)

diff --git a/column.go b/column.go
index 637c9e6..ca98f6b 100644
--- a/column.go
+++ b/column.go
@@ -36,20 +36,7 @@ SELECT table_schema
     , is_identity
     , identity_generation
 	, substring(udt_name from 2) AS array_type
-	, attndims AS array_dimensions
 FROM information_schema.columns
-JOIN (SELECT attname
-		, attndims
-		, relname
-		, nspname
-	FROM pg_attribute
-	JOIN pg_class
-	ON attrelid = pg_class.oid
-	JOIN pg_namespace
-	ON pg_class.relnamespace = pg_namespace.oid) s
-ON (table_name = s.relname
-	AND column_name = s.attname
-	AND table_schema = s.nspname)
 WHERE is_updatable = 'YES'
 {{if eq $.DbSchema "*" }}
 AND table_schema NOT LIKE 'pg_%' 
@@ -151,9 +138,7 @@ func (c *ColumnSchema) Add() {
 			//fmt.Println("-- Note that adding of array data types are not yet generated properly.")
 		//}
 		if dataType == "ARRAY" {
-			dimensions, err := strconv.Atoi(c.get("array_dimensions"))
-			check("converting string to int", err)
-			dataType = getArrayDefinition(c.get("array_type"), dimensions)
+			dataType = c.get("array_type")+"[]"
 		}
 		//fmt.Printf("ALTER TABLE %s.%s ADD COLUMN %s %s", schema, c.get("table_name"), c.get("column_name"), c.get("data_type"))
 		fmt.Printf("ALTER TABLE %s.%s ADD COLUMN %s %s", schema, c.get("table_name"), c.get("column_name"), dataType)
@@ -186,10 +171,20 @@ func (c *ColumnSchema) Change(obj interface{}) {
 		fmt.Println("Error!!!, ColumnSchema.Change(obj) needs a ColumnSchema instance", c2)
 	}
 
+	// Adjust data type for array columns
+	dataType1 := c.get("data_type")
+	if dataType1 == "ARRAY" {
+		dataType1 = c.get("array_type")+"[]"
+	}
+	dataType2 := c2.get("data_type")
+	if dataType2 == "ARRAY" {
+		dataType2 = c2.get("array_type")+"[]"
+	}
+
 	// Detect column type change (mostly varchar length, or number size increase)
 	// (integer to/from bigint is OK)
-	if c.get("data_type") == c2.get("data_type") {
-		if c.get("data_type") == "character varying" {
+	if dataType1 != dataType2 {
+		if dataType1 == "character varying" {
 			max1, max1Valid := getMaxLength(c.get("character_maximum_length"))
 			max2, max2Valid := getMaxLength(c2.get("character_maximum_length"))
 			if !max1Valid && !max2Valid {
@@ -212,16 +207,16 @@ func (c *ColumnSchema) Change(obj interface{}) {
 	}
 
 	// Code and test a column change from integer to bigint
-	if c.get("data_type") != c2.get("data_type") {
-		fmt.Printf("-- WARNING: This type change may not work well: (%s to %s).\n", c2.get("data_type"), c.get("data_type"))
-		if strings.HasPrefix(c.get("data_type"), "character") {
+	if dataType1 != dataType2 {
+		fmt.Printf("-- WARNING: This type change may not work well: (%s to %s).\n", dataType2, dataType1)
+		if strings.HasPrefix(dataType1, "character") {
 			max1, max1Valid := getMaxLength(c.get("character_maximum_length"))
 			if !max1Valid {
 				fmt.Println("-- WARNING: varchar column has no maximum length.  Setting to 1024")
 			}
-			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)
+			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"), dataType1, max1)
 		} else {
-			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"))
+			fmt.Printf("ALTER TABLE %s.%s ALTER COLUMN %s TYPE %s;\n", c2.get("table_schema"), c.get("table_name"), c.get("column_name"), dataType1)
 		}
 	}
 
diff --git a/test/test-column b/test/test-column
index c4cca1a..6d9e5c9 100755
--- a/test/test-column
+++ b/test/test-column
@@ -74,3 +74,32 @@ echo
           -u "u1" -w "asdf" -h "localhost" -d "db2" -s "*" -o "sslmode=disable" \
           COLUMN | grep -v '^-- '
 echo
+echo ==============================================================
+
+./populate-db.sh db1 "
+	CREATE SCHEMA s3;
+    CREATE TABLE s3.table12 (
+        ids integer[],
+		bigids bigint[],
+		something text[][]
+    );
+
+	CREATE SCHEMA s4;
+    CREATE TABLE s4.table12 (
+		bigids integer[],
+		something text
+	);
+"
+
+echo
+echo "# Compare array columns between two tables"
+echo "# Expect:"
+echo "#   Add s4.table12.ids int4[]"
+echo "#   Change s4.table12.bigids from to int8[]"
+echo "#   Change s4.table12.something to text[]"
+echo
+
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s3" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s4" -o "sslmode=disable" \
+          COLUMN | grep -v '^-- '
+echo

From 4f2425b10940583f5ae841dbe5745933c0f70494 Mon Sep 17 00:00:00 2001
From: Daniel Woznicki <danwoz@docspot.com>
Date: Thu, 22 Feb 2018 23:52:33 -0800
Subject: [PATCH 50/74] Fix small bug with data type check in Change() Add
 multidimensional array test

---
 column.go        | 2 +-
 test/test-column | 9 ++++-----
 2 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/column.go b/column.go
index ca98f6b..a4ec825 100644
--- a/column.go
+++ b/column.go
@@ -183,7 +183,7 @@ func (c *ColumnSchema) Change(obj interface{}) {
 
 	// Detect column type change (mostly varchar length, or number size increase)
 	// (integer to/from bigint is OK)
-	if dataType1 != dataType2 {
+	if dataType1 == dataType2 {
 		if dataType1 == "character varying" {
 			max1, max1Valid := getMaxLength(c.get("character_maximum_length"))
 			max2, max2Valid := getMaxLength(c2.get("character_maximum_length"))
diff --git a/test/test-column b/test/test-column
index 6d9e5c9..54e4ba1 100755
--- a/test/test-column
+++ b/test/test-column
@@ -81,13 +81,13 @@ echo ==============================================================
     CREATE TABLE s3.table12 (
         ids integer[],
 		bigids bigint[],
-		something text[][]
+		something text[][] -- dimensions don't seem to matter, to ignore them
     );
 
 	CREATE SCHEMA s4;
-    CREATE TABLE s4.table12 (
-		bigids integer[],
-		something text
+    CREATE TABLE s4.table12 ( -- add ids column
+		bigids integer[], -- change bigids to int8[]
+		something text[] -- no change
 	);
 "
 
@@ -96,7 +96,6 @@ echo "# Compare array columns between two tables"
 echo "# Expect:"
 echo "#   Add s4.table12.ids int4[]"
 echo "#   Change s4.table12.bigids from to int8[]"
-echo "#   Change s4.table12.something to text[]"
 echo
 
 ../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s3" -O "sslmode=disable" \

From 1fe2aa880add1ab1237e024557b90df3a8cc83b9 Mon Sep 17 00:00:00 2001
From: Daniel Woznicki <danwoz@docspot.com>
Date: Fri, 23 Feb 2018 00:16:47 -0800
Subject: [PATCH 51/74] Remove old function for creating multidimensional array
 text

---
 column.go | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/column.go b/column.go
index a4ec825..579e1c3 100644
--- a/column.go
+++ b/column.go
@@ -312,6 +312,3 @@ func getMaxLength(maxLength string) (string, bool) {
 	return maxLength, true
 }
 
-func getArrayDefinition(arrayType string, dimensions int) string {
-	return arrayType + strings.Repeat("[]", dimensions)
-}

From 739fffa993c2bbbe1c79769cc7530553a9897885 Mon Sep 17 00:00:00 2001
From: Daniel Woznicki <daniel.woznicki@gmail.com>
Date: Tue, 27 Feb 2018 23:35:07 -0800
Subject: [PATCH 52/74] Fix indentation for column.go

---
 column.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/column.go b/column.go
index 579e1c3..ca6f732 100644
--- a/column.go
+++ b/column.go
@@ -35,7 +35,7 @@ SELECT table_schema
     , character_maximum_length
     , is_identity
     , identity_generation
-	, substring(udt_name from 2) AS array_type
+    , substring(udt_name from 2) AS array_type
 FROM information_schema.columns
 WHERE is_updatable = 'YES'
 {{if eq $.DbSchema "*" }}

From 78da9d3197841df78d167eefba7f259f942b2c79 Mon Sep 17 00:00:00 2001
From: Daniel Woznicki <daniel.woznicki@gmail.com>
Date: Tue, 27 Feb 2018 23:35:48 -0800
Subject: [PATCH 53/74] Fix small spelling error

---
 test/test-column | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/test-column b/test/test-column
index 54e4ba1..031c413 100755
--- a/test/test-column
+++ b/test/test-column
@@ -81,7 +81,7 @@ echo ==============================================================
     CREATE TABLE s3.table12 (
         ids integer[],
 		bigids bigint[],
-		something text[][] -- dimensions don't seem to matter, to ignore them
+		something text[][] -- dimensions don't seem to matter, so ignore them
     );
 
 	CREATE SCHEMA s4;

From a4a4ffb72b76f9887758eabf94d49463271ce032 Mon Sep 17 00:00:00 2001
From: Daniel Woznicki <daniel.woznicki@gmail.com>
Date: Tue, 27 Feb 2018 23:37:36 -0800
Subject: [PATCH 54/74] Update test-column

---
 test/test-column | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/test/test-column b/test/test-column
index 031c413..a26bb13 100755
--- a/test/test-column
+++ b/test/test-column
@@ -77,18 +77,17 @@ echo
 echo ==============================================================
 
 ./populate-db.sh db1 "
-	CREATE SCHEMA s3;
+    CREATE SCHEMA s3;
     CREATE TABLE s3.table12 (
         ids integer[],
-		bigids bigint[],
-		something text[][] -- dimensions don't seem to matter, so ignore them
+        bigids bigint[],
+        something text[][] -- dimensions don't seem to matter, so ignore them
     );
-
-	CREATE SCHEMA s4;
+    CREATE SCHEMA s4;
     CREATE TABLE s4.table12 ( -- add ids column
-		bigids integer[], -- change bigids to int8[]
-		something text[] -- no change
-	);
+        bigids integer[], -- change bigids to int8[]
+        something text[] -- no change
+    );
 "
 
 echo

From c40d3c2f63fc498ae231d7832c13bf47848a0b1f Mon Sep 17 00:00:00 2001
From: Eugene Kalinin <ekalinin@users.noreply.github.com>
Date: Mon, 7 May 2018 16:08:34 +0300
Subject: [PATCH 55/74] Update README.md

Added schema type - ALL
---
 README.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/README.md b/README.md
index 24e5058..4ca2dc2 100644
--- a/README.md
+++ b/README.md
@@ -35,6 +35,7 @@ Schema type ordering:
 1. OWNER
 1. GRANT\_RELATIONSHIP
 1. GRANT\_ATTRIBUTE
+1. ALL (all above in one run)
 
 
 ### example

From 5f6d07d84938abcacf5e1edc227a2969b83d7bc7 Mon Sep 17 00:00:00 2001
From: Vadim Geshel <vadim@yummly.com>
Date: Fri, 9 Nov 2018 14:34:43 -0800
Subject: [PATCH 56/74] Use pg_roles instead of pg_authid

pg_authid is not accessible, for example, on AWS RDS Postgres even when using the master user.

Postgres documentation [recommends](https://www.postgresql.org/docs/9.6/catalog-pg-authid.html) using pg_roles.
---
 owner.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/owner.go b/owner.go
index 2f0f7d7..3751d67 100644
--- a/owner.go
+++ b/owner.go
@@ -32,7 +32,7 @@ SELECT n.nspname AS schema_name
         WHEN c.relkind = 'v' THEN 'VIEW' 
         ELSE c.relkind::varchar END AS type
 FROM pg_class AS c
-INNER JOIN pg_authid AS a ON (a.oid = c.relowner)
+INNER JOIN pg_roles AS a ON (a.oid = c.relowner)
 INNER JOIN pg_namespace AS n ON (n.oid = c.relnamespace)
 WHERE c.relkind IN ('r', 'S', 'v')
 {{if eq $.DbSchema "*" }}

From dcde1b851ca808f7f15b862c91760c73ec21c284 Mon Sep 17 00:00:00 2001
From: Dave Peticolas <dpeticolas@counsyl.com>
Date: Mon, 25 Feb 2019 08:24:20 -0800
Subject: [PATCH 57/74] Remove superfluous output.

---
 function.go | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/function.go b/function.go
index e0cc197..ded686a 100644
--- a/function.go
+++ b/function.go
@@ -108,8 +108,6 @@ func (c *FunctionSchema) Compare(obj interface{}) int {
 
 // Add returns SQL to create the function
 func (c FunctionSchema) Add() {
-	fmt.Println("-- Add")
-
 	// If we are comparing two different schemas against each other, we need to do some
 	// modification of the first function definition so we create it in the right schema
 	functionDef := c.get("definition")
@@ -128,7 +126,6 @@ func (c FunctionSchema) Add() {
 
 // Drop returns SQL to drop the function
 func (c FunctionSchema) Drop() {
-	fmt.Println("-- Drop")
 	fmt.Println("-- Note that CASCADE in the statement below will also drop any triggers depending on this function.")
 	fmt.Println("-- Also, if there are two functions with this name, you will want to add arguments to identify the correct one to drop.")
 	fmt.Println("-- (See http://www.postgresql.org/docs/9.4/interactive/sql-dropfunction.html) ")
@@ -137,7 +134,6 @@ func (c FunctionSchema) Drop() {
 
 // Change handles the case where the function names match, but the definition does not
 func (c FunctionSchema) Change(obj interface{}) {
-	fmt.Println("-- Change")
 	c2, ok := obj.(*FunctionSchema)
 	if !ok {
 		fmt.Println("Error!!!, Change needs a FunctionSchema instance", c2)

From bc032409389aa1e218513d38ee4863a83220409b Mon Sep 17 00:00:00 2001
From: Dave Peticolas <dpeticolas@counsyl.com>
Date: Mon, 25 Feb 2019 08:24:35 -0800
Subject: [PATCH 58/74] Fix Errorf call.

---
 grant_test.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/grant_test.go b/grant_test.go
index b807435..552b57f 100644
--- a/grant_test.go
+++ b/grant_test.go
@@ -20,6 +20,6 @@ func doParseAcls(t *testing.T, acl string, expectedRole string, expectedPermCoun
 		t.Error("Wrong role parsed: " + role + " instead of " + expectedRole)
 	}
 	if len(perms) != expectedPermCount {
-		t.Error("Incorrect number of permissions parsed: %d instead of %d", len(perms), expectedPermCount)
+		t.Errorf("Incorrect number of permissions parsed: %d instead of %d", len(perms), expectedPermCount)
 	}
 }

From e23a04e00045228593ab58f7aa2305a303041abd Mon Sep 17 00:00:00 2001
From: Dave Peticolas <dpeticolas@counsyl.com>
Date: Mon, 25 Feb 2019 08:25:02 -0800
Subject: [PATCH 59/74] Do not diff internal triggers, remove superfluous
 output.

---
 trigger.go | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/trigger.go b/trigger.go
index d69c35f..e362096 100644
--- a/trigger.go
+++ b/trigger.go
@@ -33,7 +33,7 @@ func initTriggerSqlTemplate() *template.Template {
     FROM pg_catalog.pg_trigger t
     INNER JOIN pg_catalog.pg_class c ON (c.oid = t.tgrelid)
     INNER JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
-	WHERE true
+	WHERE not t.tgisinternal
     {{if eq $.DbSchema "*" }}
     AND n.nspname NOT LIKE 'pg_%' 
     AND n.nspname <> 'information_schema' 
@@ -106,8 +106,6 @@ func (c *TriggerSchema) Compare(obj interface{}) int {
 
 // Add returns SQL to create the trigger
 func (c TriggerSchema) Add() {
-	fmt.Println("-- Add")
-
 	// If we are comparing two different schemas against each other, we need to do some
 	// modification of the first trigger definition so we create it in the right schema
 	triggerDef := c.get("trigger_def")
@@ -131,7 +129,6 @@ func (c TriggerSchema) Drop() {
 
 // Change handles the case where the trigger names match, but the definition does not
 func (c TriggerSchema) Change(obj interface{}) {
-	fmt.Println("-- Change")
 	c2, ok := obj.(*TriggerSchema)
 	if !ok {
 		fmt.Println("Error!!!, Change needs a TriggerSchema instance", c2)

From 7747cd9e72931a4fc5008e35fc0a2e737e579627 Mon Sep 17 00:00:00 2001
From: Dave Peticolas <dpeticolas@counsyl.com>
Date: Mon, 25 Feb 2019 08:25:10 -0800
Subject: [PATCH 60/74] Update schema type options.

---
 pgdiff.go | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/pgdiff.go b/pgdiff.go
index 0bb9ab6..921f8d9 100644
--- a/pgdiff.go
+++ b/pgdiff.go
@@ -203,8 +203,7 @@ Options:
   -S, --schema1 : first schema.  default is all schemas
   -s, --schema2 : second schema. default is all schemas
 
-<schemaTpe> can be: SCHEMA ROLE, SEQUENCE, TABLE, VIEW, COLUMN, INDEX, FOREIGN_KEY, OWNER, GRANT_RELATIONSHIP, GRANT_ATTRIBUTE
-`)
+<schemaTpe> can be: ALL, SCHEMA, ROLE, SEQUENCE, TABLE, VIEW, COLUMN, INDEX, FOREIGN_KEY, OWNER, GRANT_RELATIONSHIP, GRANT_ATTRIBUTE, TRIGGER, FUNCTION`)
 
 	os.Exit(2)
 }

From 3972da44043060575c3e5b9d497b100ec7de6e36 Mon Sep 17 00:00:00 2001
From: Daniel Woznicki <danwoz@docspot.com>
Date: Sat, 6 Jan 2018 00:49:06 -0800
Subject: [PATCH 61/74] Add basic support for PostgreSQL 10 identity column

---
 column.go                 | 40 +++++++++++++++++++++++++++++++++++-
 test/test-identity-column | 43 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 82 insertions(+), 1 deletion(-)
 create mode 100755 test/test-identity-column

diff --git a/column.go b/column.go
index 6177a8f..9996170 100644
--- a/column.go
+++ b/column.go
@@ -33,6 +33,8 @@ SELECT table_schema
     , is_nullable
     , column_default
     , character_maximum_length
+    , is_identity
+    , identity_generation
 FROM information_schema.columns 
 WHERE is_updatable = 'YES'
 {{if eq $.DbSchema "*" }}
@@ -153,6 +155,12 @@ func (c *ColumnSchema) Add() {
 		schema = c.get("table_schema")
 	}
 
+    // Knowing the version of db2 would eliminate the need for this warning
+    if c.get("is_identity") == "YES" {
+        fmt.Println("-- WARNING: identity columns are not supported in PostgreSQL versions < 10.")
+        fmt.Println("-- Attempting to create identity columns in earlier versions will probably result in errors.")
+    }
+
 	if c.get("data_type") == "character varying" {
 		maxLength, valid := getMaxLength(c.get("character_maximum_length"))
 		if !valid {
@@ -173,6 +181,11 @@ func (c *ColumnSchema) Add() {
 	if c.get("column_default") != "null" {
 		fmt.Printf(" DEFAULT %s", c.get("column_default"))
 	}
+    // NOTE: there are more identity column sequence options according to the PostgreSQL 
+    // CREATE TABLE docs, but these do not appear to be available as of version 10.1
+    if c.get("is_identity") == "YES" {
+        fmt.Printf(" GENERATED %s AS IDENTITY", c.get("identity_generation"))
+    }
 	fmt.Printf(";\n")
 }
 
@@ -237,14 +250,39 @@ func (c *ColumnSchema) Change(obj interface{}) {
 		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"))
 	}
 
+    // Detect identity column change
+    // Save result to variable instead of printing because order for adding/removing
+    // is_nullable affects identity columns
+    var identitySql string
+    if c.get("is_identity") != c2.get("is_identity") {
+        // Knowing the version of db2 would eliminate the need for this warning
+        fmt.Println("-- WARNING: identity columns are not supported in PostgreSQL versions < 10.")
+        fmt.Println("-- Attempting to create identity columns in earlier versions will probably result in errors.")
+        if c.get("is_identity") == "YES" {
+            identitySql = fmt.Sprintf("ALTER TABLE \"%s\".\"%s\" ALTER COLUMN \"%s\" ADD GENERATED %s AS IDENTITY;\n", c2.get("table_schema"), c.get("table_name"), c.get("column_name"), c.get("identity_generation"))
+        } else {
+            identitySql = fmt.Sprintf("ALTER TABLE \"%s\".\"%s\" ALTER COLUMN \"%s\" DROP IDENTITY;\n", c2.get("table_schema"), c.get("table_name"), c.get("column_name"))
+        }
+    }
+
 	// Detect not-null and nullable change
 	if c.get("is_nullable") != c2.get("is_nullable") {
 		if c.get("is_nullable") == "YES" {
+            if identitySql != "" {
+                fmt.Printf(identitySql)
+            }
 			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"))
 		} else {
 			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"))
+            if identitySql != "" {
+                fmt.Printf(identitySql)
+            }
 		}
-	}
+	} else {
+        if identitySql != "" {
+            fmt.Printf(identitySql)
+        }
+    }
 }
 
 // ==================================
diff --git a/test/test-identity-column b/test/test-identity-column
new file mode 100755
index 0000000..ded8bb1
--- /dev/null
+++ b/test/test-identity-column
@@ -0,0 +1,43 @@
+#!/bin/bash
+#
+# Useful for visually inspecting the output SQL to verify it is doing what it should
+#
+
+source ./start-fresh.sh >/dev/null 
+
+echo
+echo ==============================================================
+
+#
+# Compare the columns between two schemas in the same database
+#
+#psql -U u1 -h localhost -d db1 <<'EOS'
+./populate-db.sh db1 "
+    CREATE SCHEMA s1;
+    CREATE TABLE s1.table13 (
+        id1 integer,
+        id2 bigint generated by default as identity,
+        id3 integer generated by default as identity
+    );
+    
+    CREATE SCHEMA s2;
+    CREATE TABLE s2.table13 (
+        id1 integer generated by default as identity, -- drop identity
+        id2 integer -- int to bigint, add identity
+        -- add identity column
+    );
+"
+
+echo
+echo "# Compare differences in identity columns between two tables"
+echo "# Expect SQL:"
+echo "#   Change s2.table13.id1 drop identity"
+echo "#   Change s2.table13.id2 to bigint, add identity"
+echo "#   Add s2.table13.id3"
+
+echo
+../pgdiff -U "u1" -W "asdf" -H "localhost" -D "db1" -S "s1" -O "sslmode=disable" \
+          -u "u1" -w "asdf" -h "localhost" -d "db1" -s "s2" -o "sslmode=disable" \
+          COLUMN | grep -v '^-- '
+echo
+

From 985695ab805535efa533a35e970e9554e981ddab Mon Sep 17 00:00:00 2001
From: Daniel Woznicki <danwoz@docspot.com>
Date: Sat, 6 Jan 2018 01:04:11 -0800
Subject: [PATCH 62/74] Fix indentation issues

---
 column.go | 68 +++++++++++++++++++++++++++----------------------------
 1 file changed, 34 insertions(+), 34 deletions(-)

diff --git a/column.go b/column.go
index 9996170..f7a24cc 100644
--- a/column.go
+++ b/column.go
@@ -155,11 +155,11 @@ func (c *ColumnSchema) Add() {
 		schema = c.get("table_schema")
 	}
 
-    // Knowing the version of db2 would eliminate the need for this warning
-    if c.get("is_identity") == "YES" {
-        fmt.Println("-- WARNING: identity columns are not supported in PostgreSQL versions < 10.")
-        fmt.Println("-- Attempting to create identity columns in earlier versions will probably result in errors.")
-    }
+	// Knowing the version of db2 would eliminate the need for this warning
+	if c.get("is_identity") == "YES" {
+		fmt.Println("-- WARNING: identity columns are not supported in PostgreSQL versions < 10.")
+		fmt.Println("-- Attempting to create identity columns in earlier versions will probably result in errors.")
+	}
 
 	if c.get("data_type") == "character varying" {
 		maxLength, valid := getMaxLength(c.get("character_maximum_length"))
@@ -181,11 +181,11 @@ func (c *ColumnSchema) Add() {
 	if c.get("column_default") != "null" {
 		fmt.Printf(" DEFAULT %s", c.get("column_default"))
 	}
-    // NOTE: there are more identity column sequence options according to the PostgreSQL 
-    // CREATE TABLE docs, but these do not appear to be available as of version 10.1
-    if c.get("is_identity") == "YES" {
-        fmt.Printf(" GENERATED %s AS IDENTITY", c.get("identity_generation"))
-    }
+	// NOTE: there are more identity column sequence options according to the PostgreSQL 
+	// CREATE TABLE docs, but these do not appear to be available as of version 10.1
+	if c.get("is_identity") == "YES" {
+		fmt.Printf(" GENERATED %s AS IDENTITY", c.get("identity_generation"))
+	}
 	fmt.Printf(";\n")
 }
 
@@ -250,39 +250,39 @@ func (c *ColumnSchema) Change(obj interface{}) {
 		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"))
 	}
 
-    // Detect identity column change
-    // Save result to variable instead of printing because order for adding/removing
-    // is_nullable affects identity columns
-    var identitySql string
-    if c.get("is_identity") != c2.get("is_identity") {
-        // Knowing the version of db2 would eliminate the need for this warning
-        fmt.Println("-- WARNING: identity columns are not supported in PostgreSQL versions < 10.")
-        fmt.Println("-- Attempting to create identity columns in earlier versions will probably result in errors.")
-        if c.get("is_identity") == "YES" {
-            identitySql = fmt.Sprintf("ALTER TABLE \"%s\".\"%s\" ALTER COLUMN \"%s\" ADD GENERATED %s AS IDENTITY;\n", c2.get("table_schema"), c.get("table_name"), c.get("column_name"), c.get("identity_generation"))
-        } else {
-            identitySql = fmt.Sprintf("ALTER TABLE \"%s\".\"%s\" ALTER COLUMN \"%s\" DROP IDENTITY;\n", c2.get("table_schema"), c.get("table_name"), c.get("column_name"))
-        }
-    }
+	// Detect identity column change
+	// Save result to variable instead of printing because order for adding/removing
+	// is_nullable affects identity columns
+	var identitySql string
+	if c.get("is_identity") != c2.get("is_identity") {
+		// Knowing the version of db2 would eliminate the need for this warning
+		fmt.Println("-- WARNING: identity columns are not supported in PostgreSQL versions < 10.")
+		fmt.Println("-- Attempting to create identity columns in earlier versions will probably result in errors.")
+		if c.get("is_identity") == "YES" {
+			identitySql = fmt.Sprintf("ALTER TABLE \"%s\".\"%s\" ALTER COLUMN \"%s\" ADD GENERATED %s AS IDENTITY;\n", c2.get("table_schema"), c.get("table_name"), c.get("column_name"), c.get("identity_generation"))
+		} else {
+			identitySql = fmt.Sprintf("ALTER TABLE \"%s\".\"%s\" ALTER COLUMN \"%s\" DROP IDENTITY;\n", c2.get("table_schema"), c.get("table_name"), c.get("column_name"))
+		}
+	}
 
 	// Detect not-null and nullable change
 	if c.get("is_nullable") != c2.get("is_nullable") {
 		if c.get("is_nullable") == "YES" {
-            if identitySql != "" {
-                fmt.Printf(identitySql)
-            }
+			if identitySql != "" {
+				fmt.Printf(identitySql)
+			}
 			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"))
 		} else {
 			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"))
-            if identitySql != "" {
-                fmt.Printf(identitySql)
-            }
+			if identitySql != "" {
+				fmt.Printf(identitySql)
+			}
 		}
 	} else {
-        if identitySql != "" {
-            fmt.Printf(identitySql)
-        }
-    }
+		if identitySql != "" {
+			fmt.Printf(identitySql)
+		}
+	}
 }
 
 // ==================================

From 065fe0c45f70400ca7aedf422d580563590d0423 Mon Sep 17 00:00:00 2001
From: Florian Wiesweg <florian.wiesweg@campus.tu-berlin.de>
Date: Tue, 24 Mar 2020 10:09:54 +0100
Subject: [PATCH 63/74] Show table name in different defs warning

---
 index.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/index.go b/index.go
index 59e1334..080a266 100644
--- a/index.go
+++ b/index.go
@@ -196,7 +196,7 @@ func (c *IndexSchema) Change(obj interface{}) {
 
 	if c.get("constraint_def") != c2.get("constraint_def") {
 		// c1.constraint and c2.constraint are just different
-		fmt.Printf("-- CHANGE: Different defs:\n--    %s\n--    %s\n", c.get("constraint_def"), c2.get("constraint_def"))
+		fmt.Printf("-- CHANGE: Different defs on %s:\n--    %s\n--    %s\n", c.get("table_name"), c.get("constraint_def"), c2.get("constraint_def"))
 		if c.get("constraint_def") == "null" {
 			// c1.constraint does not exist, c2.constraint does, so
 			// Drop constraint

From 70ef394a0dab57160cbc6fb3d9d0d530bfdfbc88 Mon Sep 17 00:00:00 2001
From: nikita1024 <nikita1024@gmail.com>
Date: Sat, 26 Dec 2020 14:40:26 -0500
Subject: [PATCH 64/74] Add a semicolon after the index definition to make the
 diff script valid.

Without it the script fails to run using psql. Without it the script looks like this:
CREATE UNIQUE INDEX test123_pkey ON test123 USING btree (s1)
ALTER TABLE public.test123 ADD CONSTRAINT test123_pkey PRIMARY KEY USING INDEX test123_pkey; -- (1)

And the error looks like this:
psql:5-INDEX.sql:6: ERROR:  syntax error at or near "ALTER"
LINE 2: ALTER TABLE public.test123 ADD CONSTRAINT test123_pkey PRIMA...
        ^
---
 index.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/index.go b/index.go
index 080a266..8ac2438 100644
--- a/index.go
+++ b/index.go
@@ -152,7 +152,7 @@ func (c *IndexSchema) Add() {
 			-1)
 	}
 
-	fmt.Println(indexDef)
+	fmt.Printf("%v;\n", indexDef)
 
 	if c.get("constraint_def") != "null" {
 		// Create the constraint using the index we just created

From 99d1bab7f89859cc04df099ff522397ec8c91628 Mon Sep 17 00:00:00 2001
From: Amerieum <46352117+Amerieum@users.noreply.github.com>
Date: Tue, 29 Dec 2020 16:18:22 +0100
Subject: [PATCH 65/74] Proposed solution for issue #37

https://github.com/joncrlsn/pgdiff/issues/37

to solve this issue I had to to replace the following line in column.go
, {{if eq $.DbSchema "*" }}table_schema || '.' || {{end}}table_name || '.' || column_name AS compare_name
by

, {{if eq $.DbSchema "*" }}table_schema || '.' || {{end}}table_name || '.' ||lpad(cast (ordinal_position as varchar), 5, '0')|| column_name AS compare_name

this would add up the cardinal position of the column in the sort criteria compare_name leading to correct order
---
 column.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/column.go b/column.go
index ed4258a..bb48d43 100644
--- a/column.go
+++ b/column.go
@@ -26,7 +26,7 @@ var (
 func initColumnSqlTemplate() *template.Template {
 	sql := `
 SELECT table_schema
-    , {{if eq $.DbSchema "*" }}table_schema || '.' || {{end}}table_name || '.' || column_name  AS compare_name
+    ,  {{if eq $.DbSchema "*" }}table_schema || '.' || {{end}}table_name || '.' ||lpad(cast (ordinal_position as varchar), 5, '0')|| column_name AS compare_name
 	, table_name
     , column_name
     , data_type

From b56afeba1905630f764a683484df4341ed34aff5 Mon Sep 17 00:00:00 2001
From: Gustav <tvarsis@hotmail.com>
Date: Fri, 12 Feb 2021 16:18:08 +0100
Subject: [PATCH 66/74] added materialized view support

---
 mat_view.go | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 pgdiff.go   |  26 +++++++----
 pgdiff.sh   |   2 +-
 3 files changed, 148 insertions(+), 10 deletions(-)
 create mode 100644 mat_view.go

diff --git a/mat_view.go b/mat_view.go
new file mode 100644
index 0000000..ec356cd
--- /dev/null
+++ b/mat_view.go
@@ -0,0 +1,130 @@
+//
+// Copyright (c) 2016 Jon Carlson.  All rights reserved.
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file.
+//
+
+package main
+
+import (
+	"database/sql"
+	"fmt"
+	"sort"
+
+	"github.com/joncrlsn/misc"
+	"github.com/joncrlsn/pgutil"
+)
+
+// ==================================
+// MatViewRows definition
+// ==================================
+
+// MatViewRows is a sortable slice of string maps
+type MatViewRows []map[string]string
+
+func (slice MatViewRows) Len() int {
+	return len(slice)
+}
+
+func (slice MatViewRows) Less(i, j int) bool {
+	return slice[i]["matviewname"] < slice[j]["matviewname"]
+}
+
+func (slice MatViewRows) Swap(i, j int) {
+	slice[i], slice[j] = slice[j], slice[i]
+}
+
+// MatViewSchema holds a channel streaming matview information from one of the databases as well as
+// a reference to the current row of data we're matviewing.
+//
+// MatViewSchema implements the Schema interface defined in pgdiff.go
+type MatViewSchema struct {
+	rows   MatViewRows
+	rowNum int
+	done   bool
+}
+
+// get returns the value from the current row for the given key
+func (c *MatViewSchema) get(key string) string {
+	if c.rowNum >= len(c.rows) {
+		return ""
+	}
+	return c.rows[c.rowNum][key]
+}
+
+// NextRow increments the rowNum and tells you whether or not there are more
+func (c *MatViewSchema) NextRow() bool {
+	if c.rowNum >= len(c.rows)-1 {
+		c.done = true
+	}
+	c.rowNum = c.rowNum + 1
+	return !c.done
+}
+
+// Compare tells you, in one pass, whether or not the first row matches, is less than, or greater than the second row
+func (c *MatViewSchema) Compare(obj interface{}) int {
+	c2, ok := obj.(*MatViewSchema)
+	if !ok {
+		fmt.Println("Error!!!, Compare(obj) needs a MatViewSchema instance", c2)
+		return +999
+	}
+
+	val := misc.CompareStrings(c.get("matviewname"), c2.get("matviewname"))
+	//fmt.Printf("-- Compared %v: %s with %s \n", val, c.get("matviewname"), c2.get("matviewname"))
+	return val
+}
+
+// Add returns SQL to create the matview
+func (c MatViewSchema) Add() {
+	fmt.Printf("CREATE MATERIALIZED VIEW %s AS %s \n\n", c.get("matviewname"), c.get("definition"))
+}
+
+// Drop returns SQL to drop the matview
+func (c MatViewSchema) Drop() {
+	fmt.Printf("DROP MATERIALIZED VIEW %s;\n\n", c.get("matviewname"))
+}
+
+// Change handles the case where the names match, but the definition does not
+func (c MatViewSchema) Change(obj interface{}) {
+	c2, ok := obj.(*MatViewSchema)
+	if !ok {
+		fmt.Println("Error!!!, Change needs a MatViewSchema instance", c2)
+	}
+	if c.get("definition") != c2.get("definition") {
+		fmt.Printf("DROP MATERIALIZED VIEW %s;\n", c.get("matviewname"))
+		fmt.Printf("CREATE MATERIALIZED VIEW %s AS %s \n\n", c.get("matviewname"), c.get("definition"))
+	}
+}
+
+// compareMatViews outputs SQL to make the matviews match between DBs
+func compareMatViews(conn1 *sql.DB, conn2 *sql.DB) {
+	sql := `
+	SELECT schemaname || '.' || matviewname AS matviewname
+	, definition 
+	FROM pg_matviews 
+	WHERE schemaname NOT LIKE 'pg_%' 
+	ORDER BY matviewname;
+	`
+
+	rowChan1, _ := pgutil.QueryStrings(conn1, sql)
+	rowChan2, _ := pgutil.QueryStrings(conn2, sql)
+
+	rows1 := make(MatViewRows, 0)
+	for row := range rowChan1 {
+		rows1 = append(rows1, row)
+	}
+	sort.Sort(rows1)
+
+	rows2 := make(MatViewRows, 0)
+	for row := range rowChan2 {
+		rows2 = append(rows2, row)
+	}
+	sort.Sort(rows2)
+
+	// We have to explicitly type this as Schema here
+	var schema1 Schema = &MatViewSchema{rows: rows1, rowNum: -1}
+	var schema2 Schema = &MatViewSchema{rows: rows2, rowNum: -1}
+
+	// Compare the matviews
+	doDiff(schema1, schema2)
+}
diff --git a/pgdiff.go b/pgdiff.go
index 921f8d9..bbaf597 100644
--- a/pgdiff.go
+++ b/pgdiff.go
@@ -6,13 +6,18 @@
 
 package main
 
-import "fmt"
-import "log"
-import flag "github.com/ogier/pflag"
-import "os"
-import "strings"
-import _ "github.com/lib/pq"
-import "github.com/joncrlsn/pgutil"
+import (
+	"fmt"
+	"log"
+
+	"os"
+	"strings"
+
+	flag "github.com/ogier/pflag"
+
+	"github.com/joncrlsn/pgutil"
+	_ "github.com/lib/pq"
+)
 
 // Schema is a database definition (table, column, constraint, indes, role, etc) that can be
 // added, dropped, or changed to match another database.
@@ -67,7 +72,7 @@ func main() {
 	}
 
 	if len(args) == 0 {
-		fmt.Println("The required first argument is SchemaType: SCHEMA, ROLE, SEQUENCE, TABLE, VIEW, COLUMN, INDEX, FOREIGN_KEY, OWNER, GRANT_RELATIONSHIP, GRANT_ATTRIBUTE")
+		fmt.Println("The required first argument is SchemaType: SCHEMA, ROLE, SEQUENCE, TABLE, VIEW, MATVIEW, COLUMN, INDEX, FOREIGN_KEY, OWNER, GRANT_RELATIONSHIP, GRANT_ATTRIBUTE")
 		os.Exit(1)
 	}
 
@@ -105,6 +110,7 @@ func main() {
 		compareColumns(conn1, conn2)
 		compareIndexes(conn1, conn2) // includes PK and Unique constraints
 		compareViews(conn1, conn2)
+		compareMatViews(conn1, conn2)
 		compareForeignKeys(conn1, conn2)
 		compareFunctions(conn1, conn2)
 		compareTriggers(conn1, conn2)
@@ -125,6 +131,8 @@ func main() {
 		compareIndexes(conn1, conn2)
 	} else if schemaType == "VIEW" {
 		compareViews(conn1, conn2)
+	} else if schemaType == "MATVIEW" {
+		compareMatViews(conn1, conn2)
 	} else if schemaType == "FOREIGN_KEY" {
 		compareForeignKeys(conn1, conn2)
 	} else if schemaType == "FUNCTION" {
@@ -203,7 +211,7 @@ Options:
   -S, --schema1 : first schema.  default is all schemas
   -s, --schema2 : second schema. default is all schemas
 
-<schemaTpe> can be: ALL, SCHEMA, ROLE, SEQUENCE, TABLE, VIEW, COLUMN, INDEX, FOREIGN_KEY, OWNER, GRANT_RELATIONSHIP, GRANT_ATTRIBUTE, TRIGGER, FUNCTION`)
+<schemaTpe> can be: ALL, SCHEMA, ROLE, SEQUENCE, TABLE, VIEW, MATVIEW, COLUMN, INDEX, FOREIGN_KEY, OWNER, GRANT_RELATIONSHIP, GRANT_ATTRIBUTE, TRIGGER, FUNCTION`)
 
 	os.Exit(2)
 }
diff --git a/pgdiff.sh b/pgdiff.sh
index 8effe41..9a16343 100755
--- a/pgdiff.sh
+++ b/pgdiff.sh
@@ -75,6 +75,7 @@ rundiff SCHEMA
 rundiff SEQUENCE
 rundiff TABLE
 rundiff COLUMN
+rundiff MATVIEW
 rundiff INDEX
 rundiff VIEW
 rundiff TRIGGER
@@ -85,4 +86,3 @@ rundiff GRANT_ATTRIBUTE
 
 echo
 echo "Done!"
-

From f0f3d8a09c1d4c775359afd07e4c9b007580ad3e Mon Sep 17 00:00:00 2001
From: Gustav <tvarsis@hotmail.com>
Date: Fri, 12 Feb 2021 16:20:43 +0100
Subject: [PATCH 67/74] added go mod

---
 go.mod | 14 ++++++++++++++
 go.sum | 27 +++++++++++++++++++++++++++
 2 files changed, 41 insertions(+)
 create mode 100644 go.mod
 create mode 100644 go.sum

diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..603eafb
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,14 @@
+module pgdiff
+
+go 1.13
+
+require (
+	github.com/joncrlsn/fileutil v0.0.0-20150212043926-71757336e569 // indirect
+	github.com/joncrlsn/misc v0.0.0-20160408024000-193a3fcec166
+	github.com/joncrlsn/pgutil v0.0.0-20171213024902-4c8aab9306b4
+	github.com/kr/pretty v0.2.1 // indirect
+	github.com/lib/pq v1.9.0
+	github.com/ogier/pflag v0.0.1
+	github.com/stvp/assert v0.0.0-20170616060220-4bc16443988b // indirect
+	golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..597ba70
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,27 @@
+github.com/joncrlsn/fileutil v0.0.0-20150212043926-71757336e569 h1:bGOVwE4GrUyU3Pz22yNL7mML4tc/8b8zSjUZzofLpFA=
+github.com/joncrlsn/fileutil v0.0.0-20150212043926-71757336e569/go.mod h1:YFE9T2vDUoqBSIywxQRZi1FWDcLsBgo+KDbLSw7HDNM=
+github.com/joncrlsn/misc v0.0.0-20160408024000-193a3fcec166 h1:urNZ026xorI3t6Nzivkd8KSNACPjHxeeuxG1FGQXBD8=
+github.com/joncrlsn/misc v0.0.0-20160408024000-193a3fcec166/go.mod h1:ZnHyWkKQ3JfdVYRo3PrjvB4RMdLs7SaQqTyUmk/rjIg=
+github.com/joncrlsn/pgutil v0.0.0-20171213024902-4c8aab9306b4 h1:wTFs1uYdQfopjUVlbpJj0k2pHqKGa4M6D6gxGSH54Z8=
+github.com/joncrlsn/pgutil v0.0.0-20171213024902-4c8aab9306b4/go.mod h1:iKyJCP0yj+Z+jEAobsEBh+t6WrUFxMQiClq3r7yI4z8=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
+github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/ogier/pflag v0.0.1 h1:RW6JSWSu/RkSatfcLtogGfFgpim5p7ARQ10ECk5O750=
+github.com/ogier/pflag v0.0.1/go.mod h1:zkFki7tvTa0tafRvTBIZTvzYyAu6kQhPZFnshFFPE+g=
+github.com/stvp/assert v0.0.0-20170616060220-4bc16443988b h1:GlTM/aMVIwU3luIuSN2SIVRuTqGPt1P97YxAi514ulw=
+github.com/stvp/assert v0.0.0-20170616060220-4bc16443988b/go.mod h1:CC7OXV9IjEZRA+znA6/Kz5vbSwh69QioernOHeDCatU=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
+golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM=
+golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

From f2186834c5cb8d54b71df15fac3824445bb8fc83 Mon Sep 17 00:00:00 2001
From: Gustav <tvarsis@hotmail.com>
Date: Sun, 14 Feb 2021 15:11:23 +0100
Subject: [PATCH 68/74] automatically recreates indexes after matview
 recreation

---
 mat_view.go | 23 ++++++++++++++++-------
 1 file changed, 16 insertions(+), 7 deletions(-)

diff --git a/mat_view.go b/mat_view.go
index ec356cd..1b1ab89 100644
--- a/mat_view.go
+++ b/mat_view.go
@@ -76,7 +76,7 @@ func (c *MatViewSchema) Compare(obj interface{}) int {
 
 // Add returns SQL to create the matview
 func (c MatViewSchema) Add() {
-	fmt.Printf("CREATE MATERIALIZED VIEW %s AS %s \n\n", c.get("matviewname"), c.get("definition"))
+	fmt.Printf("CREATE OR REPLACE MATERIALIZED VIEW %s AS %s \n\n%s \n\n", c.get("matviewname"), c.get("definition"), c.get("indexdef"))
 }
 
 // Drop returns SQL to drop the matview
@@ -91,19 +91,28 @@ func (c MatViewSchema) Change(obj interface{}) {
 		fmt.Println("Error!!!, Change needs a MatViewSchema instance", c2)
 	}
 	if c.get("definition") != c2.get("definition") {
-		fmt.Printf("DROP MATERIALIZED VIEW %s;\n", c.get("matviewname"))
-		fmt.Printf("CREATE MATERIALIZED VIEW %s AS %s \n\n", c.get("matviewname"), c.get("definition"))
+		fmt.Printf("DROP MATERIALIZED VIEW %s;\n\n", c.get("matviewname"))
+		fmt.Printf("CREATE OR REPLACE MATERIALIZED VIEW %s AS %s \n\n%s \n\n", c.get("matviewname"), c.get("definition"), c.get("indexdef"))
 	}
 }
 
 // compareMatViews outputs SQL to make the matviews match between DBs
 func compareMatViews(conn1 *sql.DB, conn2 *sql.DB) {
 	sql := `
-	SELECT schemaname || '.' || matviewname AS matviewname
-	, definition 
-	FROM pg_matviews 
+	WITH matviews as ( SELECT schemaname || '.' || matviewname AS matviewname,
+	definition
+	FROM pg_catalog.pg_matviews 
 	WHERE schemaname NOT LIKE 'pg_%' 
-	ORDER BY matviewname;
+	)
+	SELECT
+	matviewname,
+	definition,
+	string_agg(indexdef, ';' || E'\n\n') || ';' as indexdef
+	FROM matviews
+	LEFT JOIN  pg_catalog.pg_indexes on matviewname = schemaname || '.' || tablename
+	group by matviewname, definition
+	ORDER BY
+	matviewname;
 	`
 
 	rowChan1, _ := pgutil.QueryStrings(conn1, sql)

From 127ba212d005fd0d40684ad3e4c3c89341dfdfc4 Mon Sep 17 00:00:00 2001
From: Gustav <tvarsis@hotmail.com>
Date: Fri, 5 Mar 2021 15:40:27 +0100
Subject: [PATCH 69/74] added gitignore

---
 vendor/github.com/lib/pq/.gitignore | 7 +++++++
 1 file changed, 7 insertions(+)
 create mode 100644 vendor/github.com/lib/pq/.gitignore

diff --git a/vendor/github.com/lib/pq/.gitignore b/vendor/github.com/lib/pq/.gitignore
new file mode 100644
index 0000000..1f5a7d8
--- /dev/null
+++ b/vendor/github.com/lib/pq/.gitignore
@@ -0,0 +1,7 @@
+.db
+*.test
+*~
+*.swp
+.idea
+.vscode
+vendor/
\ No newline at end of file

From 9324506400229d379b64fc6f58fa3e2260a76936 Mon Sep 17 00:00:00 2001
From: Gustav <tvarsis@hotmail.com>
Date: Fri, 5 Mar 2021 15:43:04 +0100
Subject: [PATCH 70/74] revert change

---
 vendor/github.com/lib/pq/.gitignore | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/vendor/github.com/lib/pq/.gitignore b/vendor/github.com/lib/pq/.gitignore
index 1f5a7d8..3243952 100644
--- a/vendor/github.com/lib/pq/.gitignore
+++ b/vendor/github.com/lib/pq/.gitignore
@@ -3,5 +3,4 @@
 *~
 *.swp
 .idea
-.vscode
-vendor/
\ No newline at end of file
+.vscode
\ No newline at end of file

From 1e5965cfb8e262637857c692d8356d52a6c6181d Mon Sep 17 00:00:00 2001
From: Gustav <tvarsis@hotmail.com>
Date: Fri, 5 Mar 2021 15:51:31 +0100
Subject: [PATCH 71/74] removed wrong replace statement. fixed null index

---
 .gitignore  | 1 +
 mat_view.go | 6 +++---
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/.gitignore b/.gitignore
index 8ae2e97..2d5fb46 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
 pgdiff
+vendor/*
\ No newline at end of file
diff --git a/mat_view.go b/mat_view.go
index 1b1ab89..38b09e6 100644
--- a/mat_view.go
+++ b/mat_view.go
@@ -76,7 +76,7 @@ func (c *MatViewSchema) Compare(obj interface{}) int {
 
 // Add returns SQL to create the matview
 func (c MatViewSchema) Add() {
-	fmt.Printf("CREATE OR REPLACE MATERIALIZED VIEW %s AS %s \n\n%s \n\n", c.get("matviewname"), c.get("definition"), c.get("indexdef"))
+	fmt.Printf("CREATE MATERIALIZED VIEW %s AS %s \n\n%s \n\n", c.get("matviewname"), c.get("definition"), c.get("indexdef"))
 }
 
 // Drop returns SQL to drop the matview
@@ -92,7 +92,7 @@ func (c MatViewSchema) Change(obj interface{}) {
 	}
 	if c.get("definition") != c2.get("definition") {
 		fmt.Printf("DROP MATERIALIZED VIEW %s;\n\n", c.get("matviewname"))
-		fmt.Printf("CREATE OR REPLACE MATERIALIZED VIEW %s AS %s \n\n%s \n\n", c.get("matviewname"), c.get("definition"), c.get("indexdef"))
+		fmt.Printf("CREATE MATERIALIZED VIEW %s AS %s \n\n%s \n\n", c.get("matviewname"), c.get("definition"), c.get("indexdef"))
 	}
 }
 
@@ -107,7 +107,7 @@ func compareMatViews(conn1 *sql.DB, conn2 *sql.DB) {
 	SELECT
 	matviewname,
 	definition,
-	string_agg(indexdef, ';' || E'\n\n') || ';' as indexdef
+	COALESCE(string_agg(indexdef, ';' || E'\n\n') || ';', '')  as indexdef
 	FROM matviews
 	LEFT JOIN  pg_catalog.pg_indexes on matviewname = schemaname || '.' || tablename
 	group by matviewname, definition

From 97a94236151bd48abd8210dc064e1e51de04eaa1 Mon Sep 17 00:00:00 2001
From: imac <11695765+imac-beep@users.noreply.github.com>
Date: Wed, 10 Nov 2021 17:25:19 +0800
Subject: [PATCH 72/74] fix:  comparison for column default value

In the column default value comparison, there is a typo which should be fixed.
---
 column.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/column.go b/column.go
index 06ed6b3..76c12d3 100644
--- a/column.go
+++ b/column.go
@@ -259,7 +259,7 @@ func (c *ColumnSchema) Change(obj interface{}) {
 
 	// Detect column default change (or added, dropped)
 	if c.get("column_default") == "null" {
-		if c.get("column_default") != "null" {
+		if c2.get("column_default") != "null" {
 			fmt.Printf("ALTER TABLE %s.%s ALTER COLUMN %s DROP DEFAULT;\n", c2.get("table_schema"), c.get("table_name"), c.get("column_name"))
 		}
 	} else if c.get("column_default") != c2.get("column_default") {

From ef51a72904878a3662d0db0d6f0f5dfa25f10666 Mon Sep 17 00:00:00 2001
From: Asif J <asif.jamadar@sysdig.com>
Date: Thu, 7 Nov 2024 10:39:37 +0530
Subject: [PATCH 73/74] Fixing PG15 related bugs

---
 go.mod                |  2 +-
 go.sum                |  2 ++
 grant-attribute.go    |  2 +-
 grant-relationship.go |  2 +-
 grant.go              | 12 +++++++-----
 role.go               |  4 ++--
 view.go               | 14 ++++++++------
 7 files changed, 22 insertions(+), 16 deletions(-)

diff --git a/go.mod b/go.mod
index 603eafb..b715a54 100644
--- a/go.mod
+++ b/go.mod
@@ -7,7 +7,7 @@ require (
 	github.com/joncrlsn/misc v0.0.0-20160408024000-193a3fcec166
 	github.com/joncrlsn/pgutil v0.0.0-20171213024902-4c8aab9306b4
 	github.com/kr/pretty v0.2.1 // indirect
-	github.com/lib/pq v1.9.0
+	github.com/lib/pq v1.10.9
 	github.com/ogier/pflag v0.0.1
 	github.com/stvp/assert v0.0.0-20170616060220-4bc16443988b // indirect
 	golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
diff --git a/go.sum b/go.sum
index 597ba70..fbedfd7 100644
--- a/go.sum
+++ b/go.sum
@@ -11,6 +11,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
 github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
+github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/ogier/pflag v0.0.1 h1:RW6JSWSu/RkSatfcLtogGfFgpim5p7ARQ10ECk5O750=
 github.com/ogier/pflag v0.0.1/go.mod h1:zkFki7tvTa0tafRvTBIZTvzYyAu6kQhPZFnshFFPE+g=
 github.com/stvp/assert v0.0.0-20170616060220-4bc16443988b h1:GlTM/aMVIwU3luIuSN2SIVRuTqGPt1P97YxAi514ulw=
diff --git a/grant-attribute.go b/grant-attribute.go
index 69d076a..aa7d0ff 100644
--- a/grant-attribute.go
+++ b/grant-attribute.go
@@ -27,7 +27,7 @@ func initGrantAttributeSqlTemplate() *template.Template {
 -- Attribute/Column ACL only
 SELECT
   n.nspname AS schema_name
-  , {{ if eq $.DbSchema "*" }}n.nspname || '.' || {{ end }}c.relkind || '.' || c.relname || '.' || a.attname AS compare_name
+  , {{ if eq $.DbSchema "*" }}n.nspname::text || '.' || {{ end }}c.relkind::text  || '.' || c.relname::text || '.' || a.attname AS compare_name
   , CASE c.relkind
     WHEN 'r' THEN 'TABLE'
     WHEN 'v' THEN 'VIEW'
diff --git a/grant-relationship.go b/grant-relationship.go
index c4c12cd..5a2bc64 100644
--- a/grant-relationship.go
+++ b/grant-relationship.go
@@ -25,7 +25,7 @@ var (
 func initGrantRelationshipSqlTemplate() *template.Template {
 	sql := `
 SELECT n.nspname AS schema_name
-  , {{ if eq $.DbSchema "*" }}n.nspname || '.' || {{ end }}c.relkind || '.' || c.relname AS compare_name
+  , {{ if eq $.DbSchema "*" }}n.nspname::text  || '.' || {{ end }}c.relkind::text  || '.' || c.relname::text  AS compare_name
   , CASE c.relkind
     WHEN 'r' THEN 'TABLE'
     WHEN 'v' THEN 'VIEW'
diff --git a/grant.go b/grant.go
index 4843f8f..6d7e5d6 100644
--- a/grant.go
+++ b/grant.go
@@ -8,12 +8,14 @@
 
 package main
 
-import "sort"
-import "fmt"
-import "strings"
-import "regexp"
+import (
+		"sort"
+ 		"fmt"
+ 		"strings"
+ 		"regexp"
+)
 
-var aclRegex = regexp.MustCompile(`([a-zA-Z0-9]+)*=([rwadDxtXUCcT]+)/([a-zA-Z0-9]+)$`)
+var aclRegex = regexp.MustCompile(`([a-zA-Z0-9_]+)*=([rwadDxtXUCcT]+)/([a-zA-Z0-9_]+)$`)
 
 var permMap = map[string]string{
 	"a": "INSERT",
diff --git a/role.go b/role.go
index 34ed1e8..b0bb5eb 100644
--- a/role.go
+++ b/role.go
@@ -252,13 +252,13 @@ func (c RoleSchema) Change(obj interface{}) {
 		// TODO: Define INHERIT or not
 		for _, mo1 := range membersof1 {
 			if !misc.ContainsString(membersof2, mo1) {
-				fmt.Printf("GRANT %s TO %s;\n", mo1, c.get("rolename"))
+				fmt.Printf("GRANT %s TO %s;\n", mo1, c.get("rolname"))
 			}
 		}
 
 		for _, mo2 := range membersof2 {
 			if !misc.ContainsString(membersof1, mo2) {
-				fmt.Printf("REVOKE %s FROM %s;\n", mo2, c.get("rolename"))
+				fmt.Printf("REVOKE %s FROM %s;\n", mo2, c.get("rolname"))
 			}
 		}
 
diff --git a/view.go b/view.go
index e2f39b9..5954388 100644
--- a/view.go
+++ b/view.go
@@ -6,11 +6,13 @@
 
 package main
 
-import "fmt"
-import "sort"
-import "database/sql"
-import "github.com/joncrlsn/pgutil"
-import "github.com/joncrlsn/misc"
+import (
+		"fmt"
+		"sort"
+		"database/sql"
+		"github.com/joncrlsn/pgutil"
+		"github.com/joncrlsn/misc"
+)
 
 // ==================================
 // ViewRows definition
@@ -99,7 +101,7 @@ func compareViews(conn1 *sql.DB, conn2 *sql.DB) {
 	SELECT schemaname || '.' || viewname AS viewname
 		, definition 
 	FROM pg_views 
-	WHERE schemaname NOT LIKE 'pg_%' 
+	WHERE schemaname NOT LIKE 'pg_%' AND schemaname!='infromation_schema'
 	ORDER BY viewname;
 	`
 

From 4e1873f219c5b8e7ea6bfec40f1ea9323a37298d Mon Sep 17 00:00:00 2001
From: Naphat Deepar <naphat_d@zoxa.io>
Date: Sat, 8 Mar 2025 12:02:21 +0700
Subject: [PATCH 74/74] add build for mac arm64

---
 README-macos-arm64.md | 16 ++++++++++++++++
 build.sh              | 28 ++++++++++++++++++++++++++++
 2 files changed, 44 insertions(+)
 create mode 100644 README-macos-arm64.md

diff --git a/README-macos-arm64.md b/README-macos-arm64.md
new file mode 100644
index 0000000..110f729
--- /dev/null
+++ b/README-macos-arm64.md
@@ -0,0 +1,16 @@
+## OSX / Mac / ARM64 pgdiff instructions
+
+These instructions will guide you through the process of generating SQL, reviewing it, and optionally running it on the target database.  It requires a familiarity with the bash shell in OSX.
+
+1. download pgdiff-arm64-\<version\>.tar.gz to your machine
+1. untar it (a new directory will be created: called pgdiff)
+1. cd into the new pgdiff directory
+1. edit pgdiff.sh to change the db access values... or set them at runtime (i.e. USER1=joe NAME1=mydb USER2=joe NAME2=myotherdb ./pgdiff.sh)
+1. run pgdiff.sh
+
+## tar contents
+* pgdiff - an OSX executable
+* pgrun - an OSX executable for running SQL
+* pgdiff.sh - a bash shell script to coordinate your interaction with pgdiff and pgrun
+
+If you write a Go version of pgdiff.sh, please share it so we can include it or link to it for others to use.
diff --git a/build.sh b/build.sh
index 20de750..305cbd7 100755
--- a/build.sh
+++ b/build.sh
@@ -24,6 +24,9 @@ LINUX_FILE="${APPNAME}-linux-${VERSION}.tar.gz"
 OSX_README=README-osx.md
 OSX_FILE="${APPNAME}-osx-${VERSION}.tar.gz"
 
+ARM64_README=README-macos-arm64.md
+ARM64_FILE="${APPNAME}-macos-arm64-${VERSION}.tar.gz"
+
 WIN_README=README-win.md
 WIN_FILE="${APPNAME}-win-${VERSION}.zip"
 
@@ -77,6 +80,31 @@ else
     echo "Skipping osx.  No $OSX_README file."
 fi
 
+if [[ -f $ARM64_README ]]; then
+    echo "  ==== Building macOS-arm64 ===="
+    tempdir="$(mktemp -d)"
+    workdir="$tempdir/$APPNAME"
+    echo $workdir
+    mkdir -p $workdir
+    # Build the executable
+    GOOS=darwin GOARCH=arm64 go build -o "$workdir/$APPNAME"
+    # Download pgrun to the work directory 
+    wget -O "$workdir/pgrun" "https://github.com/feverxai/pgrun/raw/master/bin-arm64/pgrun" # once PR is accepted change to "https://github.com/joncrlsn/pgrun/raw/master/bin-arm64/pgrun"
+    # Copy the bash runtime script to the temp directory
+    cp pgdiff.sh "$workdir/"
+    cp "${SCRIPT_DIR}/${ARM64_README}" "$workdir/README.md"
+    cd "$tempdir"
+    # Make everything executable
+    chmod -v ugo+x $APPNAME/*
+    tarName="${tempdir}/${ARM64_FILE}"
+    COPYFILE_DISABLE=true tar -cvzf "$tarName" $APPNAME
+    cd -
+    mv "$tarName" "${SCRIPT_DIR}/"
+    echo "Built macOS-arm64."
+else
+    echo "Skipping macOS-arm64.  No $ARM64_README file."
+fi
+
 if [[ -f $WIN_README ]]; then
     echo "  ==== Building Windows ===="
     tempdir="$(mktemp -d)"