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  }