2013-05-15 15:00:42 +04:00
|
|
|
package postgis
|
2013-05-06 21:14:37 +04:00
|
|
|
|
|
|
|
import (
|
|
|
|
"database/sql"
|
2013-05-14 18:15:35 +04:00
|
|
|
"errors"
|
2013-05-08 18:45:14 +04:00
|
|
|
"fmt"
|
2013-05-15 13:47:06 +04:00
|
|
|
"github.com/bmizerany/pq"
|
2013-05-15 15:00:42 +04:00
|
|
|
"goposm/database"
|
2013-05-28 16:07:06 +04:00
|
|
|
"goposm/logging"
|
2013-05-14 18:15:35 +04:00
|
|
|
"goposm/mapping"
|
2013-05-08 18:45:14 +04:00
|
|
|
"strings"
|
2013-05-06 21:14:37 +04:00
|
|
|
)
|
|
|
|
|
2013-05-28 16:07:06 +04:00
|
|
|
var log = logging.NewLogger("PostGIS")
|
|
|
|
|
2013-05-08 18:45:14 +04:00
|
|
|
type ColumnSpec struct {
|
2013-06-11 12:29:59 +04:00
|
|
|
Name string
|
|
|
|
FieldType mapping.FieldType
|
|
|
|
Type ColumnType
|
2013-05-08 18:45:14 +04:00
|
|
|
}
|
|
|
|
type TableSpec struct {
|
|
|
|
Name string
|
|
|
|
Schema string
|
|
|
|
Columns []ColumnSpec
|
|
|
|
GeometryType string
|
|
|
|
Srid int
|
|
|
|
}
|
|
|
|
|
2013-05-22 13:48:34 +04:00
|
|
|
type GeneralizedTableSpec struct {
|
|
|
|
Name string
|
|
|
|
SourceName string
|
|
|
|
Source *TableSpec
|
|
|
|
SourceGeneralized *GeneralizedTableSpec
|
|
|
|
Tolerance float64
|
|
|
|
Where string
|
|
|
|
created bool
|
|
|
|
}
|
|
|
|
|
2013-05-08 18:45:14 +04:00
|
|
|
func (col *ColumnSpec) AsSQL() string {
|
2013-05-15 13:05:02 +04:00
|
|
|
return fmt.Sprintf("\"%s\" %s", col.Name, col.Type.Name())
|
2013-05-08 18:45:14 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (spec *TableSpec) CreateTableSQL() string {
|
|
|
|
cols := []string{
|
|
|
|
"id SERIAL PRIMARY KEY",
|
|
|
|
}
|
|
|
|
for _, col := range spec.Columns {
|
2013-05-15 13:05:02 +04:00
|
|
|
if col.Type.Name() == "GEOMETRY" {
|
2013-05-15 10:15:33 +04:00
|
|
|
continue
|
|
|
|
}
|
2013-05-08 18:45:14 +04:00
|
|
|
cols = append(cols, col.AsSQL())
|
|
|
|
}
|
|
|
|
columnSQL := strings.Join(cols, ",\n")
|
|
|
|
return fmt.Sprintf(`
|
|
|
|
CREATE TABLE IF NOT EXISTS "%s"."%s" (
|
|
|
|
%s
|
|
|
|
);`,
|
|
|
|
spec.Schema,
|
|
|
|
spec.Name,
|
|
|
|
columnSQL,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (spec *TableSpec) InsertSQL() string {
|
2013-05-15 13:05:02 +04:00
|
|
|
var cols []string
|
|
|
|
var vars []string
|
2013-05-14 18:15:35 +04:00
|
|
|
for _, col := range spec.Columns {
|
2013-05-21 16:36:19 +04:00
|
|
|
cols = append(cols, "\""+col.Name+"\"")
|
2013-05-15 13:05:02 +04:00
|
|
|
vars = append(vars,
|
|
|
|
col.Type.PrepareInsertSql(len(vars)+1, spec))
|
2013-05-08 18:45:14 +04:00
|
|
|
}
|
|
|
|
columns := strings.Join(cols, ", ")
|
|
|
|
placeholders := strings.Join(vars, ", ")
|
|
|
|
|
|
|
|
return fmt.Sprintf(`INSERT INTO "%s"."%s" (%s) VALUES (%s)`,
|
|
|
|
spec.Schema,
|
|
|
|
spec.Name,
|
|
|
|
columns,
|
|
|
|
placeholders,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2013-06-11 12:29:59 +04:00
|
|
|
func (spec *TableSpec) DeleteSQL() string {
|
|
|
|
var idColumName string
|
|
|
|
for _, col := range spec.Columns {
|
|
|
|
if col.FieldType.Name == "id" {
|
|
|
|
idColumName = col.Name
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if idColumName == "" {
|
|
|
|
panic("missing id column")
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf(`DELETE FROM "%s"."%s" WHERE "%s" = $1`,
|
|
|
|
spec.Schema,
|
|
|
|
spec.Name,
|
|
|
|
idColumName,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2013-05-22 10:36:37 +04:00
|
|
|
func NewTableSpec(pg *PostGIS, t *mapping.Table) *TableSpec {
|
2013-05-14 18:15:35 +04:00
|
|
|
spec := TableSpec{
|
2013-05-22 10:36:37 +04:00
|
|
|
Name: pg.Prefix + t.Name,
|
|
|
|
Schema: pg.Schema,
|
2013-05-14 18:15:35 +04:00
|
|
|
GeometryType: t.Type,
|
2013-05-22 10:36:37 +04:00
|
|
|
Srid: pg.Config.Srid,
|
2013-05-14 18:15:35 +04:00
|
|
|
}
|
|
|
|
for _, field := range t.Fields {
|
2013-05-31 16:48:16 +04:00
|
|
|
fieldType := field.FieldType()
|
|
|
|
if fieldType == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
pgType, ok := pgTypes[fieldType.GoType]
|
2013-05-15 13:05:02 +04:00
|
|
|
if !ok {
|
2013-05-31 16:48:16 +04:00
|
|
|
log.Errorf("unhandled field type %v, using string type", fieldType)
|
2013-05-15 13:05:02 +04:00
|
|
|
pgType = pgTypes["string"]
|
2013-05-15 10:15:33 +04:00
|
|
|
}
|
2013-06-11 12:29:59 +04:00
|
|
|
col := ColumnSpec{field.Name, *fieldType, pgType}
|
2013-05-14 18:15:35 +04:00
|
|
|
spec.Columns = append(spec.Columns, col)
|
|
|
|
}
|
|
|
|
return &spec
|
|
|
|
}
|
|
|
|
|
2013-05-22 13:48:34 +04:00
|
|
|
func NewGeneralizedTableSpec(pg *PostGIS, t *mapping.GeneralizedTable) *GeneralizedTableSpec {
|
|
|
|
spec := GeneralizedTableSpec{
|
|
|
|
Name: pg.Prefix + t.Name,
|
|
|
|
Tolerance: t.Tolerance,
|
|
|
|
Where: t.SqlFilter,
|
|
|
|
SourceName: t.SourceTableName,
|
|
|
|
}
|
|
|
|
return &spec
|
|
|
|
}
|
|
|
|
|
2013-05-08 18:45:14 +04:00
|
|
|
type SQLError struct {
|
|
|
|
query string
|
|
|
|
originalError error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *SQLError) Error() string {
|
|
|
|
return fmt.Sprintf("SQL Error: %s in query %s", e.originalError.Error(), e.query)
|
|
|
|
}
|
|
|
|
|
2013-05-13 17:19:39 +04:00
|
|
|
type SQLInsertError struct {
|
|
|
|
SQLError
|
|
|
|
data interface{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *SQLInsertError) Error() string {
|
|
|
|
return fmt.Sprintf("SQL Error: %s in query %s (%+v)", e.originalError.Error(), e.query, e.data)
|
|
|
|
}
|
|
|
|
|
2013-05-08 18:45:14 +04:00
|
|
|
func (pg *PostGIS) createTable(spec TableSpec) error {
|
|
|
|
var sql string
|
|
|
|
var err error
|
|
|
|
sql = fmt.Sprintf(`DROP TABLE IF EXISTS "%s"."%s"`, spec.Schema, spec.Name)
|
|
|
|
_, err = pg.Db.Exec(sql)
|
2013-05-06 21:14:37 +04:00
|
|
|
if err != nil {
|
2013-05-08 18:45:14 +04:00
|
|
|
return &SQLError{sql, err}
|
2013-05-06 21:14:37 +04:00
|
|
|
}
|
|
|
|
|
2013-05-08 18:45:14 +04:00
|
|
|
sql = spec.CreateTableSQL()
|
|
|
|
_, err = pg.Db.Exec(sql)
|
2013-05-06 21:14:37 +04:00
|
|
|
if err != nil {
|
2013-05-08 18:45:14 +04:00
|
|
|
return &SQLError{sql, err}
|
2013-05-06 21:14:37 +04:00
|
|
|
}
|
2013-05-17 13:42:19 +04:00
|
|
|
geomType := strings.ToUpper(spec.GeometryType)
|
|
|
|
if geomType == "POLYGON" {
|
|
|
|
geomType = "GEOMETRY" // for multipolygon support
|
|
|
|
}
|
2013-05-08 18:45:14 +04:00
|
|
|
sql = fmt.Sprintf("SELECT AddGeometryColumn('%s', '%s', 'geometry', %d, '%s', 2);",
|
2013-05-17 13:42:19 +04:00
|
|
|
spec.Schema, spec.Name, spec.Srid, geomType)
|
2013-05-14 18:15:35 +04:00
|
|
|
row := pg.Db.QueryRow(sql)
|
|
|
|
var void interface{}
|
|
|
|
err = row.Scan(&void)
|
2013-05-06 21:14:37 +04:00
|
|
|
if err != nil {
|
2013-05-08 18:45:14 +04:00
|
|
|
return &SQLError{sql, err}
|
2013-05-06 21:14:37 +04:00
|
|
|
}
|
2013-05-08 18:45:14 +04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-05-22 10:36:37 +04:00
|
|
|
func (pg *PostGIS) createSchema(schema string) error {
|
2013-05-08 18:45:14 +04:00
|
|
|
var sql string
|
|
|
|
var err error
|
|
|
|
|
2013-05-22 10:36:37 +04:00
|
|
|
if schema == "public" {
|
2013-05-10 12:29:44 +04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-05-10 12:26:37 +04:00
|
|
|
sql = fmt.Sprintf("SELECT EXISTS(SELECT schema_name FROM information_schema.schemata WHERE schema_name = '%s');",
|
2013-05-22 10:36:37 +04:00
|
|
|
schema)
|
2013-05-10 12:26:37 +04:00
|
|
|
row := pg.Db.QueryRow(sql)
|
|
|
|
var exists bool
|
|
|
|
err = row.Scan(&exists)
|
|
|
|
if err != nil {
|
|
|
|
return &SQLError{sql, err}
|
|
|
|
}
|
|
|
|
if exists {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-05-22 10:36:37 +04:00
|
|
|
sql = fmt.Sprintf("CREATE SCHEMA \"%s\"", schema)
|
2013-05-08 18:45:14 +04:00
|
|
|
_, err = pg.Db.Exec(sql)
|
2013-05-06 21:14:37 +04:00
|
|
|
if err != nil {
|
2013-05-08 18:45:14 +04:00
|
|
|
return &SQLError{sql, err}
|
2013-05-06 21:14:37 +04:00
|
|
|
}
|
2013-05-08 18:45:14 +04:00
|
|
|
return nil
|
|
|
|
}
|
2013-05-06 21:14:37 +04:00
|
|
|
|
2013-05-08 18:45:14 +04:00
|
|
|
type PostGIS struct {
|
2013-05-22 13:48:34 +04:00
|
|
|
Db *sql.DB
|
|
|
|
Schema string
|
|
|
|
BackupSchema string
|
|
|
|
Config database.Config
|
|
|
|
Tables map[string]*TableSpec
|
|
|
|
GeneralizedTables map[string]*GeneralizedTableSpec
|
|
|
|
Prefix string
|
2013-05-08 18:45:14 +04:00
|
|
|
}
|
|
|
|
|
2013-05-22 10:36:37 +04:00
|
|
|
func schemasFromConnectionParams(params string) (string, string) {
|
2013-05-15 13:47:06 +04:00
|
|
|
parts := strings.Fields(params)
|
2013-05-22 10:36:37 +04:00
|
|
|
var schema, backupSchema string
|
2013-05-15 13:47:06 +04:00
|
|
|
for _, p := range parts {
|
|
|
|
if strings.HasPrefix(p, "schema=") {
|
2013-05-22 10:36:37 +04:00
|
|
|
schema = strings.Replace(p, "schema=", "", 1)
|
|
|
|
} else if strings.HasPrefix(p, "backupschema=") {
|
|
|
|
backupSchema = strings.Replace(p, "backupschema=", "", 1)
|
2013-05-15 13:47:06 +04:00
|
|
|
}
|
|
|
|
}
|
2013-05-22 10:36:37 +04:00
|
|
|
if schema == "" {
|
|
|
|
schema = "import"
|
|
|
|
}
|
|
|
|
if backupSchema == "" {
|
|
|
|
backupSchema = "backup"
|
|
|
|
}
|
|
|
|
return schema, backupSchema
|
|
|
|
}
|
|
|
|
|
|
|
|
func prefixFromConnectionParams(params string) string {
|
|
|
|
parts := strings.Fields(params)
|
|
|
|
var prefix string
|
|
|
|
for _, p := range parts {
|
|
|
|
if strings.HasPrefix(p, "prefix=") {
|
|
|
|
prefix = strings.Replace(p, "prefix=", "", 1)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if prefix == "" {
|
|
|
|
prefix = "osm_"
|
|
|
|
}
|
|
|
|
if prefix[len(prefix)-1] != '_' {
|
|
|
|
prefix = prefix + "_"
|
|
|
|
}
|
|
|
|
return prefix
|
2013-05-15 13:47:06 +04:00
|
|
|
}
|
|
|
|
|
2013-05-08 18:45:14 +04:00
|
|
|
func (pg *PostGIS) Open() error {
|
|
|
|
var err error
|
2013-05-15 13:47:06 +04:00
|
|
|
|
|
|
|
params, err := pq.ParseURL(pg.Config.ConnectionParams)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
pg.Db, err = sql.Open("postgres", params)
|
2013-05-10 12:01:47 +04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-05-15 13:06:35 +04:00
|
|
|
// check that the connection actually works
|
|
|
|
err = pg.Db.Ping()
|
2013-05-10 12:01:47 +04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
2013-05-08 18:45:14 +04:00
|
|
|
}
|
|
|
|
|
2013-05-14 18:15:35 +04:00
|
|
|
func (pg *PostGIS) InsertBatch(table string, rows [][]interface{}) error {
|
|
|
|
spec, ok := pg.Tables[table]
|
|
|
|
if !ok {
|
|
|
|
return errors.New("unkown table: " + table)
|
2013-05-06 21:14:37 +04:00
|
|
|
}
|
|
|
|
|
2013-05-08 18:45:14 +04:00
|
|
|
tx, err := pg.Db.Begin()
|
2013-05-06 21:14:37 +04:00
|
|
|
if err != nil {
|
2013-05-08 18:45:14 +04:00
|
|
|
return err
|
2013-05-06 21:14:37 +04:00
|
|
|
}
|
2013-05-22 11:49:03 +04:00
|
|
|
defer rollbackIfTx(&tx)
|
2013-05-06 21:14:37 +04:00
|
|
|
|
2013-05-08 18:45:14 +04:00
|
|
|
sql := spec.InsertSQL()
|
|
|
|
stmt, err := tx.Prepare(sql)
|
2013-05-06 21:14:37 +04:00
|
|
|
if err != nil {
|
2013-05-08 18:45:14 +04:00
|
|
|
return &SQLError{sql, err}
|
|
|
|
}
|
2013-05-14 18:15:35 +04:00
|
|
|
defer stmt.Close()
|
2013-05-08 18:45:14 +04:00
|
|
|
|
2013-05-14 18:15:35 +04:00
|
|
|
for _, row := range rows {
|
|
|
|
_, err := stmt.Exec(row...)
|
2013-05-08 18:45:14 +04:00
|
|
|
if err != nil {
|
2013-05-14 18:15:35 +04:00
|
|
|
return &SQLInsertError{SQLError{sql, err}, row}
|
2013-05-08 18:45:14 +04:00
|
|
|
}
|
2013-05-06 21:14:37 +04:00
|
|
|
}
|
|
|
|
|
2013-05-08 18:45:14 +04:00
|
|
|
err = tx.Commit()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-05-22 11:49:03 +04:00
|
|
|
tx = nil // set nil to prevent rollback
|
2013-05-08 18:45:14 +04:00
|
|
|
return nil
|
2013-06-11 12:29:59 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (pg *PostGIS) Delete(table string, id int64) error {
|
|
|
|
spec, ok := pg.Tables[table]
|
|
|
|
if !ok {
|
|
|
|
return errors.New("unkown table: " + table)
|
|
|
|
}
|
2013-05-14 18:15:35 +04:00
|
|
|
|
2013-06-11 12:29:59 +04:00
|
|
|
tx, err := pg.Db.Begin()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer rollbackIfTx(&tx)
|
|
|
|
|
|
|
|
sql := spec.DeleteSQL()
|
|
|
|
stmt, err := tx.Prepare(sql)
|
|
|
|
if err != nil {
|
|
|
|
return &SQLError{sql, err}
|
|
|
|
}
|
|
|
|
defer stmt.Close()
|
|
|
|
|
|
|
|
_, err = stmt.Exec(id)
|
|
|
|
if err != nil {
|
|
|
|
return &SQLInsertError{SQLError{sql, err}, id}
|
|
|
|
}
|
|
|
|
|
|
|
|
err = tx.Commit()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
tx = nil // set nil to prevent rollback
|
|
|
|
return nil
|
2013-05-06 21:14:37 +04:00
|
|
|
}
|
2013-05-08 18:45:14 +04:00
|
|
|
|
2013-05-22 10:36:37 +04:00
|
|
|
func (pg *PostGIS) Init() error {
|
|
|
|
if err := pg.createSchema(pg.Schema); err != nil {
|
2013-05-08 18:45:14 +04:00
|
|
|
return err
|
|
|
|
}
|
2013-05-14 18:15:35 +04:00
|
|
|
|
|
|
|
for _, spec := range pg.Tables {
|
|
|
|
if err := pg.createTable(*spec); err != nil {
|
2013-05-08 18:45:14 +04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-05-22 17:27:09 +04:00
|
|
|
func (pg *PostGIS) TableNames() []string {
|
|
|
|
var names []string
|
|
|
|
for name, _ := range pg.Tables {
|
|
|
|
names = append(names, name)
|
|
|
|
}
|
|
|
|
for name, _ := range pg.GeneralizedTables {
|
|
|
|
names = append(names, name)
|
|
|
|
}
|
|
|
|
return names
|
|
|
|
}
|
|
|
|
|
2013-05-22 10:36:37 +04:00
|
|
|
func tableExists(tx *sql.Tx, schema, table string) (bool, error) {
|
|
|
|
var exists bool
|
|
|
|
sql := fmt.Sprintf(`SELECT EXISTS(SELECT * FROM information_schema.tables WHERE table_name='%s' AND table_schema='%s')`,
|
|
|
|
table, schema)
|
|
|
|
row := tx.QueryRow(sql)
|
|
|
|
err := row.Scan(&exists)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
return exists, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func dropTableIfExists(tx *sql.Tx, schema, table string) error {
|
|
|
|
sql := fmt.Sprintf(`DROP TABLE IF EXISTS "%s"."%s"`, schema, table)
|
|
|
|
_, err := tx.Exec(sql)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pg *PostGIS) rotate(source, dest, backup string) error {
|
2013-05-28 16:07:06 +04:00
|
|
|
defer log.StopStep(log.StartStep(fmt.Sprintf("Rotating tables")))
|
|
|
|
|
2013-05-22 10:36:37 +04:00
|
|
|
if err := pg.createSchema(backup); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
tx, err := pg.Db.Begin()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-05-22 11:49:03 +04:00
|
|
|
defer rollbackIfTx(&tx)
|
2013-05-22 10:36:37 +04:00
|
|
|
|
2013-05-22 17:27:09 +04:00
|
|
|
for _, tableName := range pg.TableNames() {
|
2013-05-22 10:36:37 +04:00
|
|
|
tableName = pg.Prefix + tableName
|
|
|
|
|
2013-05-28 16:07:06 +04:00
|
|
|
log.Printf("Rotating %s from %s -> %s -> %s", tableName, source, dest, backup)
|
2013-05-22 10:36:37 +04:00
|
|
|
|
|
|
|
backupExists, err := tableExists(tx, backup, tableName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
sourceExists, err := tableExists(tx, source, tableName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
destExists, err := tableExists(tx, dest, tableName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !sourceExists {
|
2013-05-28 16:07:06 +04:00
|
|
|
log.Warnf("skipping rotate of %s, table does not exists in %s", tableName, source)
|
2013-05-22 10:36:37 +04:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if destExists {
|
|
|
|
log.Printf("backup of %s, to %s", tableName, backup)
|
|
|
|
if backupExists {
|
|
|
|
err = dropTableIfExists(tx, backup, tableName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sql := fmt.Sprintf(`ALTER TABLE "%s"."%s" SET SCHEMA "%s"`, dest, tableName, backup)
|
|
|
|
_, err = tx.Exec(sql)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sql := fmt.Sprintf(`ALTER TABLE "%s"."%s" SET SCHEMA "%s"`, source, tableName, dest)
|
|
|
|
_, err = tx.Exec(sql)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
err = tx.Commit()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-05-22 11:49:03 +04:00
|
|
|
tx = nil // set nil to prevent rollback
|
2013-05-22 10:36:37 +04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-05-22 10:46:39 +04:00
|
|
|
func (pg *PostGIS) Deploy() error {
|
2013-05-22 10:36:37 +04:00
|
|
|
return pg.rotate(pg.Schema, "public", pg.BackupSchema)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pg *PostGIS) RevertDeploy() error {
|
|
|
|
return pg.rotate(pg.BackupSchema, "public", pg.Schema)
|
|
|
|
}
|
|
|
|
|
2013-05-22 11:49:03 +04:00
|
|
|
func rollbackIfTx(tx **sql.Tx) {
|
|
|
|
if *tx != nil {
|
|
|
|
if err := tx.Rollback(); err != nil {
|
2013-05-28 16:07:06 +04:00
|
|
|
log.Fatal("rollback failed", err)
|
2013-05-22 11:49:03 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-22 10:46:39 +04:00
|
|
|
func (pg *PostGIS) RemoveBackup() error {
|
|
|
|
tx, err := pg.Db.Begin()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-05-22 11:49:03 +04:00
|
|
|
defer rollbackIfTx(&tx)
|
2013-05-22 10:46:39 +04:00
|
|
|
|
|
|
|
backup := pg.BackupSchema
|
|
|
|
|
2013-05-22 17:27:09 +04:00
|
|
|
for _, tableName := range pg.TableNames() {
|
2013-05-22 10:46:39 +04:00
|
|
|
tableName = pg.Prefix + tableName
|
|
|
|
|
|
|
|
backupExists, err := tableExists(tx, backup, tableName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if backupExists {
|
|
|
|
log.Printf("removing backup of %s from %s", tableName, backup)
|
|
|
|
err = dropTableIfExists(tx, backup, tableName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
err = tx.Commit()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-05-22 11:49:03 +04:00
|
|
|
tx = nil // set nil to prevent rollback
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finish creates spatial indices on all tables.
|
|
|
|
func (pg *PostGIS) Finish() error {
|
2013-05-28 16:07:06 +04:00
|
|
|
defer log.StopStep(log.StartStep(fmt.Sprintf("Creating geometry indices")))
|
|
|
|
|
2013-05-22 11:49:03 +04:00
|
|
|
tx, err := pg.Db.Begin()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer rollbackIfTx(&tx)
|
|
|
|
|
|
|
|
for tableName, table := range pg.Tables {
|
|
|
|
tableName := pg.Prefix + tableName
|
|
|
|
for _, col := range table.Columns {
|
|
|
|
if col.Type.Name() == "GEOMETRY" {
|
|
|
|
sql := fmt.Sprintf(`CREATE INDEX "%s_geom" ON "%s"."%s" USING GIST ("%s")`,
|
|
|
|
tableName, pg.Schema, tableName, col.Name)
|
2013-05-28 16:07:06 +04:00
|
|
|
step := log.StartStep(fmt.Sprintf("Creating geometry index on %s", tableName))
|
2013-05-22 11:49:03 +04:00
|
|
|
_, err := tx.Exec(sql)
|
2013-05-28 16:07:06 +04:00
|
|
|
log.StopStep(step)
|
2013-05-22 11:49:03 +04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2013-06-11 12:30:16 +04:00
|
|
|
if col.FieldType.Name == "id" {
|
|
|
|
sql := fmt.Sprintf(`CREATE INDEX "%s_osm_id_idx" ON "%s"."%s" USING BTREE ("%s")`,
|
|
|
|
tableName, pg.Schema, tableName, col.Name)
|
|
|
|
step := log.StartStep(fmt.Sprintf("Creating OSM id index on %s", tableName))
|
|
|
|
_, err := tx.Exec(sql)
|
|
|
|
log.StopStep(step)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-22 11:49:03 +04:00
|
|
|
}
|
|
|
|
}
|
2013-05-22 17:27:09 +04:00
|
|
|
for tableName, table := range pg.GeneralizedTables {
|
|
|
|
tableName := pg.Prefix + tableName
|
|
|
|
for _, col := range table.Source.Columns {
|
|
|
|
if col.Type.Name() == "GEOMETRY" {
|
|
|
|
sql := fmt.Sprintf(`CREATE INDEX "%s_geom" ON "%s"."%s" USING GIST ("%s")`,
|
|
|
|
tableName, pg.Schema, tableName, col.Name)
|
2013-05-28 16:07:06 +04:00
|
|
|
step := log.StartStep(fmt.Sprintf("Creating geometry index on %s", tableName))
|
2013-05-22 17:27:09 +04:00
|
|
|
_, err := tx.Exec(sql)
|
2013-05-28 16:07:06 +04:00
|
|
|
log.StopStep(step)
|
2013-05-22 17:27:09 +04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-05-22 11:49:03 +04:00
|
|
|
err = tx.Commit()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
tx = nil // set nil to prevent rollback
|
2013-05-22 10:46:39 +04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-05-22 13:48:34 +04:00
|
|
|
func (pg *PostGIS) checkGeneralizedTableSources() {
|
|
|
|
for name, table := range pg.GeneralizedTables {
|
|
|
|
if source, ok := pg.Tables[table.SourceName]; ok {
|
|
|
|
table.Source = source
|
|
|
|
} else if source, ok := pg.GeneralizedTables[table.SourceName]; ok {
|
|
|
|
table.SourceGeneralized = source
|
|
|
|
} else {
|
|
|
|
log.Printf("missing source '%s' for generalized table '%s'\n",
|
|
|
|
table.SourceName, name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
filled := true
|
|
|
|
for filled {
|
|
|
|
filled = false
|
|
|
|
for _, table := range pg.GeneralizedTables {
|
|
|
|
if table.Source == nil {
|
|
|
|
if source, ok := pg.GeneralizedTables[table.SourceName]; ok && source.Source != nil {
|
|
|
|
table.Source = source.Source
|
|
|
|
}
|
|
|
|
filled = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pg *PostGIS) Generalize() error {
|
2013-05-28 16:07:06 +04:00
|
|
|
defer log.StopStep(log.StartStep(fmt.Sprintf("Creating generalized tables")))
|
|
|
|
|
2013-05-22 13:48:34 +04:00
|
|
|
// generalized tables can depend on other generalized tables
|
|
|
|
// create tables with non-generalized sources first
|
|
|
|
for _, table := range pg.GeneralizedTables {
|
|
|
|
if table.SourceGeneralized == nil {
|
|
|
|
if err := pg.generalizeTable(table); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
table.created = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// next create tables with created generalized sources until
|
|
|
|
// no new source is created
|
|
|
|
created := true
|
|
|
|
for created {
|
|
|
|
created = false
|
|
|
|
for _, table := range pg.GeneralizedTables {
|
|
|
|
if !table.created && table.SourceGeneralized.created {
|
|
|
|
if err := pg.generalizeTable(table); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
table.created = true
|
|
|
|
created = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pg *PostGIS) generalizeTable(table *GeneralizedTableSpec) error {
|
2013-05-28 16:07:06 +04:00
|
|
|
defer log.StopStep(log.StartStep(fmt.Sprintf("Generalizing %s into %s",
|
|
|
|
pg.Prefix+table.SourceName, pg.Prefix+table.Name)))
|
|
|
|
|
2013-05-22 13:48:34 +04:00
|
|
|
tx, err := pg.Db.Begin()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer rollbackIfTx(&tx)
|
|
|
|
|
|
|
|
var where string
|
|
|
|
if table.Where != "" {
|
|
|
|
where = " WHERE " + table.Where
|
|
|
|
}
|
|
|
|
var cols []string
|
|
|
|
|
|
|
|
for _, col := range table.Source.Columns {
|
|
|
|
cols = append(cols, col.Type.GeneralizeSql(&col, table))
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := dropTableIfExists(tx, pg.Schema, table.Name); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
columnSQL := strings.Join(cols, ",\n")
|
|
|
|
sql := fmt.Sprintf(`CREATE TABLE "%s"."%s" AS (SELECT %s FROM "%s"."%s"%s)`,
|
|
|
|
pg.Schema, table.Name, columnSQL, pg.Schema,
|
|
|
|
pg.Prefix+table.SourceName, where)
|
2013-05-28 16:07:06 +04:00
|
|
|
|
2013-05-22 13:48:34 +04:00
|
|
|
_, err = tx.Exec(sql)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = tx.Commit()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
tx = nil // set nil to prevent rollback
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-05-22 10:36:37 +04:00
|
|
|
func New(conf database.Config, m *mapping.Mapping) (database.DB, error) {
|
2013-05-08 18:45:14 +04:00
|
|
|
db := &PostGIS{}
|
2013-05-14 18:15:35 +04:00
|
|
|
db.Tables = make(map[string]*TableSpec)
|
2013-05-22 13:48:34 +04:00
|
|
|
db.GeneralizedTables = make(map[string]*GeneralizedTableSpec)
|
|
|
|
|
2013-05-08 18:45:14 +04:00
|
|
|
db.Config = conf
|
2013-05-22 10:36:37 +04:00
|
|
|
|
|
|
|
if strings.HasPrefix(db.Config.ConnectionParams, "postgis://") {
|
|
|
|
db.Config.ConnectionParams = strings.Replace(
|
|
|
|
db.Config.ConnectionParams,
|
|
|
|
"postgis", "postgres", 1,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
params, err := pq.ParseURL(db.Config.ConnectionParams)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
db.Schema, db.BackupSchema = schemasFromConnectionParams(params)
|
|
|
|
db.Prefix = prefixFromConnectionParams(params)
|
|
|
|
|
|
|
|
for name, table := range m.Tables {
|
|
|
|
db.Tables[name] = NewTableSpec(db, table)
|
|
|
|
}
|
2013-05-22 13:48:34 +04:00
|
|
|
for name, table := range m.GeneralizedTables {
|
|
|
|
db.GeneralizedTables[name] = NewGeneralizedTableSpec(db, table)
|
|
|
|
}
|
|
|
|
db.checkGeneralizedTableSources()
|
|
|
|
|
2013-05-22 10:36:37 +04:00
|
|
|
err = db.Open()
|
2013-05-08 18:45:14 +04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return db, nil
|
|
|
|
}
|
2013-05-15 15:00:42 +04:00
|
|
|
|
|
|
|
func init() {
|
|
|
|
database.Register("postgres", New)
|
|
|
|
database.Register("postgis", New)
|
|
|
|
}
|