Updated GORM driver.

Fixes https://github.com/go-gorm/sqlite/issues/192.
This commit is contained in:
Nuno Cruces
2025-01-07 12:29:16 +00:00
parent 8175407754
commit 31c5000875
6 changed files with 192 additions and 32 deletions

View File

@@ -17,23 +17,11 @@ var (
indexRegexp = regexp.MustCompile(fmt.Sprintf(`(?is)CREATE(?: UNIQUE)? INDEX [%v]?[\w\d-]+[%v]?(?s:.*?)ON (.*)$`, sqliteSeparator, sqliteSeparator)) indexRegexp = regexp.MustCompile(fmt.Sprintf(`(?is)CREATE(?: UNIQUE)? INDEX [%v]?[\w\d-]+[%v]?(?s:.*?)ON (.*)$`, sqliteSeparator, sqliteSeparator))
tableRegexp = regexp.MustCompile(fmt.Sprintf(`(?is)(CREATE TABLE [%v]?[\w\d-]+[%v]?)(?:\s*\((.*)\))?`, sqliteSeparator, sqliteSeparator)) tableRegexp = regexp.MustCompile(fmt.Sprintf(`(?is)(CREATE TABLE [%v]?[\w\d-]+[%v]?)(?:\s*\((.*)\))?`, sqliteSeparator, sqliteSeparator))
separatorRegexp = regexp.MustCompile(fmt.Sprintf("[%v]", sqliteSeparator)) separatorRegexp = regexp.MustCompile(fmt.Sprintf("[%v]", sqliteSeparator))
columnsRegexp = regexp.MustCompile(fmt.Sprintf(`[(,][%v]?(\w+)[%v]?`, sqliteSeparator, sqliteSeparator))
columnRegexp = regexp.MustCompile(fmt.Sprintf(`^[%v]?([\w\d]+)[%v]?\s+([\w\(\)\d]+)(.*)$`, sqliteSeparator, sqliteSeparator)) columnRegexp = regexp.MustCompile(fmt.Sprintf(`^[%v]?([\w\d]+)[%v]?\s+([\w\(\)\d]+)(.*)$`, sqliteSeparator, sqliteSeparator))
defaultValueRegexp = regexp.MustCompile(`(?i) DEFAULT \(?(.+)?\)?( |COLLATE|GENERATED|$)`) defaultValueRegexp = regexp.MustCompile(`(?i) DEFAULT \(?(.+)?\)?( |COLLATE|GENERATED|$)`)
regRealDataType = regexp.MustCompile(`[^\d](\d+)[^\d]?`) regRealDataType = regexp.MustCompile(`[^\d](\d+)[^\d]?`)
) )
func getAllColumns(s string) []string {
allMatches := columnsRegexp.FindAllStringSubmatch(s, -1)
columns := make([]string, 0, len(allMatches))
for _, matches := range allMatches {
if len(matches) > 1 {
columns = append(columns, matches[1])
}
}
return columns
}
type ddl struct { type ddl struct {
head string head string
fields []string fields []string
@@ -110,9 +98,10 @@ func parseDDL(strs ...string) (*ddl, error) {
if strings.HasPrefix(fUpper, "CONSTRAINT") { if strings.HasPrefix(fUpper, "CONSTRAINT") {
matches := uniqueRegexp.FindStringSubmatch(f) matches := uniqueRegexp.FindStringSubmatch(f)
if len(matches) > 0 { if len(matches) > 0 {
if columns := getAllColumns(matches[1]); len(columns) == 1 { cols, err := parseAllColumns(matches[1])
if err == nil && len(cols) == 1 {
for idx, column := range result.columns { for idx, column := range result.columns {
if column.NameValue.String == columns[0] { if column.NameValue.String == cols[0] {
column.UniqueValue = sql.NullBool{Bool: true, Valid: true} column.UniqueValue = sql.NullBool{Bool: true, Valid: true}
result.columns[idx] = column result.columns[idx] = column
break break
@@ -123,12 +112,15 @@ func parseDDL(strs ...string) (*ddl, error) {
continue continue
} }
if strings.HasPrefix(fUpper, "PRIMARY KEY") { if strings.HasPrefix(fUpper, "PRIMARY KEY") {
for _, name := range getAllColumns(f) { cols, err := parseAllColumns(f)
for idx, column := range result.columns { if err == nil {
if column.NameValue.String == name { for _, name := range cols {
column.PrimaryKeyValue = sql.NullBool{Bool: true, Valid: true} for idx, column := range result.columns {
result.columns[idx] = column if column.NameValue.String == name {
break column.PrimaryKeyValue = sql.NullBool{Bool: true, Valid: true}
result.columns[idx] = column
break
}
} }
} }
} }

View File

@@ -0,0 +1,117 @@
package gormlite
import (
"errors"
"fmt"
)
type parseAllColumnsState int
const (
parseAllColumnsState_NONE parseAllColumnsState = iota
parseAllColumnsState_Beginning
parseAllColumnsState_ReadingRawName
parseAllColumnsState_ReadingQuotedName
parseAllColumnsState_EndOfName
parseAllColumnsState_State_End
)
func parseAllColumns(in string) ([]string, error) {
s := []rune(in)
columns := make([]string, 0)
state := parseAllColumnsState_NONE
quote := rune(0)
name := make([]rune, 0)
for i := 0; i < len(s); i++ {
switch state {
case parseAllColumnsState_NONE:
if s[i] == '(' {
state = parseAllColumnsState_Beginning
}
case parseAllColumnsState_Beginning:
if isSpace(s[i]) {
continue
}
if isQuote(s[i]) {
state = parseAllColumnsState_ReadingQuotedName
quote = s[i]
continue
}
if s[i] == '[' {
state = parseAllColumnsState_ReadingQuotedName
quote = ']'
continue
} else if s[i] == ')' {
return columns, fmt.Errorf("unexpected token: %s", string(s[i]))
}
state = parseAllColumnsState_ReadingRawName
name = append(name, s[i])
case parseAllColumnsState_ReadingRawName:
if isSeparator(s[i]) {
state = parseAllColumnsState_Beginning
columns = append(columns, string(name))
name = make([]rune, 0)
continue
}
if s[i] == ')' {
state = parseAllColumnsState_State_End
columns = append(columns, string(name))
}
if isQuote(s[i]) {
return nil, fmt.Errorf("unexpected token: %s", string(s[i]))
}
if isSpace(s[i]) {
state = parseAllColumnsState_EndOfName
columns = append(columns, string(name))
name = make([]rune, 0)
continue
}
name = append(name, s[i])
case parseAllColumnsState_ReadingQuotedName:
if s[i] == quote {
// check if quote character is escaped
if i+1 < len(s) && s[i+1] == quote {
name = append(name, quote)
i++
continue
}
state = parseAllColumnsState_EndOfName
columns = append(columns, string(name))
name = make([]rune, 0)
continue
}
name = append(name, s[i])
case parseAllColumnsState_EndOfName:
if isSpace(s[i]) {
continue
}
if isSeparator(s[i]) {
state = parseAllColumnsState_Beginning
continue
}
if s[i] == ')' {
state = parseAllColumnsState_State_End
continue
}
return nil, fmt.Errorf("unexpected token: %s", string(s[i]))
case parseAllColumnsState_State_End:
break
}
}
if state != parseAllColumnsState_State_End {
return nil, errors.New("unexpected end")
}
return columns, nil
}
func isSpace(r rune) bool {
return r == ' ' || r == '\t'
}
func isQuote(r rune) bool {
return r == '`' || r == '"' || r == '\''
}
func isSeparator(r rune) bool {
return r == ','
}

View File

@@ -0,0 +1,48 @@
package gormlite
import "testing"
func TestParseAllColumns(t *testing.T) {
tc := []struct {
name string
input string
expected []string
}{
{
name: "Simple case",
input: "PRIMARY KEY (column1, column2)",
expected: []string{"column1", "column2"},
},
{
name: "Quoted column name",
input: "PRIMARY KEY (`column,xxx`, \"column 2\", \"column)3\", 'column''4', \"column\"\"5\")",
expected: []string{"column,xxx", "column 2", "column)3", "column'4", "column\"5"},
},
{
name: "Japanese column name",
input: "PRIMARY KEY (カラム1, `カラム2`)",
expected: []string{"カラム1", "カラム2"},
},
{
name: "Column name quoted with []",
input: "PRIMARY KEY ([column1], [column2])",
expected: []string{"column1", "column2"},
},
}
for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
cols, err := parseAllColumns(tt.input)
if err != nil {
t.Errorf("Failed to parse columns: %s", err)
}
if len(cols) != len(tt.expected) {
t.Errorf("Expected %d columns, got %d", len(tt.expected), len(cols))
}
for i, col := range cols {
if col != tt.expected[i] {
t.Errorf("Expected %s, got %s", tt.expected[i], col)
}
}
})
}
}

View File

@@ -3,11 +3,13 @@ set -euo pipefail
cd -P -- "$(dirname -- "$0")" cd -P -- "$(dirname -- "$0")"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.6/ddlmod.go" curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/ddlmod.go"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.6/ddlmod_test.go" curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/ddlmod_test.go"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.6/error_translator.go" curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/ddlmod_parse_all_columns.go"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.6/migrator.go" curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/ddlmod_parse_all_columns_test.go"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.6/sqlite.go" curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/error_translator.go"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.6/sqlite_test.go" curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/migrator.go"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.6/sqlite_test.go" curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/sqlite.go"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/sqlite_test.go"
curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.7/sqlite_test.go"
curl -#L "https://github.com/glebarez/sqlite/raw/v1.11.0/sqlite_error_translator_test.go" > error_translator_test.go curl -#L "https://github.com/glebarez/sqlite/raw/v1.11.0/sqlite_error_translator_test.go" > error_translator_test.go

View File

@@ -8,6 +8,7 @@ import (
"github.com/ncruces/go-sqlite3" "github.com/ncruces/go-sqlite3"
) )
// Translate it will translate the error to native gorm errors.
func (_Dialector) Translate(err error) error { func (_Dialector) Translate(err error) error {
switch { switch {
case case

View File

@@ -14,6 +14,11 @@ import (
"github.com/ncruces/go-sqlite3/driver" "github.com/ncruces/go-sqlite3/driver"
) )
type _Dialector struct {
DSN string
Conn gorm.ConnPool
}
// Open opens a GORM dialector from a data source name. // Open opens a GORM dialector from a data source name.
func Open(dsn string) gorm.Dialector { func Open(dsn string) gorm.Dialector {
return &_Dialector{DSN: dsn} return &_Dialector{DSN: dsn}
@@ -24,11 +29,6 @@ func OpenDB(db gorm.ConnPool) gorm.Dialector {
return &_Dialector{Conn: db} return &_Dialector{Conn: db}
} }
type _Dialector struct {
DSN string
Conn gorm.ConnPool
}
func (dialector _Dialector) Name() string { func (dialector _Dialector) Name() string {
return "sqlite" return "sqlite"
} }