|
|
@@ -0,0 +1,998 @@
|
|
|
+package vrtxsql
|
|
|
+
|
|
|
+// JOIN 查询执行: 多表嵌套循环 + 右表索引点查, 结果物化后立即放锁。
|
|
|
+// 仅 SELECT ... FROM t1 JOIN t2 ON ... 走本路径 (单表查询仍走流式路径,
|
|
|
+// 见 exec.go queryStatement)。
|
|
|
+//
|
|
|
+// 执行策略:
|
|
|
+// - 左表全扫描 (本表 WHERE 条件下推), 每行存 别名(小写) → 列(小写) → driver.Value;
|
|
|
+// - 逐 JOIN: 取组合行里 ON 左列的值, 对右表做索引点查
|
|
|
+// (tbl.Query().Where(core.Eq(...)) + 右表本表条件), 命中行做笛卡尔组合;
|
|
|
+// INNER 无匹配丢行, LEFT 无匹配时右表列全 NULL 保留行;
|
|
|
+// - WHERE 按列归属拆分: 叶子条件 (AND 结构) 下推到所属表, 跨表 OR/NOT/列比较
|
|
|
+// 作为残余谓词在组合行上用 Go 值比较过滤 (= != < <= > >=);
|
|
|
+// - 投影 (*/t.*/t.col/col 混用, 重名列用 别名_列名 去重) → ORDER BY 内存
|
|
|
+// 稳定排序 (NULL 视为最小) → LIMIT/OFFSET, 返回物化 *rows。
|
|
|
+
|
|
|
+import (
|
|
|
+ "database/sql/driver"
|
|
|
+ "fmt"
|
|
|
+ "sort"
|
|
|
+ "strconv"
|
|
|
+ "strings"
|
|
|
+ "time"
|
|
|
+
|
|
|
+ "git.ycl.pub/yl32940/TsDB2/DB/core"
|
|
|
+ "git.ycl.pub/yl32940/TsDB2/DB/core/types"
|
|
|
+)
|
|
|
+
|
|
|
+// =============================================================================
|
|
|
+// 限定名列引用的解析辅助 (单表路径解限定 + JOIN 路径归属判定共用)
|
|
|
+// =============================================================================
|
|
|
+
|
|
|
+// splitQualCol 把列引用拆成 (限定词, 列名); 无 '.' 时 qual 为空。
|
|
|
+func splitQualCol(ref string) (qual, col string) {
|
|
|
+ if i := strings.IndexByte(ref, '.'); i >= 0 {
|
|
|
+ return ref[:i], ref[i+1:]
|
|
|
+ }
|
|
|
+ return "", ref
|
|
|
+}
|
|
|
+
|
|
|
+// dequalifyColRef 校验并剥掉列引用的限定词 ("t.col" → "col"); 非限定名原样返回。
|
|
|
+func dequalifyColRef(ref string, quals ...string) (string, error) {
|
|
|
+ qual, col := splitQualCol(ref)
|
|
|
+ if qual == "" {
|
|
|
+ return ref, nil
|
|
|
+ }
|
|
|
+ for _, q := range quals {
|
|
|
+ if strings.EqualFold(q, qual) {
|
|
|
+ return col, nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return "", fmt.Errorf("列限定 %q 与 FROM 表 (%s) 不匹配", qual, strings.Join(quals, "/"))
|
|
|
+}
|
|
|
+
|
|
|
+// dequalifyExpr 剥掉表达式里所有列引用的限定词 (单表 SELECT 用; 值原样保留)。
|
|
|
+func dequalifyExpr(e expr, quals ...string) (expr, error) {
|
|
|
+ if e == nil {
|
|
|
+ return nil, nil
|
|
|
+ }
|
|
|
+ switch t := e.(type) {
|
|
|
+ case *andExpr:
|
|
|
+ l, err := dequalifyExpr(t.l, quals...)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ r, err := dequalifyExpr(t.r, quals...)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return &andExpr{l: l, r: r}, nil
|
|
|
+ case *orExpr:
|
|
|
+ l, err := dequalifyExpr(t.l, quals...)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ r, err := dequalifyExpr(t.r, quals...)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return &orExpr{l: l, r: r}, nil
|
|
|
+ case *notExpr:
|
|
|
+ inner, err := dequalifyExpr(t.e, quals...)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return ¬Expr{e: inner}, nil
|
|
|
+ case *cmpExpr:
|
|
|
+ col, err := dequalifyColRef(t.col, quals...)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return &cmpExpr{col: col, op: t.op, val: t.val}, nil
|
|
|
+ case *colCmpExpr:
|
|
|
+ l, err := dequalifyColRef(t.left, quals...)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ r, err := dequalifyColRef(t.right, quals...)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return &colCmpExpr{left: l, op: t.op, right: r}, nil
|
|
|
+ case *inExpr:
|
|
|
+ col, err := dequalifyColRef(t.col, quals...)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return &inExpr{col: col, vals: t.vals, neg: t.neg}, nil
|
|
|
+ case *isNullExpr:
|
|
|
+ col, err := dequalifyColRef(t.col, quals...)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return &isNullExpr{col: col, neg: t.neg}, nil
|
|
|
+ case *likeExpr:
|
|
|
+ col, err := dequalifyColRef(t.col, quals...)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return &likeExpr{col: col, pattern: t.pattern, neg: t.neg}, nil
|
|
|
+ }
|
|
|
+ return nil, fmt.Errorf("无法处理的 WHERE 表达式")
|
|
|
+}
|
|
|
+
|
|
|
+// selectColsSingle 单表 SELECT 的列清单展开: 空 / "*" / "t.*" → 全部活跃列;
|
|
|
+// "t.col" → 校验限定词后取列; 列存在性校验与旧行为一致 (ErrFieldNotFound)。
|
|
|
+func selectColsSingle(tbl *core.Table, sel *selectStmt, quals ...string) ([]string, error) {
|
|
|
+ if len(sel.columns) == 0 {
|
|
|
+ return activeColumnNames(tbl), nil
|
|
|
+ }
|
|
|
+ out := make([]string, 0, len(sel.columns))
|
|
|
+ for _, c := range sel.columns {
|
|
|
+ if c == "*" {
|
|
|
+ out = append(out, activeColumnNames(tbl)...)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if strings.HasSuffix(c, ".*") {
|
|
|
+ if _, err := dequalifyColRef(c, quals...); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ out = append(out, activeColumnNames(tbl)...)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ col, err := dequalifyColRef(c, quals...)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ if findField(tbl, col) == nil {
|
|
|
+ return nil, fmt.Errorf("%w: %q", core.ErrFieldNotFound, c)
|
|
|
+ }
|
|
|
+ out = append(out, col)
|
|
|
+ }
|
|
|
+ return out, nil
|
|
|
+}
|
|
|
+
|
|
|
+// =============================================================================
|
|
|
+// JOIN 执行
|
|
|
+// =============================================================================
|
|
|
+
|
|
|
+// joinTable 参与 JOIN 的一张表: 引擎表 + 归一化别名 + 活跃列。
|
|
|
+type joinTable struct {
|
|
|
+ idx int
|
|
|
+ name string
|
|
|
+ alias string // 限定名归一化结果 (alias 缺省 = 表名), 比较时大小写不敏感
|
|
|
+ tbl *core.Table
|
|
|
+ cols []string // 活跃列名 (原始大小写, 投影展示用)
|
|
|
+ ft map[string]types.FieldType // 小写列名 → 列类型 (typed 值转换用)
|
|
|
+}
|
|
|
+
|
|
|
+func buildJoinTable(idx int, ref tableRef, tbl *core.Table) *joinTable {
|
|
|
+ alias := ref.alias
|
|
|
+ if alias == "" {
|
|
|
+ alias = ref.name
|
|
|
+ }
|
|
|
+ jt := &joinTable{idx: idx, name: ref.name, alias: alias, tbl: tbl, ft: map[string]types.FieldType{}}
|
|
|
+ for i := range tbl.Field {
|
|
|
+ f := &tbl.Field[i]
|
|
|
+ if f.IsDropped() {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ cn := string(f.FieldName)
|
|
|
+ jt.cols = append(jt.cols, cn)
|
|
|
+ jt.ft[strings.ToLower(cn)] = f.FieldType
|
|
|
+ }
|
|
|
+ return jt
|
|
|
+}
|
|
|
+
|
|
|
+// joinRow 组合行: 表别名(小写) → 列名(小写) → driver.Value (未匹配侧为 NULL)。
|
|
|
+type joinRow map[string]map[string]driver.Value
|
|
|
+
|
|
|
+func (r joinRow) get(jt *joinTable, col string) driver.Value {
|
|
|
+ return r[strings.ToLower(jt.alias)][strings.ToLower(col)]
|
|
|
+}
|
|
|
+
|
|
|
+func cloneJoinRow(r joinRow) joinRow {
|
|
|
+ nr := make(joinRow, len(r)+1)
|
|
|
+ for k, v := range r {
|
|
|
+ nr[k] = v
|
|
|
+ }
|
|
|
+ return nr
|
|
|
+}
|
|
|
+
|
|
|
+// nullJoinRow 右表无匹配时 (LEFT JOIN) 的全 NULL 行。
|
|
|
+func nullJoinRow(jt *joinTable) map[string]driver.Value {
|
|
|
+ m := make(map[string]driver.Value, len(jt.cols))
|
|
|
+ for _, cn := range jt.cols {
|
|
|
+ m[strings.ToLower(cn)] = nil
|
|
|
+ }
|
|
|
+ return m
|
|
|
+}
|
|
|
+
|
|
|
+// findQual 按限定词找表 (别名或表名, 大小写不敏感)。
|
|
|
+func findQual(ts []*joinTable, qual string) *joinTable {
|
|
|
+ for _, jt := range ts {
|
|
|
+ if strings.EqualFold(jt.alias, qual) || strings.EqualFold(jt.name, qual) {
|
|
|
+ return jt
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// ownerOfCol 解析列引用归属的表: 限定名直查; 非限定名要求在恰好一张表上存在
|
|
|
+// (存在于多张报"列不明确", 没有报列不存在)。返回归属表与剥掉限定后的列名。
|
|
|
+func ownerOfCol(ts []*joinTable, ref string) (*joinTable, string, error) {
|
|
|
+ qual, col := splitQualCol(ref)
|
|
|
+ if qual != "" {
|
|
|
+ jt := findQual(ts, qual)
|
|
|
+ if jt == nil {
|
|
|
+ return nil, "", fmt.Errorf("未知的表限定 %q (列 %q)", qual, ref)
|
|
|
+ }
|
|
|
+ if findField(jt.tbl, col) == nil {
|
|
|
+ return nil, "", fmt.Errorf("%w: %q", core.ErrFieldNotFound, ref)
|
|
|
+ }
|
|
|
+ return jt, col, nil
|
|
|
+ }
|
|
|
+ var hit *joinTable
|
|
|
+ for _, jt := range ts {
|
|
|
+ if findField(jt.tbl, col) != nil {
|
|
|
+ if hit != nil {
|
|
|
+ return nil, "", fmt.Errorf("列 %q 不明确 (同时存在于 %q 和 %q)", col, hit.alias, jt.alias)
|
|
|
+ }
|
|
|
+ hit = jt
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if hit == nil {
|
|
|
+ return nil, "", fmt.Errorf("%w: %q", core.ErrFieldNotFound, ref)
|
|
|
+ }
|
|
|
+ return hit, col, nil
|
|
|
+}
|
|
|
+
|
|
|
+// scanJoinTable 扫一张表 (应用下推条件), 每行 → 小写列名 → driver.Value。
|
|
|
+func scanJoinTable(jt *joinTable, cond core.Condition) ([]map[string]driver.Value, error) {
|
|
|
+ q := jt.tbl.Query()
|
|
|
+ if cond != nil {
|
|
|
+ q = q.Where(cond)
|
|
|
+ }
|
|
|
+ var out []map[string]driver.Value
|
|
|
+ var convErr error
|
|
|
+ err := q.Scan(func(full []types.FieldValue) bool {
|
|
|
+ m := make(map[string]driver.Value, len(full))
|
|
|
+ for _, fv := range full {
|
|
|
+ key := strings.ToLower(string(fv.FieldName))
|
|
|
+ dv, err := fieldValueToDriverTyped(jt.ft[key], fv.Value)
|
|
|
+ if err != nil {
|
|
|
+ convErr = err
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ m[key] = joinTypedValue(jt.ft[key], dv)
|
|
|
+ }
|
|
|
+ out = append(out, m)
|
|
|
+ return true
|
|
|
+ })
|
|
|
+ if convErr != nil {
|
|
|
+ return nil, convErr
|
|
|
+ }
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return out, nil
|
|
|
+}
|
|
|
+
|
|
|
+// joinTypedValue 把整型/浮点列的文本化值归一回数值: fieldValueToDriver 对
|
|
|
+// int32 等小整型走 fmt.Sprint 兜底成 []byte, 残余谓词/排序会因此变成字符串
|
|
|
+// 比较 ("100" < "25"), 这里按列类型归一, 与 BIGINT 的输出形态 (int64) 对齐。
|
|
|
+func joinTypedValue(ft types.FieldType, v driver.Value) driver.Value {
|
|
|
+ if v == nil {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ var s string
|
|
|
+ switch t := v.(type) {
|
|
|
+ case []byte:
|
|
|
+ s = string(t)
|
|
|
+ case string:
|
|
|
+ s = t
|
|
|
+ default:
|
|
|
+ return v
|
|
|
+ }
|
|
|
+ if isIntType(ft) {
|
|
|
+ if n, err := strconv.ParseInt(strings.TrimSpace(s), 10, 64); err == nil {
|
|
|
+ return n
|
|
|
+ }
|
|
|
+ }
|
|
|
+ switch ft {
|
|
|
+ case types.FieldType_Float64, types.FieldType_Float32:
|
|
|
+ if f, err := strconv.ParseFloat(strings.TrimSpace(s), 64); err == nil {
|
|
|
+ return f
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return v
|
|
|
+}
|
|
|
+
|
|
|
+// =============================================================================
|
|
|
+// WHERE 按归属拆分: 下推条件 + 残余谓词
|
|
|
+// =============================================================================
|
|
|
+
|
|
|
+// joinWherePlan WHERE 拆分结果: perTable[i] 下推到第 i 张表扫描的 AND 条件,
|
|
|
+// residual 为不可下推的表达式 (跨表 OR/NOT、列与列比较), 组合行上过滤。
|
|
|
+type joinWherePlan struct {
|
|
|
+ perTable []core.Condition
|
|
|
+ residual expr
|
|
|
+}
|
|
|
+
|
|
|
+func planJoinWhere(ts []*joinTable, where expr, args []driver.NamedValue) (*joinWherePlan, error) {
|
|
|
+ plan := &joinWherePlan{perTable: make([]core.Condition, len(ts))}
|
|
|
+ if where == nil {
|
|
|
+ return plan, nil
|
|
|
+ }
|
|
|
+ if err := splitJoinWhere(ts, where, args, plan); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return plan, nil
|
|
|
+}
|
|
|
+
|
|
|
+// splitJoinWhere 递归拆分 AND 结构; 叶子条件按列归属下推, 其余整体留作残余。
|
|
|
+func splitJoinWhere(ts []*joinTable, e expr, args []driver.NamedValue, plan *joinWherePlan) error {
|
|
|
+ switch t := e.(type) {
|
|
|
+ case *andExpr:
|
|
|
+ if err := splitJoinWhere(ts, t.l, args, plan); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return splitJoinWhere(ts, t.r, args, plan)
|
|
|
+ case *orExpr, *notExpr:
|
|
|
+ // 结构条件: 整棵子树只涉及一张表仍可下推该表, 跨表则留作残余谓词
|
|
|
+ owners := map[*joinTable]bool{}
|
|
|
+ if err := exprOwners(ts, e, owners); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if len(owners) == 1 {
|
|
|
+ for jt := range owners {
|
|
|
+ return pushJoinWhere(jt, e, args, plan)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ plan.residual = andResidual(plan.residual, e)
|
|
|
+ return nil
|
|
|
+ case *colCmpExpr:
|
|
|
+ // 列与列比较: 引擎 Condition 只支持列与值, 一律留作残余 (列归属校验在此做)
|
|
|
+ if _, _, err := ownerOfCol(ts, t.left); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if _, _, err := ownerOfCol(ts, t.right); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ plan.residual = andResidual(plan.residual, e)
|
|
|
+ return nil
|
|
|
+ case *cmpExpr:
|
|
|
+ return pushJoinLeaf(ts, t.col, e, args, plan)
|
|
|
+ case *inExpr:
|
|
|
+ return pushJoinLeaf(ts, t.col, e, args, plan)
|
|
|
+ case *isNullExpr:
|
|
|
+ return pushJoinLeaf(ts, t.col, e, args, plan)
|
|
|
+ case *likeExpr:
|
|
|
+ return pushJoinLeaf(ts, t.col, e, args, plan)
|
|
|
+ }
|
|
|
+ return fmt.Errorf("无法拆分的 WHERE 表达式")
|
|
|
+}
|
|
|
+
|
|
|
+// pushJoinLeaf 叶子条件按列归属下推到对应表。
|
|
|
+func pushJoinLeaf(ts []*joinTable, ref string, e expr, args []driver.NamedValue, plan *joinWherePlan) error {
|
|
|
+ jt, _, err := ownerOfCol(ts, ref)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return pushJoinWhere(jt, e, args, plan)
|
|
|
+}
|
|
|
+
|
|
|
+// pushJoinWhere 把表达式翻译后 AND 进对应表的下推条件 (先剥限定词再走标准翻译)。
|
|
|
+func pushJoinWhere(jt *joinTable, e expr, args []driver.NamedValue, plan *joinWherePlan) error {
|
|
|
+ dq, err := dequalifyExpr(e, jt.alias, jt.name)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ cond, err := translateWhere(jt.tbl, dq, args)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if cond == nil {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ if plan.perTable[jt.idx] == nil {
|
|
|
+ plan.perTable[jt.idx] = cond
|
|
|
+ } else {
|
|
|
+ plan.perTable[jt.idx] = core.And(plan.perTable[jt.idx], cond)
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// andResidual 把不可下推的条件用 AND 串起来。
|
|
|
+func andResidual(a, b expr) expr {
|
|
|
+ if a == nil {
|
|
|
+ return b
|
|
|
+ }
|
|
|
+ return &andExpr{l: a, r: b}
|
|
|
+}
|
|
|
+
|
|
|
+// exprOwners 收集表达式引用的全部表 (按指针去重); 列归属解析错误直接透出。
|
|
|
+func exprOwners(ts []*joinTable, e expr, owners map[*joinTable]bool) error {
|
|
|
+ mark := func(ref string) error {
|
|
|
+ jt, _, err := ownerOfCol(ts, ref)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ owners[jt] = true
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ switch t := e.(type) {
|
|
|
+ case *andExpr:
|
|
|
+ if err := exprOwners(ts, t.l, owners); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return exprOwners(ts, t.r, owners)
|
|
|
+ case *orExpr:
|
|
|
+ if err := exprOwners(ts, t.l, owners); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return exprOwners(ts, t.r, owners)
|
|
|
+ case *notExpr:
|
|
|
+ return exprOwners(ts, t.e, owners)
|
|
|
+ case *cmpExpr:
|
|
|
+ return mark(t.col)
|
|
|
+ case *colCmpExpr:
|
|
|
+ if err := mark(t.left); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return mark(t.right)
|
|
|
+ case *inExpr:
|
|
|
+ return mark(t.col)
|
|
|
+ case *isNullExpr:
|
|
|
+ return mark(t.col)
|
|
|
+ case *likeExpr:
|
|
|
+ return mark(t.col)
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// =============================================================================
|
|
|
+// 残余谓词求值: 组合行上的 Go 值比较
|
|
|
+// =============================================================================
|
|
|
+
|
|
|
+// evalJoinExpr 在组合行上求值残余谓词 (跨表 WHERE 条件)。
|
|
|
+func evalJoinExpr(e expr, ts []*joinTable, r joinRow, args []driver.NamedValue) (bool, error) {
|
|
|
+ switch t := e.(type) {
|
|
|
+ case *andExpr:
|
|
|
+ l, err := evalJoinExpr(t.l, ts, r, args)
|
|
|
+ if err != nil || !l {
|
|
|
+ return l, err
|
|
|
+ }
|
|
|
+ return evalJoinExpr(t.r, ts, r, args)
|
|
|
+ case *orExpr:
|
|
|
+ l, err := evalJoinExpr(t.l, ts, r, args)
|
|
|
+ if err != nil || l {
|
|
|
+ return l, err
|
|
|
+ }
|
|
|
+ return evalJoinExpr(t.r, ts, r, args)
|
|
|
+ case *notExpr:
|
|
|
+ v, err := evalJoinExpr(t.e, ts, r, args)
|
|
|
+ if err != nil {
|
|
|
+ return false, err
|
|
|
+ }
|
|
|
+ return !v, nil
|
|
|
+ case *cmpExpr:
|
|
|
+ jt, col, err := ownerOfCol(ts, t.col)
|
|
|
+ if err != nil {
|
|
|
+ return false, err
|
|
|
+ }
|
|
|
+ fi := findField(jt.tbl, col)
|
|
|
+ raw, err := resolveValue(fi, t.val, args)
|
|
|
+ if err != nil {
|
|
|
+ return false, err
|
|
|
+ }
|
|
|
+ rv, err := fieldValueToDriverTyped(fi.FieldType, raw)
|
|
|
+ if err != nil {
|
|
|
+ return false, err
|
|
|
+ }
|
|
|
+ return compareOp(r.get(jt, col), rv, t.op), nil
|
|
|
+ case *colCmpExpr:
|
|
|
+ ljt, lcol, err := ownerOfCol(ts, t.left)
|
|
|
+ if err != nil {
|
|
|
+ return false, err
|
|
|
+ }
|
|
|
+ rjt, rcol, err := ownerOfCol(ts, t.right)
|
|
|
+ if err != nil {
|
|
|
+ return false, err
|
|
|
+ }
|
|
|
+ return compareOp(r.get(ljt, lcol), r.get(rjt, rcol), t.op), nil
|
|
|
+ case *inExpr:
|
|
|
+ jt, col, err := ownerOfCol(ts, t.col)
|
|
|
+ if err != nil {
|
|
|
+ return false, err
|
|
|
+ }
|
|
|
+ fi := findField(jt.tbl, col)
|
|
|
+ lv := r.get(jt, col)
|
|
|
+ hit := false
|
|
|
+ for _, tv := range t.vals {
|
|
|
+ raw, err := resolveValue(fi, tv, args)
|
|
|
+ if err != nil {
|
|
|
+ return false, err
|
|
|
+ }
|
|
|
+ ev, err := fieldValueToDriverTyped(fi.FieldType, raw)
|
|
|
+ if err != nil {
|
|
|
+ return false, err
|
|
|
+ }
|
|
|
+ if c, ok := compareDriverValues(lv, ev); ok && c == 0 {
|
|
|
+ hit = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return hit != t.neg, nil
|
|
|
+ case *isNullExpr:
|
|
|
+ jt, col, err := ownerOfCol(ts, t.col)
|
|
|
+ if err != nil {
|
|
|
+ return false, err
|
|
|
+ }
|
|
|
+ return (r.get(jt, col) == nil) != t.neg, nil
|
|
|
+ case *likeExpr:
|
|
|
+ jt, col, err := ownerOfCol(ts, t.col)
|
|
|
+ if err != nil {
|
|
|
+ return false, err
|
|
|
+ }
|
|
|
+ pat, err := resolveLikePattern(t.pattern, args)
|
|
|
+ if err != nil {
|
|
|
+ return false, err
|
|
|
+ }
|
|
|
+ s, ok := toCmpString(r.get(jt, col))
|
|
|
+ if !ok {
|
|
|
+ // NULL / 非文本: LIKE 不匹配 (NOT LIKE 相应为真)
|
|
|
+ return t.neg, nil
|
|
|
+ }
|
|
|
+ return likeMatch(s, pat) != t.neg, nil
|
|
|
+ }
|
|
|
+ return false, fmt.Errorf("无法求值的 WHERE 表达式")
|
|
|
+}
|
|
|
+
|
|
|
+// compareOp 按比较符求值; 类型不可比视为不匹配。
|
|
|
+func compareOp(l, r driver.Value, op string) bool {
|
|
|
+ c, ok := compareDriverValues(l, r)
|
|
|
+ if !ok {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ switch op {
|
|
|
+ case "=":
|
|
|
+ return c == 0
|
|
|
+ case "!=":
|
|
|
+ return c != 0
|
|
|
+ case "<":
|
|
|
+ return c < 0
|
|
|
+ case "<=":
|
|
|
+ return c <= 0
|
|
|
+ case ">":
|
|
|
+ return c > 0
|
|
|
+ case ">=":
|
|
|
+ return c >= 0
|
|
|
+ }
|
|
|
+ return false
|
|
|
+}
|
|
|
+
|
|
|
+// compareDriverValues 归一化比较两个 driver.Value, 返回 -1/0/1; 类型不可比返回
|
|
|
+// ok=false。NULL 视为最小 (两 NULL 相等); 数值按数值比, 文本按字典序比。
|
|
|
+func compareDriverValues(a, b driver.Value) (int, bool) {
|
|
|
+ if a == nil || b == nil {
|
|
|
+ switch {
|
|
|
+ case a == nil && b == nil:
|
|
|
+ return 0, true
|
|
|
+ case a == nil:
|
|
|
+ return -1, true
|
|
|
+ default:
|
|
|
+ return 1, true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if av, ok := toCmpFloat(a); ok {
|
|
|
+ bv, ok := toCmpFloat(b)
|
|
|
+ if !ok {
|
|
|
+ return 0, false
|
|
|
+ }
|
|
|
+ switch {
|
|
|
+ case av < bv:
|
|
|
+ return -1, true
|
|
|
+ case av > bv:
|
|
|
+ return 1, true
|
|
|
+ default:
|
|
|
+ return 0, true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if av, ok := toCmpString(a); ok {
|
|
|
+ bv, ok := toCmpString(b)
|
|
|
+ if !ok {
|
|
|
+ return 0, false
|
|
|
+ }
|
|
|
+ return strings.Compare(av, bv), true
|
|
|
+ }
|
|
|
+ if av, ok := a.(time.Time); ok {
|
|
|
+ bv, ok := b.(time.Time)
|
|
|
+ if !ok {
|
|
|
+ return 0, false
|
|
|
+ }
|
|
|
+ switch {
|
|
|
+ case av.Before(bv):
|
|
|
+ return -1, true
|
|
|
+ case av.After(bv):
|
|
|
+ return 1, true
|
|
|
+ default:
|
|
|
+ return 0, true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 0, false
|
|
|
+}
|
|
|
+
|
|
|
+// toCmpFloat 数值型 driver.Value → float64 (bool 按 0/1)。
|
|
|
+func toCmpFloat(v driver.Value) (float64, bool) {
|
|
|
+ switch t := v.(type) {
|
|
|
+ case int64:
|
|
|
+ return float64(t), true
|
|
|
+ case float64:
|
|
|
+ return t, true
|
|
|
+ case bool:
|
|
|
+ if t {
|
|
|
+ return 1, true
|
|
|
+ }
|
|
|
+ return 0, true
|
|
|
+ }
|
|
|
+ return 0, false
|
|
|
+}
|
|
|
+
|
|
|
+// toCmpString 文本型 driver.Value → string。
|
|
|
+func toCmpString(v driver.Value) (string, bool) {
|
|
|
+ switch t := v.(type) {
|
|
|
+ case string:
|
|
|
+ return t, true
|
|
|
+ case []byte:
|
|
|
+ return string(t), true
|
|
|
+ }
|
|
|
+ return "", false
|
|
|
+}
|
|
|
+
|
|
|
+// likeMatch SQL LIKE 匹配: % 任意长度任意字符, _ 单字符, 其余原样。
|
|
|
+func likeMatch(s, pat string) bool {
|
|
|
+ sr, pr := []rune(s), []rune(pat)
|
|
|
+ si, pi := 0, 0
|
|
|
+ starP, starS := -1, 0
|
|
|
+ for si < len(sr) {
|
|
|
+ switch {
|
|
|
+ case pi < len(pr) && (pr[pi] == '_' || pr[pi] == sr[si]):
|
|
|
+ si++
|
|
|
+ pi++
|
|
|
+ case pi < len(pr) && pr[pi] == '%':
|
|
|
+ starP, starS = pi, si
|
|
|
+ pi++
|
|
|
+ case starP != -1:
|
|
|
+ pi = starP + 1
|
|
|
+ starS++
|
|
|
+ si = starS
|
|
|
+ default:
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ for pi < len(pr) && pr[pi] == '%' {
|
|
|
+ pi++
|
|
|
+ }
|
|
|
+ return pi == len(pr)
|
|
|
+}
|
|
|
+
|
|
|
+// =============================================================================
|
|
|
+// ON 解析 / 投影 / 排序
|
|
|
+// =============================================================================
|
|
|
+
|
|
|
+// resolvedJoinOn 定好左右的 ON 条件: right 指向本 JOIN 的新表, left 指向其左侧
|
|
|
+// 已连接的表 (允许 "b.y = a.x" 写法, 解析期交换回来)。
|
|
|
+type resolvedJoinOn struct {
|
|
|
+ leftIdx int
|
|
|
+ leftCol string
|
|
|
+ rightIdx int
|
|
|
+ rightCol string
|
|
|
+}
|
|
|
+
|
|
|
+func resolveJoinOn(ts []*joinTable, newIdx int, jc *joinClause) (resolvedJoinOn, error) {
|
|
|
+ li, lcol, err := qualIndexCol(ts, jc.onL)
|
|
|
+ if err != nil {
|
|
|
+ return resolvedJoinOn{}, err
|
|
|
+ }
|
|
|
+ ri, rcol, err := qualIndexCol(ts, jc.onR)
|
|
|
+ if err != nil {
|
|
|
+ return resolvedJoinOn{}, err
|
|
|
+ }
|
|
|
+ if li == newIdx && ri < newIdx {
|
|
|
+ li, lcol, ri, rcol = ri, rcol, li, lcol
|
|
|
+ }
|
|
|
+ if ri != newIdx || li >= newIdx {
|
|
|
+ return resolvedJoinOn{}, fmt.Errorf("JOIN ON 条件 %q = %q 必须连接新表 %q 与其左侧的表", jc.onL, jc.onR, ts[newIdx].alias)
|
|
|
+ }
|
|
|
+ return resolvedJoinOn{leftIdx: li, leftCol: lcol, rightIdx: ri, rightCol: rcol}, nil
|
|
|
+}
|
|
|
+
|
|
|
+// qualIndexCol 解析 ON 里的限定列名为 (表下标, 列名); 必须是限定名且列存在。
|
|
|
+func qualIndexCol(ts []*joinTable, ref string) (int, string, error) {
|
|
|
+ qual, col := splitQualCol(ref)
|
|
|
+ if qual == "" {
|
|
|
+ return -1, "", fmt.Errorf("JOIN ON 列 %q 必须是限定名 (别名.列)", ref)
|
|
|
+ }
|
|
|
+ for i, jt := range ts {
|
|
|
+ if strings.EqualFold(jt.alias, qual) || strings.EqualFold(jt.name, qual) {
|
|
|
+ if findField(jt.tbl, col) == nil {
|
|
|
+ return -1, "", fmt.Errorf("%w: %q", core.ErrFieldNotFound, ref)
|
|
|
+ }
|
|
|
+ return i, col, nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return -1, "", fmt.Errorf("未知的表限定 %q (列 %q)", qual, ref)
|
|
|
+}
|
|
|
+
|
|
|
+// joinProjCol 投影列: 归属表 + 小写列名 (行内取值) + 列元信息 + 展示名 (已去重)。
|
|
|
+type joinProjCol struct {
|
|
|
+ jt *joinTable
|
|
|
+ col string
|
|
|
+ fi *types.FieldInfo
|
|
|
+ name string
|
|
|
+}
|
|
|
+
|
|
|
+// buildJoinProjection 展开 SELECT 清单 (*/t.*/t.col/col 混用); 展示名为列名,
|
|
|
+// 重名列用 "别名_列名" 去重。
|
|
|
+func buildJoinProjection(ts []*joinTable, columns []string) ([]joinProjCol, error) {
|
|
|
+ var out []joinProjCol
|
|
|
+ seen := map[string]bool{}
|
|
|
+ add := func(jt *joinTable, col string) error {
|
|
|
+ fi := findField(jt.tbl, col)
|
|
|
+ if fi == nil {
|
|
|
+ return fmt.Errorf("%w: %q", core.ErrFieldNotFound, col)
|
|
|
+ }
|
|
|
+ name := string(fi.FieldName)
|
|
|
+ if seen[strings.ToLower(name)] {
|
|
|
+ name = jt.alias + "_" + name
|
|
|
+ for seen[strings.ToLower(name)] {
|
|
|
+ name += "_"
|
|
|
+ }
|
|
|
+ }
|
|
|
+ seen[strings.ToLower(name)] = true
|
|
|
+ out = append(out, joinProjCol{jt: jt, col: strings.ToLower(col), fi: fi, name: name})
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ expand := func(jt *joinTable) error {
|
|
|
+ for _, cn := range jt.cols {
|
|
|
+ if err := add(jt, cn); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ expandAll := func() error {
|
|
|
+ for _, jt := range ts {
|
|
|
+ if err := expand(jt); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ if len(columns) == 0 {
|
|
|
+ return out, expandAll()
|
|
|
+ }
|
|
|
+ for _, c := range columns {
|
|
|
+ switch {
|
|
|
+ case c == "*":
|
|
|
+ if err := expandAll(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ case strings.HasSuffix(c, ".*"):
|
|
|
+ jt := findQual(ts, c[:len(c)-2])
|
|
|
+ if jt == nil {
|
|
|
+ return nil, fmt.Errorf("未知的表限定 %q (列 %q)", c[:len(c)-2], c)
|
|
|
+ }
|
|
|
+ if err := expand(jt); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ jt, col, err := ownerOfCol(ts, c) // 限定名/非限定名统一走归属解析
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ if err := add(jt, col); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return out, nil
|
|
|
+}
|
|
|
+
|
|
|
+// joinOrderKey 排序键: 归属表 + 列 + 方向。
|
|
|
+type joinOrderKey struct {
|
|
|
+ jt *joinTable
|
|
|
+ col string
|
|
|
+ desc bool
|
|
|
+}
|
|
|
+
|
|
|
+// resolveJoinOrderKeys 解析 ORDER BY 键 (限定名直查, 非限定名按唯一归属)。
|
|
|
+func resolveJoinOrderKeys(ts []*joinTable, specs []orderSpec) ([]joinOrderKey, error) {
|
|
|
+ keys := make([]joinOrderKey, len(specs))
|
|
|
+ for i, o := range specs {
|
|
|
+ jt, col, err := ownerOfCol(ts, o.col)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ keys[i] = joinOrderKey{jt: jt, col: strings.ToLower(col), desc: o.desc}
|
|
|
+ }
|
|
|
+ return keys, nil
|
|
|
+}
|
|
|
+
|
|
|
+// =============================================================================
|
|
|
+// queryJoin: JOIN 查询主流程
|
|
|
+// =============================================================================
|
|
|
+
|
|
|
+func queryJoin(db *core.DB, sel *selectStmt, args []driver.NamedValue, release func()) (driver.Rows, error) {
|
|
|
+ if release != nil {
|
|
|
+ defer release() // 物化结果集, 算完即放锁
|
|
|
+ }
|
|
|
+ if sel.agg != nil {
|
|
|
+ return nil, fmt.Errorf("JOIN 与聚合函数暂不支持同时使用")
|
|
|
+ }
|
|
|
+
|
|
|
+ // a. 解析所有表并归一化别名 (别名冲突提前报错)
|
|
|
+ refs := make([]tableRef, 0, len(sel.joins)+1)
|
|
|
+ refs = append(refs, sel.from)
|
|
|
+ for _, j := range sel.joins {
|
|
|
+ refs = append(refs, j.table)
|
|
|
+ }
|
|
|
+ ts := make([]*joinTable, len(refs))
|
|
|
+ seenAlias := map[string]bool{}
|
|
|
+ for i, ref := range refs {
|
|
|
+ tbl, err := findTable(db, ref.name)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ ts[i] = buildJoinTable(i, ref, tbl)
|
|
|
+ key := strings.ToLower(ts[i].alias)
|
|
|
+ if seenAlias[key] {
|
|
|
+ return nil, fmt.Errorf("JOIN 表别名 %q 重复", ts[i].alias)
|
|
|
+ }
|
|
|
+ seenAlias[key] = true
|
|
|
+ }
|
|
|
+
|
|
|
+ // b. WHERE 按归属拆分: 单表条件下推, 跨表条件留作残余谓词
|
|
|
+ plan, err := planJoinWhere(ts, sel.where, args)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ // c. 左表扫描 (本表条件下推), 每行包一层别名空间
|
|
|
+ leftRows, err := scanJoinTable(ts[0], plan.perTable[0])
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ rows := make([]joinRow, 0, len(leftRows))
|
|
|
+ for _, m := range leftRows {
|
|
|
+ rows = append(rows, joinRow{strings.ToLower(ts[0].alias): m})
|
|
|
+ }
|
|
|
+
|
|
|
+ // d. 逐 JOIN: 左行 ON 列值 → 右表索引点查 (+右表条件下推), 组合笛卡尔行
|
|
|
+ for i := range sel.joins {
|
|
|
+ jc := &sel.joins[i]
|
|
|
+ jt := ts[i+1]
|
|
|
+ on, err := resolveJoinOn(ts, i+1, jc)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ onFi := findField(jt.tbl, on.rightCol)
|
|
|
+ next := make([]joinRow, 0, len(rows))
|
|
|
+ for _, lr := range rows {
|
|
|
+ onVal := lr.get(ts[on.leftIdx], on.leftCol)
|
|
|
+ var matched []map[string]driver.Value
|
|
|
+ if onVal != nil {
|
|
|
+ // NULL 与任何值都不相等, 右表点查直接跳过 (INNER 丢行 / LEFT 补 NULL)
|
|
|
+ ev, err := driverToEngineValue(onFi, onVal)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("JOIN ON 列 %q: %w", on.rightCol, err)
|
|
|
+ }
|
|
|
+ cond := core.Eq(on.rightCol, ev, onFi.FieldType)
|
|
|
+ if plan.perTable[i+1] != nil {
|
|
|
+ cond = core.And(cond, plan.perTable[i+1])
|
|
|
+ }
|
|
|
+ matched, err = scanJoinTable(jt, cond)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if len(matched) == 0 {
|
|
|
+ if jc.left {
|
|
|
+ nr := cloneJoinRow(lr)
|
|
|
+ nr[strings.ToLower(jt.alias)] = nullJoinRow(jt)
|
|
|
+ next = append(next, nr)
|
|
|
+ }
|
|
|
+ continue // INNER JOIN: 无匹配丢行
|
|
|
+ }
|
|
|
+ for _, rm := range matched {
|
|
|
+ nr := cloneJoinRow(lr)
|
|
|
+ nr[strings.ToLower(jt.alias)] = rm
|
|
|
+ next = append(next, nr)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ rows = next
|
|
|
+ }
|
|
|
+
|
|
|
+ // e. 残余谓词: 组合行上过滤 (跨表比较)
|
|
|
+ if plan.residual != nil {
|
|
|
+ kept := make([]joinRow, 0, len(rows))
|
|
|
+ for _, r := range rows {
|
|
|
+ ok, err := evalJoinExpr(plan.residual, ts, r, args)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ if ok {
|
|
|
+ kept = append(kept, r)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ rows = kept
|
|
|
+ }
|
|
|
+
|
|
|
+ // f. 投影列清单 (*/t.*/t.col/col 混用, 重名列 别名_列名 去重)
|
|
|
+ proj, err := buildJoinProjection(ts, sel.columns)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ // g. ORDER BY: 投影前对组合行内存稳定排序 (NULL 视为最小), 再 LIMIT/OFFSET
|
|
|
+ if len(sel.orderBy) > 0 {
|
|
|
+ keys, err := resolveJoinOrderKeys(ts, sel.orderBy)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ sort.SliceStable(rows, func(x, y int) bool {
|
|
|
+ for _, k := range keys {
|
|
|
+ c, ok := compareDriverValues(rows[x].get(k.jt, k.col), rows[y].get(k.jt, k.col))
|
|
|
+ if !ok || c == 0 {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if k.desc {
|
|
|
+ return c > 0
|
|
|
+ }
|
|
|
+ return c < 0
|
|
|
+ }
|
|
|
+ return false
|
|
|
+ })
|
|
|
+ }
|
|
|
+ if sel.offset != nil {
|
|
|
+ o := int(*sel.offset)
|
|
|
+ if o >= len(rows) {
|
|
|
+ rows = nil
|
|
|
+ } else {
|
|
|
+ rows = rows[o:]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if sel.limit != nil && int(*sel.limit) < len(rows) {
|
|
|
+ rows = rows[:int(*sel.limit)]
|
|
|
+ }
|
|
|
+
|
|
|
+ // 物化投影行 (DISTINCT 按投影值去重)
|
|
|
+ colNames := make([]string, len(proj))
|
|
|
+ metas := make([]colMeta, len(proj))
|
|
|
+ for i, pc := range proj {
|
|
|
+ colNames[i] = pc.name
|
|
|
+ metas[i] = colMetaFor(pc.fi)
|
|
|
+ }
|
|
|
+ data := make([][]driver.Value, 0, len(rows))
|
|
|
+ var seen map[string]bool
|
|
|
+ if sel.distinct {
|
|
|
+ seen = map[string]bool{}
|
|
|
+ }
|
|
|
+ for _, r := range rows {
|
|
|
+ vals := make([]driver.Value, len(proj))
|
|
|
+ for i, pc := range proj {
|
|
|
+ vals[i] = r.get(pc.jt, pc.col) // LEFT JOIN 未匹配侧 → NULL
|
|
|
+ }
|
|
|
+ if seen != nil {
|
|
|
+ key := distinctKey(vals)
|
|
|
+ if seen[key] {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ seen[key] = true
|
|
|
+ }
|
|
|
+ data = append(data, vals)
|
|
|
+ }
|
|
|
+ return materialRows(colNames, metas, data), nil
|
|
|
+}
|