github.com/gogf/gf@v1.16.9/database/gdb/gdb_driver_oracle.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/mattn/go-oci8" 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 "reflect" 20 "strconv" 21 "strings" 22 "time" 23 24 "github.com/gogf/gf/errors/gerror" 25 "github.com/gogf/gf/internal/intlog" 26 "github.com/gogf/gf/text/gregex" 27 "github.com/gogf/gf/text/gstr" 28 "github.com/gogf/gf/util/gconv" 29 ) 30 31 // DriverOracle is the driver for oracle database. 32 type DriverOracle struct { 33 *Core 34 } 35 36 // New creates and returns a database object for oracle. 37 // It implements the interface of gdb.Driver for extra database driver installation. 38 func (d *DriverOracle) New(core *Core, node *ConfigNode) (DB, error) { 39 return &DriverOracle{ 40 Core: core, 41 }, nil 42 } 43 44 // Open creates and returns a underlying sql.DB object for oracle. 45 func (d *DriverOracle) Open(config *ConfigNode) (*sql.DB, error) { 46 var source string 47 if config.Link != "" { 48 source = config.Link 49 } else { 50 source = fmt.Sprintf( 51 "%s/%s@%s:%s/%s", 52 config.User, config.Pass, config.Host, config.Port, config.Name, 53 ) 54 } 55 intlog.Printf(d.GetCtx(), "Open: %s", source) 56 if db, err := sql.Open("oci8", 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 *DriverOracle) FilteredLink() string { 66 linkInfo := d.GetConfig().Link 67 if linkInfo == "" { 68 return "" 69 } 70 s, _ := gregex.ReplaceString( 71 `(.+?)\s*/\s*(.+)\s*@\s*(.+)\s*:\s*(\d+)\s*/\s*(.+)`, 72 `$1/xxx@$3:$4/$5`, 73 linkInfo, 74 ) 75 return s 76 } 77 78 // GetChars returns the security char for this type of database. 79 func (d *DriverOracle) 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 *DriverOracle) 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 ":vx". 91 newSql, _ = gregex.ReplaceStringFunc("\\?", sql, func(s string) string { 92 index++ 93 return fmt.Sprintf(":v%d", index) 94 }) 95 newSql, _ = gregex.ReplaceString("\"", "", newSql) 96 // Handle string datetime argument. 97 for i, v := range args { 98 if reflect.TypeOf(v).Kind() == reflect.String { 99 valueStr := gconv.String(v) 100 if gregex.IsMatchString(`^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$`, valueStr) { 101 //args[i] = fmt.Sprintf(`TO_DATE('%s','yyyy-MM-dd HH:MI:SS')`, valueStr) 102 args[i], _ = time.ParseInLocation("2006-01-02 15:04:05", valueStr, time.Local) 103 } 104 } 105 } 106 newSql = d.parseSql(newSql) 107 newArgs = args 108 return 109 } 110 111 // parseSql does some replacement of the sql before commits it to underlying driver, 112 // for support of oracle server. 113 func (d *DriverOracle) parseSql(sql string) string { 114 var ( 115 patten = `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,{0,1}\s*(\d*))` 116 allMatch, _ = gregex.MatchAllString(patten, sql) 117 ) 118 if len(allMatch) == 0 { 119 return sql 120 } 121 var ( 122 index = 0 123 keyword = strings.ToUpper(strings.TrimSpace(allMatch[index][0])) 124 ) 125 index++ 126 switch keyword { 127 case "SELECT": 128 if len(allMatch) < 2 || strings.HasPrefix(allMatch[index][0], "LIMIT") == false { 129 break 130 } 131 if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false { 132 break 133 } 134 queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) 135 if len(queryExpr) != 4 || 136 strings.EqualFold(queryExpr[1], "SELECT") == false || 137 strings.EqualFold(queryExpr[3], "LIMIT") == false { 138 break 139 } 140 first, limit := 0, 0 141 for i := 1; i < len(allMatch[index]); i++ { 142 if len(strings.TrimSpace(allMatch[index][i])) == 0 { 143 continue 144 } 145 146 if strings.HasPrefix(allMatch[index][i], "LIMIT") { 147 if allMatch[index][i+2] != "" { 148 first, _ = strconv.Atoi(allMatch[index][i+1]) 149 limit, _ = strconv.Atoi(allMatch[index][i+2]) 150 } else { 151 limit, _ = strconv.Atoi(allMatch[index][i+1]) 152 } 153 break 154 } 155 } 156 sql = fmt.Sprintf( 157 "SELECT * FROM "+ 158 "(SELECT GFORM.*, ROWNUM ROWNUM_ FROM (%s %s) GFORM WHERE ROWNUM <= %d)"+ 159 " WHERE ROWNUM_ >= %d", 160 queryExpr[1], queryExpr[2], limit, first, 161 ) 162 } 163 return sql 164 } 165 166 // Tables retrieves and returns the tables of current schema. 167 // It's mainly used in cli tool chain for automatically generating the models. 168 // Note that it ignores the parameter `schema` in oracle database, as it is not necessary. 169 func (d *DriverOracle) Tables(ctx context.Context, schema ...string) (tables []string, err error) { 170 var result Result 171 result, err = d.DoGetAll(ctx, nil, "SELECT TABLE_NAME FROM USER_TABLES ORDER BY TABLE_NAME") 172 if err != nil { 173 return 174 } 175 for _, m := range result { 176 for _, v := range m { 177 tables = append(tables, v.String()) 178 } 179 } 180 return 181 } 182 183 // TableFields retrieves and returns the fields information of specified table of current schema. 184 // 185 // Also see DriverMysql.TableFields. 186 func (d *DriverOracle) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*TableField, err error) { 187 charL, charR := d.GetChars() 188 table = gstr.Trim(table, charL+charR) 189 if gstr.Contains(table, " ") { 190 return nil, gerror.NewCode(gcode.CodeInvalidParameter, "function TableFields supports only single table operations") 191 } 192 useSchema := d.db.GetSchema() 193 if len(schema) > 0 && schema[0] != "" { 194 useSchema = schema[0] 195 } 196 tableFieldsCacheKey := fmt.Sprintf( 197 `oracle_table_fields_%s_%s@group:%s`, 198 table, useSchema, d.GetGroup(), 199 ) 200 v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} { 201 var ( 202 result Result 203 link, err = d.SlaveLink(useSchema) 204 structureSql = fmt.Sprintf(` 205 SELECT 206 COLUMN_NAME AS FIELD, 207 CASE DATA_TYPE 208 WHEN 'NUMBER' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')' 209 WHEN 'FLOAT' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')' 210 ELSE DATA_TYPE||'('||DATA_LENGTH||')' END AS TYPE 211 FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`, 212 strings.ToUpper(table), 213 ) 214 ) 215 if err != nil { 216 return nil 217 } 218 structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql)) 219 result, err = d.DoGetAll(ctx, link, structureSql) 220 if err != nil { 221 return nil 222 } 223 fields = make(map[string]*TableField) 224 for i, m := range result { 225 fields[strings.ToLower(m["FIELD"].String())] = &TableField{ 226 Index: i, 227 Name: strings.ToLower(m["FIELD"].String()), 228 Type: strings.ToLower(m["TYPE"].String()), 229 } 230 } 231 return fields 232 }) 233 if v != nil { 234 fields = v.(map[string]*TableField) 235 } 236 return 237 } 238 239 // DoInsert inserts or updates data for given table. 240 // This function is usually used for custom interface definition, you do not need call it manually. 241 // The parameter `data` can be type of map/gmap/struct/*struct/[]map/[]struct, etc. 242 // Eg: 243 // Data(g.Map{"uid": 10000, "name":"john"}) 244 // Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}) 245 // 246 // The parameter `option` values are as follows: 247 // 0: insert: just insert, if there's unique/primary key in the data, it returns error; 248 // 1: replace: if there's unique/primary key in the data, it deletes it from table and inserts a new one; 249 // 2: save: if there's unique/primary key in the data, it updates it or else inserts a new one; 250 // 3: ignore: if there's unique/primary key in the data, it ignores the inserting; 251 func (d *DriverOracle) DoInsert(ctx context.Context, link Link, table string, list List, option DoInsertOption) (result sql.Result, err error) { 252 switch option.InsertOption { 253 case insertOptionSave: 254 return nil, gerror.NewCode(gcode.CodeNotSupported, `Save operation is not supported by mssql driver`) 255 256 case insertOptionReplace: 257 return nil, gerror.NewCode(gcode.CodeNotSupported, `Replace operation is not supported by mssql driver`) 258 } 259 260 var ( 261 keys []string 262 values []string 263 params []interface{} 264 ) 265 // Retrieve the table fields and length. 266 var ( 267 listLength = len(list) 268 valueHolder = make([]string, 0) 269 ) 270 for k, _ := range list[0] { 271 keys = append(keys, k) 272 valueHolder = append(valueHolder, "?") 273 } 274 var ( 275 batchResult = new(SqlResult) 276 charL, charR = d.db.GetChars() 277 keyStr = charL + strings.Join(keys, charL+","+charR) + charR 278 valueHolderStr = strings.Join(valueHolder, ",") 279 ) 280 // Format "INSERT...INTO..." statement. 281 intoStr := make([]string, 0) 282 for i := 0; i < len(list); i++ { 283 for _, k := range keys { 284 params = append(params, list[i][k]) 285 } 286 values = append(values, valueHolderStr) 287 intoStr = append(intoStr, fmt.Sprintf("INTO %s(%s) VALUES(%s)", table, keyStr, valueHolderStr)) 288 if len(intoStr) == option.BatchCount || (i == listLength-1 && len(valueHolder) > 0) { 289 r, err := d.DoExec(ctx, link, fmt.Sprintf( 290 "INSERT ALL %s SELECT * FROM DUAL", 291 strings.Join(intoStr, " "), 292 ), params...) 293 if err != nil { 294 return r, err 295 } 296 if n, err := r.RowsAffected(); err != nil { 297 return r, err 298 } else { 299 batchResult.result = r 300 batchResult.affected += n 301 } 302 params = params[:0] 303 intoStr = intoStr[:0] 304 } 305 } 306 return batchResult, nil 307 }