[ref] name things what they are

Signed-off-by: Anton Nesterov <anton@demiurg.io>
This commit is contained in:
Anton Nesterov 2024-08-11 21:49:19 +02:00
parent 25f3e18f4a
commit 43dd4e9234
No known key found for this signature in database
GPG key ID: 59121E8AE2851FB5
33 changed files with 127 additions and 64 deletions

View file

@ -0,0 +1,53 @@
package tests
import (
"fmt"
"testing"
_ "github.com/mattn/go-sqlite3"
"l12.xyz/dal/adapter"
"l12.xyz/dal/builder"
)
func TestBuilderBasic(t *testing.T) {
a := adapter.DBAdapter{Type: "sqlite3"}
db, err := a.Open("file::memory:?cache=shared")
if err != nil {
t.Fatalf("failed to open db: %v", err)
}
defer db.Close()
_, err = db.Exec("CREATE TABLE test (id INTEGER PRIMARY KEY, name BLOB)")
if err != nil {
t.Fatalf("failed to create table: %v", err)
}
insert, values := builder.New(adapter.SQLite{}).In("test t").Insert([]builder.Map{
{"name": "a"},
{"name": 'b'},
}).Sql()
fmt.Println(insert, values)
_, err = db.Exec(insert, values...)
if err != nil {
t.Fatalf("failed to insert data: %v", err)
}
expr, _ := builder.New(adapter.SQLite{}).In("test t").Find(builder.Find{"name": builder.Is{"$in": []interface{}{"a", 'b'}}}).Sql()
fmt.Println(expr)
rows, err := a.Query(adapter.Query{
Db: "file::memory:?cache=shared",
Expression: expr,
})
if err != nil {
t.Fatalf("failed to query data: %v", err)
}
defer rows.Close()
for rows.Next() {
var id int
var name string
err = rows.Scan(&id, &name)
if err != nil {
t.Fatalf("failed to scan row: %v", err)
}
fmt.Printf("id: %d, name: %s\n", id, name)
}
}

View file

@ -2,6 +2,7 @@ module l12.xyz/dal/tests
go 1.22.6 go 1.22.6
require l12.xyz/dal/builder v0.0.0
replace l12.xyz/dal/builder v0.0.0 => ../builder replace l12.xyz/dal/builder v0.0.0 => ../builder
replace l12.xyz/dal/utils v0.0.0 => ../utils replace l12.xyz/dal/utils v0.0.0 => ../utils
@ -9,9 +10,11 @@ replace l12.xyz/dal/utils v0.0.0 => ../utils
require l12.xyz/dal/adapter v0.0.0 require l12.xyz/dal/adapter v0.0.0
replace l12.xyz/dal/adapter v0.0.0 => ../adapter replace l12.xyz/dal/adapter v0.0.0 => ../adapter
replace l12.xyz/dal/filters v0.0.0 => ../filters
require ( require (
github.com/mattn/go-sqlite3 v1.14.22 github.com/mattn/go-sqlite3 v1.14.22
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
l12.xyz/dal/utils v0.0.0 // indirect l12.xyz/dal/utils v0.0.0 // indirect
l12.xyz/dal/filters v0.0.0 // indirect
) )

View file

@ -7,13 +7,13 @@ import (
utils "l12.xyz/dal/utils" utils "l12.xyz/dal/utils"
) )
type SQLiteContext struct { type SQLite struct {
TableName string TableName string
TableAlias string TableAlias string
FieldName string FieldName string
} }
func (c SQLiteContext) New(opts CtxOpts) Context { func (c SQLite) New(opts DialectOpts) Dialect {
tn := opts["TableName"] tn := opts["TableName"]
if tn == "" { if tn == "" {
tn = c.TableName tn = c.TableName
@ -26,18 +26,18 @@ func (c SQLiteContext) New(opts CtxOpts) Context {
if fn == "" { if fn == "" {
fn = c.FieldName fn = c.FieldName
} }
return SQLiteContext{ return SQLite{
TableName: tn, TableName: tn,
TableAlias: ta, TableAlias: ta,
FieldName: fn, FieldName: fn,
} }
} }
func (c SQLiteContext) GetTableName() string { func (c SQLite) GetTableName() string {
return c.TableName return c.TableName
} }
func (c SQLiteContext) GetFieldName() string { func (c SQLite) GetFieldName() string {
if strings.Contains(c.FieldName, ".") { if strings.Contains(c.FieldName, ".") {
return c.FieldName return c.FieldName
} }
@ -47,7 +47,7 @@ func (c SQLiteContext) GetFieldName() string {
return c.FieldName return c.FieldName
} }
func (c SQLiteContext) GetColumnName(key string) string { func (c SQLite) GetColumnName(key string) string {
if strings.Contains(key, ".") { if strings.Contains(key, ".") {
return key return key
} }
@ -57,7 +57,7 @@ func (c SQLiteContext) GetColumnName(key string) string {
return key return key
} }
func (c SQLiteContext) NormalizeValue(value interface{}) interface{} { func (c SQLite) NormalizeValue(value interface{}) interface{} {
str, ok := value.(string) str, ok := value.(string)
if utils.IsSQLFunction(str) { if utils.IsSQLFunction(str) {
return str return str

View file

@ -6,10 +6,10 @@ type Query struct {
Data []interface{} `json:"data"` Data []interface{} `json:"data"`
} }
type CtxOpts map[string]string type DialectOpts map[string]string
type Context interface { type Dialect interface {
New(opts CtxOpts) Context New(opts DialectOpts) Dialect
GetTableName() string GetTableName() string
GetFieldName() string GetFieldName() string
GetColumnName(key string) string GetColumnName(key string) string

View file

@ -9,7 +9,7 @@ type Builder struct {
TableName string TableName string
TableAlias string TableAlias string
Parts SQLParts Parts SQLParts
Ctx Context Dialect Dialect
} }
type SQLParts struct { type SQLParts struct {
@ -28,20 +28,20 @@ type SQLParts struct {
Update UpdateData Update UpdateData
} }
func New(ctx Context) *Builder { func New(dialect Dialect) *Builder {
return &Builder{ return &Builder{
Parts: SQLParts{ Parts: SQLParts{
Operation: "SELECT", Operation: "SELECT",
From: "FROM", From: "FROM",
}, },
Ctx: ctx, Dialect: dialect,
} }
} }
func (b *Builder) In(table string) *Builder { func (b *Builder) In(table string) *Builder {
b.TableName, b.TableAlias = getTableAlias(table) b.TableName, b.TableAlias = getTableAlias(table)
b.Parts.FromExp = table b.Parts.FromExp = table
b.Ctx = b.Ctx.New(CtxOpts{ b.Dialect = b.Dialect.New(DialectOpts{
"TableName": b.TableName, "TableName": b.TableName,
"TableAlias": b.TableAlias, "TableAlias": b.TableAlias,
}) })
@ -50,7 +50,7 @@ func (b *Builder) In(table string) *Builder {
func (b *Builder) Find(query Find) *Builder { func (b *Builder) Find(query Find) *Builder {
b.Parts.FiterExp = covertFind( b.Parts.FiterExp = covertFind(
b.Ctx, b.Dialect,
query, query,
) )
if b.Parts.Operation == "" { if b.Parts.Operation == "" {
@ -79,18 +79,18 @@ func (b *Builder) Fields(fields ...Map) *Builder {
} }
func (b *Builder) Join(joins ...interface{}) *Builder { func (b *Builder) Join(joins ...interface{}) *Builder {
b.Parts.JoinExps = convertJoin(b.Ctx, joins...) b.Parts.JoinExps = convertJoin(b.Dialect, joins...)
return b return b
} }
func (b *Builder) Group(keys ...string) *Builder { func (b *Builder) Group(keys ...string) *Builder {
b.Parts.HavingExp = "HAVING" b.Parts.HavingExp = "HAVING"
b.Parts.GroupExp = convertGroup(b.Ctx, keys) b.Parts.GroupExp = convertGroup(b.Dialect, keys)
return b return b
} }
func (b *Builder) Sort(sort Map) *Builder { func (b *Builder) Sort(sort Map) *Builder {
b.Parts.OrderExp, _ = convertSort(b.Ctx, sort) b.Parts.OrderExp, _ = convertSort(b.Dialect, sort)
return b return b
} }
@ -110,7 +110,7 @@ func (b *Builder) Delete() *Builder {
} }
func (b *Builder) Insert(inserts []Map) *Builder { func (b *Builder) Insert(inserts []Map) *Builder {
insertData, _ := convertInsert(b.Ctx, inserts) insertData, _ := convertInsert(b.Dialect, inserts)
b.Parts = SQLParts{ b.Parts = SQLParts{
Operation: "INSERT INTO", Operation: "INSERT INTO",
Insert: insertData, Insert: insertData,
@ -119,7 +119,7 @@ func (b *Builder) Insert(inserts []Map) *Builder {
} }
func (b *Builder) Set(updates Map) *Builder { func (b *Builder) Set(updates Map) *Builder {
updateData := convertUpdate(b.Ctx, updates) updateData := convertUpdate(b.Dialect, updates)
b.Parts = SQLParts{ b.Parts = SQLParts{
Operation: "UPDATE", Operation: "UPDATE",
Update: updateData, Update: updateData,
@ -133,7 +133,7 @@ func (b *Builder) Update(updates Map) *Builder {
func (b *Builder) OnConflict(fields ...string) *Builder { func (b *Builder) OnConflict(fields ...string) *Builder {
if b.Parts.Operation == "UPDATE" { if b.Parts.Operation == "UPDATE" {
b.Parts.Update.Upsert = convertConflict(b.Ctx, fields...) b.Parts.Update.Upsert = convertConflict(b.Dialect, fields...)
b.Parts.Update.UpsertExp = "DO NOTHING" b.Parts.Update.UpsertExp = "DO NOTHING"
} else { } else {
panic("OnConflict is only available for UPDATE operation") panic("OnConflict is only available for UPDATE operation")

View file

@ -7,7 +7,7 @@ import (
utils "l12.xyz/dal/utils" utils "l12.xyz/dal/utils"
) )
func convertConflict(ctx Context, fields ...string) string { func convertConflict(ctx Dialect, fields ...string) string {
keys := utils.Map(fields, ctx.GetColumnName) keys := utils.Map(fields, ctx.GetColumnName)
return fmt.Sprintf("ON CONFLICT (%s)", strings.Join(keys, ",")) return fmt.Sprintf("ON CONFLICT (%s)", strings.Join(keys, ","))
} }

View file

@ -7,11 +7,11 @@ import (
filters "l12.xyz/dal/filters" filters "l12.xyz/dal/filters"
) )
func covertFind(ctx Context, find Find) string { func covertFind(ctx Dialect, find Find) string {
return covert_find(ctx, find, "") return covert_find(ctx, find, "")
} }
func covert_find(ctx Context, find Find, join string) string { func covert_find(ctx Dialect, find Find, join string) string {
if join == "" { if join == "" {
join = " AND " join = " AND "
} }
@ -29,7 +29,7 @@ func covert_find(ctx Context, find Find, join string) string {
expressions = append(expressions, fmt.Sprintf("(%s)", v)) expressions = append(expressions, fmt.Sprintf("(%s)", v))
continue continue
} }
context := ctx.New(CtxOpts{ context := ctx.New(DialectOpts{
"FieldName": key, "FieldName": key,
}) })
values, _ := filters.Convert(context, value) values, _ := filters.Convert(context, value)

View file

@ -7,7 +7,7 @@ import (
"l12.xyz/dal/utils" "l12.xyz/dal/utils"
) )
func convertGroup(ctx Context, keys []string) string { func convertGroup(ctx Dialect, keys []string) string {
set := utils.Map(keys, ctx.GetColumnName) set := utils.Map(keys, ctx.GetColumnName)
return fmt.Sprintf("GROUP BY %s", strings.Join(set, ", ")) return fmt.Sprintf("GROUP BY %s", strings.Join(set, ", "))
} }

View file

@ -10,27 +10,33 @@ type InsertData struct {
Values []interface{} Values []interface{}
} }
func convertInsert(ctx Context, inserts []Map) (InsertData, error) { func convertInsert(ctx Dialect, inserts []Map) (InsertData, error) {
keys := aggregateSortedKeys(inserts) keys := aggregateSortedKeys(inserts)
placeholder := make([]string, 0) posEnum := make([]string, 0)
for range keys { for range keys {
placeholder = append(placeholder, "?") posEnum = append(posEnum, "?")
} }
placeholder := strings.Join(posEnum, ",")
positional := []string{}
values := make([]interface{}, 0) values := make([]interface{}, 0)
for _, insert := range inserts { for _, insert := range inserts {
vals := make([]interface{}, 0) vals := make([]interface{}, 0)
for _, key := range keys { for _, key := range keys {
vals = append(vals, insert[key]) vals = append(vals, insert[key])
} }
values = append(values, vals) values = append(values, vals...)
positional = append(
positional,
fmt.Sprintf("(%s)", placeholder),
)
} }
sfmt := fmt.Sprintf( sfmt := fmt.Sprintf(
"INSERT INTO %s (%s) VALUES (%s)", "INSERT INTO %s (%s) VALUES %s",
ctx.GetTableName(), ctx.GetTableName(),
strings.Join(keys, ","), strings.Join(keys, ","),
strings.Join(placeholder, ","), strings.Join(positional, ","),
) )
return InsertData{ return InsertData{
Statement: sfmt, Statement: sfmt,

View file

@ -1,6 +1,7 @@
package builder package builder
import ( import (
"fmt"
"testing" "testing"
) )
@ -15,12 +16,12 @@ func TestConvertInsert(t *testing.T) {
} }
result, _ := convertInsert(ctx, insert) result, _ := convertInsert(ctx, insert)
if result.Statement != `INSERT INTO test (a,b,c) VALUES (?,?,?)` { if result.Statement != `INSERT INTO test (a,b,c) VALUES (?,?,?),(?,?,?)` {
t.Errorf(`Expected "INSERT INTO test (a,b,c) VALUES (?,?,?)", got %s`, result.Statement) t.Errorf(`Expected "INSERT INTO test (a,b,c) VALUES (?,?,?),(?,?,?)", got %s`, result.Statement)
} }
// for _, r := range result.Values { for _, r := range result.Values {
// fmt.Println(r) fmt.Println(r)
// } }
} }

View file

@ -11,7 +11,7 @@ type Join struct {
As string `json:"$as"` As string `json:"$as"`
} }
func (j Join) Convert(ctx Context) string { func (j Join) Convert(ctx Dialect) string {
if j.For == "" { if j.For == "" {
return "" return ""
} }
@ -23,7 +23,7 @@ func (j Join) Convert(ctx Context) string {
return as + fmt.Sprintf("JOIN %s ON %s", j.For, filter) return as + fmt.Sprintf("JOIN %s ON %s", j.For, filter)
} }
func convertJoin(ctx Context, joins ...interface{}) []string { func convertJoin(ctx Dialect, joins ...interface{}) []string {
var result []string var result []string
for _, join := range joins { for _, join := range joins {
jstr, ok := join.(string) jstr, ok := join.(string)

View file

@ -6,7 +6,7 @@ import (
adapter "l12.xyz/dal/adapter" adapter "l12.xyz/dal/adapter"
) )
type SQLiteContext = adapter.SQLiteContext type SQLiteContext = adapter.SQLite
func TestJoin(t *testing.T) { func TestJoin(t *testing.T) {
j := Join{ j := Join{

View file

@ -5,7 +5,7 @@ import (
"strings" "strings"
) )
func convertSort(ctx Context, sort Map) (string, error) { func convertSort(ctx Dialect, sort Map) (string, error) {
if sort == nil { if sort == nil {
return "", nil return "", nil
} }

View file

@ -12,7 +12,7 @@ type UpdateData struct {
Values []interface{} Values []interface{}
} }
func convertUpdate(ctx Context, updates Map) UpdateData { func convertUpdate(ctx Dialect, updates Map) UpdateData {
keys := aggregateSortedKeys([]Map{updates}) keys := aggregateSortedKeys([]Map{updates})
set := make([]string, 0) set := make([]string, 0)
values := make([]interface{}, 0) values := make([]interface{}, 0)

View file

@ -11,5 +11,5 @@ type Find = filters.Find
type Query = filters.Find type Query = filters.Find
type Filter = filters.Filter type Filter = filters.Filter
type Is = filters.Filter type Is = filters.Filter
type Context = adapter.Context type Dialect = adapter.Dialect
type CtxOpts = adapter.CtxOpts type DialectOpts = adapter.DialectOpts

View file

@ -9,7 +9,7 @@ type And struct {
And []string `json:"$and"` And []string `json:"$and"`
} }
func (f And) ToSQLPart(ctx Context) string { func (f And) ToSQLPart(ctx Dialect) string {
if f.And == nil { if f.And == nil {
return "" return ""
} }

View file

@ -14,7 +14,7 @@ func (f Between) FromJSON(data interface{}) IFilter {
return FromJson[Between](data) return FromJson[Between](data)
} }
func (f Between) ToSQLPart(ctx Context) string { func (f Between) ToSQLPart(ctx Dialect) string {
if f.Between == nil { if f.Between == nil {
return "" return ""
} }

View file

@ -12,7 +12,7 @@ func (f Eq) FromJSON(data interface{}) IFilter {
return FromJson[Eq](data) return FromJson[Eq](data)
} }
func (f Eq) ToSQLPart(ctx Context) string { func (f Eq) ToSQLPart(ctx Dialect) string {
if f.Eq == nil { if f.Eq == nil {
return "" return ""
} }

View file

@ -10,7 +10,7 @@ func (f Glob) FromJSON(data interface{}) IFilter {
return FromJson[Glob](data) return FromJson[Glob](data)
} }
func (f Glob) ToSQLPart(ctx Context) string { func (f Glob) ToSQLPart(ctx Dialect) string {
if f.Glob == nil { if f.Glob == nil {
return "" return ""
} }

View file

@ -12,7 +12,7 @@ func (f Gt) FromJSON(data interface{}) IFilter {
return FromJson[Gt](data) return FromJson[Gt](data)
} }
func (f Gt) ToSQLPart(ctx Context) string { func (f Gt) ToSQLPart(ctx Dialect) string {
if f.Gt == nil { if f.Gt == nil {
return "" return ""
} }

View file

@ -12,7 +12,7 @@ func (f Gte) FromJSON(data interface{}) IFilter {
return FromJson[Gte](data) return FromJson[Gte](data)
} }
func (f Gte) ToSQLPart(ctx Context) string { func (f Gte) ToSQLPart(ctx Dialect) string {
if f.Gte == nil { if f.Gte == nil {
return "" return ""
} }

View file

@ -15,7 +15,7 @@ func (f In) FromJSON(data interface{}) IFilter {
return FromJson[In](data) return FromJson[In](data)
} }
func (f In) ToSQLPart(ctx Context) string { func (f In) ToSQLPart(ctx Dialect) string {
if f.In == nil { if f.In == nil {
return "" return ""
} }

View file

@ -10,7 +10,7 @@ func (f Like) FromJSON(data interface{}) IFilter {
return FromJson[Like](data) return FromJson[Like](data)
} }
func (f Like) ToSQLPart(ctx Context) string { func (f Like) ToSQLPart(ctx Dialect) string {
if f.Like == nil { if f.Like == nil {
return "" return ""
} }

View file

@ -12,7 +12,7 @@ func (f Lt) FromJSON(data interface{}) IFilter {
return FromJson[Lt](data) return FromJson[Lt](data)
} }
func (f Lt) ToSQLPart(ctx Context) string { func (f Lt) ToSQLPart(ctx Dialect) string {
if f.Lt == nil { if f.Lt == nil {
return "" return ""
} }

View file

@ -12,7 +12,7 @@ func (f Lte) FromJSON(data interface{}) IFilter {
return FromJson[Lte](data) return FromJson[Lte](data)
} }
func (f Lte) ToSQLPart(ctx Context) string { func (f Lte) ToSQLPart(ctx Dialect) string {
if f.Lte == nil { if f.Lte == nil {
return "" return ""
} }

View file

@ -10,7 +10,7 @@ func (f Ne) FromJSON(data interface{}) IFilter {
return FromJson[Ne](data) return FromJson[Ne](data)
} }
func (f Ne) ToSQLPart(ctx Context) string { func (f Ne) ToSQLPart(ctx Dialect) string {
if f.Ne == nil { if f.Ne == nil {
return "" return ""
} }

View file

@ -14,7 +14,7 @@ func (f NotBetween) FromJSON(data interface{}) IFilter {
return FromJson[NotBetween](data) return FromJson[NotBetween](data)
} }
func (f NotBetween) ToSQLPart(ctx Context) string { func (f NotBetween) ToSQLPart(ctx Dialect) string {
if f.NotBetween == nil { if f.NotBetween == nil {
return "" return ""
} }

View file

@ -15,7 +15,7 @@ func (f NotIn) FromJSON(data interface{}) IFilter {
return FromJson[NotIn](data) return FromJson[NotIn](data)
} }
func (f NotIn) ToSQLPart(ctx Context) string { func (f NotIn) ToSQLPart(ctx Dialect) string {
if f.NotIn == nil { if f.NotIn == nil {
return "" return ""
} }

View file

@ -10,7 +10,7 @@ func (f NotLike) FromJSON(data interface{}) IFilter {
return FromJson[NotLike](data) return FromJson[NotLike](data)
} }
func (f NotLike) ToSQLPart(ctx Context) string { func (f NotLike) ToSQLPart(ctx Dialect) string {
if f.NotLike == nil { if f.NotLike == nil {
return "" return ""
} }

View file

@ -9,7 +9,7 @@ type Or struct {
Or []string `json:"$or"` Or []string `json:"$or"`
} }
func (f Or) ToSQLPart(ctx Context) string { func (f Or) ToSQLPart(ctx Dialect) string {
if f.Or == nil { if f.Or == nil {
return "" return ""
} }

View file

@ -22,7 +22,7 @@ var FilterRegistry = map[string]IFilter{
"NotLike": &NotLike{}, "NotLike": &NotLike{},
} }
func Convert(ctx Context, data interface{}) (string, error) { func Convert(ctx Dialect, data interface{}) (string, error) {
for _, impl := range FilterRegistry { for _, impl := range FilterRegistry {
filter := impl.FromJSON(data) filter := impl.FromJSON(data)
if reflect.DeepEqual(impl, filter) { if reflect.DeepEqual(impl, filter) {

View file

@ -2,11 +2,11 @@ package filters
import "l12.xyz/dal/adapter" import "l12.xyz/dal/adapter"
type CtxOpts = adapter.CtxOpts type DialectOpts = adapter.DialectOpts
type Context = adapter.Context type Dialect = adapter.Dialect
type IFilter interface { type IFilter interface {
ToSQLPart(ctx Context) string ToSQLPart(ctx Dialect) string
FromJSON(interface{}) IFilter FromJSON(interface{}) IFilter
} }

View file

@ -6,7 +6,7 @@ import (
adapter "l12.xyz/dal/adapter" adapter "l12.xyz/dal/adapter"
) )
type SQLiteContext = adapter.SQLiteContext type SQLiteContext = adapter.SQLite
func TestEq(t *testing.T) { func TestEq(t *testing.T) {
ctx := SQLiteContext{ ctx := SQLiteContext{