@orieljs/framework 0.1.0-alpha.5 → 0.1.0-alpha.7

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.
Files changed (87) hide show
  1. package/NOTICE +7 -0
  2. package/README.md +10 -1
  3. package/TRADEMARKS.md +33 -0
  4. package/dist/{chunk-WNJP46B7.js → chunk-4MWG3B7G.js} +1785 -626
  5. package/dist/chunk-4MWG3B7G.js.map +1 -0
  6. package/dist/chunk-7ZQZYDOC.js +3253 -0
  7. package/dist/chunk-7ZQZYDOC.js.map +1 -0
  8. package/dist/{chunk-BSQKOLSK.js → chunk-GVNLZZTN.js} +1 -1
  9. package/dist/chunk-GVNLZZTN.js.map +1 -0
  10. package/dist/{chunk-A5XRLEWM.js → chunk-VOYW3R5J.js} +4 -4
  11. package/dist/chunk-VOYW3R5J.js.map +1 -0
  12. package/dist/contracts.d.ts +535 -17
  13. package/dist/contracts.js +1 -1
  14. package/dist/index.d-WQpfdIbG.d.ts +630 -0
  15. package/dist/index.d.ts +2202 -5952
  16. package/dist/index.js +4900 -944
  17. package/dist/index.js.map +1 -1
  18. package/dist/{jsx-runtime-WKTl3EEw.d.ts → jsx-runtime-6um4Mwfb.d.ts} +97 -51
  19. package/dist/jsx-runtime.d.ts +2 -2
  20. package/dist/jsx-runtime.js +3 -3
  21. package/dist/{testing-BErengYY.d.ts → testing-CKwfWJGn.d.ts} +313 -994
  22. package/dist/testing.d.ts +2 -2
  23. package/dist/testing.js +19 -5
  24. package/docs/authentication.md +487 -0
  25. package/docs/authorization.md +435 -0
  26. package/docs/cache.md +647 -0
  27. package/docs/collections.md +2270 -0
  28. package/docs/configuration.md +334 -0
  29. package/docs/console.md +651 -0
  30. package/docs/container.md +430 -0
  31. package/docs/controllers.md +406 -0
  32. package/docs/csrf.md +126 -0
  33. package/docs/database-testing.md +144 -0
  34. package/docs/database.md +236 -0
  35. package/docs/deployment.md +276 -0
  36. package/docs/eloquent-collections.md +218 -0
  37. package/docs/eloquent-factories.md +255 -0
  38. package/docs/eloquent-mutators.md +254 -0
  39. package/docs/eloquent-observers.md +145 -0
  40. package/docs/eloquent-relationships.md +557 -0
  41. package/docs/eloquent.md +494 -0
  42. package/docs/encryption.md +104 -0
  43. package/docs/errors.md +318 -0
  44. package/docs/events.md +336 -0
  45. package/docs/facades.md +278 -0
  46. package/docs/hashing.md +118 -0
  47. package/docs/helpers.md +1281 -0
  48. package/docs/http-tests.md +386 -0
  49. package/docs/inertia.md +485 -0
  50. package/docs/installation.md +209 -0
  51. package/docs/lifecycle.md +234 -0
  52. package/docs/live-tests.md +226 -0
  53. package/docs/live.md +910 -0
  54. package/docs/logging.md +255 -0
  55. package/docs/mail.md +619 -0
  56. package/docs/middleware.md +384 -0
  57. package/docs/migrations.md +416 -0
  58. package/docs/mocking.md +264 -0
  59. package/docs/navigation.json +273 -0
  60. package/docs/notifications.md +225 -0
  61. package/docs/pagination.md +247 -0
  62. package/docs/passwords.md +207 -0
  63. package/docs/providers.md +187 -0
  64. package/docs/queries.md +600 -0
  65. package/docs/queues.md +330 -0
  66. package/docs/requests.md +533 -0
  67. package/docs/responses.md +444 -0
  68. package/docs/routing.md +738 -0
  69. package/docs/scheduling.md +277 -0
  70. package/docs/seeding.md +122 -0
  71. package/docs/session.md +409 -0
  72. package/docs/starter-kits.md +396 -0
  73. package/docs/storage.md +192 -0
  74. package/docs/structure.md +264 -0
  75. package/docs/testing.md +165 -0
  76. package/docs/time-travel.md +138 -0
  77. package/docs/validation.md +1792 -0
  78. package/docs/verification.md +152 -0
  79. package/docs/views.md +566 -0
  80. package/docs/vite.md +322 -0
  81. package/package.json +34 -32
  82. package/dist/chunk-A5XRLEWM.js.map +0 -1
  83. package/dist/chunk-BSQKOLSK.js.map +0 -1
  84. package/dist/chunk-PM5YTG57.js +0 -880
  85. package/dist/chunk-PM5YTG57.js.map +0 -1
  86. package/dist/chunk-WNJP46B7.js.map +0 -1
  87. package/dist/index.d-DOlTeG-8.d.ts +0 -99
@@ -0,0 +1,600 @@
1
+ # Database: query builder
2
+
3
+ The query builder writes your SQL for you, one chained call at a time, and binds every value so it is safe from SQL
4
+ injection. It works the same on SQLite, PostgreSQL and MySQL.
5
+
6
+ ## Introduction
7
+
8
+ `DB.table()` starts a query on a table. Each method adds to the query and returns it, so you chain them, and the
9
+ method that runs the query, such as `get()`, returns a promise:
10
+
11
+ ```ts
12
+ const users = await DB.table('users').where('votes', '>', 100).orderBy('name').get()
13
+ ```
14
+
15
+ Your [models](eloquent.md) use the same builder: `User.where('votes', '>', 100).get()` takes every method on this page
16
+ and returns models.
17
+
18
+ ## Running database queries
19
+
20
+ #### Retrieving all rows from a table
21
+
22
+ `get()` runs the query and returns the rows in a [collection](collections.md), as plain objects:
23
+
24
+ ```ts title="app/Http/Controllers/UserController.ts"
25
+ import { DB, view } from '@orieljs/framework'
26
+ import { Controller } from './Controller'
27
+
28
+ export class UserController extends Controller {
29
+ /** Show a list of all of the application's users. */
30
+ async index() {
31
+ const users = await DB.table('users').get()
32
+
33
+ return view('user.index', { users: users.all() })
34
+ }
35
+ }
36
+ ```
37
+
38
+ The rows are typed `Record<string, unknown>` unless you give their type, which is a cast, not a check:
39
+ `DB.table<{ id: number; name: string }>('users')`.
40
+
41
+ #### Retrieving a single row or column from a table
42
+
43
+ `first()` returns the first row, or `undefined` when there is none. `value()` returns one column of the first row,
44
+ and `find()` the row with that `id`:
45
+
46
+ ```ts
47
+ const user = await DB.table('users').where('name', 'John').first()
48
+
49
+ const email = await DB.table('users').where('name', 'John').value('email')
50
+
51
+ const found = await DB.table('users').find(3)
52
+ ```
53
+
54
+ #### Retrieving a list of column values
55
+
56
+ `pluck()` returns one column of every row, in a collection:
57
+
58
+ ```ts
59
+ const titles = await DB.table('users').pluck('title')
60
+
61
+ for (const title of titles) {
62
+ // ...
63
+ }
64
+ ```
65
+
66
+ > [!NOTE]
67
+ > Laravel's `pluck()` takes a second column to key the results by. Oriel's takes one column; for a dictionary, use
68
+ > the collection's `keyBy()` or `mapWithKeys()` on `get()`'s rows.
69
+
70
+ ### Chunking results
71
+
72
+ `chunk()` reads a large table a part at a time, and calls your function with each part. The query needs an
73
+ `orderBy()`, so the parts don't overlap:
74
+
75
+ ```ts
76
+ await DB.table('users')
77
+ .orderBy('id')
78
+ .chunk(100, async (users) => {
79
+ for (const user of users) {
80
+ // ...
81
+ }
82
+ })
83
+ ```
84
+
85
+ Return `false` from the function to stop. When the function updates or deletes the rows it reads, use `chunkById()`,
86
+ which pages by the `id` column rather than by offset, so no row is skipped:
87
+
88
+ ```ts
89
+ await DB.table('users')
90
+ .where('active', false)
91
+ .chunkById(100, async (users) => {
92
+ for (const user of users) {
93
+ await DB.table('users').where('id', user.id).update({ active: true })
94
+ }
95
+ })
96
+ ```
97
+
98
+ `each()` calls your function with each row in turn, reading them a part at a time, 1,000 unless you give a number. It
99
+ needs an `orderBy()` too, as `chunk()` does.
100
+
101
+ ### Streaming results lazily
102
+
103
+ `lazy()` returns a [lazy collection](collections.md#lazy-collections), which reads the rows a part at a time as you go
104
+ through them with `for await`. It needs an `orderBy()` too; `lazyById()` pages by `id`:
105
+
106
+ ```ts
107
+ for await (const user of DB.table('users').orderBy('id').lazy()) {
108
+ // ...
109
+ }
110
+ ```
111
+
112
+ ### Aggregates
113
+
114
+ `count()`, `max()`, `min()`, `avg()` and `sum()` run an aggregate:
115
+
116
+ ```ts
117
+ const users = await DB.table('users').count()
118
+
119
+ const price = await DB.table('orders').max('price')
120
+
121
+ const average = await DB.table('orders').where('finalized', 1).avg('price')
122
+ ```
123
+
124
+ `count()` and `sum()` return a number. `avg()` returns a number, or `undefined` for no rows, and `max()` and `min()` the
125
+ value, or `undefined`.
126
+
127
+ #### Determining if records exist
128
+
129
+ ```ts
130
+ if (await DB.table('orders').where('finalized', 1).exists()) {
131
+ // ...
132
+ }
133
+
134
+ if (await DB.table('orders').where('finalized', 1).doesntExist()) {
135
+ // ...
136
+ }
137
+ ```
138
+
139
+ ## Select statements
140
+
141
+ #### Specifying a select clause
142
+
143
+ `select()` chooses the columns, and `as` renames one:
144
+
145
+ ```ts
146
+ const users = await DB.table('users').select('name', 'email as user_email').get()
147
+ ```
148
+
149
+ `distinct()` returns only distinct rows, and `addSelect()` adds columns to the select:
150
+
151
+ ```ts
152
+ const query = DB.table('users').select('name')
153
+
154
+ const users = await query.addSelect('age').get()
155
+ ```
156
+
157
+ ## Raw expressions
158
+
159
+ `DB.raw()` writes SQL into a query as it is:
160
+
161
+ ```ts
162
+ const users = await DB.table('users')
163
+ .select(DB.raw('count(*) as user_count, status'))
164
+ .where('status', '<>', 1)
165
+ .groupBy('status')
166
+ .get()
167
+ ```
168
+
169
+ > [!WARNING]
170
+ > A raw expression is SQL you write. Never put a user's input in it: pass values as bindings, with `?`.
171
+
172
+ ### Raw methods
173
+
174
+ `selectRaw()`, `whereRaw()`, `orWhereRaw()`, `havingRaw()` and `orderByRaw()` take SQL and its bindings:
175
+
176
+ ```ts
177
+ const orders = await DB.table('orders').selectRaw('price * ? as price_with_tax', [1.0825]).get()
178
+
179
+ const big = await DB.table('orders').whereRaw('price > IF(state = "TX", ?, 100)', [200]).get()
180
+
181
+ const totals = await DB.table('orders')
182
+ .select('department', DB.raw('SUM(price) as total_sales'))
183
+ .groupBy('department')
184
+ .havingRaw('SUM(price) > ?', [2500])
185
+ .get()
186
+
187
+ const recent = await DB.table('orders').orderByRaw('updated_at - created_at DESC').get()
188
+ ```
189
+
190
+ ## Joins
191
+
192
+ #### Inner join clause
193
+
194
+ `join()` takes the table, then the two columns and the operator between them:
195
+
196
+ ```ts
197
+ const users = await DB.table('users')
198
+ .join('contacts', 'users.id', '=', 'contacts.user_id')
199
+ .join('orders', 'users.id', '=', 'orders.user_id')
200
+ .select('users.*', 'contacts.phone', 'orders.price')
201
+ .get()
202
+ ```
203
+
204
+ #### Left join / right join clause
205
+
206
+ ```ts
207
+ const users = await DB.table('users').leftJoin('posts', 'users.id', '=', 'posts.user_id').get()
208
+
209
+ const authors = await DB.table('users').rightJoin('posts', 'users.id', '=', 'posts.user_id').get()
210
+ ```
211
+
212
+ #### Cross join clause
213
+
214
+ ```ts
215
+ const sizes = await DB.table('sizes').crossJoin('colors').get()
216
+ ```
217
+
218
+ #### Advanced join clauses
219
+
220
+ Give `join()` a function for a join with several conditions. `on()` and `orOn()` compare columns, and `where()` a
221
+ column with a value:
222
+
223
+ ```ts
224
+ const users = await DB.table('users')
225
+ .join('contacts', (join) => {
226
+ join.on('users.id', '=', 'contacts.user_id').where('contacts.user_id', '>', 5)
227
+ })
228
+ .get()
229
+ ```
230
+
231
+ > [!NOTE]
232
+ > Laravel's subquery joins (`joinSub()`), lateral joins and unions aren't in Oriel.
233
+
234
+ ## Basic where clauses
235
+
236
+ ### Where clauses
237
+
238
+ `where()` takes a column, an operator and a value. Without an operator, it compares with `=`:
239
+
240
+ ```ts
241
+ const users = await DB.table('users').where('votes', '=', 100).where('age', '>', 35).get()
242
+
243
+ const same = await DB.table('users').where('votes', 100).get()
244
+ ```
245
+
246
+ The operators are `=`, `<`, `>`, `<=`, `>=`, `<>`, `!=`, `like`, `not like`, `ilike` and `not ilike`. Any other throws
247
+ "Illegal operator [...].", so an operator can never carry SQL.
248
+
249
+ An object gives several `=` conditions at once:
250
+
251
+ ```ts
252
+ const users = await DB.table('users').where({ status: 1, subscribed: 1 }).get()
253
+ ```
254
+
255
+ A value of `null` or `undefined` compares with `IS NULL`: `where('deleted_at', null)` finds the rows without one.
256
+
257
+ > [!WARNING]
258
+ > `where('email', request().input('email'))` with no `email` in the request finds the rows whose email is `NULL`.
259
+ > Validate the input first, or check it before you query.
260
+
261
+ ### Or where clauses
262
+
263
+ `orWhere()` joins a condition with `or`. Give it a function to group conditions in parentheses:
264
+
265
+ ```ts
266
+ const users = await DB.table('users')
267
+ .where('votes', '>', 100)
268
+ .orWhere((query) => {
269
+ query.where('name', 'Abigail').where('votes', '>', 50)
270
+ })
271
+ .get()
272
+ ```
273
+
274
+ ```sql
275
+ select * from users where votes > 100 or (name = 'Abigail' and votes > 50)
276
+ ```
277
+
278
+ ### Where not clauses
279
+
280
+ `whereNot()` and `orWhereNot()` negate a group of conditions:
281
+
282
+ ```ts
283
+ const products = await DB.table('products')
284
+ .whereNot((query) => {
285
+ query.where('clearance', true).orWhere('price', '<', 10)
286
+ })
287
+ .get()
288
+ ```
289
+
290
+ ### Where like clauses
291
+
292
+ `whereLike()` matches a pattern, with `%` and `_`, ignoring case. `true` as a third argument makes it case-sensitive.
293
+ `orWhereLike()`, `whereNotLike()` and `orWhereNotLike()` are its other forms:
294
+
295
+ ```ts
296
+ const users = await DB.table('users').whereLike('name', '%John%').get()
297
+
298
+ const exact = await DB.table('users').whereLike('name', '%John%', true).get()
299
+ ```
300
+
301
+ It uses `ilike` on PostgreSQL and `like` on MySQL and SQLite to ignore case, and `like`, `like binary` or `glob` to
302
+ respect it.
303
+
304
+ ### Additional where clauses
305
+
306
+ **whereBetween / orWhereBetween / whereNotBetween**
307
+
308
+ ```ts
309
+ const users = await DB.table('users').whereBetween('votes', [1, 100]).get()
310
+
311
+ const others = await DB.table('users').whereNotBetween('votes', [1, 100]).get()
312
+ ```
313
+
314
+ **whereIn / whereNotIn / orWhereIn / orWhereNotIn**
315
+
316
+ ```ts
317
+ const users = await DB.table('users').whereIn('id', [1, 2, 3]).get()
318
+
319
+ const others = await DB.table('users').whereNotIn('id', [1, 2, 3]).get()
320
+ ```
321
+
322
+ A query in place of the list matches the values it selects:
323
+
324
+ ```ts
325
+ const activeUsers = DB.table('users').select('id').where('is_active', 1)
326
+
327
+ const comments = await DB.table('comments').whereIn('user_id', activeUsers).get()
328
+ ```
329
+
330
+ **whereNull / whereNotNull / orWhereNull / orWhereNotNull**
331
+
332
+ ```ts
333
+ const users = await DB.table('users').whereNull('updated_at').get()
334
+
335
+ const others = await DB.table('users').whereNotNull('updated_at').get()
336
+ ```
337
+
338
+ **whereDate / whereMonth / whereDay / whereYear / whereTime**
339
+
340
+ These compare a part of a date column: its date, month, day of the month, year or time. Each takes a value, or an
341
+ operator and a value, and has an `or` form:
342
+
343
+ ```ts
344
+ const users = await DB.table('users').whereDate('created_at', '2016-12-31').get()
345
+
346
+ const december = await DB.table('users').whereMonth('created_at', 12).get()
347
+
348
+ const recent = await DB.table('users').whereYear('created_at', '>=', 2025).get()
349
+
350
+ const late = await DB.table('users').whereTime('created_at', '>', '11:20:45').get()
351
+ ```
352
+
353
+ A `Date` as the value compares in UTC, as Oriel stores dates.
354
+
355
+ **whereColumn / orWhereColumn**
356
+
357
+ `whereColumn()` compares two columns:
358
+
359
+ ```ts
360
+ const users = await DB.table('users').whereColumn('first_name', 'last_name').get()
361
+
362
+ const updated = await DB.table('users').whereColumn('updated_at', '>', 'created_at').get()
363
+ ```
364
+
365
+ ### Logical grouping
366
+
367
+ Wrap conditions that belong together in a function, so their `or` doesn't reach the rest of the query:
368
+
369
+ ```ts
370
+ const users = await DB.table('users')
371
+ .where('name', '=', 'John')
372
+ .where((query) => {
373
+ query.where('votes', '>', 100).orWhere('title', '=', 'Admin')
374
+ })
375
+ .get()
376
+ ```
377
+
378
+ ```sql
379
+ select * from users where name = 'John' and (votes > 100 or title = 'Admin')
380
+ ```
381
+
382
+ ## Advanced where clauses
383
+
384
+ ### Where exists clauses
385
+
386
+ `whereExists()` takes a query, or a function that builds one, and matches the rows for which it finds a row:
387
+
388
+ ```ts
389
+ const users = await DB.table('users')
390
+ .whereExists((query) => {
391
+ query.select(DB.raw('1')).from('orders').whereColumn('orders.user_id', 'users.id')
392
+ })
393
+ .get()
394
+ ```
395
+
396
+ `whereNotExists()` and `orWhereExists()` are its other forms.
397
+
398
+ ### Subquery where clauses
399
+
400
+ `whereSub()` compares the result of a query with a value:
401
+
402
+ ```ts
403
+ const users = await DB.table('users')
404
+ .whereSub(
405
+ DB.table('memberships').select('type').whereColumn('memberships.user_id', 'users.id').orderByDesc('start_date').limit(1),
406
+ '=',
407
+ 'Pro',
408
+ )
409
+ .get()
410
+ ```
411
+
412
+ > [!WARNING]
413
+ > Laravel writes this as `where(closure, 'Pro')`. In Oriel, `where()` with a function is a nested group, as the
414
+ > section above shows, and the value after it is ignored, so the query matches the wrong rows. Use `whereSub()`.
415
+
416
+ > [!NOTE]
417
+ > Laravel's JSON where clauses (`where('options->language', …)`, `whereJsonContains()`), `whereAny()`, `whereAll()`,
418
+ > full text clauses and vector clauses aren't in Oriel. Use `whereRaw()` for them.
419
+
420
+ ## Ordering, grouping, limit and offset
421
+
422
+ ### Ordering
423
+
424
+ `orderBy()` sorts by a column, `asc` unless you say `desc`; call it again to sort by more columns:
425
+
426
+ ```ts
427
+ const users = await DB.table('users').orderBy('name', 'desc').orderBy('email', 'asc').get()
428
+ ```
429
+
430
+ `orderByDesc()` sorts descending. `latest()` and `oldest()` sort by `created_at`, or the column you give, newest or
431
+ oldest first. `inRandomOrder()` shuffles the rows, and `reorder()` removes the orders so far, or replaces them with
432
+ the one you give:
433
+
434
+ ```ts
435
+ const user = await DB.table('users').latest().first()
436
+
437
+ const randomUser = await DB.table('users').inRandomOrder().first()
438
+
439
+ const byEmail = await DB.table('users').orderBy('name').reorder('email', 'desc').get()
440
+ ```
441
+
442
+ ### Grouping
443
+
444
+ `groupBy()` and `having()` group the rows and filter the groups:
445
+
446
+ ```ts
447
+ const users = await DB.table('users').groupBy('account_id').having('account_id', '>', 100).get()
448
+ ```
449
+
450
+ ### Limit and offset
451
+
452
+ `limit()` (or `take()`) and `offset()` (or `skip()`) choose a slice of the rows, and `forPage()` the rows of a page:
453
+
454
+ ```ts
455
+ const users = await DB.table('users').offset(10).limit(5).get()
456
+
457
+ const page = await DB.table('users').forPage(3, 15).get()
458
+ ```
459
+
460
+ For pages with links, see [Pagination](pagination.md).
461
+
462
+ ## Conditional clauses
463
+
464
+ `when()` adds to the query only when its first argument is truthy, and passes that value to your function. A third
465
+ function runs when it is falsy:
466
+
467
+ ```ts
468
+ const role = request().string('role').toString()
469
+
470
+ const users = await DB.table('users')
471
+ .when(role, (query, role) => {
472
+ query.where('role_id', role)
473
+ })
474
+ .get()
475
+ ```
476
+
477
+ `unless()` does the opposite.
478
+
479
+ > [!NOTE]
480
+ > "Truthy" is JavaScript's: `'0'` and `[]` are truthy, where PHP finds them falsy, so a `?active=0` filter applies.
481
+ > Convert such input first: `request().boolean('active')`.
482
+
483
+ ## Insert statements
484
+
485
+ `insert()` inserts a row, or a list of rows:
486
+
487
+ ```ts
488
+ await DB.table('users').insert({ email: 'kayla@example.com', votes: 0 })
489
+
490
+ await DB.table('users').insert([
491
+ { email: 'picard@example.com', votes: 0 },
492
+ { email: 'janeway@example.com', votes: 0 },
493
+ ])
494
+ ```
495
+
496
+ `insertOrIgnore()` skips the rows that would break a unique index, and returns how many it inserted.
497
+
498
+ #### Auto-incrementing IDs
499
+
500
+ `insertGetId()` inserts a row and returns its new `id`:
501
+
502
+ ```ts
503
+ const id = await DB.table('users').insertGetId({ email: 'john@example.com', votes: 0 })
504
+ ```
505
+
506
+ ### Upserts
507
+
508
+ `upsert()` inserts the rows, and updates those that already exist. The second argument lists the columns that tell
509
+ whether a row exists, which must have a unique index, and the third the columns to update:
510
+
511
+ ```ts
512
+ await DB.table('flights').upsert(
513
+ [
514
+ { departure: 'Oakland', destination: 'San Diego', price: 99 },
515
+ { departure: 'Chicago', destination: 'New York', price: 150 },
516
+ ],
517
+ ['departure', 'destination'],
518
+ ['price'],
519
+ )
520
+ ```
521
+
522
+ ## Update statements
523
+
524
+ `update()` changes the rows the query matches, and returns how many it changed:
525
+
526
+ ```ts
527
+ const affected = await DB.table('users').where('id', 1).update({ votes: 1 })
528
+ ```
529
+
530
+ `update()` and `delete()` refuse a query with joins or an offset, and with a limit except on MySQL: they throw rather
531
+ than change rows you didn't mean.
532
+
533
+ #### Update or insert
534
+
535
+ `updateOrInsert()` updates the row that matches its first argument with its second, or inserts both together when
536
+ there is none:
537
+
538
+ ```ts
539
+ await DB.table('users').updateOrInsert({ email: 'john@example.com', name: 'John' }, { votes: '2' })
540
+ ```
541
+
542
+ ### Increment and decrement
543
+
544
+ `increment()` and `decrement()` add to or take from a column, by 1 or by the amount you give, and can set other
545
+ columns at the same time:
546
+
547
+ ```ts
548
+ await DB.table('users').increment('votes')
549
+
550
+ await DB.table('users').increment('votes', 5)
551
+
552
+ await DB.table('users').decrement('votes', 1, { name: 'John' })
553
+ ```
554
+
555
+ ## Delete statements
556
+
557
+ `delete()` deletes the rows the query matches, and returns how many it deleted. Given an id, it deletes that row.
558
+ `truncate()` empties the table:
559
+
560
+ ```ts
561
+ const deleted = await DB.table('users').delete()
562
+
563
+ await DB.table('users').where('votes', '>', 100).delete()
564
+
565
+ await DB.table('users').truncate()
566
+ ```
567
+
568
+ ## Pessimistic locking
569
+
570
+ `sharedLock()` keeps the selected rows from being changed until your transaction commits, and `lockForUpdate()` from
571
+ being changed or locked by anyone else. Use them in a [transaction](database.md#database-transactions):
572
+
573
+ ```ts
574
+ await DB.transaction(async () => {
575
+ const sender = await DB.table('users').lockForUpdate().find(1)
576
+ const receiver = await DB.table('users').lockForUpdate().find(2)
577
+
578
+ // Move money from the sender to the receiver...
579
+
580
+ return { sender, receiver }
581
+ })
582
+ ```
583
+
584
+ SQLite has no row locks: there, both do nothing, and SQLite's write lock covers the transaction.
585
+
586
+ ## Debugging
587
+
588
+ `toSql()` returns the query's SQL, with a placeholder for each value (`?`, or `$1`, `$2` on PostgreSQL), and
589
+ `getBindings()` the values:
590
+
591
+ ```ts
592
+ const query = DB.table('users').where('votes', '>', 100)
593
+
594
+ const sql = query.toSql()
595
+ const bindings = query.getBindings()
596
+ ```
597
+
598
+ > [!NOTE]
599
+ > Laravel's `dd()`, `dump()` and `toRawSql()` aren't in Oriel. Log `toSql()` and `getBindings()`, or turn on the
600
+ > [query log](database.md#logging-queries).