Replace self-hacked SQLite Gorm driver with 3rd party one
The new Gorm driver is made by the creators of the pure-Go SQLite library we were already using.
This commit is contained in:
parent
2b04623e00
commit
9b9c6bffff
@ -42,7 +42,3 @@ in this process.
|
|||||||
## License
|
## License
|
||||||
|
|
||||||
Flamenco is licensed under the GPLv3+ license.
|
Flamenco is licensed under the GPLv3+ license.
|
||||||
|
|
||||||
Flamenco Manager includes a copy of https://github.com/go-gorm/sqlite.git,
|
|
||||||
adjusted to use the pure-Go SQLite from https://modernc.org/sqlite. It is
|
|
||||||
licensed under the MIT license by Jinzhu <wosmvp@gmail.com>.
|
|
||||||
|
2
go.mod
2
go.mod
@ -28,6 +28,8 @@ require (
|
|||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect
|
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect
|
||||||
github.com/ghodss/yaml v1.0.0 // indirect
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
|
github.com/glebarez/go-sqlite v1.14.7 // indirect
|
||||||
|
github.com/glebarez/sqlite v1.3.5 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||||
github.com/go-openapi/swag v0.19.5 // indirect
|
github.com/go-openapi/swag v0.19.5 // indirect
|
||||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
|
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
|
||||||
|
11
go.sum
11
go.sum
@ -26,6 +26,10 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
|||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||||
|
github.com/glebarez/go-sqlite v1.14.7 h1:eXrKp59O5eWBfxv2Xfq5d7uex4+clKrOtWfMzzGSkoM=
|
||||||
|
github.com/glebarez/go-sqlite v1.14.7/go.mod h1:TKAw5tjyB/ocvVht7Xv4772qRAun5CG/xLCEbkDwNUc=
|
||||||
|
github.com/glebarez/sqlite v1.3.5 h1:R9op5nxb9Z10t4VXQSdAVyqRalLhWdLrlaT/iuvOGHI=
|
||||||
|
github.com/glebarez/sqlite v1.3.5/go.mod h1:ZffEtp/afVhV+jvIzQi8wlYEIkuGAYshr9OPKM/NmQc=
|
||||||
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
|
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
|
||||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
@ -245,6 +249,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/gorm v1.22.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||||
gorm.io/gorm v1.23.2 h1:xmq9QRMWL8HTJyhAUBXy8FqIIQCYESeKfJL4DoGKiWQ=
|
gorm.io/gorm v1.23.2 h1:xmq9QRMWL8HTJyhAUBXy8FqIIQCYESeKfJL4DoGKiWQ=
|
||||||
gorm.io/gorm v1.23.2/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
gorm.io/gorm v1.23.2/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||||
lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU=
|
lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU=
|
||||||
@ -304,6 +309,7 @@ modernc.org/ccgo/v3 v3.12.86/go.mod h1:dN7S26DLTgVSni1PVA3KxxHTcykyDurf3OgUzNqTS
|
|||||||
modernc.org/ccgo/v3 v3.12.90/go.mod h1:obhSc3CdivCRpYZmrvO88TXlW0NvoSVvdh/ccRjJYko=
|
modernc.org/ccgo/v3 v3.12.90/go.mod h1:obhSc3CdivCRpYZmrvO88TXlW0NvoSVvdh/ccRjJYko=
|
||||||
modernc.org/ccgo/v3 v3.12.92/go.mod h1:5yDdN7ti9KWPi5bRVWPl8UNhpEAtCjuEE7ayQnzzqHA=
|
modernc.org/ccgo/v3 v3.12.92/go.mod h1:5yDdN7ti9KWPi5bRVWPl8UNhpEAtCjuEE7ayQnzzqHA=
|
||||||
modernc.org/ccgo/v3 v3.13.1/go.mod h1:aBYVOUfIlcSnrsRVU8VRS35y2DIfpgkmVkYZ0tpIXi4=
|
modernc.org/ccgo/v3 v3.13.1/go.mod h1:aBYVOUfIlcSnrsRVU8VRS35y2DIfpgkmVkYZ0tpIXi4=
|
||||||
|
modernc.org/ccgo/v3 v3.14.0/go.mod h1:hBrkiBlUwvr5vV/ZH9YzXIp982jKE8Ek8tR1ytoAL6Q=
|
||||||
modernc.org/ccgo/v3 v3.15.1/go.mod h1:md59wBwDT2LznX/OTCPoVS6KIsdRgY8xqQwBV+hkTH0=
|
modernc.org/ccgo/v3 v3.15.1/go.mod h1:md59wBwDT2LznX/OTCPoVS6KIsdRgY8xqQwBV+hkTH0=
|
||||||
modernc.org/ccgo/v3 v3.15.9/go.mod h1:md59wBwDT2LznX/OTCPoVS6KIsdRgY8xqQwBV+hkTH0=
|
modernc.org/ccgo/v3 v3.15.9/go.mod h1:md59wBwDT2LznX/OTCPoVS6KIsdRgY8xqQwBV+hkTH0=
|
||||||
modernc.org/ccgo/v3 v3.15.10/go.mod h1:wQKxoFn0ynxMuCLfFD09c8XPUCc8obfchoVR9Cn0fI8=
|
modernc.org/ccgo/v3 v3.15.10/go.mod h1:wQKxoFn0ynxMuCLfFD09c8XPUCc8obfchoVR9Cn0fI8=
|
||||||
@ -353,6 +359,8 @@ modernc.org/libc v1.11.88/go.mod h1:h3oIVe8dxmTcchcFuCcJ4nAWaoiwzKCdv82MM0oiIdQ=
|
|||||||
modernc.org/libc v1.11.98/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c=
|
modernc.org/libc v1.11.98/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c=
|
||||||
modernc.org/libc v1.11.101/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI=
|
modernc.org/libc v1.11.101/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI=
|
||||||
modernc.org/libc v1.12.0/go.mod h1:2MH3DaF/gCU8i/UBiVE1VFRos4o523M7zipmwH8SIgQ=
|
modernc.org/libc v1.12.0/go.mod h1:2MH3DaF/gCU8i/UBiVE1VFRos4o523M7zipmwH8SIgQ=
|
||||||
|
modernc.org/libc v1.13.1/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk=
|
||||||
|
modernc.org/libc v1.13.2/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk=
|
||||||
modernc.org/libc v1.14.1/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk=
|
modernc.org/libc v1.14.1/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk=
|
||||||
modernc.org/libc v1.14.2/go.mod h1:MX1GBLnRLNdvmK9azU9LCxZ5lMyhrbEMK8rG3X/Fe34=
|
modernc.org/libc v1.14.2/go.mod h1:MX1GBLnRLNdvmK9azU9LCxZ5lMyhrbEMK8rG3X/Fe34=
|
||||||
modernc.org/libc v1.14.3/go.mod h1:GPIvQVOVPizzlqyRX3l756/3ppsAgg1QgPxjr5Q4agQ=
|
modernc.org/libc v1.14.3/go.mod h1:GPIvQVOVPizzlqyRX3l756/3ppsAgg1QgPxjr5Q4agQ=
|
||||||
@ -368,13 +376,16 @@ modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14=
|
|||||||
modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
|
modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
|
||||||
modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
|
modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
|
||||||
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||||
|
modernc.org/sqlite v1.14.5/go.mod h1:YyX5Rx0WbXokitdWl2GJIDy4BrPxBP0PwwhpXOHCDLE=
|
||||||
modernc.org/sqlite v1.14.6 h1:Jt5P3k80EtDBWaq1beAxnWW+5MdHXbZITujnRS7+zWg=
|
modernc.org/sqlite v1.14.6 h1:Jt5P3k80EtDBWaq1beAxnWW+5MdHXbZITujnRS7+zWg=
|
||||||
modernc.org/sqlite v1.14.6/go.mod h1:yiCvMv3HblGmzENNIaNtFhfaNIwcla4u2JQEwJPzfEc=
|
modernc.org/sqlite v1.14.6/go.mod h1:yiCvMv3HblGmzENNIaNtFhfaNIwcla4u2JQEwJPzfEc=
|
||||||
modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs=
|
modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs=
|
||||||
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
|
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
|
||||||
|
modernc.org/tcl v1.10.0/go.mod h1:WzWapmP/7dHVhFoyPpEaNSVTL8xtewhouN/cqSJ5A2s=
|
||||||
modernc.org/tcl v1.11.0 h1:B/zzEYjINeaki38KcIqdQRQx7W3WE7TkrlTwGnbm2II=
|
modernc.org/tcl v1.11.0 h1:B/zzEYjINeaki38KcIqdQRQx7W3WE7TkrlTwGnbm2II=
|
||||||
modernc.org/tcl v1.11.0/go.mod h1:zsTUpbQ+NxQEjOjCUlImDLPv1sG8Ww0qp66ZvyOxCgw=
|
modernc.org/tcl v1.11.0/go.mod h1:zsTUpbQ+NxQEjOjCUlImDLPv1sG8Ww0qp66ZvyOxCgw=
|
||||||
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
|
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
|
||||||
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
|
modernc.org/z v1.2.21/go.mod h1:uXrObx4pGqXWIMliC5MiKuwAyMrltzwpteOFUP1PWCc=
|
||||||
modernc.org/z v1.3.0 h1:4RWULo1Nvaq5ZBhbLe74u8p6tV4Mmm0ZrPBXYPm/xjM=
|
modernc.org/z v1.3.0 h1:4RWULo1Nvaq5ZBhbLe74u8p6tV4Mmm0ZrPBXYPm/xjM=
|
||||||
modernc.org/z v1.3.0/go.mod h1:+mvgLH814oDjtATDdT3rs84JnUIpkvAF5B8AVkNlE2g=
|
modernc.org/z v1.3.0/go.mod h1:+mvgLH814oDjtATDdT3rs84JnUIpkvAF5B8AVkNlE2g=
|
||||||
|
@ -27,7 +27,7 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
sqlite "git.blender.org/flamenco/pkg/gorm-modernc-sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DB provides the database interface.
|
// DB provides the database interface.
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2013-NOW Jinzhu <wosmvp@gmail.com>
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
@ -1,20 +0,0 @@
|
|||||||
# GORM Sqlite Driver
|
|
||||||
|
|
||||||
This is a clone of the [Gorm SQLite driver](https://github.com/go-gorm/sqlite),
|
|
||||||
adjusted by Sybren Stüvel <sybren@blender.org> to use modernc.org/sqlite instead
|
|
||||||
of the SQLite C-bindings wrapper.
|
|
||||||
|
|
||||||
|
|
||||||
## USAGE
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
"gorm.io/driver/sqlite"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// modernc.org/sqlite
|
|
||||||
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{})
|
|
||||||
```
|
|
||||||
|
|
||||||
Checkout [https://gorm.io](https://gorm.io) for details.
|
|
@ -1,216 +0,0 @@
|
|||||||
package sqlite
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gorm.io/gorm/migrator"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
sqliteSeparator = "`|\"|'|\t"
|
|
||||||
indexRegexp = regexp.MustCompile(fmt.Sprintf("CREATE(?: UNIQUE)? INDEX [%v][\\w\\d]+[%v] ON (.*)$", sqliteSeparator, sqliteSeparator))
|
|
||||||
tableRegexp = regexp.MustCompile(fmt.Sprintf("(?i)(CREATE TABLE [%v]?[\\w\\d]+[%v]?)(?: \\((.*)\\))?", sqliteSeparator, sqliteSeparator))
|
|
||||||
separatorRegexp = regexp.MustCompile(fmt.Sprintf("[%v]", sqliteSeparator))
|
|
||||||
columnsRegexp = regexp.MustCompile(fmt.Sprintf("\\([%v]?([\\w\\d]+)[%v]?(?:,[%v]?([\\w\\d]+)[%v]){0,}\\)", sqliteSeparator, sqliteSeparator, sqliteSeparator, sqliteSeparator))
|
|
||||||
columnRegexp = regexp.MustCompile(fmt.Sprintf("^[%v]?([\\w\\d]+)[%v]?\\s+([\\w\\(\\)\\d]+)(.*)$", sqliteSeparator, sqliteSeparator))
|
|
||||||
defaultValueRegexp = regexp.MustCompile("(?i) DEFAULT \\(?(.+)?\\)?( |COLLATE|GENERATED|$)")
|
|
||||||
)
|
|
||||||
|
|
||||||
type ddl struct {
|
|
||||||
head string
|
|
||||||
fields []string
|
|
||||||
columns []migrator.ColumnType
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDDL(strs ...string) (*ddl, error) {
|
|
||||||
var result ddl
|
|
||||||
for _, str := range strs {
|
|
||||||
if sections := tableRegexp.FindStringSubmatch(str); len(sections) > 0 {
|
|
||||||
var (
|
|
||||||
ddlBody = sections[2]
|
|
||||||
bracketLevel int
|
|
||||||
quote rune
|
|
||||||
buf string
|
|
||||||
)
|
|
||||||
|
|
||||||
result.head = sections[1]
|
|
||||||
|
|
||||||
for idx, c := range []rune(ddlBody) {
|
|
||||||
var next rune = 0
|
|
||||||
if idx+1 < len(ddlBody) {
|
|
||||||
next = []rune(ddlBody)[idx+1]
|
|
||||||
}
|
|
||||||
|
|
||||||
if sc := string(c); separatorRegexp.MatchString(sc) {
|
|
||||||
if c == next {
|
|
||||||
buf += sc // Skip escaped quote
|
|
||||||
idx++
|
|
||||||
} else if quote > 0 {
|
|
||||||
quote = 0
|
|
||||||
} else {
|
|
||||||
quote = c
|
|
||||||
}
|
|
||||||
} else if quote == 0 {
|
|
||||||
if c == '(' {
|
|
||||||
bracketLevel++
|
|
||||||
} else if c == ')' {
|
|
||||||
bracketLevel--
|
|
||||||
} else if bracketLevel == 0 {
|
|
||||||
if c == ',' {
|
|
||||||
result.fields = append(result.fields, strings.TrimSpace(buf))
|
|
||||||
buf = ""
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if bracketLevel < 0 {
|
|
||||||
return nil, errors.New("invalid DDL, unbalanced brackets")
|
|
||||||
}
|
|
||||||
|
|
||||||
buf += string(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
if bracketLevel != 0 {
|
|
||||||
return nil, errors.New("invalid DDL, unbalanced brackets")
|
|
||||||
}
|
|
||||||
|
|
||||||
if buf != "" {
|
|
||||||
result.fields = append(result.fields, strings.TrimSpace(buf))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range result.fields {
|
|
||||||
fUpper := strings.ToUpper(f)
|
|
||||||
if strings.HasPrefix(fUpper, "CHECK") ||
|
|
||||||
strings.HasPrefix(fUpper, "CONSTRAINT") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(fUpper, "PRIMARY KEY") {
|
|
||||||
matches := columnsRegexp.FindStringSubmatch(f)
|
|
||||||
if len(matches) > 1 {
|
|
||||||
for _, name := range matches[1:] {
|
|
||||||
for idx, column := range result.columns {
|
|
||||||
if column.NameValue.String == name {
|
|
||||||
column.PrimaryKeyValue = sql.NullBool{Bool: true, Valid: true}
|
|
||||||
result.columns[idx] = column
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if matches := columnRegexp.FindStringSubmatch(f); len(matches) > 0 {
|
|
||||||
columnType := migrator.ColumnType{
|
|
||||||
NameValue: sql.NullString{String: matches[1], Valid: true},
|
|
||||||
DataTypeValue: sql.NullString{String: matches[2], Valid: true},
|
|
||||||
ColumnTypeValue: sql.NullString{String: matches[2], Valid: true},
|
|
||||||
PrimaryKeyValue: sql.NullBool{Valid: true},
|
|
||||||
UniqueValue: sql.NullBool{Valid: true},
|
|
||||||
NullableValue: sql.NullBool{Valid: true},
|
|
||||||
DefaultValueValue: sql.NullString{Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
matchUpper := strings.ToUpper(matches[3])
|
|
||||||
if strings.Contains(matchUpper, " NOT NULL") {
|
|
||||||
columnType.NullableValue = sql.NullBool{Bool: false, Valid: true}
|
|
||||||
} else if strings.Contains(matchUpper, " NULL") {
|
|
||||||
columnType.NullableValue = sql.NullBool{Bool: true, Valid: true}
|
|
||||||
}
|
|
||||||
if strings.Contains(matchUpper, " UNIQUE") {
|
|
||||||
columnType.UniqueValue = sql.NullBool{Bool: true, Valid: true}
|
|
||||||
}
|
|
||||||
if strings.Contains(matchUpper, " PRIMARY") {
|
|
||||||
columnType.PrimaryKeyValue = sql.NullBool{Bool: true, Valid: true}
|
|
||||||
}
|
|
||||||
if defaultMatches := defaultValueRegexp.FindStringSubmatch(matches[3]); len(defaultMatches) > 1 {
|
|
||||||
columnType.DefaultValueValue = sql.NullString{String: strings.Trim(defaultMatches[1], `"`), Valid: true}
|
|
||||||
}
|
|
||||||
|
|
||||||
result.columns = append(result.columns, columnType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if matches := indexRegexp.FindStringSubmatch(str); len(matches) > 0 {
|
|
||||||
if columns := columnsRegexp.FindStringSubmatch(matches[1]); len(columns) == 1 {
|
|
||||||
for idx, c := range result.columns {
|
|
||||||
if c.NameValue.String == columns[0] {
|
|
||||||
c.UniqueValue = sql.NullBool{Bool: true, Valid: true}
|
|
||||||
result.columns[idx] = c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("invalid DDL")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ddl) compile() string {
|
|
||||||
if len(d.fields) == 0 {
|
|
||||||
return d.head
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%s (%s)", d.head, strings.Join(d.fields, ","))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ddl) addConstraint(name string, sql string) {
|
|
||||||
reg := regexp.MustCompile("^CONSTRAINT [\"`]?" + regexp.QuoteMeta(name) + "[\"` ]")
|
|
||||||
|
|
||||||
for i := 0; i < len(d.fields); i++ {
|
|
||||||
if reg.MatchString(d.fields[i]) {
|
|
||||||
d.fields[i] = sql
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
d.fields = append(d.fields, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ddl) removeConstraint(name string) bool {
|
|
||||||
reg := regexp.MustCompile("^CONSTRAINT [\"`]?" + regexp.QuoteMeta(name) + "[\"` ]")
|
|
||||||
|
|
||||||
for i := 0; i < len(d.fields); i++ {
|
|
||||||
if reg.MatchString(d.fields[i]) {
|
|
||||||
d.fields = append(d.fields[:i], d.fields[i+1:]...)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ddl) hasConstraint(name string) bool {
|
|
||||||
reg := regexp.MustCompile("^CONSTRAINT [\"`]?" + regexp.QuoteMeta(name) + "[\"` ]")
|
|
||||||
|
|
||||||
for _, f := range d.fields {
|
|
||||||
if reg.MatchString(f) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ddl) getColumns() []string {
|
|
||||||
res := []string{}
|
|
||||||
|
|
||||||
for _, f := range d.fields {
|
|
||||||
fUpper := strings.ToUpper(f)
|
|
||||||
if strings.HasPrefix(fUpper, "PRIMARY KEY") ||
|
|
||||||
strings.HasPrefix(fUpper, "CHECK") ||
|
|
||||||
strings.HasPrefix(fUpper, "CONSTRAINT") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
reg := regexp.MustCompile("^[\"`']?([\\w\\d]+)[\"`']?")
|
|
||||||
match := reg.FindStringSubmatch(f)
|
|
||||||
|
|
||||||
if match != nil {
|
|
||||||
res = append(res, "`"+match[1]+"`")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
@ -1,197 +0,0 @@
|
|||||||
package sqlite
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"gorm.io/gorm/migrator"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParseDDL(t *testing.T) {
|
|
||||||
params := []struct {
|
|
||||||
name string
|
|
||||||
sql []string
|
|
||||||
nFields int
|
|
||||||
columns []migrator.ColumnType
|
|
||||||
}{
|
|
||||||
{"with_fk", []string{
|
|
||||||
"CREATE TABLE `notes` (`id` integer NOT NULL,`text` varchar(500) DEFAULT \"hello\",`age` integer DEFAULT 18,`user_id` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_users_notes` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`))",
|
|
||||||
"CREATE UNIQUE INDEX `idx_profiles_refer` ON `profiles`(`text`)",
|
|
||||||
}, 6, []migrator.ColumnType{
|
|
||||||
{NameValue: sql.NullString{String: "id", Valid: true}, DataTypeValue: sql.NullString{String: "integer", Valid: true}, ColumnTypeValue: sql.NullString{String: "integer", Valid: true}, PrimaryKeyValue: sql.NullBool{Bool: true, Valid: true}, NullableValue: sql.NullBool{Valid: true}, UniqueValue: sql.NullBool{Valid: true}, DefaultValueValue: sql.NullString{Valid: true}},
|
|
||||||
{NameValue: sql.NullString{String: "text", Valid: true}, DataTypeValue: sql.NullString{String: "varchar(500)", Valid: true}, ColumnTypeValue: sql.NullString{String: "varchar(500)", Valid: true}, DefaultValueValue: sql.NullString{String: "hello", Valid: true}, NullableValue: sql.NullBool{Valid: true}, UniqueValue: sql.NullBool{Valid: true}, PrimaryKeyValue: sql.NullBool{Valid: true}},
|
|
||||||
{NameValue: sql.NullString{String: "age", Valid: true}, DataTypeValue: sql.NullString{String: "integer", Valid: true}, ColumnTypeValue: sql.NullString{String: "integer", Valid: true}, DefaultValueValue: sql.NullString{String: "18", Valid: true}, NullableValue: sql.NullBool{Valid: true}, UniqueValue: sql.NullBool{Valid: true}, PrimaryKeyValue: sql.NullBool{Valid: true}},
|
|
||||||
{NameValue: sql.NullString{String: "user_id", Valid: true}, DataTypeValue: sql.NullString{String: "integer", Valid: true}, ColumnTypeValue: sql.NullString{String: "integer", Valid: true}, DefaultValueValue: sql.NullString{Valid: true}, NullableValue: sql.NullBool{Valid: true}, UniqueValue: sql.NullBool{Valid: true}, PrimaryKeyValue: sql.NullBool{Valid: true}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{"with_check", []string{"CREATE TABLE Persons (ID int NOT NULL,LastName varchar(255) NOT NULL,FirstName varchar(255),Age int,CHECK (Age>=18),CHECK (FirstName<>'John'))"}, 6, []migrator.ColumnType{
|
|
||||||
{NameValue: sql.NullString{String: "ID", Valid: true}, DataTypeValue: sql.NullString{String: "int", Valid: true}, ColumnTypeValue: sql.NullString{String: "int", Valid: true}, NullableValue: sql.NullBool{Valid: true}, DefaultValueValue: sql.NullString{Valid: true}, UniqueValue: sql.NullBool{Valid: true}, PrimaryKeyValue: sql.NullBool{Valid: true}},
|
|
||||||
{NameValue: sql.NullString{String: "LastName", Valid: true}, DataTypeValue: sql.NullString{String: "varchar(255)", Valid: true}, ColumnTypeValue: sql.NullString{String: "varchar(255)", Valid: true}, NullableValue: sql.NullBool{Bool: false, Valid: true}, DefaultValueValue: sql.NullString{Valid: true}, UniqueValue: sql.NullBool{Valid: true}, PrimaryKeyValue: sql.NullBool{Valid: true}},
|
|
||||||
{NameValue: sql.NullString{String: "FirstName", Valid: true}, DataTypeValue: sql.NullString{String: "varchar(255)", Valid: true}, ColumnTypeValue: sql.NullString{String: "varchar(255)", Valid: true}, DefaultValueValue: sql.NullString{Valid: true}, NullableValue: sql.NullBool{Valid: true}, UniqueValue: sql.NullBool{Valid: true}, PrimaryKeyValue: sql.NullBool{Valid: true}},
|
|
||||||
{NameValue: sql.NullString{String: "Age", Valid: true}, DataTypeValue: sql.NullString{String: "int", Valid: true}, ColumnTypeValue: sql.NullString{String: "int", Valid: true}, DefaultValueValue: sql.NullString{Valid: true}, NullableValue: sql.NullBool{Valid: true}, UniqueValue: sql.NullBool{Valid: true}, PrimaryKeyValue: sql.NullBool{Valid: true}},
|
|
||||||
}},
|
|
||||||
{"lowercase", []string{"create table test (ID int NOT NULL)"}, 1, []migrator.ColumnType{
|
|
||||||
{NameValue: sql.NullString{String: "ID", Valid: true}, DataTypeValue: sql.NullString{String: "int", Valid: true}, ColumnTypeValue: sql.NullString{String: "int", Valid: true}, NullableValue: sql.NullBool{Bool: false, Valid: true}, DefaultValueValue: sql.NullString{Valid: true}, UniqueValue: sql.NullBool{Valid: true}, PrimaryKeyValue: sql.NullBool{Valid: true}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{"no brackets", []string{"create table test"}, 0, nil},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range params {
|
|
||||||
t.Run(p.name, func(t *testing.T) {
|
|
||||||
ddl, err := parseDDL(p.sql...)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, p.sql[0], ddl.compile())
|
|
||||||
assert.Len(t, ddl.fields, p.nFields)
|
|
||||||
assert.Equal(t, ddl.columns, p.columns)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseDDL_error(t *testing.T) {
|
|
||||||
params := []struct {
|
|
||||||
name string
|
|
||||||
sql string
|
|
||||||
}{
|
|
||||||
{"invalid_cmd", "CREATE TABLE"},
|
|
||||||
{"unbalanced_brackets", "CREATE TABLE test (ID int NOT NULL,Name varchar(255)"},
|
|
||||||
{"unbalanced_brackets2", "CREATE TABLE test (ID int NOT NULL,Name varchar(255)))"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range params {
|
|
||||||
t.Run(p.name, func(t *testing.T) {
|
|
||||||
_, err := parseDDL(p.sql)
|
|
||||||
if err == nil {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddConstraint(t *testing.T) {
|
|
||||||
params := []struct {
|
|
||||||
name string
|
|
||||||
fields []string
|
|
||||||
cName string
|
|
||||||
sql string
|
|
||||||
expect []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "add_new",
|
|
||||||
fields: []string{"`id` integer NOT NULL"},
|
|
||||||
cName: "fk_users_notes",
|
|
||||||
sql: "CONSTRAINT `fk_users_notes` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`))",
|
|
||||||
expect: []string{"`id` integer NOT NULL", "CONSTRAINT `fk_users_notes` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`))"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "update",
|
|
||||||
fields: []string{"`id` integer NOT NULL", "CONSTRAINT `fk_users_notes` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`))"},
|
|
||||||
cName: "fk_users_notes",
|
|
||||||
sql: "CONSTRAINT `fk_users_notes` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`)) ON UPDATE CASCADE ON DELETE CASCADE",
|
|
||||||
expect: []string{"`id` integer NOT NULL", "CONSTRAINT `fk_users_notes` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`)) ON UPDATE CASCADE ON DELETE CASCADE"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "add_check",
|
|
||||||
fields: []string{"`id` integer NOT NULL"},
|
|
||||||
cName: "name_checker",
|
|
||||||
sql: "CONSTRAINT `name_checker` CHECK (`name` <> 'jinzhu')",
|
|
||||||
expect: []string{"`id` integer NOT NULL", "CONSTRAINT `name_checker` CHECK (`name` <> 'jinzhu')"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "update_check",
|
|
||||||
fields: []string{"`id` integer NOT NULL", "CONSTRAINT `name_checker` CHECK (`name` <> 'thetadev')"},
|
|
||||||
cName: "name_checker",
|
|
||||||
sql: "CONSTRAINT `name_checker` CHECK (`name` <> 'jinzhu')",
|
|
||||||
expect: []string{"`id` integer NOT NULL", "CONSTRAINT `name_checker` CHECK (`name` <> 'jinzhu')"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range params {
|
|
||||||
t.Run(p.name, func(t *testing.T) {
|
|
||||||
testDDL := ddl{fields: p.fields}
|
|
||||||
|
|
||||||
testDDL.addConstraint(p.cName, p.sql)
|
|
||||||
assert.Equal(t, p.expect, testDDL.fields)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRemoveConstraint(t *testing.T) {
|
|
||||||
params := []struct {
|
|
||||||
name string
|
|
||||||
fields []string
|
|
||||||
cName string
|
|
||||||
success bool
|
|
||||||
expect []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "fk",
|
|
||||||
fields: []string{"`id` integer NOT NULL", "CONSTRAINT `fk_users_notes` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`))"},
|
|
||||||
cName: "fk_users_notes",
|
|
||||||
success: true,
|
|
||||||
expect: []string{"`id` integer NOT NULL"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "check",
|
|
||||||
fields: []string{"CONSTRAINT `name_checker` CHECK (`name` <> 'thetadev')", "`id` integer NOT NULL"},
|
|
||||||
cName: "name_checker",
|
|
||||||
success: true,
|
|
||||||
expect: []string{"`id` integer NOT NULL"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "none",
|
|
||||||
fields: []string{"CONSTRAINT `name_checker` CHECK (`name` <> 'thetadev')", "`id` integer NOT NULL"},
|
|
||||||
cName: "nothing",
|
|
||||||
success: false,
|
|
||||||
expect: []string{"CONSTRAINT `name_checker` CHECK (`name` <> 'thetadev')", "`id` integer NOT NULL"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range params {
|
|
||||||
t.Run(p.name, func(t *testing.T) {
|
|
||||||
testDDL := ddl{fields: p.fields}
|
|
||||||
|
|
||||||
success := testDDL.removeConstraint(p.cName)
|
|
||||||
|
|
||||||
assert.Equal(t, p.success, success)
|
|
||||||
assert.Equal(t, p.expect, testDDL.fields)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetColumns(t *testing.T) {
|
|
||||||
params := []struct {
|
|
||||||
name string
|
|
||||||
ddl string
|
|
||||||
columns []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "with_fk",
|
|
||||||
ddl: "CREATE TABLE `notes` (`id` integer NOT NULL,`text` varchar(500),`user_id` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_users_notes` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`))",
|
|
||||||
columns: []string{"`id`", "`text`", "`user_id`"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "with_check",
|
|
||||||
ddl: "CREATE TABLE Persons (ID int NOT NULL,LastName varchar(255) NOT NULL,FirstName varchar(255),Age int,CHECK (Age>=18),CHECK (FirstName!='John'))",
|
|
||||||
columns: []string{"`ID`", "`LastName`", "`FirstName`", "`Age`"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range params {
|
|
||||||
t.Run(p.name, func(t *testing.T) {
|
|
||||||
testDDL, err := parseDDL(p.ddl)
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
cols := testDDL.getColumns()
|
|
||||||
|
|
||||||
assert.Equal(t, p.columns, cols)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package sqlite
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrConstraintsNotImplemented = errors.New("constraints not implemented on sqlite, consider using DisableForeignKeyConstraintWhenMigrating, more details https://github.com/go-gorm/gorm/wiki/GORM-V2-Release-Note-Draft#all-new-migrator")
|
|
||||||
)
|
|
@ -1,418 +0,0 @@
|
|||||||
package sqlite
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"gorm.io/gorm/clause"
|
|
||||||
"gorm.io/gorm/migrator"
|
|
||||||
"gorm.io/gorm/schema"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Migrator struct {
|
|
||||||
migrator.Migrator
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Migrator) RunWithoutForeignKey(fc func() error) error {
|
|
||||||
var enabled int
|
|
||||||
m.DB.Raw("PRAGMA foreign_keys").Scan(&enabled)
|
|
||||||
if enabled == 1 {
|
|
||||||
m.DB.Exec("PRAGMA foreign_keys = OFF")
|
|
||||||
defer m.DB.Exec("PRAGMA foreign_keys = ON")
|
|
||||||
}
|
|
||||||
|
|
||||||
return fc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Migrator) HasTable(value interface{}) bool {
|
|
||||||
var count int
|
|
||||||
m.Migrator.RunWithValue(value, func(stmt *gorm.Statement) error {
|
|
||||||
return m.DB.Raw("SELECT count(*) FROM sqlite_master WHERE type='table' AND name=?", stmt.Table).Row().Scan(&count)
|
|
||||||
})
|
|
||||||
return count > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Migrator) DropTable(values ...interface{}) error {
|
|
||||||
return m.RunWithoutForeignKey(func() error {
|
|
||||||
values = m.ReorderModels(values, false)
|
|
||||||
tx := m.DB.Session(&gorm.Session{})
|
|
||||||
|
|
||||||
for i := len(values) - 1; i >= 0; i-- {
|
|
||||||
if err := m.RunWithValue(values[i], func(stmt *gorm.Statement) error {
|
|
||||||
return tx.Exec("DROP TABLE IF EXISTS ?", clause.Table{Name: stmt.Table}).Error
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Migrator) GetTables() (tableList []string, err error) {
|
|
||||||
return tableList, m.DB.Raw("SELECT name FROM sqlite_master where type=?", "table").Scan(&tableList).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Migrator) HasColumn(value interface{}, name string) bool {
|
|
||||||
var count int
|
|
||||||
m.Migrator.RunWithValue(value, func(stmt *gorm.Statement) error {
|
|
||||||
if stmt.Schema != nil {
|
|
||||||
if field := stmt.Schema.LookUpField(name); field != nil {
|
|
||||||
name = field.DBName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if name != "" {
|
|
||||||
m.DB.Raw(
|
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type = ? AND tbl_name = ? AND (sql LIKE ? OR sql LIKE ? OR sql LIKE ? OR sql LIKE ? OR sql LIKE ?)",
|
|
||||||
"table", stmt.Table, `%"`+name+`" %`, `%`+name+` %`, "%`"+name+"`%", "%["+name+"]%", "%\t"+name+"\t%",
|
|
||||||
).Row().Scan(&count)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return count > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Migrator) AlterColumn(value interface{}, name string) error {
|
|
||||||
return m.RunWithoutForeignKey(func() error {
|
|
||||||
return m.recreateTable(value, nil, func(rawDDL string, stmt *gorm.Statement) (sql string, sqlArgs []interface{}, err error) {
|
|
||||||
if field := stmt.Schema.LookUpField(name); field != nil {
|
|
||||||
reg, err := regexp.Compile("(`|'|\"| )" + field.DBName + "(`|'|\"| ) .*?,")
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
createSQL := reg.ReplaceAllString(rawDDL, fmt.Sprintf("`%v` ?,", field.DBName))
|
|
||||||
|
|
||||||
return createSQL, []interface{}{m.FullDataTypeOf(field)}, nil
|
|
||||||
}
|
|
||||||
return "", nil, fmt.Errorf("failed to alter field with name %v", name)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ColumnTypes return columnTypes []gorm.ColumnType and execErr error
|
|
||||||
func (m Migrator) ColumnTypes(value interface{}) ([]gorm.ColumnType, error) {
|
|
||||||
columnTypes := make([]gorm.ColumnType, 0)
|
|
||||||
execErr := m.RunWithValue(value, func(stmt *gorm.Statement) (err error) {
|
|
||||||
var (
|
|
||||||
sqls []string
|
|
||||||
sqlDDL *ddl
|
|
||||||
)
|
|
||||||
|
|
||||||
if err := m.DB.Raw("SELECT sql FROM sqlite_master WHERE type IN ? AND tbl_name = ? AND sql IS NOT NULL order by type = ? desc", []string{"table", "index"}, stmt.Table, "table").Scan(&sqls).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if sqlDDL, err = parseDDL(sqls...); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := m.DB.Session(&gorm.Session{}).Table(stmt.Table).Limit(1).Rows()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
err = rows.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
var rawColumnTypes []*sql.ColumnType
|
|
||||||
rawColumnTypes, err = rows.ColumnTypes()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range rawColumnTypes {
|
|
||||||
columnType := migrator.ColumnType{SQLColumnType: c}
|
|
||||||
for _, column := range sqlDDL.columns {
|
|
||||||
if column.NameValue.String == c.Name() {
|
|
||||||
column.SQLColumnType = c
|
|
||||||
columnType = column
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
columnTypes = append(columnTypes, columnType)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
|
|
||||||
return columnTypes, execErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Migrator) DropColumn(value interface{}, name string) error {
|
|
||||||
return m.recreateTable(value, nil, func(rawDDL string, stmt *gorm.Statement) (sql string, sqlArgs []interface{}, err error) {
|
|
||||||
if field := stmt.Schema.LookUpField(name); field != nil {
|
|
||||||
name = field.DBName
|
|
||||||
}
|
|
||||||
|
|
||||||
reg, err := regexp.Compile("(`|'|\"| |\\[)" + name + "(`|'|\"| |\\]) .*?,")
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
createSQL := reg.ReplaceAllString(rawDDL, "")
|
|
||||||
|
|
||||||
return createSQL, nil, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Migrator) CreateConstraint(value interface{}, name string) error {
|
|
||||||
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
|
||||||
constraint, chk, table := m.GuessConstraintAndTable(stmt, name)
|
|
||||||
|
|
||||||
return m.recreateTable(value, &table,
|
|
||||||
func(rawDDL string, stmt *gorm.Statement) (sql string, sqlArgs []interface{}, err error) {
|
|
||||||
var (
|
|
||||||
constraintName string
|
|
||||||
constraintSql string
|
|
||||||
constraintValues []interface{}
|
|
||||||
)
|
|
||||||
|
|
||||||
if constraint != nil {
|
|
||||||
constraintName = constraint.Name
|
|
||||||
constraintSql, constraintValues = buildConstraint(constraint)
|
|
||||||
} else if chk != nil {
|
|
||||||
constraintName = chk.Name
|
|
||||||
constraintSql = "CONSTRAINT ? CHECK (?)"
|
|
||||||
constraintValues = []interface{}{clause.Column{Name: chk.Name}, clause.Expr{SQL: chk.Constraint}}
|
|
||||||
} else {
|
|
||||||
return "", nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
createDDL, err := parseDDL(rawDDL)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
createDDL.addConstraint(constraintName, constraintSql)
|
|
||||||
createSQL := createDDL.compile()
|
|
||||||
|
|
||||||
return createSQL, constraintValues, nil
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Migrator) DropConstraint(value interface{}, name string) error {
|
|
||||||
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
|
||||||
constraint, chk, table := m.GuessConstraintAndTable(stmt, name)
|
|
||||||
if constraint != nil {
|
|
||||||
name = constraint.Name
|
|
||||||
} else if chk != nil {
|
|
||||||
name = chk.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.recreateTable(value, &table,
|
|
||||||
func(rawDDL string, stmt *gorm.Statement) (sql string, sqlArgs []interface{}, err error) {
|
|
||||||
createDDL, err := parseDDL(rawDDL)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
createDDL.removeConstraint(name)
|
|
||||||
createSQL := createDDL.compile()
|
|
||||||
|
|
||||||
return createSQL, nil, nil
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Migrator) HasConstraint(value interface{}, name string) bool {
|
|
||||||
var count int64
|
|
||||||
m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
|
||||||
constraint, chk, table := m.GuessConstraintAndTable(stmt, name)
|
|
||||||
if constraint != nil {
|
|
||||||
name = constraint.Name
|
|
||||||
} else if chk != nil {
|
|
||||||
name = chk.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
m.DB.Raw(
|
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type = ? AND tbl_name = ? AND (sql LIKE ? OR sql LIKE ? OR sql LIKE ? OR sql LIKE ? OR sql LIKE ?)",
|
|
||||||
"table", table, `%CONSTRAINT "`+name+`" %`, `%CONSTRAINT `+name+` %`, "%CONSTRAINT `"+name+"`%", "%CONSTRAINT ["+name+"]%", "%CONSTRAINT \t"+name+"\t%",
|
|
||||||
).Row().Scan(&count)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return count > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Migrator) CurrentDatabase() (name string) {
|
|
||||||
var null interface{}
|
|
||||||
m.DB.Raw("PRAGMA database_list").Row().Scan(&null, &name, &null)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Migrator) BuildIndexOptions(opts []schema.IndexOption, stmt *gorm.Statement) (results []interface{}) {
|
|
||||||
for _, opt := range opts {
|
|
||||||
str := stmt.Quote(opt.DBName)
|
|
||||||
if opt.Expression != "" {
|
|
||||||
str = opt.Expression
|
|
||||||
}
|
|
||||||
|
|
||||||
if opt.Collate != "" {
|
|
||||||
str += " COLLATE " + opt.Collate
|
|
||||||
}
|
|
||||||
|
|
||||||
if opt.Sort != "" {
|
|
||||||
str += " " + opt.Sort
|
|
||||||
}
|
|
||||||
results = append(results, clause.Expr{SQL: str})
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Migrator) CreateIndex(value interface{}, name string) error {
|
|
||||||
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
|
||||||
if idx := stmt.Schema.LookIndex(name); idx != nil {
|
|
||||||
opts := m.BuildIndexOptions(idx.Fields, stmt)
|
|
||||||
values := []interface{}{clause.Column{Name: idx.Name}, clause.Table{Name: stmt.Table}, opts}
|
|
||||||
|
|
||||||
createIndexSQL := "CREATE "
|
|
||||||
if idx.Class != "" {
|
|
||||||
createIndexSQL += idx.Class + " "
|
|
||||||
}
|
|
||||||
createIndexSQL += "INDEX ?"
|
|
||||||
|
|
||||||
if idx.Type != "" {
|
|
||||||
createIndexSQL += " USING " + idx.Type
|
|
||||||
}
|
|
||||||
createIndexSQL += " ON ??"
|
|
||||||
|
|
||||||
if idx.Where != "" {
|
|
||||||
createIndexSQL += " WHERE " + idx.Where
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.DB.Exec(createIndexSQL, values...).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("failed to create index with name %v", name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Migrator) HasIndex(value interface{}, name string) bool {
|
|
||||||
var count int
|
|
||||||
m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
|
||||||
if idx := stmt.Schema.LookIndex(name); idx != nil {
|
|
||||||
name = idx.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
if name != "" {
|
|
||||||
m.DB.Raw(
|
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type = ? AND tbl_name = ? AND name = ?", "index", stmt.Table, name,
|
|
||||||
).Row().Scan(&count)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return count > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Migrator) RenameIndex(value interface{}, oldName, newName string) error {
|
|
||||||
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
|
||||||
var sql string
|
|
||||||
m.DB.Raw("SELECT sql FROM sqlite_master WHERE type = ? AND tbl_name = ? AND name = ?", "index", stmt.Table, oldName).Row().Scan(&sql)
|
|
||||||
if sql != "" {
|
|
||||||
return m.DB.Exec(strings.Replace(sql, oldName, newName, 1)).Error
|
|
||||||
}
|
|
||||||
return fmt.Errorf("failed to find index with name %v", oldName)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Migrator) DropIndex(value interface{}, name string) error {
|
|
||||||
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
|
||||||
if idx := stmt.Schema.LookIndex(name); idx != nil {
|
|
||||||
name = idx.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.DB.Exec("DROP INDEX ?", clause.Column{Name: name}).Error
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildConstraint(constraint *schema.Constraint) (sql string, results []interface{}) {
|
|
||||||
sql = "CONSTRAINT ? FOREIGN KEY ? REFERENCES ??"
|
|
||||||
if constraint.OnDelete != "" {
|
|
||||||
sql += " ON DELETE " + constraint.OnDelete
|
|
||||||
}
|
|
||||||
|
|
||||||
if constraint.OnUpdate != "" {
|
|
||||||
sql += " ON UPDATE " + constraint.OnUpdate
|
|
||||||
}
|
|
||||||
|
|
||||||
var foreignKeys, references []interface{}
|
|
||||||
for _, field := range constraint.ForeignKeys {
|
|
||||||
foreignKeys = append(foreignKeys, clause.Column{Name: field.DBName})
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, field := range constraint.References {
|
|
||||||
references = append(references, clause.Column{Name: field.DBName})
|
|
||||||
}
|
|
||||||
results = append(results, clause.Table{Name: constraint.Name}, foreignKeys, clause.Table{Name: constraint.ReferenceSchema.Table}, references)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Migrator) getRawDDL(table string) (string, error) {
|
|
||||||
var createSQL string
|
|
||||||
m.DB.Raw("SELECT sql FROM sqlite_master WHERE type = ? AND tbl_name = ? AND name = ?", "table", table, table).Row().Scan(&createSQL)
|
|
||||||
|
|
||||||
if m.DB.Error != nil {
|
|
||||||
return "", m.DB.Error
|
|
||||||
}
|
|
||||||
return createSQL, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Migrator) recreateTable(value interface{}, tablePtr *string,
|
|
||||||
getCreateSQL func(rawDDL string, stmt *gorm.Statement) (sql string, sqlArgs []interface{}, err error)) error {
|
|
||||||
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
|
||||||
table := stmt.Table
|
|
||||||
if tablePtr != nil {
|
|
||||||
table = *tablePtr
|
|
||||||
}
|
|
||||||
|
|
||||||
rawDDL, err := m.getRawDDL(table)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
newTableName := table + "__temp"
|
|
||||||
|
|
||||||
createSQL, sqlArgs, err := getCreateSQL(rawDDL, stmt)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if createSQL == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tableReg, err := regexp.Compile(" ('|`|\"| )" + table + "('|`|\"| ) ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
createSQL = tableReg.ReplaceAllString(createSQL, fmt.Sprintf(" `%v` ", newTableName))
|
|
||||||
|
|
||||||
createDDL, err := parseDDL(createSQL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
columns := createDDL.getColumns()
|
|
||||||
|
|
||||||
return m.DB.Transaction(func(tx *gorm.DB) error {
|
|
||||||
if err := tx.Exec(createSQL, sqlArgs...).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
queries := []string{
|
|
||||||
fmt.Sprintf("INSERT INTO `%v`(%v) SELECT %v FROM `%v`", newTableName, strings.Join(columns, ","), strings.Join(columns, ","), table),
|
|
||||||
fmt.Sprintf("DROP TABLE `%v`", table),
|
|
||||||
fmt.Sprintf("ALTER TABLE `%v` RENAME TO `%v`", newTableName, table),
|
|
||||||
}
|
|
||||||
for _, query := range queries {
|
|
||||||
if err := tx.Exec(query).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,219 +0,0 @@
|
|||||||
package sqlite
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gorm.io/gorm/callbacks"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"gorm.io/gorm/clause"
|
|
||||||
"gorm.io/gorm/logger"
|
|
||||||
"gorm.io/gorm/migrator"
|
|
||||||
"gorm.io/gorm/schema"
|
|
||||||
|
|
||||||
_ "modernc.org/sqlite"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DriverName is the default driver name for SQLite.
|
|
||||||
const DriverName = "sqlite"
|
|
||||||
|
|
||||||
type Dialector struct {
|
|
||||||
DriverName string
|
|
||||||
DSN string
|
|
||||||
Conn gorm.ConnPool
|
|
||||||
}
|
|
||||||
|
|
||||||
func Open(dsn string) gorm.Dialector {
|
|
||||||
return &Dialector{DSN: dsn}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dialector Dialector) Name() string {
|
|
||||||
return "sqlite"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dialector Dialector) Initialize(db *gorm.DB) (err error) {
|
|
||||||
if dialector.DriverName == "" {
|
|
||||||
dialector.DriverName = DriverName
|
|
||||||
}
|
|
||||||
|
|
||||||
if dialector.Conn != nil {
|
|
||||||
db.ConnPool = dialector.Conn
|
|
||||||
} else {
|
|
||||||
conn, err := sql.Open(dialector.DriverName, dialector.DSN)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
db.ConnPool = conn
|
|
||||||
}
|
|
||||||
|
|
||||||
var version string
|
|
||||||
if err := db.ConnPool.QueryRowContext(context.Background(), "select sqlite_version()").Scan(&version); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// https://www.sqlite.org/releaselog/3_35_0.html
|
|
||||||
if compareVersion(version, "3.35.0") >= 0 {
|
|
||||||
callbacks.RegisterDefaultCallbacks(db, &callbacks.Config{
|
|
||||||
CreateClauses: []string{"INSERT", "VALUES", "ON CONFLICT", "RETURNING"},
|
|
||||||
UpdateClauses: []string{"UPDATE", "SET", "WHERE", "RETURNING"},
|
|
||||||
DeleteClauses: []string{"DELETE", "FROM", "WHERE", "RETURNING"},
|
|
||||||
LastInsertIDReversed: true,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
callbacks.RegisterDefaultCallbacks(db, &callbacks.Config{
|
|
||||||
LastInsertIDReversed: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range dialector.ClauseBuilders() {
|
|
||||||
db.ClauseBuilders[k] = v
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dialector Dialector) ClauseBuilders() map[string]clause.ClauseBuilder {
|
|
||||||
return map[string]clause.ClauseBuilder{
|
|
||||||
"INSERT": func(c clause.Clause, builder clause.Builder) {
|
|
||||||
if insert, ok := c.Expression.(clause.Insert); ok {
|
|
||||||
if stmt, ok := builder.(*gorm.Statement); ok {
|
|
||||||
stmt.WriteString("INSERT ")
|
|
||||||
if insert.Modifier != "" {
|
|
||||||
stmt.WriteString(insert.Modifier)
|
|
||||||
stmt.WriteByte(' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt.WriteString("INTO ")
|
|
||||||
if insert.Table.Name == "" {
|
|
||||||
stmt.WriteQuoted(stmt.Table)
|
|
||||||
} else {
|
|
||||||
stmt.WriteQuoted(insert.Table)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Build(builder)
|
|
||||||
},
|
|
||||||
"LIMIT": func(c clause.Clause, builder clause.Builder) {
|
|
||||||
if limit, ok := c.Expression.(clause.Limit); ok {
|
|
||||||
if limit.Limit > 0 || limit.Offset > 0 {
|
|
||||||
if limit.Limit <= 0 {
|
|
||||||
limit.Limit = -1
|
|
||||||
}
|
|
||||||
builder.WriteString("LIMIT " + strconv.Itoa(limit.Limit))
|
|
||||||
}
|
|
||||||
if limit.Offset > 0 {
|
|
||||||
builder.WriteString(" OFFSET " + strconv.Itoa(limit.Offset))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"FOR": func(c clause.Clause, builder clause.Builder) {
|
|
||||||
if _, ok := c.Expression.(clause.Locking); ok {
|
|
||||||
// SQLite3 does not support row-level locking.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Build(builder)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dialector Dialector) DefaultValueOf(field *schema.Field) clause.Expression {
|
|
||||||
if field.AutoIncrement {
|
|
||||||
return clause.Expr{SQL: "NULL"}
|
|
||||||
}
|
|
||||||
|
|
||||||
// doesn't work, will raise error
|
|
||||||
return clause.Expr{SQL: "DEFAULT"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dialector Dialector) Migrator(db *gorm.DB) gorm.Migrator {
|
|
||||||
return Migrator{migrator.Migrator{Config: migrator.Config{
|
|
||||||
DB: db,
|
|
||||||
Dialector: dialector,
|
|
||||||
CreateIndexAfterCreateTable: true,
|
|
||||||
}}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dialector Dialector) BindVarTo(writer clause.Writer, stmt *gorm.Statement, v interface{}) {
|
|
||||||
writer.WriteByte('?')
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dialector Dialector) QuoteTo(writer clause.Writer, str string) {
|
|
||||||
writer.WriteByte('`')
|
|
||||||
if strings.Contains(str, ".") {
|
|
||||||
for idx, str := range strings.Split(str, ".") {
|
|
||||||
if idx > 0 {
|
|
||||||
writer.WriteString(".`")
|
|
||||||
}
|
|
||||||
writer.WriteString(str)
|
|
||||||
writer.WriteByte('`')
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
writer.WriteString(str)
|
|
||||||
writer.WriteByte('`')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dialector Dialector) Explain(sql string, vars ...interface{}) string {
|
|
||||||
return logger.ExplainSQL(sql, nil, `"`, vars...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dialector Dialector) DataTypeOf(field *schema.Field) string {
|
|
||||||
switch field.DataType {
|
|
||||||
case schema.Bool:
|
|
||||||
return "numeric"
|
|
||||||
case schema.Int, schema.Uint:
|
|
||||||
if field.AutoIncrement && !field.PrimaryKey {
|
|
||||||
// https://www.sqlite.org/autoinc.html
|
|
||||||
return "integer PRIMARY KEY AUTOINCREMENT"
|
|
||||||
} else {
|
|
||||||
return "integer"
|
|
||||||
}
|
|
||||||
case schema.Float:
|
|
||||||
return "real"
|
|
||||||
case schema.String:
|
|
||||||
return "text"
|
|
||||||
case schema.Time:
|
|
||||||
return "datetime"
|
|
||||||
case schema.Bytes:
|
|
||||||
return "blob"
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(field.DataType)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dialectopr Dialector) SavePoint(tx *gorm.DB, name string) error {
|
|
||||||
tx.Exec("SAVEPOINT " + name)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dialectopr Dialector) RollbackTo(tx *gorm.DB, name string) error {
|
|
||||||
tx.Exec("ROLLBACK TO SAVEPOINT " + name)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func compareVersion(version1, version2 string) int {
|
|
||||||
n, m := len(version1), len(version2)
|
|
||||||
i, j := 0, 0
|
|
||||||
for i < n || j < m {
|
|
||||||
x := 0
|
|
||||||
for ; i < n && version1[i] != '.'; i++ {
|
|
||||||
x = x*10 + int(version1[i]-'0')
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
y := 0
|
|
||||||
for ; j < m && version2[j] != '.'; j++ {
|
|
||||||
y = y*10 + int(version2[j]-'0')
|
|
||||||
}
|
|
||||||
j++
|
|
||||||
if x > y {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if x < y {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
@ -1,81 +0,0 @@
|
|||||||
package sqlite
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
_ "modernc.org/sqlite"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDialector(t *testing.T) {
|
|
||||||
// This is the DSN of the in-memory SQLite database for these tests.
|
|
||||||
const InMemoryDSN = "file:testdatabase?mode=memory&cache=shared"
|
|
||||||
|
|
||||||
rows := []struct {
|
|
||||||
description string
|
|
||||||
dialector *Dialector
|
|
||||||
openSuccess bool
|
|
||||||
query string
|
|
||||||
querySuccess bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
description: "Default driver",
|
|
||||||
dialector: &Dialector{
|
|
||||||
DSN: InMemoryDSN,
|
|
||||||
},
|
|
||||||
openSuccess: true,
|
|
||||||
query: "SELECT 1",
|
|
||||||
querySuccess: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Explicit default driver",
|
|
||||||
dialector: &Dialector{
|
|
||||||
DriverName: DriverName,
|
|
||||||
DSN: InMemoryDSN,
|
|
||||||
},
|
|
||||||
openSuccess: true,
|
|
||||||
query: "SELECT 1",
|
|
||||||
querySuccess: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Bad driver",
|
|
||||||
dialector: &Dialector{
|
|
||||||
DriverName: "not-a-real-driver",
|
|
||||||
DSN: InMemoryDSN,
|
|
||||||
},
|
|
||||||
openSuccess: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for rowIndex, row := range rows {
|
|
||||||
t.Run(fmt.Sprintf("%d/%s", rowIndex, row.description), func(t *testing.T) {
|
|
||||||
db, err := gorm.Open(row.dialector, &gorm.Config{})
|
|
||||||
if !row.openSuccess {
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("Expected Open to fail.")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Expected Open to succeed; got error: %v", err)
|
|
||||||
}
|
|
||||||
if db == nil {
|
|
||||||
t.Errorf("Expected db to be non-nil.")
|
|
||||||
}
|
|
||||||
if row.query != "" {
|
|
||||||
err = db.Exec(row.query).Error
|
|
||||||
if !row.querySuccess {
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("Expected query to fail.")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Expected query to succeed; got error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user