Files
grafana/pkg/services/sqlstore/migrator/dialect.go
Matthew Jacobson 44972d0cd5 SQLStore: Fix Postgres dialect treating "false" migrator default as true (#69353)
* SQLStore: Fix Postgres dialect treating "false" migrator default as true

Previously, when creating a migration you could choose a default value for a new 
boolean column that looked correct but would be interpreted incorrectly by the 
Postgres dialect. For example, values such as "false" or "FALSE" would be treated 
as true by the Postgres dialect.

This refactors how migration dialects determine the Default column value for boolean
type columns. Each dialect now uses the same base code to parse the Default literal 
and panics if an unknown value is encountered.

So, now AddColumnMigration and AddTableMigration will ensure that across dialects:

- The exact same Default literals will be allowed.
- The literals are converted to equivalent defaults in their DDL.
- An error will be thrown if an invalid literal is provided.
2023-06-22 15:09:36 -04:00

347 lines
8.4 KiB
Go

package migrator
import (
"fmt"
"strconv"
"strings"
"xorm.io/xorm"
)
var (
ErrLockDB = fmt.Errorf("failed to obtain lock")
ErrReleaseLockDB = fmt.Errorf("failed to release lock")
)
type Dialect interface {
DriverName() string
Quote(string) string
AndStr() string
AutoIncrStr() string
OrStr() string
EqStr() string
ShowCreateNull() bool
SQLType(col *Column) string
SupportEngine() bool
LikeStr() string
Default(col *Column) string
BooleanStr(bool) string
DateTimeFunc(string) string
BatchSize() int
OrderBy(order string) string
CreateIndexSQL(tableName string, index *Index) string
CreateTableSQL(table *Table) string
AddColumnSQL(tableName string, col *Column) string
CopyTableData(sourceTable string, targetTable string, sourceCols []string, targetCols []string) string
DropTable(tableName string) string
DropIndexSQL(tableName string, index *Index) string
// RenameTable is deprecated, its use cause breaking changes
// so, it should no longer be used. Kept for legacy reasons.
RenameTable(oldName string, newName string) string
// RenameColumn is deprecated, its use cause breaking changes
// so, it should no longer be used. Kept for legacy reasons.
RenameColumn(table Table, column *Column, newName string) string
UpdateTableSQL(tableName string, columns []*Column) string
IndexCheckSQL(tableName, indexName string) (string, []interface{})
ColumnCheckSQL(tableName, columnName string) (string, []interface{})
// UpsertSQL returns the upsert sql statement for a dialect
UpsertSQL(tableName string, keyCols, updateCols []string) string
UpsertMultipleSQL(tableName string, keyCols, updateCols []string, count int) (string, error)
ColString(*Column) string
ColStringNoPk(*Column) string
Limit(limit int64) string
LimitOffset(limit int64, offset int64) string
PreInsertId(table string, sess *xorm.Session) error
PostInsertId(table string, sess *xorm.Session) error
CleanDB(engine *xorm.Engine) error
TruncateDBTables(engine *xorm.Engine) error
NoOpSQL() string
IsUniqueConstraintViolation(err error) bool
ErrorMessage(err error) string
IsDeadlock(err error) bool
Lock(LockCfg) error
Unlock(LockCfg) error
GetDBName(string) (string, error)
}
type LockCfg struct {
Session *xorm.Session
Key string
Timeout int
}
type dialectFunc func() Dialect
var supportedDialects = map[string]dialectFunc{
MySQL: NewMysqlDialect,
SQLite: NewSQLite3Dialect,
Postgres: NewPostgresDialect,
MySQL + "WithHooks": NewMysqlDialect,
SQLite + "WithHooks": NewSQLite3Dialect,
Postgres + "WithHooks": NewPostgresDialect,
}
func NewDialect(driverName string) Dialect {
if fn, exist := supportedDialects[driverName]; exist {
return fn()
}
panic("Unsupported database type: " + driverName)
}
type BaseDialect struct {
dialect Dialect
driverName string
}
func (b *BaseDialect) DriverName() string {
return b.driverName
}
func (b *BaseDialect) ShowCreateNull() bool {
return true
}
func (b *BaseDialect) AndStr() string {
return "AND"
}
func (b *BaseDialect) LikeStr() string {
return "LIKE"
}
func (b *BaseDialect) OrStr() string {
return "OR"
}
func (b *BaseDialect) EqStr() string {
return "="
}
func (b *BaseDialect) Default(col *Column) string {
if col.Type == DB_Bool {
// Ensure that all dialects support the same literals in the same way.
bl, err := strconv.ParseBool(col.Default)
if err != nil {
panic(fmt.Errorf("failed to create default value for column '%s': invalid boolean default value '%s'", col.Name, col.Default))
}
return b.dialect.BooleanStr(bl)
}
return col.Default
}
func (b *BaseDialect) DateTimeFunc(value string) string {
return value
}
func (b *BaseDialect) CreateTableSQL(table *Table) string {
sql := "CREATE TABLE IF NOT EXISTS "
sql += b.dialect.Quote(table.Name) + " (\n"
pkList := table.PrimaryKeys
for _, col := range table.Columns {
if col.IsPrimaryKey && len(pkList) == 1 {
sql += col.String(b.dialect)
} else {
sql += col.StringNoPk(b.dialect)
}
sql = strings.TrimSpace(sql)
sql += "\n, "
}
if len(pkList) > 1 {
quotedCols := []string{}
for _, col := range pkList {
quotedCols = append(quotedCols, b.dialect.Quote(col))
}
sql += "PRIMARY KEY ( " + strings.Join(quotedCols, ",") + " ), "
}
sql = sql[:len(sql)-2] + ")"
if b.dialect.SupportEngine() {
sql += " ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci"
}
sql += ";"
return sql
}
func (b *BaseDialect) AddColumnSQL(tableName string, col *Column) string {
return fmt.Sprintf("alter table %s ADD COLUMN %s", b.dialect.Quote(tableName), col.StringNoPk(b.dialect))
}
func (b *BaseDialect) CreateIndexSQL(tableName string, index *Index) string {
quote := b.dialect.Quote
var unique string
if index.Type == UniqueIndex {
unique = " UNIQUE"
}
idxName := index.XName(tableName)
quotedCols := []string{}
for _, col := range index.Cols {
quotedCols = append(quotedCols, b.dialect.Quote(col))
}
return fmt.Sprintf("CREATE%s INDEX %v ON %v (%v);", unique, quote(idxName), quote(tableName), strings.Join(quotedCols, ","))
}
func (b *BaseDialect) QuoteColList(cols []string) string {
var sourceColsSQL = ""
for _, col := range cols {
sourceColsSQL += b.dialect.Quote(col)
sourceColsSQL += "\n, "
}
return strings.TrimSuffix(sourceColsSQL, "\n, ")
}
func (b *BaseDialect) CopyTableData(sourceTable string, targetTable string, sourceCols []string, targetCols []string) string {
sourceColsSQL := b.QuoteColList(sourceCols)
targetColsSQL := b.QuoteColList(targetCols)
quote := b.dialect.Quote
return fmt.Sprintf("INSERT INTO %s (%s) SELECT %s FROM %s", quote(targetTable), targetColsSQL, sourceColsSQL, quote(sourceTable))
}
func (b *BaseDialect) DropTable(tableName string) string {
quote := b.dialect.Quote
return fmt.Sprintf("DROP TABLE IF EXISTS %s", quote(tableName))
}
func (b *BaseDialect) RenameTable(oldName string, newName string) string {
quote := b.dialect.Quote
return fmt.Sprintf("ALTER TABLE %s RENAME TO %s", quote(oldName), quote(newName))
}
func (b *BaseDialect) RenameColumn(table Table, column *Column, newName string) string {
quote := b.dialect.Quote
return fmt.Sprintf(
"ALTER TABLE %s RENAME COLUMN %s TO %s",
quote(table.Name), quote(column.Name), quote(newName),
)
}
func (b *BaseDialect) ColumnCheckSQL(tableName, columnName string) (string, []interface{}) {
return "", nil
}
func (b *BaseDialect) DropIndexSQL(tableName string, index *Index) string {
quote := b.dialect.Quote
name := index.XName(tableName)
return fmt.Sprintf("DROP INDEX %v ON %s", quote(name), quote(tableName))
}
func (b *BaseDialect) UpdateTableSQL(tableName string, columns []*Column) string {
return "-- NOT REQUIRED"
}
func (b *BaseDialect) ColString(col *Column) string {
sql := b.dialect.Quote(col.Name) + " "
sql += b.dialect.SQLType(col) + " "
if col.IsPrimaryKey {
sql += "PRIMARY KEY "
if col.IsAutoIncrement {
sql += b.dialect.AutoIncrStr() + " "
}
}
if b.dialect.ShowCreateNull() {
if col.Nullable {
sql += "NULL "
} else {
sql += "NOT NULL "
}
}
if col.Default != "" {
sql += "DEFAULT " + b.dialect.Default(col) + " "
}
return sql
}
func (b *BaseDialect) ColStringNoPk(col *Column) string {
sql := b.dialect.Quote(col.Name) + " "
sql += b.dialect.SQLType(col) + " "
if b.dialect.ShowCreateNull() {
if col.Nullable {
sql += "NULL "
} else {
sql += "NOT NULL "
}
}
if col.Default != "" {
sql += "DEFAULT " + b.dialect.Default(col) + " "
}
return sql
}
func (b *BaseDialect) Limit(limit int64) string {
return fmt.Sprintf(" LIMIT %d", limit)
}
func (b *BaseDialect) LimitOffset(limit int64, offset int64) string {
return fmt.Sprintf(" LIMIT %d OFFSET %d", limit, offset)
}
func (b *BaseDialect) PreInsertId(table string, sess *xorm.Session) error {
return nil
}
func (b *BaseDialect) PostInsertId(table string, sess *xorm.Session) error {
return nil
}
func (b *BaseDialect) CleanDB(engine *xorm.Engine) error {
return nil
}
func (b *BaseDialect) NoOpSQL() string {
return "SELECT 0;"
}
func (b *BaseDialect) TruncateDBTables(engine *xorm.Engine) error {
return nil
}
// UpsertSQL returns empty string
func (b *BaseDialect) UpsertSQL(tableName string, keyCols, updateCols []string) string {
return ""
}
func (b *BaseDialect) Lock(_ LockCfg) error {
return nil
}
func (b *BaseDialect) Unlock(_ LockCfg) error {
return nil
}
func (b *BaseDialect) OrderBy(order string) string {
return order
}
func (b *BaseDialect) GetDBName(_ string) (string, error) {
return "", nil
}