sumak 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +650 -56
- package/dist/ast/expression.d.mts +26 -0
- package/dist/ast/expression.mjs +140 -0
- package/dist/ast/nodes.d.mts +298 -0
- package/dist/ast/nodes.mjs +59 -0
- package/dist/ast/transformer.d.mts +10 -0
- package/dist/ast/transformer.mjs +140 -0
- package/dist/ast/typed-expression.d.mts +37 -0
- package/dist/ast/typed-expression.mjs +77 -0
- package/dist/ast/visitor.d.mts +13 -0
- package/dist/ast/visitor.mjs +11 -0
- package/dist/builder/delete.d.mts +18 -0
- package/dist/builder/delete.mjs +94 -0
- package/dist/builder/eb.d.mts +210 -0
- package/dist/builder/eb.mjs +399 -0
- package/dist/builder/expression.d.mts +5 -0
- package/dist/builder/expression.mjs +10 -0
- package/dist/builder/insert.d.mts +40 -0
- package/dist/builder/insert.mjs +146 -0
- package/dist/builder/merge.d.mts +20 -0
- package/dist/builder/merge.mjs +100 -0
- package/dist/builder/raw.d.mts +2 -0
- package/dist/builder/raw.mjs +4 -0
- package/dist/builder/select.d.mts +38 -0
- package/dist/builder/select.mjs +242 -0
- package/dist/builder/typed-delete.d.mts +43 -0
- package/dist/builder/typed-delete.mjs +77 -0
- package/dist/builder/typed-insert.d.mts +74 -0
- package/dist/builder/typed-insert.mjs +136 -0
- package/dist/builder/typed-merge.d.mts +31 -0
- package/dist/builder/typed-merge.mjs +93 -0
- package/dist/builder/typed-select.d.mts +125 -0
- package/dist/builder/typed-select.mjs +217 -0
- package/dist/builder/typed-update.d.mts +55 -0
- package/dist/builder/typed-update.mjs +102 -0
- package/dist/builder/update.d.mts +18 -0
- package/dist/builder/update.mjs +102 -0
- package/dist/dialect/mssql.d.mts +2 -0
- package/dist/dialect/mssql.mjs +9 -0
- package/dist/dialect/mysql.d.mts +2 -0
- package/dist/dialect/mysql.mjs +9 -0
- package/dist/dialect/pg.d.mts +2 -0
- package/dist/dialect/pg.mjs +9 -0
- package/dist/dialect/sqlite.d.mts +2 -0
- package/dist/dialect/sqlite.mjs +9 -0
- package/dist/dialect/types.d.mts +6 -0
- package/dist/dialect/types.mjs +1 -0
- package/dist/errors.d.mts +12 -0
- package/dist/errors.mjs +24 -0
- package/dist/index.d.mts +49 -660
- package/dist/index.mjs +46 -3
- package/dist/mssql.d.mts +2 -0
- package/dist/mssql.mjs +2 -0
- package/dist/mysql.d.mts +2 -2
- package/dist/mysql.mjs +2 -1
- package/dist/pg.d.mts +2 -2
- package/dist/pg.mjs +2 -1
- package/dist/plugin/camel-case.d.mts +11 -0
- package/dist/plugin/camel-case.mjs +16 -0
- package/dist/plugin/hooks.d.mts +72 -0
- package/dist/plugin/hooks.mjs +49 -0
- package/dist/plugin/plugin-manager.d.mts +17 -0
- package/dist/plugin/plugin-manager.mjs +37 -0
- package/dist/plugin/soft-delete.d.mts +27 -0
- package/dist/plugin/soft-delete.mjs +52 -0
- package/dist/plugin/types.d.mts +19 -0
- package/dist/plugin/types.mjs +1 -0
- package/dist/plugin/with-schema.d.mts +21 -0
- package/dist/plugin/with-schema.mjs +53 -0
- package/dist/printer/base.d.mts +48 -0
- package/dist/printer/base.mjs +450 -0
- package/dist/printer/document.d.mts +45 -0
- package/dist/printer/document.mjs +153 -0
- package/dist/printer/formatter.d.mts +5 -0
- package/dist/printer/formatter.mjs +134 -0
- package/dist/printer/mssql.d.mts +10 -0
- package/dist/printer/mssql.mjs +161 -0
- package/dist/printer/mysql.d.mts +8 -0
- package/dist/printer/mysql.mjs +41 -0
- package/dist/printer/pg.d.mts +6 -0
- package/dist/printer/pg.mjs +9 -0
- package/dist/printer/sqlite.d.mts +8 -0
- package/dist/printer/sqlite.mjs +29 -0
- package/dist/printer/types.d.mts +11 -0
- package/dist/printer/types.mjs +1 -0
- package/dist/schema/column.d.mts +52 -0
- package/dist/schema/column.mjs +120 -0
- package/dist/schema/index.d.mts +6 -0
- package/dist/schema/index.mjs +4 -0
- package/dist/schema/table.d.mts +37 -0
- package/dist/schema/table.mjs +7 -0
- package/dist/schema/type-utils.d.mts +46 -0
- package/dist/schema/type-utils.mjs +1 -0
- package/dist/schema/types.d.mts +64 -0
- package/dist/schema/types.mjs +1 -0
- package/dist/schema.d.mts +2 -2
- package/dist/schema.mjs +1 -1
- package/dist/sqlite.d.mts +2 -2
- package/dist/sqlite.mjs +2 -1
- package/dist/sumak.d.mts +98 -0
- package/dist/sumak.mjs +132 -0
- package/dist/types.d.mts +14 -0
- package/dist/types.mjs +1 -0
- package/dist/utils/identifier.d.mts +3 -0
- package/dist/utils/identifier.mjs +14 -0
- package/dist/utils/param.d.mts +2 -0
- package/dist/utils/param.mjs +8 -0
- package/package.json +7 -1
- package/dist/_chunks/base.mjs +0 -1
- package/dist/_chunks/errors.mjs +0 -1
- package/dist/_chunks/index.d.mts +0 -136
- package/dist/_chunks/mysql.d.mts +0 -8
- package/dist/_chunks/mysql.mjs +0 -1
- package/dist/_chunks/pg.d.mts +0 -7
- package/dist/_chunks/pg.mjs +0 -1
- package/dist/_chunks/schema.mjs +0 -1
- package/dist/_chunks/sqlite.d.mts +0 -8
- package/dist/_chunks/sqlite.mjs +0 -1
- package/dist/_chunks/types.d.mts +0 -274
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ npm install sumak
|
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
```ts
|
|
24
|
-
import { sumak, pgDialect, serial, text, boolean, integer } from "sumak"
|
|
24
|
+
import { sumak, pgDialect, serial, text, boolean, integer, jsonb } from "sumak"
|
|
25
25
|
|
|
26
26
|
const db = sumak({
|
|
27
27
|
dialect: pgDialect(),
|
|
@@ -30,7 +30,9 @@ const db = sumak({
|
|
|
30
30
|
id: serial().primaryKey(),
|
|
31
31
|
name: text().notNull(),
|
|
32
32
|
email: text().notNull(),
|
|
33
|
+
age: integer(),
|
|
33
34
|
active: boolean().defaultTo(true),
|
|
35
|
+
meta: jsonb(),
|
|
34
36
|
},
|
|
35
37
|
posts: {
|
|
36
38
|
id: serial().primaryKey(),
|
|
@@ -43,16 +45,20 @@ const db = sumak({
|
|
|
43
45
|
|
|
44
46
|
## Query Building
|
|
45
47
|
|
|
48
|
+
### SELECT
|
|
49
|
+
|
|
46
50
|
```ts
|
|
47
|
-
// SELECT
|
|
48
51
|
db.selectFrom("users")
|
|
49
52
|
.select("id", "name")
|
|
50
53
|
.where(({ age, active }) => and(age.gte(18), active.eq(true)))
|
|
51
54
|
.orderBy("name")
|
|
52
55
|
.limit(10)
|
|
53
56
|
.compile(db.printer())
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### INSERT
|
|
54
60
|
|
|
55
|
-
|
|
61
|
+
```ts
|
|
56
62
|
db.insertInto("users")
|
|
57
63
|
.values({
|
|
58
64
|
name: "Alice",
|
|
@@ -60,14 +66,20 @@ db.insertInto("users")
|
|
|
60
66
|
})
|
|
61
67
|
.returningAll()
|
|
62
68
|
.compile(db.printer())
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### UPDATE
|
|
63
72
|
|
|
64
|
-
|
|
73
|
+
```ts
|
|
65
74
|
db.update("users")
|
|
66
75
|
.set({ active: false })
|
|
67
76
|
.where(({ id }) => id.eq(1))
|
|
68
77
|
.compile(db.printer())
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### DELETE
|
|
69
81
|
|
|
70
|
-
|
|
82
|
+
```ts
|
|
71
83
|
db.deleteFrom("users")
|
|
72
84
|
.where(({ id }) => id.eq(1))
|
|
73
85
|
.returning("id")
|
|
@@ -77,11 +89,612 @@ db.deleteFrom("users")
|
|
|
77
89
|
## Joins
|
|
78
90
|
|
|
79
91
|
```ts
|
|
92
|
+
// INNER JOIN
|
|
80
93
|
db.selectFrom("users")
|
|
81
94
|
.innerJoin("posts", ({ users, posts }) => users.id.eqCol(posts.userId))
|
|
95
|
+
.select("id", "title")
|
|
96
|
+
.compile(db.printer())
|
|
97
|
+
|
|
98
|
+
// LEFT JOIN — joined columns become nullable
|
|
99
|
+
db.selectFrom("users")
|
|
100
|
+
.leftJoin("posts", ({ users, posts }) => users.id.eqCol(posts.userId))
|
|
101
|
+
.compile(db.printer())
|
|
102
|
+
|
|
103
|
+
// RIGHT JOIN
|
|
104
|
+
db.selectFrom("users")
|
|
105
|
+
.rightJoin("posts", ({ users, posts }) => users.id.eqCol(posts.userId))
|
|
106
|
+
.compile(db.printer())
|
|
107
|
+
|
|
108
|
+
// FULL JOIN — both sides become nullable
|
|
109
|
+
db.selectFrom("users")
|
|
110
|
+
.fullJoin("posts", ({ users, posts }) => users.id.eqCol(posts.userId))
|
|
82
111
|
.compile(db.printer())
|
|
112
|
+
|
|
113
|
+
// CROSS JOIN — cartesian product
|
|
114
|
+
db.selectFrom("users").crossJoin("posts").compile(db.printer())
|
|
83
115
|
```
|
|
84
116
|
|
|
117
|
+
## Expression API
|
|
118
|
+
|
|
119
|
+
### Comparisons
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
.where(({ id }) =>
|
|
123
|
+
id.eq(42),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
.where(({ age }) =>
|
|
127
|
+
age.gt(18),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
.where(({ age }) =>
|
|
131
|
+
age.gte(18),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
.where(({ age }) =>
|
|
135
|
+
age.lt(65),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
.where(({ age }) =>
|
|
139
|
+
age.lte(65),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
.where(({ active }) =>
|
|
143
|
+
active.neq(false),
|
|
144
|
+
)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### String Matching
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
.where(({ name }) =>
|
|
151
|
+
name.like("%ali%"),
|
|
152
|
+
)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Range & List
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
.where(({ age }) =>
|
|
159
|
+
age.between(18, 65),
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
.where(({ id }) =>
|
|
163
|
+
id.in([1, 2, 3]),
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
.where(({ id }) =>
|
|
167
|
+
id.notIn([99, 100]),
|
|
168
|
+
)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Null Checks
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
.where(({ bio }) =>
|
|
175
|
+
bio.isNull(),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
.where(({ email }) =>
|
|
179
|
+
email.isNotNull(),
|
|
180
|
+
)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Logical Combinators
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
// AND
|
|
187
|
+
.where(({ age, active }) =>
|
|
188
|
+
and(
|
|
189
|
+
age.gt(0),
|
|
190
|
+
active.eq(true),
|
|
191
|
+
),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
// OR
|
|
195
|
+
.where(({ name, email }) =>
|
|
196
|
+
or(
|
|
197
|
+
name.like("%alice%"),
|
|
198
|
+
email.like("%alice%"),
|
|
199
|
+
),
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
// NOT
|
|
203
|
+
.where(({ active }) =>
|
|
204
|
+
not(active.eq(true)),
|
|
205
|
+
)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Aggregates
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
import { count, countDistinct, sum, avg, min, max, coalesce } from "sumak"
|
|
212
|
+
|
|
213
|
+
db.selectFrom("users").selectExpr(count(), "total").compile(db.printer())
|
|
214
|
+
|
|
215
|
+
db.selectFrom("users").selectExpr(countDistinct(col.dept), "uniqueDepts").compile(db.printer())
|
|
216
|
+
// SELECT COUNT(DISTINCT "dept") AS "uniqueDepts" FROM "users"
|
|
217
|
+
|
|
218
|
+
db.selectFrom("orders").selectExpr(sum(col.amount), "totalAmount").compile(db.printer())
|
|
219
|
+
|
|
220
|
+
db.selectFrom("orders").selectExpr(avg(col.amount), "avgAmount").compile(db.printer())
|
|
221
|
+
|
|
222
|
+
db.selectFrom("orders")
|
|
223
|
+
.selectExpr(coalesce(col.discount, val(0)), "safeDiscount")
|
|
224
|
+
.compile(db.printer())
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### EXISTS / NOT EXISTS
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
import { exists, notExists } from "sumak"
|
|
231
|
+
|
|
232
|
+
db.selectFrom("users")
|
|
233
|
+
.where(() =>
|
|
234
|
+
exists(
|
|
235
|
+
db
|
|
236
|
+
.selectFrom("posts")
|
|
237
|
+
.where(({ userId }) => userId.eq(1))
|
|
238
|
+
.build(),
|
|
239
|
+
),
|
|
240
|
+
)
|
|
241
|
+
.compile(db.printer())
|
|
242
|
+
|
|
243
|
+
db.selectFrom("users")
|
|
244
|
+
.where(() =>
|
|
245
|
+
notExists(
|
|
246
|
+
db
|
|
247
|
+
.selectFrom("posts")
|
|
248
|
+
.where(({ userId }) => userId.eq(1))
|
|
249
|
+
.build(),
|
|
250
|
+
),
|
|
251
|
+
)
|
|
252
|
+
.compile(db.printer())
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### CASE Expression
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
import { case_, val } from "sumak"
|
|
259
|
+
|
|
260
|
+
db.selectFrom("users")
|
|
261
|
+
.selectExpr(
|
|
262
|
+
case_()
|
|
263
|
+
.when(col.active.eq(true), val("active"))
|
|
264
|
+
.when(col.active.eq(false), val("inactive"))
|
|
265
|
+
.else_(val("unknown"))
|
|
266
|
+
.end(),
|
|
267
|
+
"status",
|
|
268
|
+
)
|
|
269
|
+
.compile(db.printer())
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### CAST
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
import { cast, val } from "sumak"
|
|
276
|
+
|
|
277
|
+
db.selectFrom("users")
|
|
278
|
+
.selectExpr(cast(val(42), "text"), "idAsText")
|
|
279
|
+
.compile(db.printer())
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### JSON Operations
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
import { jsonRef } from "sumak"
|
|
286
|
+
|
|
287
|
+
// -> (JSON object)
|
|
288
|
+
db.selectFrom("users")
|
|
289
|
+
.selectExpr(jsonRef(col.meta, "address", "->"), "address")
|
|
290
|
+
.compile(db.printer())
|
|
291
|
+
|
|
292
|
+
// ->> (text value)
|
|
293
|
+
db.selectFrom("users")
|
|
294
|
+
.selectExpr(jsonRef(col.meta, "name", "->>"), "metaName")
|
|
295
|
+
.compile(db.printer())
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Window Functions
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
import { over, rowNumber, rank, denseRank, lag, lead, ntile, count, sum } from "sumak"
|
|
302
|
+
|
|
303
|
+
// ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC)
|
|
304
|
+
db.selectFrom("employees")
|
|
305
|
+
.selectExpr(
|
|
306
|
+
over(rowNumber(), (w) => w.partitionBy("dept").orderBy("salary", "DESC")),
|
|
307
|
+
"rn",
|
|
308
|
+
)
|
|
309
|
+
.compile(db.printer())
|
|
310
|
+
|
|
311
|
+
// RANK() OVER (ORDER BY score DESC)
|
|
312
|
+
db.selectFrom("students")
|
|
313
|
+
.selectExpr(
|
|
314
|
+
over(rank(), (w) => w.orderBy("score", "DESC")),
|
|
315
|
+
"rnk",
|
|
316
|
+
)
|
|
317
|
+
.compile(db.printer())
|
|
318
|
+
|
|
319
|
+
// Running total with frame
|
|
320
|
+
db.selectFrom("orders")
|
|
321
|
+
.selectExpr(
|
|
322
|
+
over(sum(col.amount), (w) =>
|
|
323
|
+
w
|
|
324
|
+
.partitionBy("userId")
|
|
325
|
+
.orderBy("createdAt")
|
|
326
|
+
.rows({ type: "unbounded_preceding" }, { type: "current_row" }),
|
|
327
|
+
),
|
|
328
|
+
"runningTotal",
|
|
329
|
+
)
|
|
330
|
+
.compile(db.printer())
|
|
331
|
+
|
|
332
|
+
// LAG / LEAD
|
|
333
|
+
db.selectFrom("prices")
|
|
334
|
+
.selectExpr(
|
|
335
|
+
over(lag(col.price, 1), (w) => w.orderBy("date")),
|
|
336
|
+
"prevPrice",
|
|
337
|
+
)
|
|
338
|
+
.compile(db.printer())
|
|
339
|
+
|
|
340
|
+
// NTILE(4)
|
|
341
|
+
db.selectFrom("employees")
|
|
342
|
+
.selectExpr(
|
|
343
|
+
over(ntile(4), (w) => w.orderBy("salary", "DESC")),
|
|
344
|
+
"quartile",
|
|
345
|
+
)
|
|
346
|
+
.compile(db.printer())
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## SQL Functions
|
|
350
|
+
|
|
351
|
+
### String Functions
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
import { upper, lower, concat, substring, trim, length } from "sumak"
|
|
355
|
+
|
|
356
|
+
db.selectFrom("users").selectExpr(upper(col.name), "upperName").compile(db.printer())
|
|
357
|
+
// SELECT UPPER("name") AS "upperName" FROM "users"
|
|
358
|
+
|
|
359
|
+
db.selectFrom("users").selectExpr(lower(col.email), "lowerEmail").compile(db.printer())
|
|
360
|
+
|
|
361
|
+
db.selectFrom("users")
|
|
362
|
+
.selectExpr(concat(col.firstName, val(" "), col.lastName), "fullName")
|
|
363
|
+
.compile(db.printer())
|
|
364
|
+
|
|
365
|
+
db.selectFrom("users")
|
|
366
|
+
.selectExpr(substring(col.name, 1, 3), "prefix")
|
|
367
|
+
.compile(db.printer())
|
|
368
|
+
|
|
369
|
+
db.selectFrom("users").selectExpr(trim(col.name), "trimmed").compile(db.printer())
|
|
370
|
+
|
|
371
|
+
db.selectFrom("users").selectExpr(length(col.name), "nameLen").compile(db.printer())
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Numeric Functions
|
|
375
|
+
|
|
376
|
+
```ts
|
|
377
|
+
import { abs, round, ceil, floor } from "sumak"
|
|
378
|
+
|
|
379
|
+
db.selectFrom("orders").selectExpr(abs(col.balance), "absBalance").compile(db.printer())
|
|
380
|
+
|
|
381
|
+
db.selectFrom("orders").selectExpr(round(col.price, 2), "rounded").compile(db.printer())
|
|
382
|
+
|
|
383
|
+
db.selectFrom("orders").selectExpr(ceil(col.amount), "ceiling").compile(db.printer())
|
|
384
|
+
|
|
385
|
+
db.selectFrom("orders").selectExpr(floor(col.amount), "floored").compile(db.printer())
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Conditional Functions
|
|
389
|
+
|
|
390
|
+
```ts
|
|
391
|
+
import { nullif, greatest, least } from "sumak"
|
|
392
|
+
|
|
393
|
+
db.selectFrom("users")
|
|
394
|
+
.selectExpr(nullif(col.age, val(0)), "ageOrNull")
|
|
395
|
+
.compile(db.printer())
|
|
396
|
+
|
|
397
|
+
db.selectFrom("products")
|
|
398
|
+
.selectExpr(greatest(col.price, col.minPrice), "effectivePrice")
|
|
399
|
+
.compile(db.printer())
|
|
400
|
+
|
|
401
|
+
db.selectFrom("products")
|
|
402
|
+
.selectExpr(least(col.price, col.maxPrice), "cappedPrice")
|
|
403
|
+
.compile(db.printer())
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Date/Time Functions
|
|
407
|
+
|
|
408
|
+
```ts
|
|
409
|
+
import { now, currentTimestamp } from "sumak"
|
|
410
|
+
|
|
411
|
+
db.selectFrom("users").selectExpr(now(), "currentTime").compile(db.printer())
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
## Set Operations
|
|
415
|
+
|
|
416
|
+
```ts
|
|
417
|
+
const active = db
|
|
418
|
+
.selectFrom("users")
|
|
419
|
+
.select("id")
|
|
420
|
+
.where(({ active }) => active.eq(true))
|
|
421
|
+
|
|
422
|
+
const premium = db
|
|
423
|
+
.selectFrom("users")
|
|
424
|
+
.select("id")
|
|
425
|
+
.where(({ active }) => active.eq(true))
|
|
426
|
+
|
|
427
|
+
// UNION
|
|
428
|
+
active.union(premium).compile(db.printer())
|
|
429
|
+
|
|
430
|
+
// UNION ALL
|
|
431
|
+
active.unionAll(premium).compile(db.printer())
|
|
432
|
+
|
|
433
|
+
// INTERSECT / INTERSECT ALL
|
|
434
|
+
active.intersect(premium).compile(db.printer())
|
|
435
|
+
active.intersectAll(premium).compile(db.printer())
|
|
436
|
+
|
|
437
|
+
// EXCEPT / EXCEPT ALL
|
|
438
|
+
active.except(premium).compile(db.printer())
|
|
439
|
+
active.exceptAll(premium).compile(db.printer())
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
## CTEs (WITH)
|
|
443
|
+
|
|
444
|
+
```ts
|
|
445
|
+
// SELECT with CTE
|
|
446
|
+
db.selectFrom("users")
|
|
447
|
+
.with(
|
|
448
|
+
"active_users",
|
|
449
|
+
db
|
|
450
|
+
.selectFrom("users")
|
|
451
|
+
.where(({ active }) => active.eq(true))
|
|
452
|
+
.build(),
|
|
453
|
+
)
|
|
454
|
+
.compile(db.printer())
|
|
455
|
+
|
|
456
|
+
// INSERT with CTE
|
|
457
|
+
db.insertInto("users")
|
|
458
|
+
.with("source", sourceCte)
|
|
459
|
+
.values({ name: "Alice", email: "a@b.com" })
|
|
460
|
+
.compile(db.printer())
|
|
461
|
+
|
|
462
|
+
// UPDATE with CTE
|
|
463
|
+
db.update("users").with("target", targetCte).set({ active: false }).compile(db.printer())
|
|
464
|
+
|
|
465
|
+
// DELETE with CTE
|
|
466
|
+
db.deleteFrom("users")
|
|
467
|
+
.with("to_delete", deleteCte)
|
|
468
|
+
.where(({ id }) => id.eq(1))
|
|
469
|
+
.compile(db.printer())
|
|
470
|
+
|
|
471
|
+
// Recursive CTE
|
|
472
|
+
db.selectFrom("users").with("tree", recursiveQuery, true).compile(db.printer())
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
## UPDATE FROM
|
|
476
|
+
|
|
477
|
+
```ts
|
|
478
|
+
db.update("users")
|
|
479
|
+
.set({ name: "Bob" })
|
|
480
|
+
.from("posts")
|
|
481
|
+
.where(({ id }) => id.eq(1))
|
|
482
|
+
.compile(db.printer())
|
|
483
|
+
// UPDATE "users" SET "name" = $1 FROM "posts" WHERE ("id" = $2)
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
## Conditional Query Building
|
|
487
|
+
|
|
488
|
+
```ts
|
|
489
|
+
const withFilter = true
|
|
490
|
+
const withOrder = false
|
|
491
|
+
|
|
492
|
+
db.selectFrom("users")
|
|
493
|
+
.select("id", "name")
|
|
494
|
+
.$if(withFilter, (qb) => qb.where(({ age }) => age.gt(18)))
|
|
495
|
+
.$if(withOrder, (qb) => qb.orderBy("name"))
|
|
496
|
+
.compile(db.printer())
|
|
497
|
+
// WHERE applied, ORDER BY skipped
|
|
498
|
+
|
|
499
|
+
// Multiple .where() calls are AND'd together
|
|
500
|
+
db.selectFrom("users")
|
|
501
|
+
.select("id")
|
|
502
|
+
.where(({ age }) => age.gt(18))
|
|
503
|
+
.where(({ active }) => active.eq(true))
|
|
504
|
+
.compile(db.printer())
|
|
505
|
+
// WHERE ("age" > $1) AND ("active" = $2)
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
## INSERT Advanced
|
|
509
|
+
|
|
510
|
+
```ts
|
|
511
|
+
// INSERT ... SELECT
|
|
512
|
+
const selectQuery = db.selectFrom("users").select("name", "age").build()
|
|
513
|
+
db.insertInto("archive").fromSelect(selectQuery).compile(db.printer())
|
|
514
|
+
|
|
515
|
+
// INSERT ... DEFAULT VALUES
|
|
516
|
+
db.insertInto("users").defaultValues().compile(db.printer())
|
|
517
|
+
|
|
518
|
+
// SQLite: INSERT OR IGNORE / INSERT OR REPLACE
|
|
519
|
+
db.insertInto("users").values({ name: "Alice" }).orIgnore().compile(db.printer())
|
|
520
|
+
// INSERT OR IGNORE INTO "users" ...
|
|
521
|
+
|
|
522
|
+
db.insertInto("users").values({ name: "Alice" }).orReplace().compile(db.printer())
|
|
523
|
+
// INSERT OR REPLACE INTO "users" ...
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
## ON CONFLICT
|
|
527
|
+
|
|
528
|
+
```ts
|
|
529
|
+
// DO NOTHING (by columns)
|
|
530
|
+
db.insertInto("users")
|
|
531
|
+
.values({ name: "Alice", email: "a@b.com" })
|
|
532
|
+
.onConflictDoNothing("email")
|
|
533
|
+
.compile(db.printer())
|
|
534
|
+
|
|
535
|
+
// DO UPDATE (by columns)
|
|
536
|
+
db.insertInto("users")
|
|
537
|
+
.values({ name: "Alice", email: "a@b.com" })
|
|
538
|
+
.onConflictDoUpdate(["email"], [{ column: "name", value: val("Alice") }])
|
|
539
|
+
.compile(db.printer())
|
|
540
|
+
|
|
541
|
+
// DO NOTHING (by constraint name)
|
|
542
|
+
db.insertInto("users")
|
|
543
|
+
.values({ name: "Alice", email: "a@b.com" })
|
|
544
|
+
.onConflictConstraintDoNothing("users_email_key")
|
|
545
|
+
.compile(db.printer())
|
|
546
|
+
// ON CONFLICT ON CONSTRAINT "users_email_key" DO NOTHING
|
|
547
|
+
|
|
548
|
+
// MySQL: ON DUPLICATE KEY UPDATE
|
|
549
|
+
db.insertInto("users")
|
|
550
|
+
.values({ name: "Alice" })
|
|
551
|
+
.onDuplicateKeyUpdate([{ column: "name", value: val("Alice") }])
|
|
552
|
+
.compile(db.printer())
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
## MERGE (SQL:2003)
|
|
556
|
+
|
|
557
|
+
```ts
|
|
558
|
+
db.mergeInto("users", "staging", "s", ({ target, source }) => target.id.eqCol(source.id))
|
|
559
|
+
.whenMatchedThenUpdate({ name: "updated" })
|
|
560
|
+
.whenNotMatchedThenInsert({
|
|
561
|
+
name: "Alice",
|
|
562
|
+
email: "alice@example.com",
|
|
563
|
+
})
|
|
564
|
+
.compile(db.printer())
|
|
565
|
+
|
|
566
|
+
// MERGE with conditional delete
|
|
567
|
+
db.mergeInto("users", "staging", "s", ({ target, source }) => target.id.eqCol(source.id))
|
|
568
|
+
.whenMatchedThenDelete()
|
|
569
|
+
.compile(db.printer())
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
## Row Locking
|
|
573
|
+
|
|
574
|
+
```ts
|
|
575
|
+
// FOR UPDATE
|
|
576
|
+
db.selectFrom("users").select("id").forUpdate().compile(db.printer())
|
|
577
|
+
|
|
578
|
+
// FOR SHARE
|
|
579
|
+
db.selectFrom("users").select("id").forShare().compile(db.printer())
|
|
580
|
+
|
|
581
|
+
// FOR NO KEY UPDATE / FOR KEY SHARE (PG)
|
|
582
|
+
db.selectFrom("users").select("id").forNoKeyUpdate().compile(db.printer())
|
|
583
|
+
db.selectFrom("users").select("id").forKeyShare().compile(db.printer())
|
|
584
|
+
|
|
585
|
+
// SKIP LOCKED / NOWAIT
|
|
586
|
+
db.selectFrom("users").select("id").forUpdate().skipLocked().compile(db.printer())
|
|
587
|
+
db.selectFrom("users").select("id").forUpdate().noWait().compile(db.printer())
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
## DISTINCT ON (PG)
|
|
591
|
+
|
|
592
|
+
```ts
|
|
593
|
+
db.selectFrom("users")
|
|
594
|
+
.selectAll()
|
|
595
|
+
.distinctOn("dept")
|
|
596
|
+
.orderBy("dept")
|
|
597
|
+
.orderBy("salary", "DESC")
|
|
598
|
+
.compile(db.printer())
|
|
599
|
+
// SELECT DISTINCT ON ("dept") * FROM "users" ORDER BY "dept" ASC, "salary" DESC
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
## DELETE USING / JOIN in UPDATE & DELETE
|
|
603
|
+
|
|
604
|
+
```ts
|
|
605
|
+
// PG: DELETE ... USING
|
|
606
|
+
db.deleteFrom("orders")
|
|
607
|
+
.using("users")
|
|
608
|
+
.where(eq(col("orders.user_id"), col("users.id")))
|
|
609
|
+
.compile(db.printer())
|
|
610
|
+
|
|
611
|
+
// MySQL: DELETE with JOIN
|
|
612
|
+
db.deleteFrom("orders")
|
|
613
|
+
.innerJoin("users", eq(col("user_id", "orders"), col("id", "users")))
|
|
614
|
+
.where(eq(col("name", "users"), lit("Alice")))
|
|
615
|
+
.compile(db.printer())
|
|
616
|
+
|
|
617
|
+
// MySQL: UPDATE with JOIN
|
|
618
|
+
db.update("orders")
|
|
619
|
+
.set({ total: 0 })
|
|
620
|
+
.innerJoin("users", eq(col("user_id", "orders"), col("id", "users")))
|
|
621
|
+
.compile(db.printer())
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
## Aggregate FILTER (WHERE)
|
|
625
|
+
|
|
626
|
+
```ts
|
|
627
|
+
import { filter, count, sum } from "sumak"
|
|
628
|
+
|
|
629
|
+
// COUNT(*) FILTER (WHERE active = true)
|
|
630
|
+
db.selectFrom("users").selectExpr(filter(count(), activeExpr), "activeCount").compile(db.printer())
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
## EXPLAIN
|
|
634
|
+
|
|
635
|
+
```ts
|
|
636
|
+
// EXPLAIN
|
|
637
|
+
db.selectFrom("users").select("id").explain().compile(db.printer())
|
|
638
|
+
// EXPLAIN SELECT "id" FROM "users"
|
|
639
|
+
|
|
640
|
+
// EXPLAIN ANALYZE
|
|
641
|
+
db.selectFrom("users").select("id").explain({ analyze: true }).compile(db.printer())
|
|
642
|
+
|
|
643
|
+
// EXPLAIN with format
|
|
644
|
+
db.selectFrom("users").select("id").explain({ format: "JSON" }).compile(db.printer())
|
|
645
|
+
// EXPLAIN (FORMAT JSON) SELECT "id" FROM "users"
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
## Full-Text Search
|
|
649
|
+
|
|
650
|
+
Dialect-aware FTS — same API, different SQL per dialect:
|
|
651
|
+
|
|
652
|
+
```ts
|
|
653
|
+
import { textSearch } from "sumak"
|
|
654
|
+
|
|
655
|
+
// PostgreSQL: to_tsvector("name") @@ to_tsquery('alice')
|
|
656
|
+
db.selectFrom("users")
|
|
657
|
+
.where(({ name }) => textSearch([name.toExpr()], val("alice")))
|
|
658
|
+
.compile(db.printer())
|
|
659
|
+
|
|
660
|
+
// With language config
|
|
661
|
+
db.selectFrom("users")
|
|
662
|
+
.where(({ name }) => textSearch([name.toExpr()], val("alice"), { language: "english" }))
|
|
663
|
+
.compile(db.printer())
|
|
664
|
+
|
|
665
|
+
// MySQL: MATCH(`name`) AGAINST(? IN BOOLEAN MODE)
|
|
666
|
+
// SQLite: ("name" MATCH ?)
|
|
667
|
+
// MSSQL: CONTAINS(([name]), @p0)
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
## Temporal Tables (SQL:2011)
|
|
671
|
+
|
|
672
|
+
Query historical data with `FOR SYSTEM_TIME`:
|
|
673
|
+
|
|
674
|
+
```ts
|
|
675
|
+
// AS OF — point-in-time query
|
|
676
|
+
db.selectFrom("users")
|
|
677
|
+
.forSystemTime({
|
|
678
|
+
kind: "as_of",
|
|
679
|
+
timestamp: lit("2024-01-01"),
|
|
680
|
+
})
|
|
681
|
+
.compile(db.printer())
|
|
682
|
+
|
|
683
|
+
// BETWEEN — time range
|
|
684
|
+
db.selectFrom("users")
|
|
685
|
+
.forSystemTime({
|
|
686
|
+
kind: "between",
|
|
687
|
+
start: lit("2024-01-01"),
|
|
688
|
+
end: lit("2024-12-31"),
|
|
689
|
+
})
|
|
690
|
+
.compile(db.printer())
|
|
691
|
+
|
|
692
|
+
// ALL — full history
|
|
693
|
+
db.selectFrom("users").forSystemTime({ kind: "all" }).compile(db.printer())
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
Supported modes: `as_of`, `from_to`, `between`, `contained_in`, `all`.
|
|
697
|
+
|
|
85
698
|
## Tree Shaking
|
|
86
699
|
|
|
87
700
|
Import only the dialect you need:
|
|
@@ -89,6 +702,7 @@ Import only the dialect you need:
|
|
|
89
702
|
```ts
|
|
90
703
|
import { sumak } from "sumak"
|
|
91
704
|
import { pgDialect } from "sumak/pg"
|
|
705
|
+
import { mssqlDialect } from "sumak/mssql"
|
|
92
706
|
import { mysqlDialect } from "sumak/mysql"
|
|
93
707
|
import { sqliteDialect } from "sumak/sqlite"
|
|
94
708
|
import { serial, text } from "sumak/schema"
|
|
@@ -102,12 +716,40 @@ Same query, different SQL:
|
|
|
102
716
|
// PostgreSQL → SELECT "id" FROM "users" WHERE ("id" = $1)
|
|
103
717
|
// MySQL → SELECT `id` FROM `users` WHERE (`id` = ?)
|
|
104
718
|
// SQLite → SELECT "id" FROM "users" WHERE ("id" = ?)
|
|
719
|
+
// MSSQL → SELECT [id] FROM [users] WHERE ([id] = @p0)
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
### MSSQL Specifics
|
|
723
|
+
|
|
724
|
+
```ts
|
|
725
|
+
import { mssqlDialect } from "sumak/mssql"
|
|
726
|
+
|
|
727
|
+
const db = sumak({
|
|
728
|
+
dialect: mssqlDialect(),
|
|
729
|
+
tables: { ... },
|
|
730
|
+
})
|
|
731
|
+
|
|
732
|
+
// LIMIT → TOP N
|
|
733
|
+
// SELECT TOP 10 * FROM [users]
|
|
734
|
+
|
|
735
|
+
// LIMIT + OFFSET → OFFSET/FETCH
|
|
736
|
+
// SELECT * FROM [users] ORDER BY [id] ASC OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY
|
|
737
|
+
|
|
738
|
+
// RETURNING → OUTPUT INSERTED.*
|
|
739
|
+
// INSERT INTO [users] ([name]) OUTPUT INSERTED.* VALUES (@p0)
|
|
740
|
+
|
|
741
|
+
// DELETE RETURNING → OUTPUT DELETED.*
|
|
742
|
+
// DELETE FROM [users] OUTPUT DELETED.* WHERE ([id] = @p0)
|
|
105
743
|
```
|
|
106
744
|
|
|
107
745
|
## Plugins
|
|
108
746
|
|
|
109
747
|
```ts
|
|
110
|
-
import {
|
|
748
|
+
import {
|
|
749
|
+
WithSchemaPlugin,
|
|
750
|
+
SoftDeletePlugin,
|
|
751
|
+
CamelCasePlugin,
|
|
752
|
+
} from "sumak"
|
|
111
753
|
|
|
112
754
|
const db = sumak({
|
|
113
755
|
dialect: pgDialect(),
|
|
@@ -116,7 +758,7 @@ const db = sumak({
|
|
|
116
758
|
new SoftDeletePlugin({ tables: ["users"] }),
|
|
117
759
|
],
|
|
118
760
|
tables: { ... },
|
|
119
|
-
})
|
|
761
|
+
})
|
|
120
762
|
|
|
121
763
|
// SELECT * FROM "public"."users" WHERE ("deleted_at" IS NULL)
|
|
122
764
|
```
|
|
@@ -152,54 +794,6 @@ const off = db.hook("query:before", handler)
|
|
|
152
794
|
off()
|
|
153
795
|
```
|
|
154
796
|
|
|
155
|
-
## Expression API
|
|
156
|
-
|
|
157
|
-
```ts
|
|
158
|
-
// Equality
|
|
159
|
-
.where(({ id }) =>
|
|
160
|
-
id.eq(42),
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
// String matching
|
|
164
|
-
.where(({ name }) =>
|
|
165
|
-
name.like("%ali%"),
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
// Range
|
|
169
|
-
.where(({ age }) =>
|
|
170
|
-
age.between(18, 65),
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
// List
|
|
174
|
-
.where(({ id }) =>
|
|
175
|
-
id.in([1, 2, 3]),
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
// Null checks
|
|
179
|
-
.where(({ bio }) =>
|
|
180
|
-
bio.isNull(),
|
|
181
|
-
)
|
|
182
|
-
.where(({ email }) =>
|
|
183
|
-
email.isNotNull(),
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
// AND
|
|
187
|
-
.where(({ a, b }) =>
|
|
188
|
-
and(
|
|
189
|
-
a.gt(0),
|
|
190
|
-
b.neq("x"),
|
|
191
|
-
),
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
// OR
|
|
195
|
-
.where(({ a, b }) =>
|
|
196
|
-
or(
|
|
197
|
-
a.eq(1),
|
|
198
|
-
b.eq(2),
|
|
199
|
-
),
|
|
200
|
-
)
|
|
201
|
-
```
|
|
202
|
-
|
|
203
797
|
## Why sumak?
|
|
204
798
|
|
|
205
799
|
| | sumak | Drizzle | Kysely |
|
|
@@ -222,7 +816,7 @@ Schema → Builder → AST → Plugin/Hook → Printer → SQL
|
|
|
222
816
|
- **Builder Layer** — `Sumak<DB>`, `TypedSelectBuilder<DB,TB,O>`, proxy-based expressions
|
|
223
817
|
- **AST Layer** — ~35 frozen node types, discriminated unions, visitor pattern
|
|
224
818
|
- **Plugin Layer** — `SumakPlugin` interface, `Hookable` lifecycle hooks
|
|
225
|
-
- **Printer Layer** — `BasePrinter` with dialect subclasses, Wadler document algebra
|
|
819
|
+
- **Printer Layer** — `BasePrinter` with 4 dialect subclasses (PG, MySQL, SQLite, MSSQL), Wadler document algebra
|
|
226
820
|
|
|
227
821
|
## License
|
|
228
822
|
|