|
|
@@ -9,6 +9,7 @@ import (
|
|
|
"fmt"
|
|
|
"math"
|
|
|
"reflect"
|
|
|
+ "strconv"
|
|
|
"strings"
|
|
|
"time"
|
|
|
|
|
|
@@ -451,6 +452,10 @@ func columnKeyIndicator(tbl *core.Table, target int) string {
|
|
|
// JOIN 查询 (join.go) 物化嵌套循环执行, 算完即放锁。
|
|
|
// release 非 nil 表示锁由本次查询 (非事务) 获取, 任何提前返回错误都必须 release。
|
|
|
func queryStatement(ctx context.Context, db *core.DB, sel *selectStmt, args []driver.NamedValue, release func()) (driver.Rows, error) {
|
|
|
+ // SELECT 值清单 (无 FROM, 如探活语句 SELECT 1): 不触引擎, 物化单行立即返回。
|
|
|
+ if sel.noFrom {
|
|
|
+ return queryLiteralRow(sel, args, release)
|
|
|
+ }
|
|
|
if len(sel.joins) > 0 {
|
|
|
return queryJoin(db, sel, args, release)
|
|
|
}
|
|
|
@@ -514,10 +519,18 @@ func queryStatement(ctx context.Context, db *core.DB, sel *selectStmt, args []dr
|
|
|
q = q.OrderBy(o.col, o.desc)
|
|
|
}
|
|
|
if sel.limit != nil {
|
|
|
- q = q.Limit(int(*sel.limit))
|
|
|
+ lim, err := resolveLimitValue("LIMIT", sel.limit, args)
|
|
|
+ if err != nil {
|
|
|
+ return fail(err)
|
|
|
+ }
|
|
|
+ q = q.Limit(int(lim))
|
|
|
}
|
|
|
if sel.offset != nil {
|
|
|
- q = q.Offset(int(*sel.offset))
|
|
|
+ off, err := resolveLimitValue("OFFSET", sel.offset, args)
|
|
|
+ if err != nil {
|
|
|
+ return fail(err)
|
|
|
+ }
|
|
|
+ q = q.Offset(int(off))
|
|
|
}
|
|
|
|
|
|
r := newStreamRows(colNames, metas, release)
|
|
|
@@ -525,6 +538,128 @@ func queryStatement(ctx context.Context, db *core.DB, sel *selectStmt, args []dr
|
|
|
return r, nil
|
|
|
}
|
|
|
|
|
|
+// queryLiteralRow 执行无 FROM 的 SELECT 值清单 ("SELECT 1" / "SELECT 'a', NULL"):
|
|
|
+// 每个值物化成 driver.Value, 拼成单行结果。不触引擎, 立即放锁。
|
|
|
+func queryLiteralRow(sel *selectStmt, args []driver.NamedValue, release func()) (driver.Rows, error) {
|
|
|
+ if release != nil {
|
|
|
+ defer release()
|
|
|
+ }
|
|
|
+ cols := make([]string, len(sel.literalVals))
|
|
|
+ metas := make([]colMeta, len(sel.literalVals))
|
|
|
+ row := make([]driver.Value, len(sel.literalVals))
|
|
|
+ for i, v := range sel.literalVals {
|
|
|
+ cols[i] = "?column?"
|
|
|
+ dv, m, err := resolveLiteralValue(v, args)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("SELECT 值清单第 %d 项: %w", i+1, err)
|
|
|
+ }
|
|
|
+ metas[i] = m
|
|
|
+ row[i] = dv
|
|
|
+ }
|
|
|
+ return materialRows(cols, metas, [][]driver.Value{row}), nil
|
|
|
+}
|
|
|
+
|
|
|
+// resolveLiteralValue 把无 FROM 值清单里的字面量/占位符物化成 driver.Value,
|
|
|
+// 并按值的种类给出列元数据 (供 ColumnType* 反射)。
|
|
|
+func resolveLiteralValue(v value, args []driver.NamedValue) (driver.Value, colMeta, error) {
|
|
|
+ intMeta := colMeta{dbName: "BIGINT", nullable: false, scanType: reflect.TypeOf(int64(0))}
|
|
|
+ floatMeta := colMeta{dbName: "DOUBLE", nullable: false, scanType: reflect.TypeOf(float64(0))}
|
|
|
+ strMeta := colMeta{dbName: "VARCHAR", nullable: false, scanType: reflect.TypeOf("")}
|
|
|
+ nullMeta := colMeta{dbName: "NULL", nullable: true, scanType: reflect.TypeOf("")}
|
|
|
+ switch t := v.(type) {
|
|
|
+ case nullValue:
|
|
|
+ return nil, nullMeta, nil
|
|
|
+ case strValue:
|
|
|
+ return []byte(t.s), strMeta, nil
|
|
|
+ case litValue:
|
|
|
+ raw := strings.TrimSpace(t.raw)
|
|
|
+ if strings.Contains(raw, ".") {
|
|
|
+ f, err := strconv.ParseFloat(raw, 64)
|
|
|
+ if err != nil {
|
|
|
+ return nil, colMeta{}, fmt.Errorf("浮点字面量 %q: %w", t.raw, err)
|
|
|
+ }
|
|
|
+ return f, floatMeta, nil
|
|
|
+ }
|
|
|
+ n, err := strconv.ParseInt(raw, 10, 64)
|
|
|
+ if err != nil {
|
|
|
+ return nil, colMeta{}, fmt.Errorf("整数字面量 %q: %w", t.raw, err)
|
|
|
+ }
|
|
|
+ return n, intMeta, nil
|
|
|
+ case argValue:
|
|
|
+ if t.idx >= len(args) {
|
|
|
+ return nil, colMeta{}, fmt.Errorf("占位符 ?%d 没有对应参数 (共 %d 个)", t.idx+1, len(args))
|
|
|
+ }
|
|
|
+ switch x := args[t.idx].Value.(type) {
|
|
|
+ case nil:
|
|
|
+ return nil, nullMeta, nil
|
|
|
+ case int64:
|
|
|
+ return x, intMeta, nil
|
|
|
+ case float64:
|
|
|
+ return x, floatMeta, nil
|
|
|
+ case bool:
|
|
|
+ return x, colMeta{dbName: "BOOLEAN", nullable: false, scanType: reflect.TypeOf(false)}, nil
|
|
|
+ case string:
|
|
|
+ return []byte(x), strMeta, nil
|
|
|
+ case []byte:
|
|
|
+ return x, strMeta, nil
|
|
|
+ case time.Time:
|
|
|
+ return x, colMeta{dbName: "DATETIME", nullable: false, scanType: reflect.TypeOf(time.Time{})}, nil
|
|
|
+ }
|
|
|
+ return nil, colMeta{}, fmt.Errorf("参数类型 %T 不能出现在 SELECT 值清单", args[t.idx].Value)
|
|
|
+ }
|
|
|
+ return nil, colMeta{}, fmt.Errorf("无法解析的值")
|
|
|
+}
|
|
|
+
|
|
|
+// resolveLimitValue 解析 LIMIT/OFFSET 的量: 字面量直解整数; 占位符从 args
|
|
|
+// 取整数 (int64/uint64/数字字符串/整值浮点均可, 非整数报错)。
|
|
|
+func resolveLimitValue(keyword string, v value, args []driver.NamedValue) (int64, error) {
|
|
|
+ switch t := v.(type) {
|
|
|
+ case litValue:
|
|
|
+ n, err := strconv.ParseInt(strings.TrimSpace(t.raw), 10, 64)
|
|
|
+ if err != nil {
|
|
|
+ return 0, fmt.Errorf("%s 期望整数, 实际是 %q", keyword, t.raw)
|
|
|
+ }
|
|
|
+ return n, nil
|
|
|
+ case argValue:
|
|
|
+ if t.idx >= len(args) {
|
|
|
+ return 0, fmt.Errorf("占位符 ?%d 没有对应参数 (共 %d 个)", t.idx+1, len(args))
|
|
|
+ }
|
|
|
+ n, err := limitArgToInt64(args[t.idx].Value)
|
|
|
+ if err != nil {
|
|
|
+ return 0, fmt.Errorf("%s: %w", keyword, err)
|
|
|
+ }
|
|
|
+ return n, nil
|
|
|
+ }
|
|
|
+ return 0, fmt.Errorf("%s 只支持整数或 ? 占位符", keyword)
|
|
|
+}
|
|
|
+
|
|
|
+// limitArgToInt64 把 LIMIT/OFFSET 的参数值规整成 int64。
|
|
|
+func limitArgToInt64(v driver.Value) (int64, error) {
|
|
|
+ switch x := v.(type) {
|
|
|
+ case int64:
|
|
|
+ return x, nil
|
|
|
+ case uint64:
|
|
|
+ if x > math.MaxInt64 {
|
|
|
+ return 0, fmt.Errorf("超出 int64 范围: %d", x)
|
|
|
+ }
|
|
|
+ return int64(x), nil
|
|
|
+ case float64:
|
|
|
+ if x != math.Trunc(x) {
|
|
|
+ return 0, fmt.Errorf("参数必须是非负整数, 实际 %v", x)
|
|
|
+ }
|
|
|
+ return int64(x), nil
|
|
|
+ case string:
|
|
|
+ n, err := strconv.ParseInt(strings.TrimSpace(x), 10, 64)
|
|
|
+ if err != nil {
|
|
|
+ return 0, fmt.Errorf("参数 %q 不是整数", x)
|
|
|
+ }
|
|
|
+ return n, nil
|
|
|
+ case []byte:
|
|
|
+ return limitArgToInt64(string(x))
|
|
|
+ }
|
|
|
+ return 0, fmt.Errorf("参数必须是整数, 实际 %T", v)
|
|
|
+}
|
|
|
+
|
|
|
func queryAggregate(tbl *core.Table, sel *selectStmt, args []driver.NamedValue) (*rows, error) {
|
|
|
cond, err := translateWhere(tbl, sel.where, args)
|
|
|
if err != nil {
|
|
|
@@ -732,7 +867,8 @@ func translateWhere(tbl *core.Table, e expr, args []driver.NamedValue) (core.Con
|
|
|
}
|
|
|
return core.Not(inner), nil
|
|
|
case *cmpExpr:
|
|
|
- fi := findField(tbl, t.col)
|
|
|
+ col := resolveColRef(t.col)
|
|
|
+ fi := findField(tbl, col)
|
|
|
if fi == nil {
|
|
|
return nil, fmt.Errorf("%w (WHERE): %q", core.ErrFieldNotFound, t.col)
|
|
|
}
|
|
|
@@ -742,17 +878,17 @@ func translateWhere(tbl *core.Table, e expr, args []driver.NamedValue) (core.Con
|
|
|
}
|
|
|
switch t.op {
|
|
|
case "=":
|
|
|
- return core.Eq(t.col, v, fi.FieldType), nil
|
|
|
+ return core.Eq(col, v, fi.FieldType), nil
|
|
|
case "!=":
|
|
|
- return core.Not(core.Eq(t.col, v, fi.FieldType)), nil
|
|
|
+ return core.Not(core.Eq(col, v, fi.FieldType)), nil
|
|
|
case ">":
|
|
|
- return core.Gt(t.col, v, fi.FieldType), nil
|
|
|
+ return core.Gt(col, v, fi.FieldType), nil
|
|
|
case ">=":
|
|
|
- return core.Ge(t.col, v, fi.FieldType), nil
|
|
|
+ return core.Ge(col, v, fi.FieldType), nil
|
|
|
case "<":
|
|
|
- return core.Lt(t.col, v, fi.FieldType), nil
|
|
|
+ return core.Lt(col, v, fi.FieldType), nil
|
|
|
case "<=":
|
|
|
- return core.Le(t.col, v, fi.FieldType), nil
|
|
|
+ return core.Le(col, v, fi.FieldType), nil
|
|
|
}
|
|
|
return nil, fmt.Errorf("不支持的比较符 %q", t.op)
|
|
|
case *colCmpExpr:
|
|
|
@@ -760,7 +896,8 @@ func translateWhere(tbl *core.Table, e expr, args []driver.NamedValue) (core.Con
|
|
|
// 组合行上求值; 单表/UPDATE/DELETE 走到这里说明用法越界, 明确报错。
|
|
|
return nil, fmt.Errorf("列与列的比较 (%s %s %s) 仅在多表 JOIN 查询中支持", t.left, t.op, t.right)
|
|
|
case *inExpr:
|
|
|
- fi := findField(tbl, t.col)
|
|
|
+ col := resolveColRef(t.col)
|
|
|
+ fi := findField(tbl, col)
|
|
|
if fi == nil {
|
|
|
return nil, fmt.Errorf("%w (WHERE): %q", core.ErrFieldNotFound, t.col)
|
|
|
}
|
|
|
@@ -772,7 +909,7 @@ func translateWhere(tbl *core.Table, e expr, args []driver.NamedValue) (core.Con
|
|
|
}
|
|
|
vals = append(vals, v)
|
|
|
}
|
|
|
- c := core.In(t.col, vals, fi.FieldType)
|
|
|
+ c := core.In(col, vals, fi.FieldType)
|
|
|
if t.neg {
|
|
|
return core.Not(c), nil
|
|
|
}
|
|
|
@@ -780,12 +917,16 @@ func translateWhere(tbl *core.Table, e expr, args []driver.NamedValue) (core.Con
|
|
|
case *isNullExpr:
|
|
|
// IS [NOT] NULL → 引擎的 NullCondition: 直接读行内 NULL 位图
|
|
|
// (与反序列化/索引提取同一口径; Eq 对 NULL 行一律不匹配, 表达不了 IS NULL)
|
|
|
+ // 列名同样要剥限定前缀 — GORM 生成 `t`.`deleted_at` IS NULL,
|
|
|
+ // 不剥的话 NullCondition 按全名找不到列、静默恒假 (UPDATE/DELETE 0 行)。
|
|
|
+ col := resolveColRef(t.col)
|
|
|
if t.neg {
|
|
|
- return core.IsNotNull(t.col), nil
|
|
|
+ return core.IsNotNull(col), nil
|
|
|
}
|
|
|
- return core.IsNull(t.col), nil
|
|
|
+ return core.IsNull(col), nil
|
|
|
case *likeExpr:
|
|
|
- fi := findField(tbl, t.col)
|
|
|
+ col := resolveColRef(t.col)
|
|
|
+ fi := findField(tbl, col)
|
|
|
if fi == nil {
|
|
|
return nil, fmt.Errorf("%w (WHERE): %q", core.ErrFieldNotFound, t.col)
|
|
|
}
|
|
|
@@ -793,7 +934,7 @@ func translateWhere(tbl *core.Table, e expr, args []driver.NamedValue) (core.Con
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
- c := core.Like(t.col, pat)
|
|
|
+ c := core.Like(col, pat)
|
|
|
if t.neg {
|
|
|
return core.Not(c), nil
|
|
|
}
|
|
|
@@ -802,6 +943,17 @@ func translateWhere(tbl *core.Table, e expr, args []driver.NamedValue) (core.Con
|
|
|
return nil, fmt.Errorf("无法翻译的 WHERE 表达式")
|
|
|
}
|
|
|
|
|
|
+// resolveColRef 列名归一: 剥掉 "表名." / "别名." 限定前缀。GORM 等 ORM 生成的
|
|
|
+// SQL 普遍带反引号限定 (`t`.`col`, 词法层拼成 "t.col"), 而引擎条件按裸列名
|
|
|
+// 匹配 — 不剥的话: 等值/IN/LIKE 报"列不存在", IS NULL 按全名找不到列、
|
|
|
+// Match 静默恒假 (UPDATE/DELETE 作用 0 行, 无任何报错)。
|
|
|
+func resolveColRef(col string) string {
|
|
|
+ if i := strings.LastIndexByte(col, '.'); i >= 0 {
|
|
|
+ return col[i+1:]
|
|
|
+ }
|
|
|
+ return col
|
|
|
+}
|
|
|
+
|
|
|
// =============================================================================
|
|
|
// 表/列/值解析辅助
|
|
|
// =============================================================================
|