an ORM-free SQL experience

refactor project entirely

Signed-off-by: oppiliappan <me@oppi.li>

oppi.li 0b277d57 4d7450dc

verified
+83
delete.go
··· 1 + package norm 2 + 3 + import ( 4 + "context" 5 + "database/sql" 6 + "fmt" 7 + "strings" 8 + ) 9 + 10 + type delete_ struct { 11 + from string 12 + where *Expr 13 + } 14 + 15 + func Delete() delete_ { 16 + return delete_{} 17 + } 18 + 19 + type DeleteOpt func(s *delete_) 20 + 21 + func (s delete_) From(table string) delete_ { 22 + s.from = table 23 + return s 24 + } 25 + 26 + func (s delete_) Where(expr Expr) delete_ { 27 + s.where = &expr 28 + return s 29 + } 30 + 31 + func (s delete_) Compile() (string, []any, error) { 32 + var sql strings.Builder 33 + var args []any 34 + 35 + sql.WriteString("DELETE ") 36 + 37 + if s.from == "" { 38 + return "", nil, fmt.Errorf("FROM clause is required") 39 + } 40 + sql.WriteString(" FROM ") 41 + sql.WriteString(s.from) 42 + 43 + if s.where != nil { 44 + sql.WriteString(" WHERE ") 45 + sql.WriteString(s.where.String()) 46 + 47 + args = s.where.Binds() 48 + } 49 + 50 + return sql.String(), args, nil 51 + } 52 + 53 + func (s delete_) Build(p Database) (*sql.Stmt, []any, error) { 54 + return Build(s, p) 55 + } 56 + 57 + func (s delete_) MustBuild(p Database) (*sql.Stmt, []any) { 58 + return MustBuild(s, p) 59 + } 60 + 61 + func (s delete_) Exec(p Database) (sql.Result, error) { 62 + return Exec(s, p) 63 + } 64 + 65 + func (s delete_) ExecContext(ctx context.Context, p Database) (sql.Result, error) { 66 + return ExecContext(ctx, s, p) 67 + } 68 + 69 + func (s delete_) Query(p Database) (*sql.Rows, error) { 70 + return Query(s, p) 71 + } 72 + 73 + func (s delete_) QueryContext(ctx context.Context, p Database) (*sql.Rows, error) { 74 + return QueryContext(ctx, s, p) 75 + } 76 + 77 + func (s delete_) QueryRow(p Database) (*sql.Row, error) { 78 + return QueryRow(s, p) 79 + } 80 + 81 + func (s delete_) QueryRowContext(ctx context.Context, p Database) (*sql.Row, error) { 82 + return QueryRowContext(ctx, s, p) 83 + }
+69
delete_test.go
··· 1 + package norm 2 + 3 + import ( 4 + _ "github.com/mattn/go-sqlite3" 5 + "testing" 6 + ) 7 + 8 + func TestDeleteIntegration_BasicQueries(t *testing.T) { 9 + tests := []struct { 10 + name string 11 + stmt Execer 12 + expectedRows int64 13 + }{ 14 + { 15 + name: "Delete all users", 16 + stmt: Delete().From("users"), 17 + expectedRows: 6, 18 + }, 19 + { 20 + name: "Delete active users only", 21 + stmt: Delete(). 22 + From("users"). 23 + Where(Eq("active", true)), 24 + expectedRows: 4, 25 + }, 26 + { 27 + name: "Select users in Engineering", 28 + stmt: Delete(). 29 + From("users"). 30 + Where(Eq("department", "Engineering")), 31 + expectedRows: 3, 32 + }, 33 + { 34 + name: "Delete users with age > 30", 35 + stmt: Delete(). 36 + From("users"). 37 + Where(Gt("age", 30)), 38 + expectedRows: 2, 39 + }, 40 + { 41 + name: "Delete users with salary between 70000 and 80000", 42 + stmt: Delete(). 43 + From("users"). 44 + Where(Gte("salary", 70000.0).And(Lte("salary", 80000.0))), 45 + expectedRows: 3, 46 + }, 47 + } 48 + 49 + for _, test := range tests { 50 + t.Run(test.name, func(t *testing.T) { 51 + db := setupTestDB(t) 52 + defer db.Close() 53 + 54 + res, err := test.stmt.Exec(db) 55 + if err != nil { 56 + t.Fatalf("Failed to execute query: %v", err) 57 + } 58 + 59 + count, err := res.RowsAffected() 60 + if err != nil { 61 + t.Fatalf("Failed to execute query: %v", err) 62 + } 63 + 64 + if count != test.expectedRows { 65 + t.Errorf("Expected %d rows, got %d", test.expectedRows, count) 66 + } 67 + }) 68 + } 69 + }
+120
expr.go
··· 1 + package norm 2 + 3 + import "fmt" 4 + 5 + type op string 6 + 7 + type Expr struct { 8 + kind ExprKind 9 + 10 + ident *IdentExpr 11 + binary *BinExpr 12 + value *ValueExpr 13 + } 14 + 15 + func (e Expr) String() string { 16 + switch e.kind { 17 + case exprKindIdent: 18 + return e.ident.String() 19 + case exprKindBinary: 20 + return e.binary.String() 21 + case exprKindValue: 22 + return e.value.String() 23 + } 24 + 25 + // unreachable 26 + return "" 27 + } 28 + 29 + func (e Expr) Binds() []any { 30 + switch e.kind { 31 + case exprKindIdent: 32 + return e.ident.Binds() 33 + case exprKindBinary: 34 + return e.binary.Binds() 35 + case exprKindValue: 36 + return e.value.Binds() 37 + } 38 + 39 + return nil 40 + } 41 + 42 + type ExprKind int 43 + 44 + const ( 45 + exprKindIdent ExprKind = iota 46 + exprKindBinary 47 + exprKindValue 48 + ) 49 + 50 + type IdentExpr string 51 + 52 + func (i IdentExpr) String() string { 53 + return string(i) 54 + } 55 + 56 + func (i IdentExpr) Binds() []any { 57 + return nil 58 + } 59 + 60 + func (i IdentExpr) AsExpr() Expr { 61 + return Expr{ 62 + kind: exprKindIdent, 63 + ident: &i, 64 + } 65 + } 66 + 67 + type ValueExpr struct { 68 + inner any 69 + } 70 + 71 + func (v ValueExpr) String() string { 72 + return "?" 73 + } 74 + 75 + func (v ValueExpr) Binds() []any { 76 + return []any{v.inner} 77 + } 78 + 79 + func (v ValueExpr) AsExpr() Expr { 80 + return Expr{ 81 + kind: exprKindValue, 82 + value: &v, 83 + } 84 + } 85 + 86 + type BinExpr struct { 87 + left Expr 88 + op op 89 + right Expr 90 + } 91 + 92 + func (b BinExpr) String() string { 93 + return fmt.Sprintf("(%s) %s (%s)", b.left.String(), b.op, b.right.String()) 94 + } 95 + 96 + func (b BinExpr) Binds() []any { 97 + binds := b.left.Binds() 98 + binds = append(binds, b.right.Binds()...) 99 + return binds 100 + } 101 + 102 + func (b BinExpr) AsExpr() Expr { 103 + return Expr{ 104 + kind: exprKindBinary, 105 + binary: &b, 106 + } 107 + } 108 + 109 + func buildBinExpr(left IdentExpr, op op, right any) Expr { 110 + return BinExpr{left.AsExpr(), op, ValueExpr{right}.AsExpr()}.AsExpr() 111 + } 112 + func Eq(left string, right any) Expr { return buildBinExpr(IdentExpr(left), "=", right) } 113 + func Neq(left string, right any) Expr { return buildBinExpr(IdentExpr(left), "<>", right) } 114 + func Gt(left string, right any) Expr { return buildBinExpr(IdentExpr(left), ">", right) } 115 + func Gte(left string, right any) Expr { return buildBinExpr(IdentExpr(left), ">=", right) } 116 + func Lt(left string, right any) Expr { return buildBinExpr(IdentExpr(left), "<", right) } 117 + func Lte(left string, right any) Expr { return buildBinExpr(IdentExpr(left), "<=", right) } 118 + 119 + func (l Expr) And(r Expr) Expr { return BinExpr{l, "and", r}.AsExpr() } 120 + func (l Expr) Or(r Expr) Expr { return BinExpr{l, "or", r}.AsExpr() }
+102
expr_test.go
··· 1 + package norm 2 + 3 + import ( 4 + "testing" 5 + ) 6 + 7 + func TestIdentExpr(t *testing.T) { 8 + ident := IdentExpr("username") 9 + 10 + if ident.String() != "username" { 11 + t.Errorf("Expected 'username', got '%s'", ident.String()) 12 + } 13 + 14 + if ident.Binds() != nil { 15 + t.Errorf("Expected nil binds, got %v", ident.Binds()) 16 + } 17 + 18 + expr := ident.AsExpr() 19 + if expr.kind != exprKindIdent { 20 + t.Errorf("Expected exprKindIdent, got %d", expr.kind) 21 + } 22 + 23 + if expr.String() != "username" { 24 + t.Errorf("Expected 'username', got '%s'", expr.String()) 25 + } 26 + } 27 + 28 + func TestValueExpr(t *testing.T) { 29 + value := ValueExpr{inner: "test"} 30 + 31 + if value.String() != "?" { 32 + t.Errorf("Expected '?', got '%s'", value.String()) 33 + } 34 + 35 + if value.Binds()[0] != "test" { 36 + t.Errorf("Expected %q, got %v", []any{"test"}, value.Binds()) 37 + } 38 + 39 + expr := value.AsExpr() 40 + if expr.kind != exprKindValue { 41 + t.Errorf("Expected exprKindValue, got %d", expr.kind) 42 + } 43 + } 44 + 45 + func TestBinaryExpressions(t *testing.T) { 46 + tests := []struct { 47 + name string 48 + expr Expr 49 + expected string 50 + }{ 51 + {"Eq", Eq("age", 25), "(age) = (?)"}, 52 + {"Neq", Neq("status", "active"), "(status) <> (?)"}, 53 + {"Gt", Gt("score", 100), "(score) > (?)"}, 54 + {"Gte", Gte("rating", 4.5), "(rating) >= (?)"}, 55 + {"Lt", Lt("count", 10), "(count) < (?)"}, 56 + {"Lte", Lte("price", 99.99), "(price) <= (?)"}, 57 + } 58 + 59 + for _, test := range tests { 60 + t.Run(test.name, func(t *testing.T) { 61 + if test.expr.String() != test.expected { 62 + t.Errorf("Expected '%s', got '%s'", test.expected, test.expr.String()) 63 + } 64 + 65 + if test.expr.kind != exprKindBinary { 66 + t.Errorf("Expected exprKindBinary, got %d", test.expr.kind) 67 + } 68 + }) 69 + } 70 + } 71 + 72 + func TestLogicalOperators(t *testing.T) { 73 + left := Eq("age", 25) 74 + right := Eq("status", "active") 75 + 76 + andExpr := left.And(right) 77 + expectedAnd := "((age) = (?)) and ((status) = (?))" 78 + if andExpr.String() != expectedAnd { 79 + t.Errorf("Expected '%s', got '%s'", expectedAnd, andExpr.String()) 80 + } 81 + 82 + orExpr := left.Or(right) 83 + expectedOr := "((age) = (?)) or ((status) = (?))" 84 + if orExpr.String() != expectedOr { 85 + t.Errorf("Expected '%s', got '%s'", expectedOr, orExpr.String()) 86 + } 87 + } 88 + 89 + func TestComplexExpressions(t *testing.T) { 90 + age := Eq("age", 25) 91 + status := Eq("status", "active") 92 + score := Gt("score", 100) 93 + 94 + complex := age. 95 + And(status). 96 + Or(score) 97 + expected := "(((age) = (?)) and ((status) = (?))) or ((score) > (?))" 98 + 99 + if complex.String() != expected { 100 + t.Errorf("Expected '%s', got '%s'", expected, complex.String()) 101 + } 102 + }
+143
insert.go
··· 1 + package norm 2 + 3 + import ( 4 + "context" 5 + "database/sql" 6 + "fmt" 7 + "strings" 8 + ) 9 + 10 + type insert struct { 11 + into string 12 + or InsertOr 13 + values []struct { 14 + col string 15 + val any 16 + } 17 + } 18 + 19 + type InsertOr int 20 + 21 + const ( 22 + None InsertOr = iota 23 + Abort 24 + Fail 25 + Ignore 26 + Replace 27 + Rollback 28 + ) 29 + 30 + func (i InsertOr) String() string { 31 + switch i { 32 + case Abort: 33 + return "ABORT" 34 + case Fail: 35 + return "FAIL" 36 + case Ignore: 37 + return "IGNORE" 38 + case Replace: 39 + return "REPLACE" 40 + case Rollback: 41 + return "ROLLBACK" 42 + default: 43 + return "" 44 + } 45 + } 46 + 47 + func Insert() insert { 48 + return insert{} 49 + } 50 + 51 + type InsertOpt func(s *insert) 52 + 53 + func (s insert) Into(table string) insert { 54 + s.into = table 55 + return s 56 + } 57 + 58 + func (s insert) Or(option InsertOr) insert { 59 + s.or = option 60 + return s 61 + } 62 + 63 + func (s insert) Value(column string, arg any) insert { 64 + s.values = append(s.values, struct { 65 + col string 66 + val any 67 + }{ 68 + column, arg, 69 + }) 70 + return s 71 + } 72 + 73 + func (s insert) Compile() (string, []any, error) { 74 + var sql strings.Builder 75 + var args []any 76 + 77 + sql.WriteString("INSERT ") 78 + 79 + if s.or != None { 80 + sql.WriteString("OR ") 81 + sql.WriteString(s.or.String()) 82 + sql.WriteString(" ") 83 + } 84 + 85 + if s.into == "" { 86 + return "", nil, fmt.Errorf("INTO clause is required") 87 + } 88 + sql.WriteString("INTO ") 89 + sql.WriteString(s.into) 90 + 91 + if len(s.values) == 0 { 92 + return "", nil, fmt.Errorf("no values supplied") 93 + } 94 + 95 + sql.WriteString(" (") 96 + 97 + for i, v := range s.values { 98 + if i != 0 { 99 + sql.WriteString(", ") 100 + } 101 + 102 + sql.WriteString(v.col) 103 + args = append(args, v.val) 104 + } 105 + 106 + sql.WriteString(") VALUES (") 107 + sql.WriteString(strings.TrimSuffix(strings.Repeat("?, ", len(s.values)), ", ")) 108 + sql.WriteString(")") 109 + 110 + return sql.String(), args, nil 111 + } 112 + 113 + func (s insert) Build(p Database) (*sql.Stmt, []any, error) { 114 + return Build(s, p) 115 + } 116 + 117 + func (s insert) MustBuild(p Database) (*sql.Stmt, []any) { 118 + return MustBuild(s, p) 119 + } 120 + 121 + func (s insert) Exec(p Database) (sql.Result, error) { 122 + return Exec(s, p) 123 + } 124 + 125 + func (s insert) ExecContext(ctx context.Context, p Database) (sql.Result, error) { 126 + return ExecContext(ctx, s, p) 127 + } 128 + 129 + func (s insert) Query(p Database) (*sql.Rows, error) { 130 + return Query(s, p) 131 + } 132 + 133 + func (s insert) QueryContext(ctx context.Context, p Database) (*sql.Rows, error) { 134 + return QueryContext(ctx, s, p) 135 + } 136 + 137 + func (s insert) QueryRow(p Database) (*sql.Row, error) { 138 + return QueryRow(s, p) 139 + } 140 + 141 + func (s insert) QueryRowContext(ctx context.Context, p Database) (*sql.Row, error) { 142 + return QueryRowContext(ctx, s, p) 143 + }
+58
insert_test.go
··· 1 + package norm 2 + 3 + import ( 4 + _ "github.com/mattn/go-sqlite3" 5 + "testing" 6 + ) 7 + 8 + func TestInsertBuild_Success(t *testing.T) { 9 + tests := []struct { 10 + name string 11 + stmt Compiler 12 + expectedSql string 13 + expectedArgs []any 14 + }{ 15 + { 16 + name: "Simple insert", 17 + stmt: Insert().Into("users").Value("name", "John"), 18 + expectedSql: "INSERT INTO users (name) VALUES (?)", 19 + expectedArgs: []any{"John"}, 20 + }, 21 + { 22 + name: "Replace clause", 23 + stmt: Insert().Or(Replace).Into("users").Value("name", "John"), 24 + expectedSql: "INSERT OR REPLACE INTO users (name) VALUES (?)", 25 + expectedArgs: []any{"John"}, 26 + }, 27 + { 28 + name: "More values", 29 + stmt: Insert().Into("users").Value("name", "John").Value("age", 35), 30 + expectedSql: "INSERT INTO users (name, age) VALUES (?, ?)", 31 + expectedArgs: []any{"John", 35}, 32 + }, 33 + } 34 + 35 + for _, test := range tests { 36 + t.Run(test.name, func(t *testing.T) { 37 + sql, args, err := test.stmt.Compile() 38 + 39 + if err != nil { 40 + t.Errorf("Expected no error, got %v", err) 41 + } 42 + 43 + if sql != test.expectedSql { 44 + t.Errorf("Expected '%s', got '%s'", test.expectedSql, sql) 45 + } 46 + 47 + if len(args) != len(test.expectedArgs) { 48 + t.Errorf("Expected '%d' args, got '%d' args", len(test.expectedArgs), len(args)) 49 + } 50 + 51 + for i := range len(args) { 52 + if args[i] != test.expectedArgs[i] { 53 + t.Errorf("Expected '%v', got '%v' at index %d", test.expectedArgs[i], args[i], i) 54 + } 55 + } 56 + }) 57 + } 58 + }
-434
query.go
··· 1 - package norm 2 - 3 - import ( 4 - "fmt" 5 - "strconv" 6 - "strings" 7 - ) 8 - 9 - // use with prepared statements 10 - func Placeholder[T any]() T { 11 - var zero T 12 - return zero 13 - } 14 - 15 - type op string 16 - 17 - type Expr struct { 18 - kind ExprKind 19 - 20 - ident *IdentExpr 21 - binary *BinExpr 22 - value *ValueExpr 23 - } 24 - 25 - func (e Expr) String() string { 26 - switch e.kind { 27 - case exprKindIdent: 28 - return e.ident.String() 29 - case exprKindBinary: 30 - return e.binary.String() 31 - case exprKindValue: 32 - return e.value.String() 33 - } 34 - 35 - // unreachable 36 - return "" 37 - } 38 - 39 - func (e Expr) Binds() []any { 40 - switch e.kind { 41 - case exprKindIdent: 42 - return e.ident.Binds() 43 - case exprKindBinary: 44 - return e.binary.Binds() 45 - case exprKindValue: 46 - return e.value.Binds() 47 - } 48 - 49 - return nil 50 - } 51 - 52 - type ExprKind int 53 - 54 - const ( 55 - exprKindIdent ExprKind = iota 56 - exprKindBinary 57 - exprKindValue 58 - ) 59 - 60 - type IdentExpr string 61 - 62 - func (i IdentExpr) String() string { 63 - return string(i) 64 - } 65 - 66 - func (i IdentExpr) Binds() []any { 67 - return nil 68 - } 69 - 70 - func (i IdentExpr) AsExpr() Expr { 71 - return Expr{ 72 - kind: exprKindIdent, 73 - ident: &i, 74 - } 75 - } 76 - 77 - type ValueExpr struct { 78 - inner any 79 - } 80 - 81 - func (v ValueExpr) String() string { 82 - return "?" 83 - } 84 - 85 - func (v ValueExpr) Binds() []any { 86 - return []any{v.inner} 87 - } 88 - 89 - func (v ValueExpr) AsExpr() Expr { 90 - return Expr{ 91 - kind: exprKindValue, 92 - value: &v, 93 - } 94 - } 95 - 96 - type BinExpr struct { 97 - left Expr 98 - op op 99 - right Expr 100 - } 101 - 102 - func (b BinExpr) String() string { 103 - return fmt.Sprintf("(%s) %s (%s)", b.left.String(), b.op, b.right.String()) 104 - } 105 - 106 - func (b BinExpr) Binds() []any { 107 - binds := b.left.Binds() 108 - binds = append(binds, b.right.Binds()...) 109 - return binds 110 - } 111 - 112 - func (b BinExpr) AsExpr() Expr { 113 - return Expr{ 114 - kind: exprKindBinary, 115 - binary: &b, 116 - } 117 - } 118 - 119 - func buildBinExpr(left IdentExpr, op op, right any) Expr { 120 - return BinExpr{left.AsExpr(), op, ValueExpr{right}.AsExpr()}.AsExpr() 121 - } 122 - func Eq(left string, right any) Expr { return buildBinExpr(IdentExpr(left), "=", right) } 123 - func Neq(left string, right any) Expr { return buildBinExpr(IdentExpr(left), "<>", right) } 124 - func Gt(left string, right any) Expr { return buildBinExpr(IdentExpr(left), ">", right) } 125 - func Gte(left string, right any) Expr { return buildBinExpr(IdentExpr(left), ">=", right) } 126 - func Lt(left string, right any) Expr { return buildBinExpr(IdentExpr(left), "<", right) } 127 - func Lte(left string, right any) Expr { return buildBinExpr(IdentExpr(left), "<=", right) } 128 - 129 - func (l Expr) And(r Expr) Expr { return BinExpr{l, "and", r}.AsExpr() } 130 - func (l Expr) Or(r Expr) Expr { return BinExpr{l, "or", r}.AsExpr() } 131 - 132 - type select_ struct { 133 - resultColumns []string // TODO: strongly type result columns 134 - from string // table-or-subquery expr 135 - where *Expr 136 - orderBy []orderBy 137 - groupBy []groupBy 138 - limit *limit 139 - } 140 - 141 - type orderBy struct { 142 - field string 143 - direction Direction 144 - } 145 - 146 - type groupBy struct { 147 - field string 148 - } 149 - 150 - type limit struct { 151 - limit int 152 - } 153 - 154 - func Select(cols ...string) select_ { 155 - return select_{ 156 - resultColumns: cols, 157 - } 158 - } 159 - 160 - type SelectOpt func(s *select_) 161 - 162 - func (s select_) From(table string) select_ { 163 - s.from = table 164 - return s 165 - } 166 - 167 - func (s select_) Where(expr Expr) select_ { 168 - s.where = &expr 169 - return s 170 - } 171 - 172 - type Direction string 173 - 174 - const ( 175 - Ascending Direction = "asc" 176 - Descending Direction = "desc" 177 - ) 178 - 179 - func (s select_) OrderBy(field string, direction Direction) select_ { 180 - s.orderBy = append(s.orderBy, orderBy{ 181 - field: field, 182 - direction: direction, 183 - }) 184 - return s 185 - } 186 - 187 - func (s select_) GroupBy(field string) select_ { 188 - s.groupBy = append(s.groupBy, groupBy{ 189 - field: field, 190 - }) 191 - return s 192 - } 193 - 194 - func (s select_) Limit(i int) select_ { 195 - s.limit = &limit{ 196 - limit: i, 197 - } 198 - return s 199 - } 200 - 201 - func (s select_) Build() (string, []any, error) { 202 - var sql strings.Builder 203 - var args []any 204 - 205 - sql.WriteString("SELECT ") 206 - if len(s.resultColumns) == 0 { 207 - return "", nil, fmt.Errorf("result columns empty") 208 - } else { 209 - for i, col := range s.resultColumns { 210 - if i > 0 { 211 - sql.WriteString(", ") 212 - } 213 - sql.WriteString(col) 214 - } 215 - } 216 - 217 - if s.from == "" { 218 - return "", nil, fmt.Errorf("FROM clause is required") 219 - } 220 - sql.WriteString(" FROM ") 221 - sql.WriteString(s.from) 222 - 223 - if s.where != nil { 224 - sql.WriteString(" WHERE ") 225 - sql.WriteString(s.where.String()) 226 - 227 - args = s.where.Binds() 228 - } 229 - 230 - // GROUP BY clause 231 - if len(s.groupBy) > 0 { 232 - sql.WriteString(" GROUP BY ") 233 - for i, gb := range s.groupBy { 234 - if i > 0 { 235 - sql.WriteString(", ") 236 - } 237 - sql.WriteString(gb.field) 238 - } 239 - } 240 - 241 - // ORDER BY clause 242 - if len(s.orderBy) > 0 { 243 - sql.WriteString(" ORDER BY ") 244 - for i, ob := range s.orderBy { 245 - if i > 0 { 246 - sql.WriteString(", ") 247 - } 248 - sql.WriteString(ob.field) 249 - sql.WriteString(" ") 250 - sql.WriteString(string(ob.direction)) 251 - } 252 - } 253 - 254 - // LIMIT clause 255 - if s.limit != nil { 256 - if s.limit.limit <= 0 { 257 - return "", nil, fmt.Errorf("LIMIT must be positive, got %d", s.limit.limit) 258 - } 259 - sql.WriteString(" LIMIT ") 260 - sql.WriteString(strconv.Itoa(s.limit.limit)) 261 - } 262 - 263 - return sql.String(), args, nil 264 - } 265 - 266 - func (s select_) MustBuild() (string, []any) { 267 - sql, args, err := s.Build() 268 - if err != nil { 269 - panic(err) 270 - } 271 - return sql, args 272 - } 273 - 274 - type delete_ struct { 275 - from string 276 - where *Expr 277 - } 278 - 279 - func Delete() delete_ { 280 - return delete_{} 281 - } 282 - 283 - type DeleteOpt func(s *delete_) 284 - 285 - func (s delete_) From(table string) delete_ { 286 - s.from = table 287 - return s 288 - } 289 - 290 - func (s delete_) Where(expr Expr) delete_ { 291 - s.where = &expr 292 - return s 293 - } 294 - 295 - func (s delete_) Build() (string, []any, error) { 296 - var sql strings.Builder 297 - var args []any 298 - 299 - sql.WriteString("DELETE ") 300 - 301 - if s.from == "" { 302 - return "", nil, fmt.Errorf("FROM clause is required") 303 - } 304 - sql.WriteString(" FROM ") 305 - sql.WriteString(s.from) 306 - 307 - if s.where != nil { 308 - sql.WriteString(" WHERE ") 309 - sql.WriteString(s.where.String()) 310 - 311 - args = s.where.Binds() 312 - } 313 - 314 - return sql.String(), args, nil 315 - } 316 - 317 - func (s delete_) MustBuild() (string, []any) { 318 - sql, args, err := s.Build() 319 - if err != nil { 320 - panic(err) 321 - } 322 - return sql, args 323 - } 324 - 325 - type insert struct { 326 - into string 327 - or InsertOr 328 - values []struct { 329 - col string 330 - val any 331 - } 332 - } 333 - 334 - type InsertOr int 335 - 336 - const ( 337 - None InsertOr = iota 338 - Abort 339 - Fail 340 - Ignore 341 - Replace 342 - Rollback 343 - ) 344 - 345 - func (i InsertOr) String() string { 346 - switch i { 347 - case Abort: 348 - return "ABORT" 349 - case Fail: 350 - return "FAIL" 351 - case Ignore: 352 - return "IGNORE" 353 - case Replace: 354 - return "REPLACE" 355 - case Rollback: 356 - return "ROLLBACK" 357 - default: 358 - return "" 359 - } 360 - } 361 - 362 - func Insert() insert { 363 - return insert{} 364 - } 365 - 366 - type InsertOpt func(s *insert) 367 - 368 - func (s insert) Into(table string) insert { 369 - s.into = table 370 - return s 371 - } 372 - 373 - func (s insert) Or(option InsertOr) insert { 374 - s.or = option 375 - return s 376 - } 377 - 378 - func (s insert) Value(column string, arg any) insert { 379 - s.values = append(s.values, struct { 380 - col string 381 - val any 382 - }{ 383 - column, arg, 384 - }) 385 - return s 386 - } 387 - 388 - func (s insert) Build() (string, []any, error) { 389 - var sql strings.Builder 390 - var args []any 391 - 392 - sql.WriteString("INSERT ") 393 - 394 - if s.or != None { 395 - sql.WriteString("OR ") 396 - sql.WriteString(s.or.String()) 397 - sql.WriteString(" ") 398 - } 399 - 400 - if s.into == "" { 401 - return "", nil, fmt.Errorf("INTO clause is required") 402 - } 403 - sql.WriteString("INTO ") 404 - sql.WriteString(s.into) 405 - 406 - if len(s.values) == 0 { 407 - return "", nil, fmt.Errorf("no values supplied") 408 - } 409 - 410 - sql.WriteString(" (") 411 - 412 - for i, v := range s.values { 413 - if i != 0 { 414 - sql.WriteString(", ") 415 - } 416 - 417 - sql.WriteString(v.col) 418 - args = append(args, v.val) 419 - } 420 - 421 - sql.WriteString(") VALUES (") 422 - sql.WriteString(strings.TrimSuffix(strings.Repeat("?, ", len(s.values)), ", ")) 423 - sql.WriteString(")") 424 - 425 - return sql.String(), args, nil 426 - } 427 - 428 - func (s insert) MustBuild() (string, []any) { 429 - sql, args, err := s.Build() 430 - if err != nil { 431 - panic(err) 432 - } 433 - return sql, args 434 - }
+13 -261
query_test.go select_test.go
··· 1 1 package norm 2 2 3 3 import ( 4 + _ "github.com/mattn/go-sqlite3" 4 5 "testing" 5 - 6 - _ "github.com/mattn/go-sqlite3" 7 6 ) 8 7 9 - func TestIdentExpr(t *testing.T) { 10 - ident := IdentExpr("username") 11 - 12 - if ident.String() != "username" { 13 - t.Errorf("Expected 'username', got '%s'", ident.String()) 14 - } 15 - 16 - if ident.Binds() != nil { 17 - t.Errorf("Expected nil binds, got %v", ident.Binds()) 18 - } 19 - 20 - expr := ident.AsExpr() 21 - if expr.kind != exprKindIdent { 22 - t.Errorf("Expected exprKindIdent, got %d", expr.kind) 23 - } 24 - 25 - if expr.String() != "username" { 26 - t.Errorf("Expected 'username', got '%s'", expr.String()) 27 - } 28 - } 29 - 30 - func TestValueExpr(t *testing.T) { 31 - value := ValueExpr{inner: "test"} 32 - 33 - if value.String() != "?" { 34 - t.Errorf("Expected '?', got '%s'", value.String()) 35 - } 36 - 37 - if value.Binds()[0] != "test" { 38 - t.Errorf("Expected %q, got %v", []any{"test"}, value.Binds()) 39 - } 40 - 41 - expr := value.AsExpr() 42 - if expr.kind != exprKindValue { 43 - t.Errorf("Expected exprKindValue, got %d", expr.kind) 44 - } 45 - } 46 - 47 - func TestBinaryExpressions(t *testing.T) { 48 - tests := []struct { 49 - name string 50 - expr Expr 51 - expected string 52 - }{ 53 - {"Eq", Eq("age", 25), "(age) = (?)"}, 54 - {"Neq", Neq("status", "active"), "(status) <> (?)"}, 55 - {"Gt", Gt("score", 100), "(score) > (?)"}, 56 - {"Gte", Gte("rating", 4.5), "(rating) >= (?)"}, 57 - {"Lt", Lt("count", 10), "(count) < (?)"}, 58 - {"Lte", Lte("price", 99.99), "(price) <= (?)"}, 59 - } 60 - 61 - for _, test := range tests { 62 - t.Run(test.name, func(t *testing.T) { 63 - if test.expr.String() != test.expected { 64 - t.Errorf("Expected '%s', got '%s'", test.expected, test.expr.String()) 65 - } 66 - 67 - if test.expr.kind != exprKindBinary { 68 - t.Errorf("Expected exprKindBinary, got %d", test.expr.kind) 69 - } 70 - }) 71 - } 72 - } 73 - 74 - func TestLogicalOperators(t *testing.T) { 75 - left := Eq("age", 25) 76 - right := Eq("status", "active") 77 - 78 - andExpr := left.And(right) 79 - expectedAnd := "((age) = (?)) and ((status) = (?))" 80 - if andExpr.String() != expectedAnd { 81 - t.Errorf("Expected '%s', got '%s'", expectedAnd, andExpr.String()) 82 - } 83 - 84 - orExpr := left.Or(right) 85 - expectedOr := "((age) = (?)) or ((status) = (?))" 86 - if orExpr.String() != expectedOr { 87 - t.Errorf("Expected '%s', got '%s'", expectedOr, orExpr.String()) 88 - } 89 - } 90 - 91 - func TestComplexExpressions(t *testing.T) { 92 - age := Eq("age", 25) 93 - status := Eq("status", "active") 94 - score := Gt("score", 100) 95 - 96 - complex := age. 97 - And(status). 98 - Or(score) 99 - expected := "(((age) = (?)) and ((status) = (?))) or ((score) > (?))" 100 - 101 - if complex.String() != expected { 102 - t.Errorf("Expected '%s', got '%s'", expected, complex.String()) 103 - } 104 - } 105 - 106 8 func TestSelectBasic(t *testing.T) { 107 9 s := Select("name", "age") 108 10 ··· 159 61 func TestSelectBuild_Success(t *testing.T) { 160 62 tests := []struct { 161 63 name string 162 - stmt select_ 64 + stmt Compiler 163 65 expectedSql string 164 66 expectedArgs []any 165 67 }{ ··· 225 127 226 128 for _, test := range tests { 227 129 t.Run(test.name, func(t *testing.T) { 228 - sql, args, err := test.stmt.Build() 130 + sql, args, err := test.stmt.Compile() 229 131 230 132 if err != nil { 231 133 t.Errorf("Expected no error, got %v", err) ··· 251 153 func TestSelectBuild_Errors(t *testing.T) { 252 154 tests := []struct { 253 155 name string 254 - stmt select_ 156 + stmt Compiler 255 157 expectedError string 256 158 }{ 257 159 { ··· 282 184 283 185 for _, test := range tests { 284 186 t.Run(test.name, func(t *testing.T) { 285 - sql, args, err := test.stmt.Build() 187 + sql, args, err := test.stmt.Compile() 286 188 287 189 if err == nil { 288 190 t.Error("Expected error, got nil") ··· 300 202 t.Errorf("Expected empty args on error, got '%q'", args) 301 203 } 302 204 }) 303 - } 304 - } 305 - 306 - func TestDirections(t *testing.T) { 307 - if Ascending != "asc" { 308 - t.Errorf("Expected ASC to be 'asc', got '%s'", Ascending) 309 - } 310 - 311 - if Descending != "desc" { 312 - t.Errorf("Expected DESC to be 'desc', got '%s'", Descending) 313 205 } 314 206 } 315 207 ··· 402 294 403 295 for _, test := range tests { 404 296 t.Run(test.name, func(t *testing.T) { 405 - sql, args, err := test.stmt.Build() 297 + rows, err := test.stmt.Query(db) 406 298 if err != nil { 407 299 t.Fatalf("Failed to build query: %v", err) 408 300 } 409 301 410 - rows, err := db.Query(sql, args...) 411 - if err != nil { 412 - t.Fatalf("Failed to execute query: %v", err) 413 - } 414 - defer rows.Close() 415 - 416 302 count := 0 417 303 for rows.Next() { 418 304 count++ ··· 438 324 Or(Eq("department", "Marketing").And(Lt("age", 30))), 439 325 ) 440 326 441 - sql, args := s.MustBuild() 442 - 443 - rows, err := db.Query(sql, args...) 327 + rows, err := s.Query(db) 444 328 if err != nil { 445 329 t.Fatalf("Failed to execute query: %v", err) 446 330 } ··· 479 363 OrderBy("department", Ascending). 480 364 OrderBy("age", Descending) 481 365 482 - sql, args := s.MustBuild() 483 - 484 - rows, err := db.Query(sql, args...) 366 + rows, err := s.Query(db) 485 367 if err != nil { 486 368 t.Fatalf("Failed to execute query: %v", err) 487 369 } ··· 522 404 GroupBy("department"). 523 405 OrderBy("user_count", Descending) 524 406 525 - sql, args := s.MustBuild() 526 - 527 - rows, err := db.Query(sql, args...) 407 + rows, err := s.Query(db) 528 408 if err != nil { 529 409 t.Fatalf("Failed to execute query: %v", err) 530 410 } ··· 563 443 OrderBy("salary", Descending). 564 444 Limit(2) 565 445 566 - sql, args := s.MustBuild() 567 - 568 - rows, err := db.Query(sql, args...) 446 + rows, err := s.Query(db) 569 447 if err != nil { 570 448 t.Fatalf("Failed to execute query: %v", err) 571 449 } ··· 610 488 From("users"). 611 489 Where(Eq("name", "John Doe")) 612 490 613 - sql, args := s.MustBuild() 614 - 615 - rows, err := db.Query(sql, args...) 491 + rows, err := s.Query(db) 616 492 if err != nil { 617 493 t.Fatalf("Failed to execute query: %v", err) 618 494 } ··· 633 509 From("users"). 634 510 Where(Eq("active", false)) 635 511 636 - sql, args := s.MustBuild() 637 - 638 - rows, err := db.Query(sql, args...) 512 + rows, err := s.Query(db) 639 513 if err != nil { 640 514 t.Fatalf("Failed to execute query: %v", err) 641 515 } ··· 656 530 From("users"). 657 531 Where(Gte("salary", 80000.0)) 658 532 659 - sql, args := s.MustBuild() 660 - 661 - rows, err := db.Query(sql, args...) 533 + rows, err := s.Query(db) 662 534 if err != nil { 663 535 t.Fatalf("Failed to execute query: %v", err) 664 536 } ··· 674 546 } 675 547 }) 676 548 } 677 - 678 - func TestDeleteIntegration_BasicQueries(t *testing.T) { 679 - tests := []struct { 680 - name string 681 - stmt delete_ 682 - expectedRows int64 683 - }{ 684 - { 685 - name: "Delete all users", 686 - stmt: Delete().From("users"), 687 - expectedRows: 6, 688 - }, 689 - { 690 - name: "Delete active users only", 691 - stmt: Delete(). 692 - From("users"). 693 - Where(Eq("active", true)), 694 - expectedRows: 4, 695 - }, 696 - { 697 - name: "Select users in Engineering", 698 - stmt: Delete(). 699 - From("users"). 700 - Where(Eq("department", "Engineering")), 701 - expectedRows: 3, 702 - }, 703 - { 704 - name: "Delete users with age > 30", 705 - stmt: Delete(). 706 - From("users"). 707 - Where(Gt("age", 30)), 708 - expectedRows: 2, 709 - }, 710 - { 711 - name: "Delete users with salary between 70000 and 80000", 712 - stmt: Delete(). 713 - From("users"). 714 - Where(Gte("salary", 70000.0).And(Lte("salary", 80000.0))), 715 - expectedRows: 3, 716 - }, 717 - } 718 - 719 - for _, test := range tests { 720 - t.Run(test.name, func(t *testing.T) { 721 - db := setupTestDB(t) 722 - defer db.Close() 723 - 724 - sql, args, err := test.stmt.Build() 725 - if err != nil { 726 - t.Fatalf("Failed to build query: %v", err) 727 - } 728 - 729 - res, err := db.Exec(sql, args...) 730 - if err != nil { 731 - t.Fatalf("Failed to execute query: %v", err) 732 - } 733 - 734 - count, err := res.RowsAffected() 735 - if err != nil { 736 - t.Fatalf("Failed to execute query: %v", err) 737 - } 738 - 739 - if count != test.expectedRows { 740 - t.Errorf("Expected %d rows, got %d", test.expectedRows, count) 741 - } 742 - }) 743 - } 744 - } 745 - 746 - func TestInsertBuild_Success(t *testing.T) { 747 - tests := []struct { 748 - name string 749 - stmt insert 750 - expectedSql string 751 - expectedArgs []any 752 - }{ 753 - { 754 - name: "Simple insert", 755 - stmt: Insert().Into("users").Value("name", "John"), 756 - expectedSql: "INSERT INTO users (name) VALUES (?)", 757 - expectedArgs: []any{"John"}, 758 - }, 759 - { 760 - name: "Replace clause", 761 - stmt: Insert().Or(Replace).Into("users").Value("name", "John"), 762 - expectedSql: "INSERT OR REPLACE INTO users (name) VALUES (?)", 763 - expectedArgs: []any{"John"}, 764 - }, 765 - { 766 - name: "More values", 767 - stmt: Insert().Into("users").Value("name", "John").Value("age", 35), 768 - expectedSql: "INSERT INTO users (name, age) VALUES (?, ?)", 769 - expectedArgs: []any{"John", 35}, 770 - }, 771 - } 772 - 773 - for _, test := range tests { 774 - t.Run(test.name, func(t *testing.T) { 775 - sql, args, err := test.stmt.Build() 776 - 777 - if err != nil { 778 - t.Errorf("Expected no error, got %v", err) 779 - } 780 - 781 - if sql != test.expectedSql { 782 - t.Errorf("Expected '%s', got '%s'", test.expectedSql, sql) 783 - } 784 - 785 - if len(args) != len(test.expectedArgs) { 786 - t.Errorf("Expected '%d' args, got '%d' args", len(test.expectedArgs), len(args)) 787 - } 788 - 789 - for i := range len(args) { 790 - if args[i] != test.expectedArgs[i] { 791 - t.Errorf("Expected '%v', got '%v' at index %d", test.expectedArgs[i], args[i], i) 792 - } 793 - } 794 - }) 795 - } 796 - }
+168
select.go
··· 1 + package norm 2 + 3 + import ( 4 + "context" 5 + "database/sql" 6 + "fmt" 7 + "strconv" 8 + "strings" 9 + ) 10 + 11 + type select_ struct { 12 + resultColumns []string // TODO: strongly type result columns 13 + from string // table-or-subquery expr 14 + where *Expr 15 + orderBy []orderBy 16 + groupBy []groupBy 17 + limit *limit 18 + } 19 + 20 + type orderBy struct { 21 + field string 22 + direction Direction 23 + } 24 + 25 + type groupBy struct { 26 + field string 27 + } 28 + 29 + type limit struct { 30 + limit int 31 + } 32 + 33 + func Select(cols ...string) select_ { 34 + return select_{ 35 + resultColumns: cols, 36 + } 37 + } 38 + 39 + type SelectOpt func(s *select_) 40 + 41 + func (s select_) From(table string) select_ { 42 + s.from = table 43 + return s 44 + } 45 + 46 + func (s select_) Where(expr Expr) select_ { 47 + s.where = &expr 48 + return s 49 + } 50 + 51 + func (s select_) OrderBy(field string, direction Direction) select_ { 52 + s.orderBy = append(s.orderBy, orderBy{ 53 + field: field, 54 + direction: direction, 55 + }) 56 + return s 57 + } 58 + 59 + func (s select_) GroupBy(field string) select_ { 60 + s.groupBy = append(s.groupBy, groupBy{ 61 + field: field, 62 + }) 63 + return s 64 + } 65 + 66 + func (s select_) Limit(i int) select_ { 67 + s.limit = &limit{ 68 + limit: i, 69 + } 70 + return s 71 + } 72 + 73 + func (s select_) Compile() (string, []any, error) { 74 + var sql strings.Builder 75 + var args []any 76 + 77 + sql.WriteString("SELECT ") 78 + if len(s.resultColumns) == 0 { 79 + return "", nil, fmt.Errorf("result columns empty") 80 + } else { 81 + for i, col := range s.resultColumns { 82 + if i > 0 { 83 + sql.WriteString(", ") 84 + } 85 + sql.WriteString(col) 86 + } 87 + } 88 + 89 + if s.from == "" { 90 + return "", nil, fmt.Errorf("FROM clause is required") 91 + } 92 + sql.WriteString(" FROM ") 93 + sql.WriteString(s.from) 94 + 95 + if s.where != nil { 96 + sql.WriteString(" WHERE ") 97 + sql.WriteString(s.where.String()) 98 + 99 + args = s.where.Binds() 100 + } 101 + 102 + // GROUP BY clause 103 + if len(s.groupBy) > 0 { 104 + sql.WriteString(" GROUP BY ") 105 + for i, gb := range s.groupBy { 106 + if i > 0 { 107 + sql.WriteString(", ") 108 + } 109 + sql.WriteString(gb.field) 110 + } 111 + } 112 + 113 + // ORDER BY clause 114 + if len(s.orderBy) > 0 { 115 + sql.WriteString(" ORDER BY ") 116 + for i, ob := range s.orderBy { 117 + if i > 0 { 118 + sql.WriteString(", ") 119 + } 120 + sql.WriteString(ob.field) 121 + sql.WriteString(" ") 122 + sql.WriteString(string(ob.direction)) 123 + } 124 + } 125 + 126 + // LIMIT clause 127 + if s.limit != nil { 128 + if s.limit.limit <= 0 { 129 + return "", nil, fmt.Errorf("LIMIT must be positive, got %d", s.limit.limit) 130 + } 131 + sql.WriteString(" LIMIT ") 132 + sql.WriteString(strconv.Itoa(s.limit.limit)) 133 + } 134 + 135 + return sql.String(), args, nil 136 + } 137 + 138 + func (s select_) Build(p Database) (*sql.Stmt, []any, error) { 139 + return Build(s, p) 140 + } 141 + 142 + func (s select_) MustBuild(p Database) (*sql.Stmt, []any) { 143 + return MustBuild(s, p) 144 + } 145 + 146 + func (s select_) Exec(p Database) (sql.Result, error) { 147 + return Exec(s, p) 148 + } 149 + 150 + func (s select_) ExecContext(ctx context.Context, p Database) (sql.Result, error) { 151 + return ExecContext(ctx, s, p) 152 + } 153 + 154 + func (s select_) Query(p Database) (*sql.Rows, error) { 155 + return Query(s, p) 156 + } 157 + 158 + func (s select_) QueryContext(ctx context.Context, p Database) (*sql.Rows, error) { 159 + return QueryContext(ctx, s, p) 160 + } 161 + 162 + func (s select_) QueryRow(p Database) (*sql.Row, error) { 163 + return QueryRow(s, p) 164 + } 165 + 166 + func (s select_) QueryRowContext(ctx context.Context, p Database) (*sql.Row, error) { 167 + return QueryRowContext(ctx, s, p) 168 + }
+112
types.go
··· 1 + package norm 2 + 3 + import ( 4 + "context" 5 + "database/sql" 6 + "fmt" 7 + ) 8 + 9 + type Database interface { 10 + Prepare(sql string) (*sql.Stmt, error) 11 + PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) 12 + 13 + Exec(query string, args ...any) (sql.Result, error) 14 + ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) 15 + } 16 + 17 + type Compiler interface { 18 + Compile() (string, []any, error) 19 + } 20 + 21 + type Builder interface { 22 + Build(db Database) (*sql.Stmt, []any, error) 23 + MustBuild(db Database) (*sql.Stmt, []any) 24 + } 25 + 26 + // anything that is a Compiler, can also be Builder: 27 + func Build(c Compiler, p Database) (*sql.Stmt, []any, error) { 28 + query, args, err := c.Compile() 29 + if err != nil { 30 + return nil, nil, err 31 + } 32 + 33 + stmt, err := p.Prepare(query) 34 + if err != nil { 35 + return nil, nil, fmt.Errorf("failed to prepare statement: `%s` : %w", query, err) 36 + } 37 + 38 + return stmt, args, nil 39 + } 40 + 41 + func MustBuild(c Compiler, p Database) (*sql.Stmt, []any) { 42 + stmt, args, err := Build(c, p) 43 + if err != nil { 44 + panic(err) 45 + } 46 + return stmt, args 47 + } 48 + 49 + type Execer interface { 50 + Exec(db Database) (sql.Result, error) 51 + ExecContext(ctx context.Context, db Database) (sql.Result, error) 52 + } 53 + 54 + // anything that is a Builder can also be an Execer 55 + func Exec(b Builder, p Database) (sql.Result, error) { 56 + return ExecContext(context.Background(), b, p) 57 + } 58 + 59 + func ExecContext(ctx context.Context, b Builder, p Database) (sql.Result, error) { 60 + stmt, args, err := b.Build(p) 61 + if err != nil { 62 + return nil, err 63 + } 64 + 65 + return stmt.ExecContext(ctx, args...) 66 + } 67 + 68 + type Querier interface { 69 + Query(db Database) (*sql.Rows, error) 70 + QueryContext(ctx context.Context, db Database) (*sql.Rows, error) 71 + QueryRow(db Database) (*sql.Row, error) 72 + QueryRowContext(ctx context.Context, db Database) (*sql.Row, error) 73 + } 74 + 75 + // anything that is a Builder can also be an Querier 76 + func Query(b Builder, p Database) (*sql.Rows, error) { 77 + return QueryContext(context.Background(), b, p) 78 + } 79 + 80 + func QueryContext(ctx context.Context, b Builder, p Database) (*sql.Rows, error) { 81 + stmt, args, err := b.Build(p) 82 + if err != nil { 83 + return nil, err 84 + } 85 + 86 + return stmt.QueryContext(ctx, args...) 87 + } 88 + 89 + func QueryRow(b Builder, p Database) (*sql.Row, error) { 90 + return QueryRowContext(context.Background(), b, p) 91 + } 92 + 93 + func QueryRowContext(ctx context.Context, b Builder, p Database) (*sql.Row, error) { 94 + stmt, args, err := b.Build(p) 95 + if err != nil { 96 + return nil, err 97 + } 98 + 99 + return stmt.QueryRowContext(ctx, args...), nil 100 + } 101 + 102 + type Direction string 103 + 104 + const ( 105 + Ascending Direction = "asc" 106 + Descending Direction = "desc" 107 + ) 108 + 109 + func Placeholder[T any]() T { 110 + var zero T 111 + return zero 112 + }