Files
db/dialect/sqlite3/sqlite3_dialect_test.go
T
what 21b80bdea4 feat: 完善扫描器、exec 及 schema 相关功能
- exec/scanner: 用 *interface{} 替换 **json.RawMessage 扫描目标,兼容 DuckDB 返回 map[string]interface{} 的场景;新增 toJSONRawMessage 转换函数
- exec/scanner: ScanVal 支持结构体指针,通过 JSON 中间层转换(DuckDB STRUCT 列)
- exec/scanner: 将 *sql.RawBytes 和 *[]byte 的处理从 ScanValContext 移入 scanner.ScanVal
- exec/query_executor: 简化 ScanValContext,移除私有 scan 方法
- exec: 补充 scanner 级别 ScanVal 测试用例
- internal/util/reflect: 重写 SafeSetVarValue,修复非指针 src 及 nil 指针字段的 panic
- internal/util/column_map: 恢复非匿名带标签结构体字段的展开逻辑
- schema: 新增 vector 列类型支持
- engine: 补充 DuckDB 相关配置
- dialect/sqlite3/vtab: 完善虚拟表适配器
- 各方言测试改用 sqlmock 虚拟连接
2026-05-20 17:52:28 +08:00

159 lines
7.4 KiB
Go

package sqlite3_test
import (
"regexp"
"testing"
dbv2 "git.fsdpf.net/go/db"
"git.fsdpf.net/go/db/exp"
"github.com/stretchr/testify/suite"
)
type (
sqlite3DialectSuite struct {
suite.Suite
}
sqlTestCase struct {
ds exp.SQLExpression
sql string
err string
isPrepared bool
args []interface{}
}
)
func (sds *sqlite3DialectSuite) GetDs(table string) *dbv2.SelectDataset {
return dbv2.Dialect("sqlite3").From(table)
}
func (sds *sqlite3DialectSuite) assertSQL(cases ...sqlTestCase) {
for i, c := range cases {
actualSQL, actualArgs, err := c.ds.ToSQL()
if c.err == "" {
sds.NoError(err, "test case %d failed", i)
} else {
sds.EqualError(err, c.err, "test case %d failed", i)
}
sds.Equal(c.sql, actualSQL, "test case %d failed", i)
if c.isPrepared && c.args != nil || len(c.args) > 0 {
sds.Equal(c.args, actualArgs, "test case %d failed", i)
} else {
sds.Empty(actualArgs, "test case %d failed", i)
}
}
}
func (sds *sqlite3DialectSuite) TestIdentifiers() {
ds := sds.GetDs("test")
sds.assertSQL(
sqlTestCase{ds: ds.Select(
"a",
dbv2.I("a.b.c"),
dbv2.I("c.d"),
dbv2.C("test").As("test"),
), sql: "SELECT `a`, `a`.`b`.`c`, `c`.`d`, `test` AS `test` FROM `test`"},
)
}
func (sds *sqlite3DialectSuite) TestUpdateSQL_multipleTables() {
ds := sds.GetDs("test").Update()
sds.assertSQL(
sqlTestCase{
ds: ds.
Set(dbv2.Record{"foo": "bar"}).
From("test_2").
Where(dbv2.I("test.id").Eq(dbv2.I("test_2.test_id"))),
err: "db: sqlite3 dialect does not support multiple tables in UPDATE",
},
)
}
func (sds *sqlite3DialectSuite) TestCompoundExpressions() {
ds1 := sds.GetDs("test").Select("a")
ds2 := sds.GetDs("test2").Select("b")
sds.assertSQL(
sqlTestCase{ds: ds1.Union(ds2), sql: "SELECT `a` FROM `test` UNION SELECT `b` FROM `test2`"},
sqlTestCase{ds: ds1.UnionAll(ds2), sql: "SELECT `a` FROM `test` UNION ALL SELECT `b` FROM `test2`"},
sqlTestCase{ds: ds1.Intersect(ds2), sql: "SELECT `a` FROM `test` INTERSECT SELECT `b` FROM `test2`"},
)
}
func (sds *sqlite3DialectSuite) TestLiteralString() {
ds := sds.GetDs("test")
sds.assertSQL(
sqlTestCase{ds: ds.Where(dbv2.C("a").Eq("test")), sql: "SELECT * FROM `test` WHERE (`a` = 'test')"},
sqlTestCase{ds: ds.Where(dbv2.C("a").Eq("test'test")), sql: "SELECT * FROM `test` WHERE (`a` = 'test''test')"},
sqlTestCase{ds: ds.Where(dbv2.C("a").Eq(`test"test`)), sql: "SELECT * FROM `test` WHERE (`a` = 'test\"test')"},
sqlTestCase{ds: ds.Where(dbv2.C("a").Eq(`test\test`)), sql: "SELECT * FROM `test` WHERE (`a` = 'test\\test')"},
sqlTestCase{ds: ds.Where(dbv2.C("a").Eq("test\ntest")), sql: "SELECT * FROM `test` WHERE (`a` = 'test\ntest')"},
sqlTestCase{ds: ds.Where(dbv2.C("a").Eq("test\rtest")), sql: "SELECT * FROM `test` WHERE (`a` = 'test\rtest')"},
sqlTestCase{ds: ds.Where(dbv2.C("a").Eq("test\x00test")), sql: "SELECT * FROM `test` WHERE (`a` = 'test\x00test')"},
sqlTestCase{ds: ds.Where(dbv2.C("a").Eq("test\x1atest")), sql: "SELECT * FROM `test` WHERE (`a` = 'test\x1atest')"},
)
}
func (sds *sqlite3DialectSuite) TestLiteralBytes() {
ds := sds.GetDs("test")
sds.assertSQL(
sqlTestCase{ds: ds.Where(dbv2.C("a").Eq([]byte("test"))), sql: "SELECT * FROM `test` WHERE (`a` = 'test')"},
sqlTestCase{ds: ds.Where(dbv2.C("a").Eq([]byte("test'test"))), sql: "SELECT * FROM `test` WHERE (`a` = 'test''test')"},
sqlTestCase{ds: ds.Where(dbv2.C("a").Eq([]byte(`test"test`))), sql: "SELECT * FROM `test` WHERE (`a` = 'test\"test')"},
sqlTestCase{ds: ds.Where(dbv2.C("a").Eq([]byte(`test\test`))), sql: "SELECT * FROM `test` WHERE (`a` = 'test\\test')"},
sqlTestCase{ds: ds.Where(dbv2.C("a").Eq([]byte("test\ntest"))), sql: "SELECT * FROM `test` WHERE (`a` = 'test\ntest')"},
sqlTestCase{ds: ds.Where(dbv2.C("a").Eq([]byte("test\rtest"))), sql: "SELECT * FROM `test` WHERE (`a` = 'test\rtest')"},
sqlTestCase{ds: ds.Where(dbv2.C("a").Eq([]byte("test\x00test"))), sql: "SELECT * FROM `test` WHERE (`a` = 'test\x00test')"},
sqlTestCase{ds: ds.Where(dbv2.C("a").Eq([]byte("test\x1atest"))), sql: "SELECT * FROM `test` WHERE (`a` = 'test\x1atest')"},
)
}
func (sds *sqlite3DialectSuite) TestBooleanOperations() {
ds := sds.GetDs("test")
sds.assertSQL(
sqlTestCase{ds: ds.Where(dbv2.C("a").Eq(true)), sql: "SELECT * FROM `test` WHERE (`a` IS 1)"},
sqlTestCase{ds: ds.Where(dbv2.C("a").Eq(false)), sql: "SELECT * FROM `test` WHERE (`a` IS 0)"},
sqlTestCase{ds: ds.Where(dbv2.C("a").Is(true)), sql: "SELECT * FROM `test` WHERE (`a` IS 1)"},
sqlTestCase{ds: ds.Where(dbv2.C("a").Is(false)), sql: "SELECT * FROM `test` WHERE (`a` IS 0)"},
sqlTestCase{ds: ds.Where(dbv2.C("a").IsTrue()), sql: "SELECT * FROM `test` WHERE (`a` IS 1)"},
sqlTestCase{ds: ds.Where(dbv2.C("a").IsFalse()), sql: "SELECT * FROM `test` WHERE (`a` IS 0)"},
sqlTestCase{ds: ds.Where(dbv2.C("a").Neq(true)), sql: "SELECT * FROM `test` WHERE (`a` IS NOT 1)"},
sqlTestCase{ds: ds.Where(dbv2.C("a").Neq(false)), sql: "SELECT * FROM `test` WHERE (`a` IS NOT 0)"},
sqlTestCase{ds: ds.Where(dbv2.C("a").IsNot(true)), sql: "SELECT * FROM `test` WHERE (`a` IS NOT 1)"},
sqlTestCase{ds: ds.Where(dbv2.C("a").IsNot(false)), sql: "SELECT * FROM `test` WHERE (`a` IS NOT 0)"},
sqlTestCase{ds: ds.Where(dbv2.C("a").IsNotTrue()), sql: "SELECT * FROM `test` WHERE (`a` IS NOT 1)"},
sqlTestCase{ds: ds.Where(dbv2.C("a").IsNotFalse()), sql: "SELECT * FROM `test` WHERE (`a` IS NOT 0)"},
sqlTestCase{ds: ds.Where(dbv2.C("a").Like("a%")), sql: "SELECT * FROM `test` WHERE (`a` LIKE 'a%')"},
sqlTestCase{ds: ds.Where(dbv2.C("a").NotLike("a%")), sql: "SELECT * FROM `test` WHERE (`a` NOT LIKE 'a%')"},
sqlTestCase{ds: ds.Where(dbv2.C("a").ILike("a%")), sql: "SELECT * FROM `test` WHERE (`a` LIKE 'a%')"},
sqlTestCase{ds: ds.Where(dbv2.C("a").NotILike("a%")), sql: "SELECT * FROM `test` WHERE (`a` NOT LIKE 'a%')"},
sqlTestCase{ds: ds.Where(dbv2.C("a").Like(regexp.MustCompile("[ab]"))), sql: "SELECT * FROM `test` WHERE (`a` REGEXP '[ab]')"},
sqlTestCase{ds: ds.Where(dbv2.C("a").NotLike(regexp.MustCompile("[ab]"))), sql: "SELECT * FROM `test` WHERE (`a` NOT REGEXP '[ab]')"},
sqlTestCase{ds: ds.Where(dbv2.C("a").ILike(regexp.MustCompile("[ab]"))), sql: "SELECT * FROM `test` WHERE (`a` REGEXP '[ab]')"},
sqlTestCase{ds: ds.Where(dbv2.C("a").NotILike(regexp.MustCompile("[ab]"))), sql: "SELECT * FROM `test` WHERE (`a` NOT REGEXP '[ab]')"},
)
}
func (sds *sqlite3DialectSuite) TestBitwiseOperations() {
col := dbv2.C("a")
ds := sds.GetDs("test")
sds.assertSQL(
sqlTestCase{ds: ds.Where(col.BitwiseInversion()), err: "db: bitwise operator 'Inversion' not supported"},
sqlTestCase{ds: ds.Where(col.BitwiseAnd(1)), sql: "SELECT * FROM `test` WHERE (`a` & 1)"},
sqlTestCase{ds: ds.Where(col.BitwiseOr(1)), sql: "SELECT * FROM `test` WHERE (`a` | 1)"},
sqlTestCase{ds: ds.Where(col.BitwiseXor(1)), err: "db: bitwise operator 'XOR' not supported"},
sqlTestCase{ds: ds.Where(col.BitwiseLeftShift(1)), sql: "SELECT * FROM `test` WHERE (`a` << 1)"},
sqlTestCase{ds: ds.Where(col.BitwiseRightShift(1)), sql: "SELECT * FROM `test` WHERE (`a` >> 1)"},
)
}
func (sds *sqlite3DialectSuite) TestForUpdate() {
ds := sds.GetDs("test")
sds.assertSQL(
sqlTestCase{ds: ds.Where(dbv2.C("a").Eq(1)).ForUpdate(dbv2.Wait), sql: "SELECT * FROM `test` WHERE (`a` = 1)"},
sqlTestCase{ds: ds.Where(dbv2.C("a").Eq(1)).ForUpdate(dbv2.NoWait), sql: "SELECT * FROM `test` WHERE (`a` = 1)"},
)
}
func TestDatasetAdapterSuite(t *testing.T) {
suite.Run(t, new(sqlite3DialectSuite))
}