新增: @use 同文件片段复用

支持 @use("name") 引用同一文件内 @tpl 定义的块,
消除 _list/_count 模板中 WHERE 条件重复问题。
This commit is contained in:
2026-04-01 01:59:51 +08:00
parent a6847c7d18
commit 1b5b6aff8f
7 changed files with 190 additions and 8 deletions

View File

@@ -941,4 +941,136 @@ ORDER BY id`
t.Errorf("SQL = %q, want :1/:2 placeholders", result.SQL)
}
})
}
// ---------- 9. TestUse — @use same-file fragment reuse ----------
func TestUse(t *testing.T) {
t.Run("basic @use expands block inline", func(t *testing.T) {
src := `@tpl("where") {
@if(status != "") { AND status = #{status} }
@if(keyword != "") { AND name LIKE #{keyword} }
}
@tpl("list") {
SELECT * FROM users WHERE 1=1 @use("where")
ORDER BY id
}
@tpl("count") {
SELECT COUNT(*) FROM users WHERE 1=1 @use("where")
}`
tpl, err := New().Parse("test", src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
r, err := tpl.ExecuteBlock("list", map[string]any{
"status": "active",
"keyword": "%alice%",
})
if err != nil {
t.Fatalf("execute list failed: %v", err)
}
if !strings.Contains(r.SQL, "AND status = ?") {
t.Errorf("list SQL missing status: %q", r.SQL)
}
if !strings.Contains(r.SQL, "AND name LIKE ?") {
t.Errorf("list SQL missing keyword: %q", r.SQL)
}
if !strings.Contains(r.SQL, "ORDER BY id") {
t.Errorf("list SQL missing ORDER BY: %q", r.SQL)
}
if len(r.Args) != 2 {
t.Errorf("list Args = %v, want 2", r.Args)
}
r2, err := tpl.ExecuteBlock("count", map[string]any{
"status": "active",
"keyword": "",
})
if err != nil {
t.Fatalf("execute count failed: %v", err)
}
if !strings.Contains(r2.SQL, "SELECT COUNT(*)") {
t.Errorf("count SQL missing SELECT COUNT(*): %q", r2.SQL)
}
if !strings.Contains(r2.SQL, "AND status = ?") {
t.Errorf("count SQL missing status: %q", r2.SQL)
}
if strings.Contains(r2.SQL, "AND name LIKE") {
t.Errorf("count SQL should not contain keyword: %q", r2.SQL)
}
if len(r2.Args) != 1 {
t.Errorf("count Args = %v, want 1", r2.Args)
}
})
t.Run("@use inside @if block", func(t *testing.T) {
src := `@tpl("cond") { AND status = #{status} }
@tpl("main") {
SELECT * FROM t WHERE 1=1 @if(filter) { @use("cond") }
}`
tpl, err := New().Parse("test", src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
r, err := tpl.ExecuteBlock("main", map[string]any{"filter": true, "status": "active"})
if err != nil {
t.Fatalf("execute failed: %v", err)
}
if !strings.Contains(r.SQL, "AND status = ?") {
t.Errorf("SQL = %q, want 'AND status = ?'", r.SQL)
}
if len(r.Args) != 1 || r.Args[0] != "active" {
t.Errorf("Args = %v, want [active]", r.Args)
}
r2, err := tpl.ExecuteBlock("main", map[string]any{"filter": false, "status": "active"})
if err != nil {
t.Fatalf("execute failed: %v", err)
}
if strings.Contains(r2.SQL, "AND status") {
t.Errorf("SQL = %q, should not contain condition", r2.SQL)
}
})
t.Run("@use references nonexistent block", func(t *testing.T) {
src := `SELECT * FROM t @use("nonexistent")`
tpl, err := New().Parse("test", src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
_, err = tpl.Execute(nil)
if err == nil {
t.Fatal("expected error for nonexistent @use block")
}
if !strings.Contains(err.Error(), "not found") {
t.Errorf("error = %v, want 'not found'", err)
}
})
t.Run("@use with param args collected correctly", func(t *testing.T) {
src := `@tpl("conds") {
@if(a != nil) { AND a = #{a} }
@if(b != nil) { AND b = #{b} }
}
@tpl("main") {
SELECT * FROM t WHERE 1=1 @use("conds")
}`
tpl, err := New().Parse("test", src)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
r, err := tpl.ExecuteBlock("main", map[string]any{"a": 1, "b": "hello"})
if err != nil {
t.Fatalf("execute failed: %v", err)
}
if len(r.Args) != 2 {
t.Errorf("Args = %v, want 2", r.Args)
}
if r.Args[0] != 1 || r.Args[1] != "hello" {
t.Errorf("Args = %v, want [1, hello]", r.Args)
}
})
}