forked from joncrlsn/pgdiff
-
Notifications
You must be signed in to change notification settings - Fork 0
/
grant-relationship.go
211 lines (178 loc) · 6.21 KB
/
grant-relationship.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
//
// 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.
//
package main
import "sort"
import "fmt"
import "strings"
import "database/sql"
import "github.com/joncrlsn/pgutil"
import "github.com/joncrlsn/misc"
// ==================================
// GrantRelationshipRows definition
// ==================================
// GrantRelationshipRows is a sortable slice of string maps
type GrantRelationshipRows []map[string]string
func (slice GrantRelationshipRows) Len() int {
return len(slice)
}
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"]
}
// Only compare the role part of the ACL
// Not yet sure if this is absolutely necessary
// (or if we could just compare the entire ACL string)
relRole1, _ := parseAcl(slice[i]["relationship_acl"])
relRole2, _ := parseAcl(slice[j]["relationship_acl"])
if relRole1 != relRole2 {
return relRole1 < relRole2
}
return false
}
func (slice GrantRelationshipRows) Swap(i, j int) {
slice[i], slice[j] = slice[j], slice[i]
}
// ==================================
// GrantRelationshipSchema definition
// (implements Schema -- defined in pgdiff.go)
// ==================================
// GrantRelationshipSchema holds a slice of rows from one of the databases as well as
// a reference to the current row of data we're viewing.
type GrantRelationshipSchema struct {
rows GrantRelationshipRows
rowNum int
done bool
}
// get returns the value from the current row for the given key
func (c *GrantRelationshipSchema) get(key string) string {
if c.rowNum >= len(c.rows) {
return ""
}
return c.rows[c.rowNum][key]
}
// get returns the current row for the given key
func (c *GrantRelationshipSchema) getRow() map[string]string {
if c.rowNum >= len(c.rows) {
return make(map[string]string)
}
return c.rows[c.rowNum]
}
// NextRow increments the rowNum and tells you whether or not there are more
func (c *GrantRelationshipSchema) 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 *GrantRelationshipSchema) Compare(obj interface{}) int {
c2, ok := obj.(*GrantRelationshipSchema)
if !ok {
fmt.Println("Error!!!, Compare needs a GrantRelationshipSchema instance", c2)
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
}
relRole1, _ := parseAcl(c.get("relationship_acl"))
relRole2, _ := parseAcl(c2.get("relationship_acl"))
val = misc.CompareStrings(relRole1, relRole2)
return val
}
// Add prints SQL to add the column
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() {
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)
}
// Change handles the case where the relationship and column match, but the details do not
func (c *GrantRelationshipSchema) Change(obj interface{}) {
c2, ok := obj.(*GrantRelationshipSchema)
if !ok {
fmt.Println("-- Error!!!, change needs a GrantRelationshipSchema instance", c2)
}
role, grants1 := parseGrants(c.get("relationship_acl"))
_, grants2 := parseGrants(c2.get("relationship_acl"))
// Find grants in the first db that are not in the second
// (for this relationship and owner)
var grantList []string
for _, g := range grants1 {
if !misc.ContainsString(grants2, g) {
grantList = append(grantList, g)
}
}
if len(grantList) > 0 {
fmt.Printf("GRANT %s ON %s TO %s; -- Change\n", strings.Join(grantList, ", "), c.get("relationship_name"), role)
}
// Find grants in the second db that are not in the first
// (for this relationship and owner)
var revokeList []string
for _, g := range grants2 {
if !misc.ContainsString(grants1, g) {
revokeList = append(revokeList, g)
}
}
if len(revokeList) > 0 {
fmt.Printf("REVOKE %s ON %s FROM %s; -- Change\n", strings.Join(revokeList, ", "), 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"))
// fmt.Printf("--2 rel:%s, relAcl:%s, col:%s, colAcl:%s\n", c2.get("relationship_name"), c2.get("relationship_acl"), c2.get("column_name"), c2.get("column_acl"))
}
// ==================================
// Functions
// ==================================
/*
* Compare the columns in the two databases
*/
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 !~ '^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)
rows1 := make(GrantRelationshipRows, 0)
for row := range rowChan1 {
rows1 = append(rows1, row)
}
sort.Sort(rows1)
rows2 := make(GrantRelationshipRows, 0)
for row := range rowChan2 {
rows2 = append(rows2, row)
}
sort.Sort(rows2)
// We have to explicitly type this as Schema here for some unknown reason
var schema1 Schema = &GrantRelationshipSchema{rows: rows1, rowNum: -1}
var schema2 Schema = &GrantRelationshipSchema{rows: rows2, rowNum: -1}
doDiff(schema1, schema2)
}