|
| 1 | +package main |
| 2 | + |
| 3 | +import "fmt" |
| 4 | +import "database/sql" |
| 5 | +import "sort" |
| 6 | +import "github.com/joncrlsn/pgutil" |
| 7 | + |
| 8 | +// ================================== |
| 9 | +// OwnerRows definition (an array of string maps) |
| 10 | +// ================================== |
| 11 | +type OwnerRows []map[string]string |
| 12 | + |
| 13 | +func (slice OwnerRows) Len() int { |
| 14 | + return len(slice) |
| 15 | +} |
| 16 | + |
| 17 | +func (slice OwnerRows) Less(i, j int) bool { |
| 18 | + return slice[i]["relationship_name"] < slice[j]["relationship_name"] |
| 19 | +} |
| 20 | + |
| 21 | +func (slice OwnerRows) Swap(i, j int) { |
| 22 | + slice[i], slice[j] = slice[j], slice[i] |
| 23 | +} |
| 24 | + |
| 25 | +// ================================== |
| 26 | +// OwnerSchema definition |
| 27 | +// (implements Schema -- defined in pgdiff.go) |
| 28 | +// ================================== |
| 29 | + |
| 30 | +// OwnerSchema holds a slice of rows from one of the databases as well as |
| 31 | +// a reference to the current row of data we're viewing. |
| 32 | +type OwnerSchema struct { |
| 33 | + rows OwnerRows |
| 34 | + rowNum int |
| 35 | + done bool |
| 36 | +} |
| 37 | + |
| 38 | +// get returns the value from the current row for the given key |
| 39 | +func (c *OwnerSchema) get(key string) string { |
| 40 | + if c.rowNum >= len(c.rows) { |
| 41 | + return "" |
| 42 | + } |
| 43 | + return c.rows[c.rowNum][key] |
| 44 | +} |
| 45 | + |
| 46 | +// get returns the current row for the given key |
| 47 | +func (c *OwnerSchema) getRow() map[string]string { |
| 48 | + if c.rowNum >= len(c.rows) { |
| 49 | + return make(map[string]string) |
| 50 | + } |
| 51 | + return c.rows[c.rowNum] |
| 52 | +} |
| 53 | + |
| 54 | +// NextRow increments the rowNum and tells you whether or not there are more |
| 55 | +func (c *OwnerSchema) NextRow() bool { |
| 56 | + if c.rowNum >= len(c.rows)-1 { |
| 57 | + c.done = true |
| 58 | + } |
| 59 | + c.rowNum = c.rowNum + 1 |
| 60 | + return !c.done |
| 61 | +} |
| 62 | + |
| 63 | +// Compare tells you, in one pass, whether or not the first row matches, is less than, or greater than the second row |
| 64 | +func (c *OwnerSchema) Compare(obj interface{}) int { |
| 65 | + c2, ok := obj.(*OwnerSchema) |
| 66 | + if !ok { |
| 67 | + fmt.Println("Error!!!, Compare needs a OwnerSchema instance", c2) |
| 68 | + return +999 |
| 69 | + } |
| 70 | + |
| 71 | + val := _compareString(c.get("relationship_name"), c2.get("relationship_name")) |
| 72 | + return val |
| 73 | +} |
| 74 | + |
| 75 | +// Add generates SQL to add the table/view owner |
| 76 | +func (c OwnerSchema) Add() { |
| 77 | + if c.get("type") == "TABLE" || c.get("type") == "SEQUENCE" { |
| 78 | + fmt.Printf("-- Notice, db2 has no %s. You probably need to run pgdiff with the %s option first.\n", c.get("relationship_name"), c.get("type")) |
| 79 | + } else if c.get("type") == "VIEW" { |
| 80 | + fmt.Printf("-- Notice, db2 has no view named %s. pgdiff does not yet compare view definitions.\n", c.get("relationship_name")) |
| 81 | + } |
| 82 | +} |
| 83 | + |
| 84 | +// Drop generates SQL to drop the role |
| 85 | +func (c OwnerSchema) Drop() { |
| 86 | + if c.get("type") == "TABLE" || c.get("type") == "SEQUENCE" { |
| 87 | + fmt.Printf("-- Notice, db2 has a %s that db1 does not: %s. \n", c.get("type"), c.get("relationship_name")) |
| 88 | + } else if c.get("type") == "VIEW" { |
| 89 | + fmt.Printf("-- Notice, db2 has a view that db1 does not: %s. pgdiff does not yet compare view definitions.\n", c.get("relationship_name")) |
| 90 | + } |
| 91 | +} |
| 92 | + |
| 93 | +// Change handles the case where the role name matches, but the details do not |
| 94 | +func (c OwnerSchema) Change(obj interface{}) { |
| 95 | + c2, ok := obj.(*OwnerSchema) |
| 96 | + if !ok { |
| 97 | + fmt.Println("-- Error!!!, Change needs a OwnerSchema instance", c2) |
| 98 | + } |
| 99 | + |
| 100 | + if c.get("owner") != c2.get("owner") { |
| 101 | + fmt.Printf("ALTER %s %s OWNER TO %s; \n", c.get("type"), c.get("relationship_name"), c.get("owner")) |
| 102 | + } |
| 103 | +} |
| 104 | + |
| 105 | +/* |
| 106 | + * Compare the ownership of tables, sequences, and views in the two databases |
| 107 | + */ |
| 108 | +func compareOwners(conn1 *sql.DB, conn2 *sql.DB) { |
| 109 | + sql := ` |
| 110 | +SELECT n.nspname AS schema |
| 111 | + , c.relname AS relationship_name |
| 112 | + , a.rolname AS owner |
| 113 | + , CASE WHEN c.relkind = 'r' THEN 'TABLE' |
| 114 | + WHEN c.relkind = 'S' THEN 'SEQUENCE' |
| 115 | + WHEN c.relkind = 'v' THEN 'VIEW' |
| 116 | + ELSE c.relkind::varchar END AS type |
| 117 | +FROM pg_class AS c |
| 118 | +INNER JOIN pg_authid AS a ON (a.oid = c.relowner) |
| 119 | +INNER JOIN pg_namespace AS n ON (n.oid = c.relnamespace) |
| 120 | +WHERE n.nspname = 'public' |
| 121 | +AND c.relkind IN ('r', 'S', 'v'); |
| 122 | +` |
| 123 | + rowChan1, _ := pgutil.QueryStrings(conn1, sql) |
| 124 | + rowChan2, _ := pgutil.QueryStrings(conn2, sql) |
| 125 | + |
| 126 | + rows1 := make(OwnerRows, 0) |
| 127 | + for row := range rowChan1 { |
| 128 | + rows1 = append(rows1, row) |
| 129 | + } |
| 130 | + sort.Sort(rows1) |
| 131 | + |
| 132 | + rows2 := make(OwnerRows, 0) |
| 133 | + for row := range rowChan2 { |
| 134 | + rows2 = append(rows2, row) |
| 135 | + } |
| 136 | + sort.Sort(rows2) |
| 137 | + |
| 138 | + // We have to explicitly type this as Schema here for some unknown reason |
| 139 | + var schema1 Schema = &OwnerSchema{rows: rows1, rowNum: -1} |
| 140 | + var schema2 Schema = &OwnerSchema{rows: rows2, rowNum: -1} |
| 141 | + |
| 142 | + doDiff(schema1, schema2) |
| 143 | +} |
0 commit comments