| /* |
| 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 |
| } |