| /* | 
 | Copyright 2016 The Transicator Authors | 
 |  | 
 | Licensed under the Apache License, Version 2.0 (the "License"); | 
 | you may not use this file except in compliance with the License. | 
 | You may obtain a copy of the License at | 
 |  | 
 |     http://www.apache.org/licenses/LICENSE-2.0 | 
 |  | 
 | Unless required by applicable law or agreed to in writing, software | 
 | distributed under the License is distributed on an "AS IS" BASIS, | 
 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | See the License for the specific language governing permissions and | 
 | limitations under the License. | 
 | */ | 
 | package snapshotserver | 
 |  | 
 | import "database/sql" | 
 |  | 
 | // A pgColumn describes a single column in a table | 
 | type pgColumn struct { | 
 | 	name       string | 
 | 	typid      int | 
 | 	primaryKey bool | 
 | } | 
 |  | 
 | // A pgTable describes a table | 
 | type pgTable struct { | 
 | 	schema      string | 
 | 	name        string | 
 | 	columns     []pgColumn | 
 | 	primaryKeys []string | 
 | 	hasSelector bool | 
 | } | 
 |  | 
 | // enumeratePgTables returns a list of information about each table | 
 | // and also about its columns. | 
 | func enumeratePgTables(tx *sql.Tx) (map[string]*pgTable, error) { | 
 |  | 
 | 	// A map makes this algorithm easier to understand although | 
 | 	// theoretically we can do without it. | 
 | 	tm, err := mapPgTables(tx) | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 |  | 
 | 	ps, err := tx.Prepare(` | 
 | 	select a.attname | 
 | 	from pg_namespace n, pg_class c, pg_index i, pg_attribute a | 
 | 	where n.oid = c.relnamespace and c.oid = i.indrelid and a.attrelid = c.oid | 
 | 	and c.relkind='r' | 
 | 	and a.attnum = any(i.indkey) | 
 | 	and a.attnum > 0 | 
 | 	and n.nspname = $1 | 
 | 	and c.relname = $2 | 
 |         and i.indisprimary | 
 | 	order by a.attnum | 
 | 	`) | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 | 	defer ps.Close() | 
 |  | 
 | 	for _, t := range tm { | 
 | 		err = mapPrimaryKeys(ps, t) | 
 | 		if err != nil { | 
 | 			return nil, err | 
 | 		} | 
 | 	} | 
 | 	return tm, nil | 
 | } | 
 |  | 
 | func mapPgTables(tx *sql.Tx) (map[string]*pgTable, error) { | 
 | 	// Ignore tables that don't have the column _change_selector | 
 | 	rows, err := tx.Query(` | 
 | 	select n.nspname, c.relname, a.attname, a.atttypid | 
 | 	from pg_namespace n, pg_attribute a, | 
 | 		(select c.relname, c.oid, c.relnamespace | 
 | 			from pg_class c, pg_attribute a | 
 | 		 where a.attrelid = c.oid | 
 | 			and c.relkind = 'r' | 
 | 			and a.attname = '_change_selector') c | 
 |  | 
 | 	where n.oid = c.relnamespace and a.attrelid = c.oid | 
 | 	and n.nspname not in ('information_schema', 'pg_catalog') | 
 | 	and a.attnum > 0 | 
 | 	order by n.nspname, c.relname, a.attnum | 
 | 	`) | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 | 	defer rows.Close() | 
 |  | 
 | 	tm := make(map[string]*pgTable) | 
 |  | 
 | 	for rows.Next() { | 
 | 		var schema, name, attname string | 
 | 		var typid int | 
 |  | 
 | 		err = rows.Scan(&schema, &name, &attname, &typid) | 
 | 		if err != nil { | 
 | 			return nil, err | 
 | 		} | 
 |  | 
 | 		id := schema + "." + name | 
 | 		tab := tm[id] | 
 | 		if tab == nil { | 
 | 			tab = &pgTable{ | 
 | 				schema: schema, | 
 | 				name:   name, | 
 | 			} | 
 | 			tm[id] = tab | 
 | 		} | 
 |  | 
 | 		col := pgColumn{ | 
 | 			name:  attname, | 
 | 			typid: typid, | 
 | 		} | 
 | 		tab.columns = append(tab.columns, col) | 
 |  | 
 | 		if attname == selectorColumn { | 
 | 			tab.hasSelector = true | 
 | 		} | 
 | 	} | 
 | 	return tm, nil | 
 | } | 
 |  | 
 | func mapPrimaryKeys(s *sql.Stmt, t *pgTable) error { | 
 | 	rows, err := s.Query(t.schema, t.name) | 
 | 	if err != nil { | 
 | 		return err | 
 | 	} | 
 | 	for rows.Next() { | 
 | 		var name string | 
 | 		err = rows.Scan(&name) | 
 | 		if err != nil { | 
 | 			return err | 
 | 		} | 
 | 		t.primaryKeys = append(t.primaryKeys, name) | 
 |  | 
 | 		for i, c := range t.columns { | 
 | 			if c.name == name { | 
 | 				c.primaryKey = true | 
 | 				t.columns[i] = c | 
 | 			} | 
 | 		} | 
 | 	} | 
 | 	return nil | 
 | } |