@orieljs/framework 0.1.0-alpha.6 → 0.1.0-alpha.8
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/NOTICE +4 -0
- package/README.md +10 -1
- package/TRADEMARKS.md +33 -0
- package/dist/{chunk-POEP4B2M.js → chunk-7ZQZYDOC.js} +745 -105
- package/dist/chunk-7ZQZYDOC.js.map +1 -0
- package/dist/{chunk-I63ZFMX6.js → chunk-GVNLZZTN.js} +1 -1
- package/dist/chunk-GVNLZZTN.js.map +1 -0
- package/dist/{chunk-ZOEPKBVF.js → chunk-SPQUX3C7.js} +1347 -428
- package/dist/chunk-SPQUX3C7.js.map +1 -0
- package/dist/{chunk-MFTZQMZM.js → chunk-VOYW3R5J.js} +4 -4
- package/dist/chunk-VOYW3R5J.js.map +1 -0
- package/dist/contracts.d.ts +515 -17
- package/dist/contracts.js +1 -1
- package/dist/index.d-WQpfdIbG.d.ts +630 -0
- package/dist/index.d.ts +964 -152
- package/dist/index.js +4279 -866
- package/dist/index.js.map +1 -1
- package/dist/{jsx-runtime-CBZQYYDS.d.ts → jsx-runtime-6um4Mwfb.d.ts} +96 -5
- package/dist/jsx-runtime.d.ts +2 -2
- package/dist/jsx-runtime.js +3 -3
- package/dist/{testing-BM481Hdz.d.ts → testing-CKwfWJGn.d.ts} +180 -15
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +3 -3
- package/docs/authentication.md +487 -0
- package/docs/authorization.md +435 -0
- package/docs/cache.md +647 -0
- package/docs/collections.md +2270 -0
- package/docs/configuration.md +334 -0
- package/docs/console.md +651 -0
- package/docs/container.md +430 -0
- package/docs/controllers.md +406 -0
- package/docs/csrf.md +126 -0
- package/docs/database-testing.md +144 -0
- package/docs/database.md +236 -0
- package/docs/deployment.md +276 -0
- package/docs/eloquent-collections.md +218 -0
- package/docs/eloquent-factories.md +255 -0
- package/docs/eloquent-mutators.md +254 -0
- package/docs/eloquent-observers.md +145 -0
- package/docs/eloquent-relationships.md +557 -0
- package/docs/eloquent.md +494 -0
- package/docs/encryption.md +104 -0
- package/docs/errors.md +318 -0
- package/docs/events.md +336 -0
- package/docs/facades.md +278 -0
- package/docs/hashing.md +118 -0
- package/docs/helpers.md +1281 -0
- package/docs/http-tests.md +386 -0
- package/docs/inertia.md +485 -0
- package/docs/installation.md +209 -0
- package/docs/lifecycle.md +234 -0
- package/docs/live-tests.md +226 -0
- package/docs/live.md +910 -0
- package/docs/logging.md +255 -0
- package/docs/mail.md +619 -0
- package/docs/middleware.md +384 -0
- package/docs/migrations.md +416 -0
- package/docs/mocking.md +264 -0
- package/docs/navigation.json +273 -0
- package/docs/notifications.md +225 -0
- package/docs/pagination.md +247 -0
- package/docs/passwords.md +207 -0
- package/docs/providers.md +187 -0
- package/docs/queries.md +600 -0
- package/docs/queues.md +330 -0
- package/docs/requests.md +533 -0
- package/docs/responses.md +444 -0
- package/docs/routing.md +738 -0
- package/docs/scheduling.md +277 -0
- package/docs/seeding.md +122 -0
- package/docs/session.md +409 -0
- package/docs/starter-kits.md +396 -0
- package/docs/storage.md +192 -0
- package/docs/structure.md +264 -0
- package/docs/testing.md +165 -0
- package/docs/time-travel.md +138 -0
- package/docs/validation.md +1792 -0
- package/docs/verification.md +152 -0
- package/docs/views.md +566 -0
- package/docs/vite.md +322 -0
- package/package.json +34 -32
- package/dist/chunk-I63ZFMX6.js.map +0 -1
- package/dist/chunk-MFTZQMZM.js.map +0 -1
- package/dist/chunk-POEP4B2M.js.map +0 -1
- package/dist/chunk-ZOEPKBVF.js.map +0 -1
- package/dist/index.d-BJyJx6U1.d.ts +0 -319
package/docs/queries.md
ADDED
|
@@ -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).
|