646 lines
20 KiB
Go
646 lines
20 KiB
Go
package xorm
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/go-xorm/core"
|
|
)
|
|
|
|
// func init() {
|
|
// RegisterDialect("postgres", &postgres{})
|
|
// }
|
|
var (
|
|
postgresReservedWords = map[string]bool{
|
|
"A": true,
|
|
"ABORT": true,
|
|
"ABS": true,
|
|
"ABSENT": true,
|
|
"ABSOLUTE": true,
|
|
"ACCESS": true,
|
|
"ACCORDING": true,
|
|
"ACTION": true,
|
|
"ADA": true,
|
|
"ADD": true,
|
|
"ADMIN": true,
|
|
"AFTER": true,
|
|
"AGGREGATE": true,
|
|
"ALL": true,
|
|
"ALLOCATE": true,
|
|
"ALSO": true,
|
|
"ALTER": true,
|
|
"ALWAYS": true,
|
|
"ANALYSE": true,
|
|
"ANALYZE": true,
|
|
"AND": true,
|
|
"ANY": true,
|
|
"ARE": true,
|
|
"ARRAY": true,
|
|
"ARRAY_AGG": true,
|
|
"ARRAY_MAX_CARDINALITY": true,
|
|
"AS": true,
|
|
"ASC": true,
|
|
"ASENSITIVE": true,
|
|
"ASSERTION": true,
|
|
"ASSIGNMENT": true,
|
|
"ASYMMETRIC": true,
|
|
"AT": true,
|
|
"ATOMIC": true,
|
|
"ATTRIBUTE": true,
|
|
"ATTRIBUTES": true,
|
|
"AUTHORIZATION": true,
|
|
"AVG": true,
|
|
"BACKWARD": true,
|
|
"BASE64": true,
|
|
"BEFORE": true,
|
|
"BEGIN": true,
|
|
"BEGIN_FRAME": true,
|
|
"BEGIN_PARTITION": true,
|
|
"BERNOULLI": true,
|
|
"BETWEEN": true,
|
|
"BIGINT": true,
|
|
"BINARY": true,
|
|
"BIT": true,
|
|
"BIT_LENGTH": true,
|
|
"BLOB": true,
|
|
"BLOCKED": true,
|
|
"BOM": true,
|
|
"BOOLEAN": true,
|
|
"BOTH": true,
|
|
"BREADTH": true,
|
|
"BY": true,
|
|
"C": true,
|
|
"CACHE": true,
|
|
"CALL": true,
|
|
"CALLED": true,
|
|
"CARDINALITY": true,
|
|
"CASCADE": true,
|
|
"CASCADED": true,
|
|
"CASE": true,
|
|
"CAST": true,
|
|
"CATALOG": true,
|
|
"CATALOG_NAME": true,
|
|
"CEIL": true,
|
|
"CEILING": true,
|
|
"CHAIN": true,
|
|
"CHAR": true,
|
|
"CHARACTER": true,
|
|
"CHARACTERISTICS": true,
|
|
"CHARACTERS": true,
|
|
"CHARACTER_LENGTH": true,
|
|
"CHARACTER_SET_CATALOG": true,
|
|
"CHARACTER_SET_NAME": true,
|
|
"CHARACTER_SET_SCHEMA": true,
|
|
"CHAR_LENGTH": true,
|
|
"CHECK": true,
|
|
"CHECKPOINT": true,
|
|
"CLASS": true,
|
|
"CLASS_ORIGIN": true,
|
|
"CLOB": true,
|
|
"CLOSE": true,
|
|
"CLUSTER": true,
|
|
"COALESCE": true,
|
|
"COBOL": true,
|
|
"COLLATE": true,
|
|
"COLLATION": true,
|
|
"COLLATION_CATALOG": true,
|
|
"COLLATION_NAME": true,
|
|
"COLLATION_SCHEMA": true,
|
|
"COLLECT": true,
|
|
"COLUMN": true,
|
|
"COLUMNS": true,
|
|
"COLUMN_NAME": true,
|
|
"COMMAND_FUNCTION": true,
|
|
"COMMAND_FUNCTION_CODE": true,
|
|
"COMMENT": true,
|
|
"COMMENTS": true,
|
|
"COMMIT": true,
|
|
"COMMITTED": true,
|
|
"CONCURRENTLY": true,
|
|
"CONDITION": true,
|
|
"CONDITION_NUMBER": true,
|
|
"CONFIGURATION": true,
|
|
"CONNECT": true,
|
|
"CONNECTION": true,
|
|
"CONNECTION_NAME": true,
|
|
"CONSTRAINT": true,
|
|
"CONSTRAINTS": true,
|
|
"CONSTRAINT_CATALOG": true,
|
|
"CONSTRAINT_NAME": true,
|
|
"CONSTRAINT_SCHEMA": true,
|
|
"CONSTRUCTOR": true,
|
|
"CONTAINS": true,
|
|
"CONTENT": true,
|
|
"CONTINUE": true,
|
|
"CONTROL": true,
|
|
"CONVERSION": true,
|
|
"CONVERT": true,
|
|
"COPY": true,
|
|
"CORR": true,
|
|
"CORRESPONDING": true,
|
|
"COST": true,
|
|
"COUNT": true,
|
|
"COVAR_POP": true,
|
|
"COVAR_SAMP": true,
|
|
"CREATE": true,
|
|
"CROSS": true,
|
|
"CSV": true,
|
|
"CUBE": true,
|
|
"CUME_DIST": true,
|
|
"CURRENT": true,
|
|
"CURRENT_CATALOG": true,
|
|
"CURRENT_DATE": true,
|
|
"CURRENT_DEFAULT_TRANSFORM_GROUP": true,
|
|
"CURRENT_PATH": true,
|
|
"CURRENT_ROLE": true,
|
|
"CURRENT_ROW": true,
|
|
"CURRENT_SCHEMA": true,
|
|
"CURRENT_TIME": true,
|
|
"CURRENT_TIMESTAMP": true,
|
|
"CURRENT_TRANSFORM_GROUP_FOR_TYPE": true,
|
|
"CURRENT_USER": true,
|
|
"CURSOR": true,
|
|
"CURSOR_NAME": true,
|
|
"CYCLE": true,
|
|
"DATA": true,
|
|
"DATABASE": true,
|
|
"DATALINK": true,
|
|
"DATE": true,
|
|
"DATETIME_INTERVAL_CODE": true,
|
|
"DATETIME_INTERVAL_PRECISION": true,
|
|
"DAY": true,
|
|
"DB": true,
|
|
"DEALLOCATE": true,
|
|
"DEC": true,
|
|
"DECIMAL": true,
|
|
"DECLARE": true,
|
|
"DEFAULT": true,
|
|
"DEFAULTS": true,
|
|
"DEFERRABLE": true,
|
|
"DEFERRED": true,
|
|
"DEFINED": true,
|
|
"DEFINER": true,
|
|
"DEGREE": true,
|
|
"DELETE": true,
|
|
"DELIMITER": true,
|
|
"DELIMITERS": true,
|
|
"DENSE_RANK": true,
|
|
"DEPTH": true,
|
|
"DEREF": true,
|
|
"DERIVED": true,
|
|
"DESC": true,
|
|
"DESCRIBE": true,
|
|
"DESCRIPTOR": true,
|
|
"DETERMINISTIC": true,
|
|
"DIAGNOSTICS": true,
|
|
"DICTIONARY": true,
|
|
"DISABLE": true,
|
|
"DISCARD": true,
|
|
"DISCONNECT": true,
|
|
"DISPATCH": true,
|
|
"DISTINCT": true,
|
|
"DLNEWCOPY": true,
|
|
"DLPREVIOUSCOPY": true,
|
|
"DLURLCOMPLETE": true,
|
|
"DLURLCOMPLETEONLY": true,
|
|
"DLURLCOMPLETEWRITE": true,
|
|
"DLURLPATH": true,
|
|
"DLURLPATHONLY": true,
|
|
"DLURLPATHWRITE": true,
|
|
"DLURLSCHEME": true,
|
|
"DLURLSERVER": true,
|
|
"DLVALUE": true,
|
|
"DO": true,
|
|
"DOCUMENT": true,
|
|
"DOMAIN": true,
|
|
"DOUBLE": true,
|
|
"DROP": true,
|
|
"DYNAMIC": true,
|
|
"DYNAMIC_FUNCTION": true,
|
|
"DYNAMIC_FUNCTION_CODE": true,
|
|
"EACH": true,
|
|
"ELEMENT": true,
|
|
"ELSE": true,
|
|
"EMPTY": true,
|
|
"ENABLE": true,
|
|
"ENCODING": true,
|
|
"ENCRYPTED": true,
|
|
"END": true,
|
|
"END-EXEC": true,
|
|
"END_FRAME": true,
|
|
"END_PARTITION": true,
|
|
"ENFORCED": true,
|
|
"ENUM": true,
|
|
"EQUALS": true,
|
|
"ESCAPE": true,
|
|
"EVENT": true,
|
|
"EVERY": true,
|
|
"EXCEPT": true,
|
|
"EXCEPTION": true,
|
|
"EXCLUDE": true,
|
|
"EXCLUDING": true,
|
|
"EXCLUSIVE": true,
|
|
"EXEC": true,
|
|
"EXECUTE": true,
|
|
"EXISTS": true,
|
|
"EXP": true,
|
|
"EXPLAIN": true,
|
|
"EXPRESSION": true,
|
|
"EXTENSION": true,
|
|
"EXTERNAL": true,
|
|
"EXTRACT": true,
|
|
"FALSE": true,
|
|
"FAMILY": true,
|
|
"FETCH": true,
|
|
"FILE": true,
|
|
"FILTER": true,
|
|
"FINAL": true,
|
|
"FIRST": true,
|
|
"FIRST_VALUE": true,
|
|
"FLAG": true,
|
|
"FLOAT": true,
|
|
"FLOOR": true,
|
|
"FOLLOWING": true,
|
|
"FOR": true,
|
|
"FORCE": true,
|
|
"FOREIGN": true,
|
|
"FORTRAN": true,
|
|
"FORWARD": true,
|
|
"FOUND": true,
|
|
"FRAME_ROW": true,
|
|
"FREE": true,
|
|
"FREEZE": true,
|
|
"FROM": true,
|
|
"FS": true,
|
|
"FULL": true,
|
|
"FUNCTION": true,
|
|
"FUNCTIONS": true,
|
|
"FUSION": true,
|
|
"G": true,
|
|
"GENERAL": true,
|
|
"GENERATED": true,
|
|
"GET": true,
|
|
"GLOBAL": true,
|
|
"GO": true,
|
|
"GOTO": true,
|
|
"GRANT": true,
|
|
"GRANTED": true,
|
|
"GREATEST": true,
|
|
"GROUP": true,
|
|
"GROUPING": true,
|
|
"GROUPS": true,
|
|
"HANDLER": true,
|
|
"HAVING": true,
|
|
"HEADER": true,
|
|
"HEX": true,
|
|
"HIERARCHY": true,
|
|
"HOLD": true,
|
|
"HOUR": true,
|
|
"ID": true,
|
|
"IDENTITY": true,
|
|
"IF": true,
|
|
"IGNORE": true,
|
|
"ILIKE": true,
|
|
"IMMEDIATE": true,
|
|
"IMMEDIATELY": true,
|
|
"IMMUTABLE": true,
|
|
"IMPLEMENTATION": true,
|
|
"IMPLICIT": true,
|
|
"IMPORT": true,
|
|
"IN": true,
|
|
"INCLUDING": true,
|
|
"INCREMENT": true,
|
|
"INDENT": true,
|
|
"INDEX": true,
|
|
"INDEXES": true,
|
|
"INDICATOR": true,
|
|
"INHERIT": true,
|
|
"INHERITS": true,
|
|
"INITIALLY": true,
|
|
"INLINE": true,
|
|
"INNER": true,
|
|
"INOUT": true,
|
|
"INPUT": true,
|
|
"INSENSITIVE": true,
|
|
"INSERT": true,
|
|
"INSTANCE": true,
|
|
"INSTANTIABLE": true,
|
|
"INSTEAD": true,
|
|
"INT": true,
|
|
}
|
|
)
|
|
|
|
type postgres struct {
|
|
core.Base
|
|
}
|
|
|
|
func (db *postgres) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error {
|
|
return db.Base.Init(d, db, uri, drivername, dataSourceName)
|
|
}
|
|
|
|
func (db *postgres) SqlType(c *core.Column) string {
|
|
var res string
|
|
switch t := c.SQLType.Name; t {
|
|
case core.TinyInt:
|
|
res = core.SmallInt
|
|
return res
|
|
case core.MediumInt, core.Int, core.Integer:
|
|
if c.IsAutoIncrement {
|
|
return core.Serial
|
|
}
|
|
return core.Integer
|
|
case core.Serial, core.BigSerial:
|
|
c.IsAutoIncrement = true
|
|
c.Nullable = false
|
|
res = t
|
|
case core.Binary, core.VarBinary:
|
|
return core.Bytea
|
|
case core.DateTime:
|
|
res = core.TimeStamp
|
|
case core.TimeStampz:
|
|
return "timestamp with time zone"
|
|
case core.Float:
|
|
res = core.Real
|
|
case core.TinyText, core.MediumText, core.LongText:
|
|
res = core.Text
|
|
case core.Uuid:
|
|
res = core.Uuid
|
|
case core.Blob, core.TinyBlob, core.MediumBlob, core.LongBlob:
|
|
return core.Bytea
|
|
case core.Double:
|
|
return "DOUBLE PRECISION"
|
|
default:
|
|
if c.IsAutoIncrement {
|
|
return core.Serial
|
|
}
|
|
res = t
|
|
}
|
|
|
|
var hasLen1 bool = (c.Length > 0)
|
|
var hasLen2 bool = (c.Length2 > 0)
|
|
if hasLen2 {
|
|
res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")"
|
|
} else if hasLen1 {
|
|
res += "(" + strconv.Itoa(c.Length) + ")"
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (db *postgres) SupportInsertMany() bool {
|
|
return true
|
|
}
|
|
|
|
func (db *postgres) IsReserved(name string) bool {
|
|
_, ok := postgresReservedWords[name]
|
|
return ok
|
|
}
|
|
|
|
func (db *postgres) Quote(name string) string {
|
|
return "\"" + name + "\""
|
|
}
|
|
|
|
func (db *postgres) QuoteStr() string {
|
|
return "\""
|
|
}
|
|
|
|
func (db *postgres) AutoIncrStr() string {
|
|
return ""
|
|
}
|
|
|
|
func (db *postgres) SupportEngine() bool {
|
|
return false
|
|
}
|
|
|
|
func (db *postgres) SupportCharset() bool {
|
|
return false
|
|
}
|
|
|
|
func (db *postgres) IndexOnTable() bool {
|
|
return false
|
|
}
|
|
|
|
func (db *postgres) IndexCheckSql(tableName, idxName string) (string, []interface{}) {
|
|
args := []interface{}{tableName, idxName}
|
|
return `SELECT indexname FROM pg_indexes ` +
|
|
`WHERE tablename = ? AND indexname = ?`, args
|
|
}
|
|
|
|
func (db *postgres) TableCheckSql(tableName string) (string, []interface{}) {
|
|
args := []interface{}{tableName}
|
|
return `SELECT tablename FROM pg_tables WHERE tablename = ?`, args
|
|
}
|
|
|
|
/*func (db *postgres) ColumnCheckSql(tableName, colName string) (string, []interface{}) {
|
|
args := []interface{}{tableName, colName}
|
|
return "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = ?" +
|
|
" AND column_name = ?", args
|
|
}*/
|
|
|
|
func (db *postgres) ModifyColumnSql(tableName string, col *core.Column) string {
|
|
return fmt.Sprintf("alter table %s ALTER COLUMN %s TYPE %s",
|
|
tableName, col.Name, db.SqlType(col))
|
|
}
|
|
|
|
func (db *postgres) DropIndexSql(tableName string, index *core.Index) string {
|
|
quote := db.Quote
|
|
//var unique string
|
|
var idxName string = index.Name
|
|
if !strings.HasPrefix(idxName, "UQE_") &&
|
|
!strings.HasPrefix(idxName, "IDX_") {
|
|
if index.Type == core.UniqueType {
|
|
idxName = fmt.Sprintf("UQE_%v_%v", tableName, index.Name)
|
|
} else {
|
|
idxName = fmt.Sprintf("IDX_%v_%v", tableName, index.Name)
|
|
}
|
|
}
|
|
return fmt.Sprintf("DROP INDEX %v", quote(idxName))
|
|
}
|
|
|
|
func (db *postgres) IsColumnExist(tableName string, col *core.Column) (bool, error) {
|
|
args := []interface{}{tableName, col.Name}
|
|
query := "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = $1" +
|
|
" AND column_name = $2"
|
|
rows, err := db.DB().Query(query, args...)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
return rows.Next(), nil
|
|
}
|
|
|
|
func (db *postgres) GetColumns(tableName string) ([]string, map[string]*core.Column, error) {
|
|
args := []interface{}{tableName}
|
|
s := `SELECT column_name, column_default, is_nullable, data_type, character_maximum_length, numeric_precision, numeric_precision_radix ,
|
|
CASE WHEN p.contype = 'p' THEN true ELSE false END AS primarykey,
|
|
CASE WHEN p.contype = 'u' THEN true ELSE false END AS uniquekey
|
|
FROM pg_attribute f
|
|
JOIN pg_class c ON c.oid = f.attrelid JOIN pg_type t ON t.oid = f.atttypid
|
|
LEFT JOIN pg_attrdef d ON d.adrelid = c.oid AND d.adnum = f.attnum
|
|
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
LEFT JOIN pg_constraint p ON p.conrelid = c.oid AND f.attnum = ANY (p.conkey)
|
|
LEFT JOIN pg_class AS g ON p.confrelid = g.oid
|
|
LEFT JOIN INFORMATION_SCHEMA.COLUMNS s ON s.column_name=f.attname AND c.relname=s.table_name
|
|
WHERE c.relkind = 'r'::char AND c.relname = $1 AND f.attnum > 0 ORDER BY f.attnum;`
|
|
|
|
rows, err := db.DB().Query(s, args...)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
cols := make(map[string]*core.Column)
|
|
colSeq := make([]string, 0)
|
|
|
|
for rows.Next() {
|
|
col := new(core.Column)
|
|
col.Indexes = make(map[string]bool)
|
|
|
|
var colName, isNullable, dataType string
|
|
var maxLenStr, colDefault, numPrecision, numRadix *string
|
|
var isPK, isUnique bool
|
|
err = rows.Scan(&colName, &colDefault, &isNullable, &dataType, &maxLenStr, &numPrecision, &numRadix, &isPK, &isUnique)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
//fmt.Println(args, colName, isNullable, dataType, maxLenStr, colDefault, numPrecision, numRadix, isPK, isUnique)
|
|
var maxLen int
|
|
if maxLenStr != nil {
|
|
maxLen, err = strconv.Atoi(*maxLenStr)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
col.Name = strings.Trim(colName, `" `)
|
|
|
|
if colDefault != nil || isPK {
|
|
if isPK {
|
|
col.IsPrimaryKey = true
|
|
} else {
|
|
col.Default = *colDefault
|
|
}
|
|
}
|
|
|
|
if colDefault != nil && strings.HasPrefix(*colDefault, "nextval(") {
|
|
col.IsAutoIncrement = true
|
|
}
|
|
|
|
col.Nullable = (isNullable == "YES")
|
|
|
|
switch dataType {
|
|
case "character varying", "character":
|
|
col.SQLType = core.SQLType{core.Varchar, 0, 0}
|
|
case "timestamp without time zone":
|
|
col.SQLType = core.SQLType{core.DateTime, 0, 0}
|
|
case "timestamp with time zone":
|
|
col.SQLType = core.SQLType{core.TimeStampz, 0, 0}
|
|
case "double precision":
|
|
col.SQLType = core.SQLType{core.Double, 0, 0}
|
|
case "boolean":
|
|
col.SQLType = core.SQLType{core.Bool, 0, 0}
|
|
case "time without time zone":
|
|
col.SQLType = core.SQLType{core.Time, 0, 0}
|
|
default:
|
|
col.SQLType = core.SQLType{strings.ToUpper(dataType), 0, 0}
|
|
}
|
|
if _, ok := core.SqlTypes[col.SQLType.Name]; !ok {
|
|
return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v", dataType))
|
|
}
|
|
|
|
col.Length = maxLen
|
|
|
|
if col.SQLType.IsText() || col.SQLType.IsTime() {
|
|
if col.Default != "" {
|
|
col.Default = "'" + col.Default + "'"
|
|
} else {
|
|
if col.DefaultIsEmpty {
|
|
col.Default = "''"
|
|
}
|
|
}
|
|
}
|
|
cols[col.Name] = col
|
|
colSeq = append(colSeq, col.Name)
|
|
}
|
|
|
|
return colSeq, cols, nil
|
|
}
|
|
|
|
func (db *postgres) GetTables() ([]*core.Table, error) {
|
|
args := []interface{}{}
|
|
s := "SELECT tablename FROM pg_tables where schemaname = 'public'"
|
|
|
|
rows, err := db.DB().Query(s, args...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
tables := make([]*core.Table, 0)
|
|
for rows.Next() {
|
|
table := core.NewEmptyTable()
|
|
var name string
|
|
err = rows.Scan(&name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
table.Name = name
|
|
tables = append(tables, table)
|
|
}
|
|
return tables, nil
|
|
}
|
|
|
|
func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) {
|
|
args := []interface{}{tableName}
|
|
s := "SELECT indexname, indexdef FROM pg_indexes WHERE schemaname = 'public' and tablename = $1"
|
|
|
|
rows, err := db.DB().Query(s, args...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
indexes := make(map[string]*core.Index, 0)
|
|
for rows.Next() {
|
|
var indexType int
|
|
var indexName, indexdef string
|
|
var colNames []string
|
|
err = rows.Scan(&indexName, &indexdef)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
indexName = strings.Trim(indexName, `" `)
|
|
if strings.HasSuffix(indexName, "_pkey") {
|
|
continue
|
|
}
|
|
if strings.HasPrefix(indexdef, "CREATE UNIQUE INDEX") {
|
|
indexType = core.UniqueType
|
|
} else {
|
|
indexType = core.IndexType
|
|
}
|
|
cs := strings.Split(indexdef, "(")
|
|
colNames = strings.Split(cs[1][0:len(cs[1])-1], ",")
|
|
|
|
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
|
|
newIdxName := indexName[5+len(tableName) : len(indexName)]
|
|
if newIdxName != "" {
|
|
indexName = newIdxName
|
|
}
|
|
}
|
|
|
|
index := &core.Index{Name: indexName, Type: indexType, Cols: make([]string, 0)}
|
|
for _, colName := range colNames {
|
|
index.Cols = append(index.Cols, strings.Trim(colName, `" `))
|
|
}
|
|
indexes[index.Name] = index
|
|
}
|
|
return indexes, nil
|
|
}
|
|
|
|
func (db *postgres) Filters() []core.Filter {
|
|
return []core.Filter{&core.IdFilter{}, &core.QuoteFilter{}, &core.SeqFilter{"$", 1}}
|
|
}
|