github.com/gogf/gf@v1.16.9/database/gdb/gdb_driver_pgsql.go (about) 1 // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/gogf/gf. 6 // 7 // Note: 8 // 1. It needs manually import: _ "github.com/lib/pq" 9 // 2. It does not support Save/Replace features. 10 // 3. It does not support LastInsertId. 11 12 package gdb 13 14 import ( 15 "context" 16 "database/sql" 17 "fmt" 18 "github.com/gogf/gf/errors/gcode" 19 "strings" 20 21 "github.com/gogf/gf/errors/gerror" 22 "github.com/gogf/gf/internal/intlog" 23 "github.com/gogf/gf/text/gstr" 24 25 "github.com/gogf/gf/text/gregex" 26 ) 27 28 // DriverPgsql is the driver for postgresql database. 29 type DriverPgsql struct { 30 *Core 31 } 32 33 // New creates and returns a database object for postgresql. 34 // It implements the interface of gdb.Driver for extra database driver installation. 35 func (d *DriverPgsql) New(core *Core, node *ConfigNode) (DB, error) { 36 return &DriverPgsql{ 37 Core: core, 38 }, nil 39 } 40 41 // Open creates and returns a underlying sql.DB object for pgsql. 42 func (d *DriverPgsql) Open(config *ConfigNode) (*sql.DB, error) { 43 var source string 44 if config.Link != "" { 45 source = config.Link 46 } else { 47 source = fmt.Sprintf( 48 "user=%s password=%s host=%s port=%s dbname=%s sslmode=disable", 49 config.User, config.Pass, config.Host, config.Port, config.Name, 50 ) 51 if config.Timezone != "" { 52 source = fmt.Sprintf("%s timezone=%s", source, config.Timezone) 53 } 54 } 55 intlog.Printf(d.GetCtx(), "Open: %s", source) 56 if db, err := sql.Open("postgres", source); err == nil { 57 return db, nil 58 } else { 59 return nil, err 60 } 61 } 62 63 // FilteredLink retrieves and returns filtered `linkInfo` that can be using for 64 // logging or tracing purpose. 65 func (d *DriverPgsql) FilteredLink() string { 66 linkInfo := d.GetConfig().Link 67 if linkInfo == "" { 68 return "" 69 } 70 s, _ := gregex.ReplaceString( 71 `(.+?)\s*password=(.+)\s*host=(.+)`, 72 `$1 password=xxx host=$3`, 73 linkInfo, 74 ) 75 return s 76 } 77 78 // GetChars returns the security char for this type of database. 79 func (d *DriverPgsql) GetChars() (charLeft string, charRight string) { 80 return "\"", "\"" 81 } 82 83 // DoCommit deals with the sql string before commits it to underlying sql driver. 84 func (d *DriverPgsql) DoCommit(ctx context.Context, link Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) { 85 defer func() { 86 newSql, newArgs, err = d.Core.DoCommit(ctx, link, newSql, newArgs) 87 }() 88 89 var index int 90 // Convert place holder char '?' to string "$x". 91 sql, _ = gregex.ReplaceStringFunc("\\?", sql, func(s string) string { 92 index++ 93 return fmt.Sprintf("$%d", index) 94 }) 95 newSql, _ = gregex.ReplaceString(` LIMIT (\d+),\s*(\d+)`, ` LIMIT $2 OFFSET $1`, sql) 96 return newSql, args, nil 97 } 98 99 // Tables retrieves and returns the tables of current schema. 100 // It's mainly used in cli tool chain for automatically generating the models. 101 func (d *DriverPgsql) Tables(ctx context.Context, schema ...string) (tables []string, err error) { 102 var result Result 103 link, err := d.SlaveLink(schema...) 104 if err != nil { 105 return nil, err 106 } 107 query := "SELECT TABLENAME FROM PG_TABLES WHERE SCHEMANAME = 'public' ORDER BY TABLENAME" 108 if len(schema) > 0 && schema[0] != "" { 109 query = fmt.Sprintf("SELECT TABLENAME FROM PG_TABLES WHERE SCHEMANAME = '%s' ORDER BY TABLENAME", schema[0]) 110 } 111 result, err = d.DoGetAll(ctx, link, query) 112 if err != nil { 113 return 114 } 115 for _, m := range result { 116 for _, v := range m { 117 tables = append(tables, v.String()) 118 } 119 } 120 return 121 } 122 123 // TableFields retrieves and returns the fields information of specified table of current schema. 124 // 125 // Also see DriverMysql.TableFields. 126 func (d *DriverPgsql) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*TableField, err error) { 127 charL, charR := d.GetChars() 128 table = gstr.Trim(table, charL+charR) 129 if gstr.Contains(table, " ") { 130 return nil, gerror.NewCode(gcode.CodeInvalidParameter, "function TableFields supports only single table operations") 131 } 132 table, _ = gregex.ReplaceString("\"", "", table) 133 useSchema := d.db.GetSchema() 134 if len(schema) > 0 && schema[0] != "" { 135 useSchema = schema[0] 136 } 137 tableFieldsCacheKey := fmt.Sprintf( 138 `pgsql_table_fields_%s_%s@group:%s`, 139 table, useSchema, d.GetGroup(), 140 ) 141 v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} { 142 var ( 143 result Result 144 link, err = d.SlaveLink(useSchema) 145 structureSql = fmt.Sprintf(` 146 SELECT a.attname AS field, t.typname AS type,a.attnotnull as null, 147 (case when d.contype is not null then 'pri' else '' end) as key 148 ,ic.column_default as default_value,b.description as comment 149 ,coalesce(character_maximum_length, numeric_precision, -1) as length 150 ,numeric_scale as scale 151 FROM pg_attribute a 152 left join pg_class c on a.attrelid = c.oid 153 left join pg_constraint d on d.conrelid = c.oid and a.attnum = d.conkey[1] 154 left join pg_description b ON a.attrelid=b.objoid AND a.attnum = b.objsubid 155 left join pg_type t ON a.atttypid = t.oid 156 left join information_schema.columns ic on ic.column_name = a.attname and ic.table_name = c.relname 157 WHERE c.relname = '%s' and a.attnum > 0 158 ORDER BY a.attnum`, 159 strings.ToLower(table), 160 ) 161 ) 162 if err != nil { 163 return nil 164 } 165 structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql)) 166 result, err = d.DoGetAll(ctx, link, structureSql) 167 if err != nil { 168 return nil 169 } 170 fields = make(map[string]*TableField) 171 for i, m := range result { 172 fields[m["field"].String()] = &TableField{ 173 Index: i, 174 Name: m["field"].String(), 175 Type: m["type"].String(), 176 Null: m["null"].Bool(), 177 Key: m["key"].String(), 178 Default: m["default_value"].Val(), 179 Comment: m["comment"].String(), 180 } 181 } 182 return fields 183 }) 184 if v != nil { 185 fields = v.(map[string]*TableField) 186 } 187 return 188 } 189 190 // DoInsert is not supported in pgsql. 191 func (d *DriverPgsql) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) { 192 switch option.InsertOption { 193 case insertOptionSave: 194 return nil, gerror.NewCode(gcode.CodeNotSupported, `Save operation is not supported by pgsql driver`) 195 196 case insertOptionReplace: 197 return nil, gerror.NewCode(gcode.CodeNotSupported, `Replace operation is not supported by pgsql driver`) 198 199 default: 200 return d.Core.DoInsert(ctx, link, table, list, option) 201 } 202 }