11package main
22
33import "sort"
4+ import "os"
45import "fmt"
56import "strings"
7+ import "regexp"
68import "database/sql"
79import "github.com/joncrlsn/pgutil"
8- import "regexp"
910
1011var aclRegex = regexp .MustCompile (`([a-zA-Z0-9]+)*=([rwadDxtXUCcT]+)/([a-zA-Z0-9]+)$` )
1112
@@ -43,6 +44,12 @@ func (slice GrantRows) Less(i, j int) bool {
4344 if slice [i ]["column_name" ] != slice [j ]["column_name" ] {
4445 return slice [i ]["column_name" ] < slice [j ]["column_name" ]
4546 }
47+ if slice [i ]["relationship_acl" ] != slice [j ]["relationship_acl" ] {
48+ return slice [i ]["relationship_acl" ] < slice [j ]["relationship_acl" ]
49+ }
50+ if slice [i ]["column_acl" ] != slice [j ]["column_acl" ] {
51+ return slice [i ]["column_acl" ] < slice [j ]["column_acl" ]
52+ }
4653 return false
4754}
4855
@@ -92,7 +99,7 @@ func (c *GrantSchema) NextRow() bool {
9299func (c * GrantSchema ) Compare (obj interface {}) int {
93100 c2 , ok := obj .(* GrantSchema )
94101 if ! ok {
95- fmt .Println ("Error!!!, change needs a GrantSchema instance" , c2 )
102+ fmt .Println ("Error!!!, Compare needs a GrantSchema instance" , c2 )
96103 return + 999
97104 }
98105
@@ -114,49 +121,55 @@ func (c *GrantSchema) Compare(obj interface{}) int {
114121func (c * GrantSchema ) Add () {
115122 fmt .Println ("--Add" )
116123
117- if c .get ("column_acl" ) != "null" && len (c .get ("column_acl" )) > 0 {
118- lines := strings .Split (c .get ("column_acl" ), "\n " )
119- for _ , line := range lines {
120- roleName , perms := parseGrants (line )
121- fmt .Printf ("GRANT %s (%s) ON %s TO %s; \n " , strings .Join (perms , ", " ), c .get ("column_name" ), c .get ("relationship_name" ), roleName )
122- }
123- } else if c .get ("relationship_acl" ) != "null" && len (c .get ("relationship_acl" )) > 0 {
124- lines := strings .Split (c .get ("relationship_acl" ), "\n " )
125- for _ , line := range lines {
126- roleName , perms := parseGrants (line )
127- fmt .Printf ("GRANT %s ON %s TO %s; \n " , strings .Join (perms , ", " ), c .get ("relationship_name" ), roleName )
128- }
124+ acls := parseGrants (c .get ("relationship_acl" ))
125+ for _ , acl := range acls {
126+ fmt .Printf ("GRANT %s ON %s TO %s;\n " , strings .Join (acl .grants , ", " ), c .get ("relationship_name" ), acl .role )
127+ }
128+
129+ acls = parseGrants (c .get ("column_acl" ))
130+ for _ , acl := range acls {
131+ fmt .Printf ("GRANT %s (%s) ON %s TO %s;\n " , strings .Join (acl .grants , ", " ), c .get ("column_name" ), c .get ("relationship_name" ), acl .role )
129132 }
130133}
131134
132135// Drop prints SQL to drop the column
133136func (c * GrantSchema ) Drop () {
134137 fmt .Println ("--Drop" )
135138
136- if c .get ("column_acl" ) != "null" && len (c .get ("column_acl" )) > 0 {
137- lines := strings .Split (c .get ("column_acl" ), "\n " )
138- for _ , line := range lines {
139- roleName , perms := parseGrants (line )
140- fmt .Printf ("REVOKE %s (%s) ON %s TO %s; \n " , strings .Join (perms , ", " ), c .get ("column_name" ), c .get ("relationship_name" ), roleName )
141- }
142- } else if c .get ("relationship_acl" ) != "null" && len (c .get ("relationship_acl" )) > 0 {
143- lines := strings .Split (c .get ("relationship_acl" ), "\n " )
144- for _ , line := range lines {
145- roleName , perms := parseGrants (line )
146- fmt .Printf ("REVOKE %s ON %s TO %s; \n " , strings .Join (perms , ", " ), c .get ("relationship_name" ), roleName )
147- }
139+ acls := parseGrants (c .get ("relationship_acl" ))
140+ for _ , acl := range acls {
141+ fmt .Printf ("REVOKE %s ON %s TO %s;\n " , strings .Join (acl .grants , ", " ), c .get ("relationship_name" ), acl .role )
142+ }
143+
144+ acls = parseGrants (c .get ("column_acl" ))
145+ for _ , acl := range acls {
146+ fmt .Printf ("REVOKE %s (%s) ON %s TO %s;\n " , strings .Join (acl .grants , ", " ), c .get ("column_name" ), c .get ("relationship_name" ), acl .role )
148147 }
149148}
150149
151- // Change handles the case where the table and column match, but the details do not
150+ // Change handles the case where the relationship and column match, but the details do not
152151func (c * GrantSchema ) Change (obj interface {}) {
153152 c2 , ok := obj .(* GrantSchema )
154153 if ! ok {
155154 fmt .Println ("-- Error!!!, change needs a GrantSchema instance" , c2 )
156155 }
156+
157+ {
158+ acls1 := parseGrants (c .get ("relationship_acl" ))
159+ acls2 := parseGrants (c2 .get ("relationship_acl" ))
160+ _diffGrants (acls1 , acls2 , c .get ("relationship_name" ), c .get ("column_name" ))
161+ }
162+
163+ {
164+ // if c.get("column_acl") != "null" && len(c.get("column_acl")) > 0 {
165+ acls1 := parseGrants (c .get ("column_acl" ))
166+ acls2 := parseGrants (c2 .get ("column_acl" ))
167+ _diffGrants (acls1 , acls2 , c .get ("relationship_name" ), c .get ("column_name" ))
168+ }
169+
157170 fmt .Println ("--Change" )
158- fmt .Printf ("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" ))
159- fmt .Printf ("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" ))
171+ 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" ))
172+ 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" ))
160173}
161174
162175// ==================================
@@ -167,25 +180,53 @@ func (c *GrantSchema) Change(obj interface{}) {
167180 * Compare the columns in the two databases
168181 */
169182func compareGrants (conn1 * sql.DB , conn2 * sql.DB ) {
183+ fmt .Println (" Grant is broken right now. Please come back later! :-)" )
184+ os .Exit (0 )
185+
170186 sql := `
171187SELECT
172188 n.nspname AS schema
173189 , CASE c.relkind
174- WHEN 'r' THEN 'TABLE'
175- WHEN 'v' THEN 'VIEW'
176- WHEN 'S' THEN 'SEQUENCE'
177- WHEN 'f' THEN 'FOREIGN TABLE'
178- END as type
190+ WHEN 'r' THEN 'TABLE'
191+ WHEN 'v' THEN 'VIEW'
192+ WHEN 'S' THEN 'SEQUENCE'
193+ WHEN 'f' THEN 'FOREIGN TABLE'
194+ END as type
179195 , c.relname AS relationship_name
180- , pg_catalog.array_to_string(c.relacl, E'\n') AS relationship_acl
196+ -- , pg_catalog.array_to_string(c.relacl, E'\n') AS relationship_acl
197+ -- , pg_catalog.array_to_string(a.attacl, E'\n') AS column_acl
198+ , unnest(c.relacl) AS relationship_acl
181199 , a.attname AS column_name
182- , pg_catalog.array_to_string( a.attacl, E'\n') AS column_acl
200+ , a.attacl AS column_acl
183201FROM pg_catalog.pg_class c
184202LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
185- LEFT JOIN pg_catalog.pg_attribute a ON (a.attrelid = c.oid AND NOT a.attisdropped AND a.attacl IS NOT NULL)
203+ LEFT JOIN (SELECT attname, unnest(attacl) AS attacl, attrelid, attisdropped
204+ FROM pg_catalog.pg_attribute
205+ WHERE NOT attisdropped AND attacl IS NOT NULL)
206+ AS a ON (a.attrelid = c.oid)
186207WHERE c.relkind IN ('r', 'v', 'S', 'f')
187- AND n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid);
208+ AND n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)
209+ ORDER BY n.nspname, c.relname, a.attname;
188210`
211+ //oldSql := `SELECT
212+ // n.nspname AS schema
213+ // , CASE c.relkind
214+ // WHEN 'r' THEN 'TABLE'
215+ // WHEN 'v' THEN 'VIEW'
216+ // WHEN 'S' THEN 'SEQUENCE'
217+ // WHEN 'f' THEN 'FOREIGN TABLE'
218+ // END as type
219+ // , c.relname AS relationship_name
220+ // , pg_catalog.array_to_string(c.relacl, E'\n') AS relationship_acl
221+ // , a.attname AS column_name
222+ // , pg_catalog.array_to_string(a.attacl, E'\n') AS column_acl
223+ //FROM pg_catalog.pg_class c
224+ //LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
225+ //LEFT JOIN pg_catalog.pg_attribute a ON (a.attrelid = c.oid AND NOT a.attisdropped AND a.attacl IS NOT NULL)
226+ //WHERE c.relkind IN ('r', 'v', 'S', 'f')
227+ // AND n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid);
228+ //`
229+
189230 rowChan1 , _ := pgutil .QueryStrings (conn1 , sql )
190231 rowChan2 , _ := pgutil .QueryStrings (conn2 , sql )
191232
@@ -208,10 +249,120 @@ WHERE c.relkind IN ('r', 'v', 'S', 'f')
208249 doDiff (schema1 , schema2 )
209250}
210251
252+ // ==================================
253+ // Private functions and structures
254+ // ==================================
255+
256+ func _diffGrants (acls1 RoleAcls , acls2 RoleAcls , table string , column string ) {
257+ //fmt.Printf("GRANT %s (%s) ON %s TO %s; \n", strings.Join(perms, ", "), c.get("column_name"), c.get("relationship_name"), roleName)
258+ ix1 , ix2 := 0 , 0
259+ more1 := ix1 < len (acls1 )
260+ more2 := ix2 < len (acls2 )
261+ var acl1 RoleAcl
262+ var acl2 RoleAcl
263+ if more1 {
264+ acl1 = acls1 [ix1 ]
265+ }
266+ if more2 {
267+ acl2 = acls2 [ix2 ]
268+ }
269+ for more1 || more2 {
270+ if acl1 .role == acl2 .role {
271+ //_diffRole(acl1.grants, acl2.grants, acl1.role, table, column)
272+ ix1 += 1
273+ ix2 += 1
274+ more1 := ix1 < len (acls1 )
275+ more2 := ix2 < len (acls2 )
276+ if more1 {
277+ acl1 = acls1 [ix1 ]
278+ }
279+ if more2 {
280+ acl2 = acls2 [ix2 ]
281+ }
282+ } else if acl1 .role < acl2 .role {
283+ } else if acl1 .role > acl2 .role {
284+ }
285+ // compareVal := db1.Compare(db2)
286+ // if compareVal == 0 {
287+ // // table and column match, look for non-identifying changes
288+ // db1.Change(db2)
289+ // more1 = db1.NextRow()
290+ // more2 = db2.NextRow()
291+ // } else if compareVal < 0 {
292+ // // db2 is missing a value that db1 has
293+ // if more1 {
294+ // db1.Add()
295+ // more1 = db1.NextRow()
296+ // } else {
297+ // // db1 is at the end
298+ // db2.Drop()
299+ // more2 = db2.NextRow()
300+ // }
301+ // } else if compareVal > 0 {
302+ // // db2 has an extra column that we don't want
303+ // if more2 {
304+ // db2.Drop()
305+ // more2 = db2.NextRow()
306+ // } else {
307+ // // db2 is at the end
308+ // db1.Add()
309+ // more1 = db1.NextRow()
310+ // }
311+ // }
312+ }
313+ }
314+
315+ //
316+ // RoleAcls (a sortable slice of RoleAcl instances)
317+ //
318+ type RoleAcls []RoleAcl
319+
320+ func (slice RoleAcls ) Len () int {
321+ return len (slice )
322+ }
323+
324+ func (slice RoleAcls ) Less (i , j int ) bool {
325+ return slice [i ].role < slice [j ].role
326+ }
327+
328+ func (slice RoleAcls ) Swap (i , j int ) {
329+ slice [i ], slice [j ] = slice [j ], slice [i ]
330+ }
331+
332+ func (slice RoleAcls ) get (role string ) RoleAcl {
333+ for _ , roleAcl := range slice {
334+ if roleAcl .role == role {
335+ return roleAcl
336+ }
337+ }
338+ return RoleAcl {role : "" , grants : []string {}}
339+ }
340+
341+ //
342+ // RoleAcl
343+ //
344+ type RoleAcl struct {
345+ role string
346+ grants []string
347+ }
348+
349+ // parseGrants breaks up a set of ACL lines and parses them into a slice of permission strings per line.
350+ func parseGrants (acl string ) (roleAcls RoleAcls ) {
351+ lines := strings .Split (acl , "\n " )
352+ roleAcls = make (RoleAcls , 0 )
353+ for _ , line := range lines {
354+ roleName , perms := _parseGrants (line )
355+ roleAcl := RoleAcl {role : roleName , grants : perms }
356+ roleAcls = append (roleAcls , roleAcl )
357+ }
358+ sort .Sort (roleAcls )
359+ return
360+ }
361+
211362/*
212- parseGrants converts an ACL string into a slice of permission strings
363+ _parseGrants converts an ACL line into a slice of permission strings
213364
214- Example of an ACL: c42ro=rwa/c42 (we want to split out the "rwa" part)
365+ Example of an ACL: c42ro=rwa/c42 (we want to separate out the "rwa" part)
215366
216367rolename=xxxx -- privileges granted to a role
217368 =xxxx -- privileges granted to PUBLIC
@@ -231,17 +382,17 @@ rolename=xxxx -- privileges granted to a role
231382 * -- grant option for preceding privilege
232383 /yyyy -- role that granted this privilege
233384*/
234- func parseGrants (acl string ) (string , []string ) {
235-
385+ func _parseGrants (acl string ) (string , sort.StringSlice ) {
236386 matches := aclRegex .FindStringSubmatch (acl )
237387 role := matches [1 ]
238388 perms := matches [2 ]
239- var permWords [] string
389+ permWords := make (sort. StringSlice , 0 )
240390 for _ , c := range strings .Split (perms , "" ) {
241391 permWord := permMap [c ]
242392 if len (permWord ) > 0 {
243393 permWords = append (permWords , permWord )
244394 }
245395 }
396+ permWords .Sort ()
246397 return role , permWords
247398}
0 commit comments