187 lines
6.7 KiB
Go
187 lines
6.7 KiB
Go
package core
|
|
|
|
import (
|
|
"strings"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
|
|
"github.com/pocketbase/dbx"
|
|
)
|
|
|
|
var _ dbx.Builder = (*dualDBBuilder)(nil)
|
|
|
|
// note: expects both builder to use the same driver
|
|
type dualDBBuilder struct {
|
|
concurrentDB dbx.Builder
|
|
nonconcurrentDB dbx.Builder
|
|
}
|
|
|
|
// Select implements the [dbx.Builder.Select] interface method.
|
|
func (b *dualDBBuilder) Select(cols ...string) *dbx.SelectQuery {
|
|
return b.concurrentDB.Select(cols...)
|
|
}
|
|
|
|
// Model implements the [dbx.Builder.Model] interface method.
|
|
func (b *dualDBBuilder) Model(data interface{}) *dbx.ModelQuery {
|
|
return b.nonconcurrentDB.Model(data)
|
|
}
|
|
|
|
// GeneratePlaceholder implements the [dbx.Builder.GeneratePlaceholder] interface method.
|
|
func (b *dualDBBuilder) GeneratePlaceholder(i int) string {
|
|
return b.concurrentDB.GeneratePlaceholder(i)
|
|
}
|
|
|
|
// Quote implements the [dbx.Builder.Quote] interface method.
|
|
func (b *dualDBBuilder) Quote(str string) string {
|
|
return b.concurrentDB.Quote(str)
|
|
}
|
|
|
|
// QuoteSimpleTableName implements the [dbx.Builder.QuoteSimpleTableName] interface method.
|
|
func (b *dualDBBuilder) QuoteSimpleTableName(table string) string {
|
|
return b.concurrentDB.QuoteSimpleTableName(table)
|
|
}
|
|
|
|
// QuoteSimpleColumnName implements the [dbx.Builder.QuoteSimpleColumnName] interface method.
|
|
func (b *dualDBBuilder) QuoteSimpleColumnName(col string) string {
|
|
return b.concurrentDB.QuoteSimpleColumnName(col)
|
|
}
|
|
|
|
// QueryBuilder implements the [dbx.Builder.QueryBuilder] interface method.
|
|
func (b *dualDBBuilder) QueryBuilder() dbx.QueryBuilder {
|
|
return b.concurrentDB.QueryBuilder()
|
|
}
|
|
|
|
// Insert implements the [dbx.Builder.Insert] interface method.
|
|
func (b *dualDBBuilder) Insert(table string, cols dbx.Params) *dbx.Query {
|
|
return b.nonconcurrentDB.Insert(table, cols)
|
|
}
|
|
|
|
// Upsert implements the [dbx.Builder.Upsert] interface method.
|
|
func (b *dualDBBuilder) Upsert(table string, cols dbx.Params, constraints ...string) *dbx.Query {
|
|
return b.nonconcurrentDB.Upsert(table, cols, constraints...)
|
|
}
|
|
|
|
// Update implements the [dbx.Builder.Update] interface method.
|
|
func (b *dualDBBuilder) Update(table string, cols dbx.Params, where dbx.Expression) *dbx.Query {
|
|
return b.nonconcurrentDB.Update(table, cols, where)
|
|
}
|
|
|
|
// Delete implements the [dbx.Builder.Delete] interface method.
|
|
func (b *dualDBBuilder) Delete(table string, where dbx.Expression) *dbx.Query {
|
|
return b.nonconcurrentDB.Delete(table, where)
|
|
}
|
|
|
|
// CreateTable implements the [dbx.Builder.CreateTable] interface method.
|
|
func (b *dualDBBuilder) CreateTable(table string, cols map[string]string, options ...string) *dbx.Query {
|
|
return b.nonconcurrentDB.CreateTable(table, cols, options...)
|
|
}
|
|
|
|
// RenameTable implements the [dbx.Builder.RenameTable] interface method.
|
|
func (b *dualDBBuilder) RenameTable(oldName, newName string) *dbx.Query {
|
|
return b.nonconcurrentDB.RenameTable(oldName, newName)
|
|
}
|
|
|
|
// DropTable implements the [dbx.Builder.DropTable] interface method.
|
|
func (b *dualDBBuilder) DropTable(table string) *dbx.Query {
|
|
return b.nonconcurrentDB.DropTable(table)
|
|
}
|
|
|
|
// TruncateTable implements the [dbx.Builder.TruncateTable] interface method.
|
|
func (b *dualDBBuilder) TruncateTable(table string) *dbx.Query {
|
|
return b.nonconcurrentDB.TruncateTable(table)
|
|
}
|
|
|
|
// AddColumn implements the [dbx.Builder.AddColumn] interface method.
|
|
func (b *dualDBBuilder) AddColumn(table, col, typ string) *dbx.Query {
|
|
return b.nonconcurrentDB.AddColumn(table, col, typ)
|
|
}
|
|
|
|
// DropColumn implements the [dbx.Builder.DropColumn] interface method.
|
|
func (b *dualDBBuilder) DropColumn(table, col string) *dbx.Query {
|
|
return b.nonconcurrentDB.DropColumn(table, col)
|
|
}
|
|
|
|
// RenameColumn implements the [dbx.Builder.RenameColumn] interface method.
|
|
func (b *dualDBBuilder) RenameColumn(table, oldName, newName string) *dbx.Query {
|
|
return b.nonconcurrentDB.RenameColumn(table, oldName, newName)
|
|
}
|
|
|
|
// AlterColumn implements the [dbx.Builder.AlterColumn] interface method.
|
|
func (b *dualDBBuilder) AlterColumn(table, col, typ string) *dbx.Query {
|
|
return b.nonconcurrentDB.AlterColumn(table, col, typ)
|
|
}
|
|
|
|
// AddPrimaryKey implements the [dbx.Builder.AddPrimaryKey] interface method.
|
|
func (b *dualDBBuilder) AddPrimaryKey(table, name string, cols ...string) *dbx.Query {
|
|
return b.nonconcurrentDB.AddPrimaryKey(table, name, cols...)
|
|
}
|
|
|
|
// DropPrimaryKey implements the [dbx.Builder.DropPrimaryKey] interface method.
|
|
func (b *dualDBBuilder) DropPrimaryKey(table, name string) *dbx.Query {
|
|
return b.nonconcurrentDB.DropPrimaryKey(table, name)
|
|
}
|
|
|
|
// AddForeignKey implements the [dbx.Builder.AddForeignKey] interface method.
|
|
func (b *dualDBBuilder) AddForeignKey(table, name string, cols, refCols []string, refTable string, options ...string) *dbx.Query {
|
|
return b.nonconcurrentDB.AddForeignKey(table, name, cols, refCols, refTable, options...)
|
|
}
|
|
|
|
// DropForeignKey implements the [dbx.Builder.DropForeignKey] interface method.
|
|
func (b *dualDBBuilder) DropForeignKey(table, name string) *dbx.Query {
|
|
return b.nonconcurrentDB.DropForeignKey(table, name)
|
|
}
|
|
|
|
// CreateIndex implements the [dbx.Builder.CreateIndex] interface method.
|
|
func (b *dualDBBuilder) CreateIndex(table, name string, cols ...string) *dbx.Query {
|
|
return b.nonconcurrentDB.CreateIndex(table, name, cols...)
|
|
}
|
|
|
|
// CreateUniqueIndex implements the [dbx.Builder.CreateUniqueIndex] interface method.
|
|
func (b *dualDBBuilder) CreateUniqueIndex(table, name string, cols ...string) *dbx.Query {
|
|
return b.nonconcurrentDB.CreateUniqueIndex(table, name, cols...)
|
|
}
|
|
|
|
// DropIndex implements the [dbx.Builder.DropIndex] interface method.
|
|
func (b *dualDBBuilder) DropIndex(table, name string) *dbx.Query {
|
|
return b.nonconcurrentDB.DropIndex(table, name)
|
|
}
|
|
|
|
// NewQuery implements the [dbx.Builder.NewQuery] interface method by
|
|
// routing the SELECT queries to the concurrent builder instance.
|
|
func (b *dualDBBuilder) NewQuery(str string) *dbx.Query {
|
|
// note: technically INSERT/UPDATE/DELETE could also have CTE but since
|
|
// it is rare for now this scase is ignored to avoid unnecessary complicating the checks
|
|
trimmed := trimLeftSpaces(str)
|
|
if hasPrefixFold(trimmed, "SELECT") || hasPrefixFold(trimmed, "WITH") {
|
|
return b.concurrentDB.NewQuery(str)
|
|
}
|
|
|
|
return b.nonconcurrentDB.NewQuery(str)
|
|
}
|
|
|
|
var asciiSpace = [256]uint8{'\t': 1, '\n': 1, '\v': 1, '\f': 1, '\r': 1, ' ': 1}
|
|
|
|
// note: similar to strings.Space() but without the right trim because it is not needed in our case
|
|
func trimLeftSpaces(str string) string {
|
|
start := 0
|
|
for ; start < len(str); start++ {
|
|
c := str[start]
|
|
if c >= utf8.RuneSelf {
|
|
return strings.TrimLeftFunc(str[start:], unicode.IsSpace)
|
|
}
|
|
if asciiSpace[c] == 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
return str[start:]
|
|
}
|
|
|
|
// note: the prefix is expected to be ASCII
|
|
func hasPrefixFold(str, prefix string) bool {
|
|
if len(str) < len(prefix) {
|
|
return false
|
|
}
|
|
|
|
return strings.EqualFold(str[:len(prefix)], prefix)
|
|
}
|