
Improved questionmark parameter placeholder replacementw Added support for subqueries in Where and Having clauses Queries can be transformed into subqueries by calling .Subquery() on a db object See main_test.go:TestQueryBuilderSubselectInWhere Fixed comment spacing Refactoring, adding Having Subquery support, allowing db.T for tablenames Removed quoting from tablename in db.T, use db.QT for that Refactoring, adding Having Subquery support, allowing db.T for tablenames Added changes Started with expression extension Refactoring, adding Having Subquery support, allowing db.T for tablenames Added method to easily update fields of the Model struct Added column comparison and Join support Added subquery support for InnerJoin querybuilder Fixed column comparison Added support for column prefixes Models can set their column prefix by implementing the method ColumnPrefix() string Fixed multi-parameter subselects and introduced aliasing Improved Related method Improved Related method to search for foreign key struct fields with the suffix "ID" (additional to "Id") Got QueryExpr support from upstream Added support for subqueries in Where and Having clauses Queries can be transformed into subqueries by calling .Subquery() on a db object See main_test.go:TestQueryBuilderSubselectInWhere Improved questionmark parameter placeholder replacementw Refactoring, adding Having Subquery support, allowing db.T for tablenames Removed quoting from tablename in db.T, use db.QT for that Removed quoting from tablename in db.T, use db.QT for that Added changes Added method to easily update fields of the Model struct Fixed column comparison Added support for column prefixes Models can set their column prefix by implementing the method ColumnPrefix() string Fixed multi-parameter subselects and introduced aliasing Improved Related method Improved Related method to search for foreign key struct fields with the suffix "ID" (additional to "Id") Added select extension for multiple columns Added support for LEFT RIGHT OUTER joins Fixed slice support for lexpr.In() Publizised LExpr Added DateFormatting for all dialects Added SUM function for columns Fixed FormatDate Added count for column Removed literal expressions LExpr Rewrote LExpr methods to work with expr structs. Added methods BAnd and BOr (bitwise & and | ) Added SetLogWriter method Added NotIn query expression Added Distinct query expression Added DistinctColumn query expression Same as Distinct but returns a string Added method OnExp to jexpr Improved query expression .Eq() method for nil pointers Fixed rebase errors
170 lines
5.7 KiB
Go
170 lines
5.7 KiB
Go
package gorm
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Dialect interface contains behaviors that differ across SQL database
|
|
type Dialect interface {
|
|
// GetName get dialect's name
|
|
GetName() string
|
|
|
|
// SetDB set db for dialect
|
|
SetDB(db SQLCommon)
|
|
|
|
// BindVar return the placeholder for actual values in SQL statements, in many dbs it is "?", Postgres using $1
|
|
BindVar(i int) string
|
|
// Quote quotes field name to avoid SQL parsing exceptions by using a reserved word as a field name
|
|
Quote(key string) string
|
|
// DataTypeOf return data's sql type
|
|
DataTypeOf(field *StructField) string
|
|
|
|
// HasIndex check has index or not
|
|
HasIndex(tableName string, indexName string) bool
|
|
// HasForeignKey check has foreign key or not
|
|
HasForeignKey(tableName string, foreignKeyName string) bool
|
|
// RemoveIndex remove index
|
|
RemoveIndex(tableName string, indexName string) error
|
|
// HasTable check has table or not
|
|
HasTable(tableName string) bool
|
|
// HasColumn check has column or not
|
|
HasColumn(tableName string, columnName string) bool
|
|
// ModifyColumn modify column's type
|
|
ModifyColumn(tableName string, columnName string, typ string) error
|
|
|
|
// LimitAndOffsetSQL return generated SQL with Limit and Offset, as mssql has special case
|
|
LimitAndOffsetSQL(limit, offset interface{}) string
|
|
// SelectFromDummyTable return select values, for most dbs, `SELECT values` just works, mysql needs `SELECT value FROM DUAL`
|
|
SelectFromDummyTable() string
|
|
// LastInsertIdReturningSuffix most dbs support LastInsertId, but postgres needs to use `RETURNING`
|
|
LastInsertIDReturningSuffix(tableName, columnName string) string
|
|
// DefaultValueStr
|
|
DefaultValueStr() string
|
|
|
|
// BuildKeyName returns a valid key name (foreign key, index key) for the given table, field and reference
|
|
BuildKeyName(kind, tableName string, fields ...string) string
|
|
|
|
// CurrentDatabase return current database name
|
|
CurrentDatabase() string
|
|
|
|
// Formates the date and tries to be as similar as possible with all databases
|
|
// | | gorm | SQLITE | MYSQL | MSSQL | POSTGRES |
|
|
// | ----------- | ---- | ----------------- | ----------------- | ---------------- | ---------------- |
|
|
// | YEAR | %y | %Y (0000-9999) | %Y (0000-9999) | YYYY (0000-9999) | YYYY (0000-9999) |
|
|
// | MONTH | %m | %m (01-12) | %m (01-12) | MM (00-12) | MM (0000-9999) |
|
|
// | WEEK | %w | %W (00-53) | %u (00-53) | -- | WW (1-53) |
|
|
// | DAY | %d | %d (00-31) | %d (00-31) | dd (00-31) | DD (00-31) |
|
|
// | DAY OF WEEK | %D | %w (SUN 0-6 SAT) | %w (SUN 0-6 SAT) | -- | D (SUN 1-7 SAT) |
|
|
// | HOUR | %h | %H (00-24) | %H (00-23) | HH (00-31) | HH24 (00-23) |
|
|
// | MINUTE | %m | %M (00-59) | %i (00-59) | mm (00-59) | MI (00-59) |
|
|
// | SECOND | %s | %S (00-59) | %S (00-59) | ss (00-59) | SS (00-59) |
|
|
FormatDate(*expr, string) *expr
|
|
}
|
|
|
|
var dialectsMap = map[string]Dialect{}
|
|
|
|
func newDialect(name string, db SQLCommon) Dialect {
|
|
if value, ok := dialectsMap[name]; ok {
|
|
dialect := reflect.New(reflect.TypeOf(value).Elem()).Interface().(Dialect)
|
|
dialect.SetDB(db)
|
|
return dialect
|
|
}
|
|
|
|
fmt.Printf("`%v` is not officially supported, running under compatibility mode.\n", name)
|
|
commontDialect := &commonDialect{}
|
|
commontDialect.SetDB(db)
|
|
return commontDialect
|
|
}
|
|
|
|
// RegisterDialect register new dialect
|
|
func RegisterDialect(name string, dialect Dialect) {
|
|
dialectsMap[name] = dialect
|
|
}
|
|
|
|
// ParseFieldStructForDialect get field's sql data type
|
|
var ParseFieldStructForDialect = func(field *StructField, dialect Dialect) (fieldValue reflect.Value, sqlType string, size int, additionalType string) {
|
|
// Get redirected field type
|
|
var (
|
|
reflectType = field.Struct.Type
|
|
dataType = field.TagSettings["TYPE"]
|
|
)
|
|
|
|
for reflectType.Kind() == reflect.Ptr {
|
|
reflectType = reflectType.Elem()
|
|
}
|
|
|
|
// Get redirected field value
|
|
fieldValue = reflect.Indirect(reflect.New(reflectType))
|
|
|
|
if gormDataType, ok := fieldValue.Interface().(interface {
|
|
GormDataType(Dialect) string
|
|
}); ok {
|
|
dataType = gormDataType.GormDataType(dialect)
|
|
}
|
|
|
|
// Get scanner's real value
|
|
if dataType == "" {
|
|
var getScannerValue func(reflect.Value)
|
|
getScannerValue = func(value reflect.Value) {
|
|
fieldValue = value
|
|
if _, isScanner := reflect.New(fieldValue.Type()).Interface().(sql.Scanner); isScanner && fieldValue.Kind() == reflect.Struct {
|
|
getScannerValue(fieldValue.Field(0))
|
|
}
|
|
}
|
|
getScannerValue(fieldValue)
|
|
}
|
|
|
|
// Default Size
|
|
if num, ok := field.TagSettings["SIZE"]; ok {
|
|
size, _ = strconv.Atoi(num)
|
|
} else {
|
|
size = 255
|
|
}
|
|
|
|
// Default type from tag setting
|
|
additionalType = field.TagSettings["NOT NULL"] + " " + field.TagSettings["UNIQUE"]
|
|
if value, ok := field.TagSettings["DEFAULT"]; ok {
|
|
additionalType = additionalType + " DEFAULT " + value
|
|
}
|
|
|
|
return fieldValue, dataType, size, strings.TrimSpace(additionalType)
|
|
}
|
|
|
|
func currentDatabaseAndTable(dialect Dialect, tableName string) (string, string) {
|
|
if strings.Contains(tableName, ".") {
|
|
splitStrings := strings.SplitN(tableName, ".", 2)
|
|
return splitStrings[0], splitStrings[1]
|
|
}
|
|
return dialect.CurrentDatabase(), tableName
|
|
}
|
|
|
|
func parseDateFormat(format string, mapping map[rune]string) string {
|
|
var parsedFormat string
|
|
isFormatter := false
|
|
|
|
for _, rune := range format {
|
|
if !isFormatter {
|
|
if rune == '%' {
|
|
isFormatter = true
|
|
} else {
|
|
parsedFormat += string(rune)
|
|
}
|
|
continue
|
|
}
|
|
|
|
isFormatter = false
|
|
|
|
if sign, ok := mapping[rune]; ok {
|
|
parsedFormat += sign
|
|
} else {
|
|
parsedFormat += "%" + string(rune)
|
|
}
|
|
}
|
|
|
|
return parsedFormat
|
|
}
|