@dockstat/sqlite-wrapper 1.2.8 → 1.3.1
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/LICENSE +373 -373
- package/README.md +553 -99
- package/index.ts +1128 -858
- package/package.json +60 -54
- package/query-builder/base.ts +187 -221
- package/query-builder/delete.ts +447 -352
- package/query-builder/index.ts +410 -431
- package/query-builder/insert.ts +286 -249
- package/query-builder/select.ts +335 -358
- package/query-builder/update.ts +314 -278
- package/query-builder/where.ts +272 -307
- package/types.ts +608 -623
- package/utils/index.ts +46 -0
- package/utils/logger.ts +216 -0
- package/utils/sql.ts +241 -0
- package/utils/transformer.ts +259 -0
package/query-builder/select.ts
CHANGED
|
@@ -1,358 +1,335 @@
|
|
|
1
|
-
import type { Database, SQLQueryBindings } from
|
|
2
|
-
import type {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
*
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const [whereClause, whereParams] = this.buildWhereClause()
|
|
117
|
-
query += whereClause
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
*
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
this.
|
|
197
|
-
|
|
198
|
-
)
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
this.
|
|
203
|
-
|
|
204
|
-
this.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
this.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
this.
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
return
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Execute
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
* Useful for getting a specific field value.
|
|
337
|
-
*
|
|
338
|
-
* @param column - Column name to extract the value from
|
|
339
|
-
* @returns The value of the specified column from the first row, or null
|
|
340
|
-
*/
|
|
341
|
-
value<K extends keyof T>(column: K): T[K] | null {
|
|
342
|
-
const row = this.first()
|
|
343
|
-
this.reset()
|
|
344
|
-
return row ? row[column] : null
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* Execute the query and return an array of values from a single column.
|
|
349
|
-
*
|
|
350
|
-
* @param column - Column name to extract values from
|
|
351
|
-
* @returns Array of values from the specified column
|
|
352
|
-
*/
|
|
353
|
-
pluck<K extends keyof T>(column: K): T[K][] {
|
|
354
|
-
const rows = this.all()
|
|
355
|
-
this.reset()
|
|
356
|
-
return rows.map((row) => row[column])
|
|
357
|
-
}
|
|
358
|
-
}
|
|
1
|
+
import type { Database, SQLQueryBindings } from "bun:sqlite"
|
|
2
|
+
import type { Logger } from "@dockstat/logger"
|
|
3
|
+
import type { ColumnNames, OrderDirection, Parser } from "../types"
|
|
4
|
+
import { createLogger, quoteIdentifier } from "../utils"
|
|
5
|
+
import { WhereQueryBuilder } from "./where"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* SelectQueryBuilder - Handles SELECT queries with ordering, limiting, and pagination
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Column selection (specific columns or *)
|
|
12
|
+
* - ORDER BY with ASC/DESC
|
|
13
|
+
* - LIMIT and OFFSET
|
|
14
|
+
* - Result transformation (JSON/Boolean parsing)
|
|
15
|
+
* - Client-side regex filtering when needed
|
|
16
|
+
*/
|
|
17
|
+
export class SelectQueryBuilder<T extends Record<string, unknown>> extends WhereQueryBuilder<T> {
|
|
18
|
+
private selectedColumns: ColumnNames<T> = ["*"]
|
|
19
|
+
private orderColumn?: keyof T
|
|
20
|
+
private orderDirection: OrderDirection = "ASC"
|
|
21
|
+
private limitValue?: number
|
|
22
|
+
private offsetValue?: number
|
|
23
|
+
|
|
24
|
+
private selectLog: ReturnType<typeof createLogger>
|
|
25
|
+
|
|
26
|
+
constructor(db: Database, tableName: string, parser: Parser<T>, baseLogger?: Logger) {
|
|
27
|
+
super(db, tableName, parser, baseLogger)
|
|
28
|
+
this.selectLog = createLogger("Select", baseLogger)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ===== Query Building Methods =====
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Specify which columns to select
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* .select(["id", "name", "email"])
|
|
38
|
+
* .select(["*"])
|
|
39
|
+
*/
|
|
40
|
+
select(columns: ColumnNames<T>): this {
|
|
41
|
+
this.selectedColumns = columns
|
|
42
|
+
return this
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Add ORDER BY clause
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* .orderBy("created_at")
|
|
50
|
+
*/
|
|
51
|
+
orderBy(column: keyof T): this {
|
|
52
|
+
this.orderColumn = column
|
|
53
|
+
return this
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Set order direction to descending
|
|
58
|
+
*/
|
|
59
|
+
desc(): this {
|
|
60
|
+
this.orderDirection = "DESC"
|
|
61
|
+
return this
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Set order direction to ascending (default)
|
|
66
|
+
*/
|
|
67
|
+
asc(): this {
|
|
68
|
+
this.orderDirection = "ASC"
|
|
69
|
+
return this
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Add LIMIT clause
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* .limit(10)
|
|
77
|
+
*/
|
|
78
|
+
limit(amount: number): this {
|
|
79
|
+
if (amount < 0) {
|
|
80
|
+
throw new Error("limit: amount must be non-negative")
|
|
81
|
+
}
|
|
82
|
+
this.limitValue = amount
|
|
83
|
+
return this
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Add OFFSET clause
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* .offset(20)
|
|
91
|
+
*/
|
|
92
|
+
offset(start: number): this {
|
|
93
|
+
if (start < 0) {
|
|
94
|
+
throw new Error("offset: start must be non-negative")
|
|
95
|
+
}
|
|
96
|
+
this.offsetValue = start
|
|
97
|
+
return this
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ===== Private Helpers =====
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Build the SELECT query SQL
|
|
104
|
+
*/
|
|
105
|
+
private buildSelectQuery(includeOrderAndLimit = true): [string, SQLQueryBindings[]] {
|
|
106
|
+
// Build column list
|
|
107
|
+
const cols =
|
|
108
|
+
this.selectedColumns[0] === "*"
|
|
109
|
+
? "*"
|
|
110
|
+
: (this.selectedColumns as string[]).map((c) => quoteIdentifier(c)).join(", ")
|
|
111
|
+
|
|
112
|
+
// Start with basic SELECT
|
|
113
|
+
let query = `SELECT ${cols} FROM ${quoteIdentifier(this.getTableName())}`
|
|
114
|
+
|
|
115
|
+
// Add WHERE clause
|
|
116
|
+
const [whereClause, whereParams] = this.buildWhereClause()
|
|
117
|
+
query += whereClause
|
|
118
|
+
|
|
119
|
+
// Add ORDER BY, LIMIT, OFFSET (unless regex conditions require client-side processing)
|
|
120
|
+
if (includeOrderAndLimit && !this.hasRegexConditions()) {
|
|
121
|
+
if (this.orderColumn) {
|
|
122
|
+
query += ` ORDER BY ${quoteIdentifier(String(this.orderColumn))} ${this.orderDirection}`
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (this.limitValue !== undefined) {
|
|
126
|
+
query += ` LIMIT ${this.limitValue}`
|
|
127
|
+
} else if (this.offsetValue !== undefined) {
|
|
128
|
+
query += ` LIMIT -1`
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (this.offsetValue !== undefined) {
|
|
132
|
+
query += ` OFFSET ${this.offsetValue}`
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return [query, whereParams]
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Apply client-side operations (sorting, pagination) when regex filtering is used
|
|
141
|
+
*/
|
|
142
|
+
private applyClientSideOperations(rows: T[]): T[] {
|
|
143
|
+
if (!this.hasRegexConditions()) {
|
|
144
|
+
return rows
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Apply regex filters first
|
|
148
|
+
let result = this.applyRegexFiltering(rows)
|
|
149
|
+
|
|
150
|
+
// Apply ordering
|
|
151
|
+
if (this.orderColumn) {
|
|
152
|
+
const col = String(this.orderColumn)
|
|
153
|
+
const direction = this.orderDirection === "ASC" ? 1 : -1
|
|
154
|
+
|
|
155
|
+
result.sort((a, b) => {
|
|
156
|
+
const va = a[col]
|
|
157
|
+
const vb = b[col]
|
|
158
|
+
|
|
159
|
+
if (va === vb) return 0
|
|
160
|
+
if (va === null || va === undefined) return -direction
|
|
161
|
+
if (vb === null || vb === undefined) return direction
|
|
162
|
+
if (va < vb) return -direction
|
|
163
|
+
return direction
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Apply offset and limit
|
|
168
|
+
const start = this.offsetValue ?? 0
|
|
169
|
+
if (this.limitValue !== undefined) {
|
|
170
|
+
result = result.slice(start, start + this.limitValue)
|
|
171
|
+
} else if (start > 0) {
|
|
172
|
+
result = result.slice(start)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return result
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ===== Execution Methods =====
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Execute the query and return all matching rows
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* const users = table.select(["*"]).where({ active: true }).all()
|
|
185
|
+
*/
|
|
186
|
+
all(): T[] {
|
|
187
|
+
const hasRegex = this.hasRegexConditions()
|
|
188
|
+
const [query, params] = this.buildSelectQuery(!hasRegex)
|
|
189
|
+
|
|
190
|
+
this.selectLog.query("SELECT", query, params)
|
|
191
|
+
|
|
192
|
+
const rows = this.getDb()
|
|
193
|
+
.prepare(query)
|
|
194
|
+
.all(...params) as T[]
|
|
195
|
+
|
|
196
|
+
this.selectLog.result("SELECT", rows.length)
|
|
197
|
+
|
|
198
|
+
// Transform rows (JSON/Boolean parsing)
|
|
199
|
+
const transformed = this.transformRowsFromDb(rows)
|
|
200
|
+
|
|
201
|
+
// Apply client-side operations if needed
|
|
202
|
+
const result = hasRegex ? this.applyClientSideOperations(transformed) : transformed
|
|
203
|
+
|
|
204
|
+
this.reset()
|
|
205
|
+
return result
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Execute the query and return the first matching row, or null
|
|
210
|
+
*
|
|
211
|
+
* Respects LIMIT if set, otherwise adds LIMIT 1 for efficiency
|
|
212
|
+
*/
|
|
213
|
+
get(): T | null {
|
|
214
|
+
// If no regex and no explicit limit, optimize with LIMIT 1
|
|
215
|
+
if (!this.hasRegexConditions() && this.limitValue === undefined) {
|
|
216
|
+
const [query, params] = this.buildSelectQuery(true)
|
|
217
|
+
const optimizedQuery = `${query} LIMIT 1`
|
|
218
|
+
|
|
219
|
+
this.selectLog.query("SELECT (get)", optimizedQuery, params)
|
|
220
|
+
|
|
221
|
+
const row = this.getDb()
|
|
222
|
+
.prepare(optimizedQuery)
|
|
223
|
+
.get(...params) as T | null
|
|
224
|
+
|
|
225
|
+
this.selectLog.result("SELECT (get)", row ? 1 : 0)
|
|
226
|
+
|
|
227
|
+
const result = row ? this.transformRowFromDb(row) : null
|
|
228
|
+
this.reset()
|
|
229
|
+
return result
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// If limit is set or regex conditions exist, use standard flow
|
|
233
|
+
if (!this.hasRegexConditions()) {
|
|
234
|
+
const [query, params] = this.buildSelectQuery(true)
|
|
235
|
+
|
|
236
|
+
this.selectLog.query("SELECT (get)", query, params)
|
|
237
|
+
|
|
238
|
+
const row = this.getDb()
|
|
239
|
+
.prepare(query)
|
|
240
|
+
.get(...params) as T | null
|
|
241
|
+
|
|
242
|
+
this.selectLog.result("SELECT (get)", row ? 1 : 0)
|
|
243
|
+
|
|
244
|
+
const result = row ? this.transformRowFromDb(row) : null
|
|
245
|
+
this.reset()
|
|
246
|
+
return result
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Has regex conditions - fall back to all() and take first
|
|
250
|
+
const results = this.all()
|
|
251
|
+
return results[0] ?? null
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Execute the query and return the first matching row, or null
|
|
256
|
+
* Always applies LIMIT 1 semantics
|
|
257
|
+
*/
|
|
258
|
+
first(): T | null {
|
|
259
|
+
const prevLimit = this.limitValue
|
|
260
|
+
this.limitValue = 1
|
|
261
|
+
const result = this.get()
|
|
262
|
+
this.limitValue = prevLimit
|
|
263
|
+
return result
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Execute a COUNT query and return the number of matching rows
|
|
268
|
+
*/
|
|
269
|
+
count(): number {
|
|
270
|
+
if (!this.hasRegexConditions()) {
|
|
271
|
+
// Use SQL COUNT for efficiency
|
|
272
|
+
const [whereClause, whereParams] = this.buildWhereClause()
|
|
273
|
+
const query = `SELECT COUNT(*) AS __count FROM ${quoteIdentifier(this.getTableName())}${whereClause}`
|
|
274
|
+
|
|
275
|
+
this.selectLog.query("COUNT", query, whereParams)
|
|
276
|
+
|
|
277
|
+
const result = this.getDb()
|
|
278
|
+
.prepare(query)
|
|
279
|
+
.get(...whereParams) as { __count: number } | null
|
|
280
|
+
|
|
281
|
+
this.reset()
|
|
282
|
+
return result?.__count ?? 0
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Has regex conditions - count client-side
|
|
286
|
+
const results = this.all()
|
|
287
|
+
return results.length
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Check if any rows match the current conditions
|
|
292
|
+
*/
|
|
293
|
+
exists(): boolean {
|
|
294
|
+
if (!this.hasRegexConditions()) {
|
|
295
|
+
// Use EXISTS for efficiency
|
|
296
|
+
const [whereClause, whereParams] = this.buildWhereClause()
|
|
297
|
+
const subquery = `SELECT 1 FROM ${quoteIdentifier(this.getTableName())}${whereClause} LIMIT 1`
|
|
298
|
+
const query = `SELECT EXISTS(${subquery}) AS __exists`
|
|
299
|
+
|
|
300
|
+
this.selectLog.query("EXISTS", query, whereParams)
|
|
301
|
+
|
|
302
|
+
const result = this.getDb()
|
|
303
|
+
.prepare(query)
|
|
304
|
+
.get(...whereParams) as { __exists: number } | null
|
|
305
|
+
|
|
306
|
+
this.reset()
|
|
307
|
+
return Boolean(result?.__exists)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Has regex conditions - check client-side
|
|
311
|
+
return this.count() > 0
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Get a single column value from the first matching row
|
|
316
|
+
*
|
|
317
|
+
* @example
|
|
318
|
+
* const name = table.where({ id: 1 }).value("name")
|
|
319
|
+
*/
|
|
320
|
+
value<K extends keyof T>(column: K): T[K] | null {
|
|
321
|
+
const row = this.first()
|
|
322
|
+
return row ? row[column] : null
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Get an array of values from a single column
|
|
327
|
+
*
|
|
328
|
+
* @example
|
|
329
|
+
* const emails = table.where({ active: true }).pluck("email")
|
|
330
|
+
*/
|
|
331
|
+
pluck<K extends keyof T>(column: K): T[K][] {
|
|
332
|
+
const rows = this.all()
|
|
333
|
+
return rows.map((row) => row[column])
|
|
334
|
+
}
|
|
335
|
+
}
|