@query-doctor/core 0.1.0 → 0.1.2

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.
@@ -1,584 +0,0 @@
1
- import assert from "node:assert";
2
- import fs from "node:fs";
3
- import test from "node:test";
4
- import dedent from "dedent";
5
- import { Analyzer } from "./analyzer.js";
6
- import { parse } from "@pgsql/parser";
7
- test("analyzer test", async function () {
8
- const analyzer = new Analyzer(parse);
9
- const query = dedent `
10
- select
11
- "public"."team_user"."team_user_id",
12
- "public"."team_user"."team_id"
13
- from
14
- "public"."team_user"
15
- where
16
- (
17
- 1 = 1
18
- and "public"."team_user"."team_id" in ($1)
19
- )
20
- offset
21
- $2`;
22
- const { indexesToCheck } = await analyzer.analyze(query);
23
- assert.deepStrictEqual(indexesToCheck, [
24
- {
25
- frequency: 1,
26
- representation: '"public"."team_user"."team_id"',
27
- parts: [
28
- { quoted: true, start: 135, text: "public" },
29
- { quoted: true, start: 144, text: "team_user" },
30
- { quoted: true, start: 156, text: "team_id" },
31
- ],
32
- ignored: false,
33
- position: { start: 135, end: 165 },
34
- },
35
- ]);
36
- });
37
- test("analyzer test with ordering", async function () {
38
- const analyzer = new Analyzer(parse);
39
- const query = dedent `
40
- select
41
- "public"."team"."team_id"
42
- from
43
- "public"."team"
44
- order by
45
- team.team_id desc nulls first
46
- `;
47
- const { indexesToCheck } = await analyzer.analyze(query);
48
- assert.deepStrictEqual(indexesToCheck, [
49
- {
50
- frequency: 1,
51
- representation: "team.team_id",
52
- parts: [
53
- { quoted: false, start: 42, text: "team" },
54
- { quoted: false, start: 74, text: "team_id" },
55
- ],
56
- ignored: false,
57
- position: { start: 69, end: 81 },
58
- sort: {
59
- dir: "SORTBY_DESC",
60
- nulls: "SORTBY_NULLS_FIRST",
61
- },
62
- },
63
- ]);
64
- });
65
- test("analyzer isnull", async function () {
66
- const analyzer = new Analyzer(parse);
67
- const query = dedent `
68
- select * from team
69
- where team.deleted_at is null
70
- `;
71
- const { indexesToCheck } = await analyzer.analyze(query);
72
- assert.deepStrictEqual(indexesToCheck, [
73
- {
74
- frequency: 1,
75
- representation: "team.deleted_at",
76
- parts: [
77
- { text: "team", start: 14, quoted: false },
78
- { text: "deleted_at", start: 30, quoted: false },
79
- ],
80
- ignored: false,
81
- position: { start: 25, end: 40 },
82
- where: { nulltest: "IS_NULL" },
83
- },
84
- ]);
85
- });
86
- test("analyzer test", async function () {
87
- const analyzer = new Analyzer(parse);
88
- const query = dedent `
89
- select
90
- COUNT(*) as "_count._all"
91
- from
92
- (
93
- select
94
- "public"."team"."team_id"
95
- from
96
- "public"."team"
97
- where
98
- (
99
- "public"."team"."deleted_at" is null
100
- and exists (
101
- select
102
- "t0"."team_id"
103
- from
104
- "public"."team_user" as "t0"
105
- where
106
- (
107
- "t0"."user_id" = $1
108
- and ("public"."team"."team_id") = ("t0"."team_id")
109
- and "t0"."team_id" is not null
110
- )
111
- )
112
- )
113
- offset
114
- $2
115
- ) as "sub"`;
116
- const { indexesToCheck } = await analyzer.analyze(query);
117
- assert.deepStrictEqual(indexesToCheck, [
118
- {
119
- frequency: 1,
120
- representation: '"t0"."team_id"',
121
- parts: [
122
- { text: "team_user", start: 273, quoted: true, alias: "t0" },
123
- { text: "team_id", start: 454, quoted: true },
124
- ],
125
- ignored: false,
126
- position: { start: 449, end: 463 },
127
- where: {
128
- nulltest: "IS_NOT_NULL",
129
- },
130
- },
131
- {
132
- frequency: 1,
133
- representation: '"public"."team"."team_id"',
134
- parts: [
135
- { text: "public", start: 385, quoted: true },
136
- { text: "team", start: 394, quoted: true },
137
- { text: "team_id", start: 401, quoted: true },
138
- ],
139
- ignored: false,
140
- position: { start: 385, end: 410 },
141
- },
142
- {
143
- frequency: 1,
144
- representation: '"t0"."user_id"',
145
- parts: [
146
- { text: "team_user", start: 273, quoted: true, alias: "t0" },
147
- { text: "user_id", start: 351, quoted: true },
148
- ],
149
- ignored: false,
150
- position: { start: 346, end: 360 },
151
- },
152
- {
153
- frequency: 1,
154
- representation: '"public"."team"."deleted_at"',
155
- parts: [
156
- { text: "public", start: 144, quoted: true },
157
- { text: "team", start: 153, quoted: true },
158
- { text: "deleted_at", start: 160, quoted: true },
159
- ],
160
- ignored: false,
161
- position: { start: 144, end: 172 },
162
- where: {
163
- nulltest: "IS_NULL",
164
- },
165
- },
166
- ]);
167
- const indexes = analyzer.deriveIndexes(testMetadata, indexesToCheck);
168
- assert.deepStrictEqual(indexes, [
169
- {
170
- schema: "public",
171
- table: "team_user",
172
- column: "team_id",
173
- where: {
174
- nulltest: "IS_NOT_NULL",
175
- },
176
- },
177
- { schema: "public", table: "team", column: "team_id" },
178
- { schema: "public", table: "team_user", column: "user_id" },
179
- {
180
- schema: "public",
181
- table: "team",
182
- column: "deleted_at",
183
- where: {
184
- nulltest: "IS_NULL",
185
- },
186
- },
187
- ]);
188
- });
189
- const testMetadata = JSON.parse(fs.readFileSync("test/umami_test.json", "utf-8"));
190
- test("analyzer with aliases", async function () {
191
- const analyzer = new Analyzer(parse);
192
- const query = dedent `
193
- select
194
- "public"."team_user"."team_user_id",
195
- "public"."team_user"."team_id",
196
- "public"."team_user"."user_id",
197
- "public"."team_user"."role",
198
- "public"."team_user"."created_at",
199
- "public"."team_user"."updated_at"
200
- from
201
- "public"."team_user"
202
- left join "public"."user" as "j0" on ("j0"."user_id") = ("public"."team_user"."user_id")
203
- where
204
- (
205
- "public"."team_user"."team_id" = $1
206
- and (
207
- "j0"."deleted_at" is null
208
- and ("j0"."user_id" is not null)
209
- )
210
- )
211
- order by
212
- "public"."team_user"."team_user_id" asc
213
- limit
214
- $2
215
- offset
216
- $3
217
- `;
218
- const { indexesToCheck, ansiHighlightedQuery } = await analyzer.analyze(query);
219
- const indexes = analyzer.deriveIndexes(testMetadata, indexesToCheck);
220
- console.log(indexes);
221
- console.log(ansiHighlightedQuery);
222
- assert.deepStrictEqual(indexes, [
223
- {
224
- schema: "public",
225
- table: "team_user",
226
- column: "team_user_id",
227
- sort: {
228
- dir: "SORTBY_ASC",
229
- nulls: "SORTBY_NULLS_DEFAULT",
230
- },
231
- },
232
- {
233
- schema: "public",
234
- table: "user",
235
- column: "user_id",
236
- where: {
237
- nulltest: "IS_NOT_NULL",
238
- },
239
- },
240
- {
241
- schema: "public",
242
- table: "user",
243
- column: "deleted_at",
244
- where: {
245
- nulltest: "IS_NULL",
246
- },
247
- },
248
- { schema: "public", table: "team_user", column: "team_id" },
249
- { schema: "public", table: "team_user", column: "user_id" },
250
- ]);
251
- });
252
- test("analyzer does not pickup aggregate aliases", async function () {
253
- const analyzer = new Analyzer(parse);
254
- const query = dedent `
255
- select
256
- "public"."team"."team_id",
257
- "public"."team"."name",
258
- "public"."team"."access_code",
259
- "public"."team"."logo_url",
260
- "public"."team"."created_at",
261
- "public"."team"."updated_at",
262
- "public"."team"."deleted_at",
263
- COALESCE(
264
- "aggr_selection_0_Website"."_aggr_count_website",
265
- 0
266
- ) as "_aggr_count_website",
267
- COALESCE(
268
- "aggr_selection_1_TeamUser"."_aggr_count_teamUser",
269
- 0
270
- ) as "_aggr_count_teamUser"
271
- from
272
- "public"."team"
273
- left join (
274
- select
275
- "public"."website"."team_id",
276
- COUNT(*) as "_aggr_count_website"
277
- from
278
- "public"."website"
279
- where
280
- "public"."website"."deleted_at" is null
281
- group by
282
- "public"."website"."team_id"
283
- ) as "aggr_selection_0_Website" on (
284
- "public"."team"."team_id" = "aggr_selection_0_Website"."team_id"
285
- )
286
- left join (
287
- select
288
- "public"."team_user"."team_id",
289
- COUNT(*) as "_aggr_count_teamUser"
290
- from
291
- "public"."team_user"
292
- left join "public"."user" as "j0" on ("j0"."user_id") = ("public"."team_user"."user_id")
293
- where
294
- (
295
- "j0"."deleted_at" is null
296
- and ("j0"."user_id" is not null)
297
- )
298
- group by
299
- "public"."team_user"."team_id"
300
- ) as "aggr_selection_1_TeamUser" on (
301
- "public"."team"."team_id" = "aggr_selection_1_TeamUser"."team_id"
302
- )
303
- where
304
- (
305
- "public"."team"."deleted_at" is null
306
- and exists (
307
- select
308
- "t1"."team_id"
309
- from
310
- "public"."team_user" as "t1"
311
- where
312
- (
313
- "t1"."user_id" = $1
314
- and ("public"."team"."team_id") = ("t1"."team_id")
315
- and "t1"."team_id" is not null
316
- )
317
- )
318
- )
319
- order by
320
- "public"."team"."team_id" asc
321
- limit
322
- $2
323
- offset
324
- $3`;
325
- const { indexesToCheck } = await analyzer.analyze(query);
326
- assert(!indexesToCheck.some((i) => /aggr_selection_0_Website/.test(i.representation)));
327
- assert(!indexesToCheck.some((i) => /aggr_selection_1_TeamUser/.test(i.representation)));
328
- });
329
- test("sqlcommenter test", async function () {
330
- const analyzer = new Analyzer(parse);
331
- const query = dedent `
332
- SELECT * FROM FOO /*action='%2Fparam*d',controller='index',framework='spring',
333
- traceparent='00-5bd66ef5095369c7b0d1f8f4bd33716a-c532cb4098ac3dd2-01',
334
- tracestate='congo%3Dt61rcWkgMzE%2Crojo%3D00f067aa0ba902b7'*/
335
- `;
336
- const { tags } = await analyzer.analyze(query);
337
- assert.deepStrictEqual(tags, [
338
- { key: "action", value: "/param*d" },
339
- { key: "controller", value: "index" },
340
- { key: "framework", value: "spring" },
341
- {
342
- key: "traceparent",
343
- value: "00-5bd66ef5095369c7b0d1f8f4bd33716a-c532cb4098ac3dd2-01",
344
- },
345
- { key: "tracestate", value: "congo=t61rcWkgMzE,rojo=00f067aa0ba902b7" },
346
- ]);
347
- });
348
- test("analyzer doesn't pick up temp table references", async function () {
349
- const analyzer = new Analyzer(parse);
350
- const query = dedent `
351
- select * from "public"."team_user" "t0"
352
- `;
353
- const { referencedTables } = await analyzer.analyze(query);
354
- assert.deepStrictEqual(referencedTables, ["team_user"]);
355
- // also check the `as` syntax
356
- const query2 = dedent `
357
- select * from "public"."team_user" as "t0"
358
- `;
359
- const { referencedTables: referencedTables2 } = await analyzer.analyze(query2);
360
- assert.deepStrictEqual(referencedTables2, ["team_user"]);
361
- });
362
- test("analyzer should use queries aliased as existing tables", async function () {
363
- const analyzer = new Analyzer(parse);
364
- const query = dedent `select * from
365
- (
366
- select * from "guests"
367
- order by
368
- "guests"."last_upload" desc,
369
- "guests"."id" desc
370
- limit
371
- 100
372
- ) "guests"
373
- cross join lateral (
374
- select * from "assets"
375
- where
376
- (
377
- "assets"."event_id" = (
378
- select
379
- "id"
380
- from
381
- "events"
382
- where
383
- "events"."event_key" = '01JKCVP4M2CH34SVTQGHSW4Y5G'
384
- )
385
- and "assets"."uploader_id" = "guests"."id"
386
- )
387
- order by
388
- "assets"."inserted_at" desc
389
- limit
390
- 100
391
- ) "userAssets";`;
392
- const { referencedTables } = await analyzer.analyze(query);
393
- assert.deepStrictEqual(referencedTables, ["guests", "assets", "events"]);
394
- });
395
- test("nudge for found star", async function () {
396
- const analyzer = new Analyzer(parse);
397
- const query = dedent `
398
- select * from "guest"
399
- `;
400
- const { nudges } = await analyzer.analyze(query);
401
- // This query triggers multiple nudges: SELECT *, missing WHERE, missing LIMIT
402
- assert(nudges.some(n => n.kind === "AVOID_SELECT_STAR"));
403
- assert(nudges.some(n => n.kind === "MISSING_WHERE_CLAUSE"));
404
- assert(nudges.some(n => n.kind === "MISSING_LIMIT_CLAUSE"));
405
- });
406
- test("nudge for functions on columns in WHERE clause", async function () {
407
- const analyzer = new Analyzer(parse);
408
- // Test LOWER function on column
409
- const query1 = dedent `
410
- select name from users where LOWER(name) = 'bob' limit 10
411
- `;
412
- const { nudges: nudges1 } = await analyzer.analyze(query1);
413
- assert.deepStrictEqual(nudges1, [{ kind: "AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE" }]);
414
- // Test DATE function on column
415
- const query2 = dedent `
416
- select id from events where DATE(created_at) = '2025-09-18' limit 5
417
- `;
418
- const { nudges: nudges2 } = await analyzer.analyze(query2);
419
- assert.deepStrictEqual(nudges2, [{ kind: "AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE" }]);
420
- // Test UPPER function on column
421
- const query3 = dedent `
422
- select email from users where UPPER(email) LIKE 'TEST%' limit 20
423
- `;
424
- const { nudges: nudges3 } = await analyzer.analyze(query3);
425
- assert.deepStrictEqual(nudges3, [{ kind: "AVOID_FUNCTIONS_ON_COLUMNS_IN_WHERE" }]);
426
- // Test function in SELECT (should NOT trigger nudge)
427
- const query4 = dedent `
428
- select LOWER(name) from users where id = 1 limit 1
429
- `;
430
- const { nudges: nudges4 } = await analyzer.analyze(query4);
431
- assert.deepStrictEqual(nudges4, []);
432
- // Test function with literal (should NOT trigger nudge)
433
- const query5 = dedent `
434
- select name from users where age > LENGTH('test') limit 100
435
- `;
436
- const { nudges: nudges5 } = await analyzer.analyze(query5);
437
- assert.deepStrictEqual(nudges5, []);
438
- });
439
- test("nudge for unbounded queries", async function () {
440
- const analyzer = new Analyzer(parse);
441
- // Test missing WHERE clause
442
- const query1 = dedent `
443
- select name from users
444
- `;
445
- const { nudges: nudges1 } = await analyzer.analyze(query1);
446
- assert(nudges1.some(n => n.kind === "MISSING_WHERE_CLAUSE"));
447
- assert(nudges1.some(n => n.kind === "MISSING_LIMIT_CLAUSE"));
448
- // Test missing LIMIT clause (has WHERE but no LIMIT)
449
- const query2 = dedent `
450
- select name from users where active = true
451
- `;
452
- const { nudges: nudges2 } = await analyzer.analyze(query2);
453
- assert(!nudges2.some(n => n.kind === "MISSING_WHERE_CLAUSE"));
454
- assert(nudges2.some(n => n.kind === "MISSING_LIMIT_CLAUSE"));
455
- // Test bounded query (has both WHERE and LIMIT - should NOT trigger)
456
- const query3 = dedent `
457
- select name from users where active = true limit 10
458
- `;
459
- const { nudges: nudges3 } = await analyzer.analyze(query3);
460
- assert(!nudges3.some(n => n.kind === "MISSING_WHERE_CLAUSE"));
461
- assert(!nudges3.some(n => n.kind === "MISSING_LIMIT_CLAUSE"));
462
- // Test subquery (should NOT trigger nudges)
463
- const query4 = dedent `
464
- select count(*) from (select name from users) as sub
465
- `;
466
- const { nudges: nudges4 } = await analyzer.analyze(query4);
467
- // The outer query has no WHERE/LIMIT but queries a subquery, not a table
468
- assert(!nudges4.some(n => n.kind === "MISSING_WHERE_CLAUSE"));
469
- assert(!nudges4.some(n => n.kind === "MISSING_LIMIT_CLAUSE"));
470
- // Test JOIN query missing WHERE/LIMIT
471
- const query5 = dedent `
472
- select u.name, p.title
473
- from users u
474
- join posts p on u.id = p.user_id
475
- `;
476
- const { nudges: nudges5 } = await analyzer.analyze(query5);
477
- assert(nudges5.some(n => n.kind === "MISSING_WHERE_CLAUSE"));
478
- assert(nudges5.some(n => n.kind === "MISSING_LIMIT_CLAUSE"));
479
- });
480
- test("nudge for NULL comparison issues", async function () {
481
- const analyzer = new Analyzer(parse);
482
- // Test = NULL (should trigger nudge)
483
- const query1 = dedent `
484
- select name from users where email = NULL limit 10
485
- `;
486
- const { nudges: nudges1 } = await analyzer.analyze(query1);
487
- assert(nudges1.some(n => n.kind === "USE_IS_NULL_NOT_EQUALS"));
488
- // Test != NULL (should trigger nudge)
489
- const query2 = dedent `
490
- select name from users where email <> NULL limit 10
491
- `;
492
- const { nudges: nudges2 } = await analyzer.analyze(query2);
493
- assert(nudges2.some(n => n.kind === "USE_IS_NULL_NOT_EQUALS"));
494
- // Test IS NULL (should NOT trigger nudge)
495
- const query3 = dedent `
496
- select name from users where email IS NULL limit 10
497
- `;
498
- const { nudges: nudges3 } = await analyzer.analyze(query3);
499
- assert(!nudges3.some(n => n.kind === "USE_IS_NULL_NOT_EQUALS"));
500
- // Test normal equality (should NOT trigger nudge)
501
- const query4 = dedent `
502
- select name from users where active = true limit 10
503
- `;
504
- const { nudges: nudges4 } = await analyzer.analyze(query4);
505
- assert(!nudges4.some(n => n.kind === "USE_IS_NULL_NOT_EQUALS"));
506
- });
507
- test("nudge for DISTINCT usage", async function () {
508
- const analyzer = new Analyzer(parse);
509
- // Test DISTINCT (should trigger nudge)
510
- const query1 = dedent `
511
- select distinct name from users where active = true limit 10
512
- `;
513
- const { nudges: nudges1 } = await analyzer.analyze(query1);
514
- assert(nudges1.some(n => n.kind === "AVOID_DISTINCT_WITHOUT_REASON"));
515
- // Test regular SELECT (should NOT trigger nudge)
516
- const query2 = dedent `
517
- select name from users where active = true limit 10
518
- `;
519
- const { nudges: nudges2 } = await analyzer.analyze(query2);
520
- assert(!nudges2.some(n => n.kind === "AVOID_DISTINCT_WITHOUT_REASON"));
521
- });
522
- test("nudge for cartesian joins", async function () {
523
- const analyzer = new Analyzer(parse);
524
- // Test JOIN without ON clause (should trigger nudge)
525
- const query1 = dedent `
526
- select u.name, p.title from users u cross join posts p where u.active = true limit 10
527
- `;
528
- const { nudges: nudges1 } = await analyzer.analyze(query1);
529
- assert(nudges1.some(n => n.kind === "MISSING_JOIN_CONDITION"));
530
- // Test old-style comma join (should trigger nudge)
531
- const query2 = dedent `
532
- select u.name, p.title from users u, posts p where u.active = true limit 10
533
- `;
534
- const { nudges: nudges2 } = await analyzer.analyze(query2);
535
- assert(nudges2.some(n => n.kind === "MISSING_JOIN_CONDITION"));
536
- // Test proper JOIN with ON clause (should NOT trigger nudge)
537
- const query3 = dedent `
538
- select u.name, p.title from users u join posts p on u.id = p.user_id where u.active = true limit 10
539
- `;
540
- const { nudges: nudges3 } = await analyzer.analyze(query3);
541
- assert(!nudges3.some(n => n.kind === "MISSING_JOIN_CONDITION"));
542
- });
543
- test("nudge for LIKE leading wildcards", async function () {
544
- const analyzer = new Analyzer(parse);
545
- // Test LIKE with leading wildcard (should trigger nudge)
546
- const query1 = dedent `
547
- select name from users where email LIKE '%@example.com' limit 10
548
- `;
549
- const { nudges: nudges1 } = await analyzer.analyze(query1);
550
- assert(nudges1.some(n => n.kind === "AVOID_LEADING_WILDCARD_LIKE"));
551
- // Test ILIKE with leading wildcard (should trigger nudge)
552
- const query2 = dedent `
553
- select name from users where email ILIKE '%EXAMPLE' limit 10
554
- `;
555
- const { nudges: nudges2 } = await analyzer.analyze(query2);
556
- assert(nudges2.some(n => n.kind === "AVOID_LEADING_WILDCARD_LIKE"));
557
- // Test LIKE without leading wildcard (should NOT trigger nudge)
558
- const query3 = dedent `
559
- select name from users where email LIKE 'user%@example.com' limit 10
560
- `;
561
- const { nudges: nudges3 } = await analyzer.analyze(query3);
562
- assert(!nudges3.some(n => n.kind === "AVOID_LEADING_WILDCARD_LIKE"));
563
- });
564
- test("nudge for multiple OR conditions", async function () {
565
- const analyzer = new Analyzer(parse);
566
- // Test 3+ OR conditions (should trigger nudge)
567
- const query1 = dedent `
568
- select name from users where status = 'active' OR status = 'pending' OR status = 'trial' limit 10
569
- `;
570
- const { nudges: nudges1 } = await analyzer.analyze(query1);
571
- assert(nudges1.some(n => n.kind === "CONSIDER_IN_INSTEAD_OF_MANY_ORS"));
572
- // Test 2 OR conditions (should NOT trigger nudge)
573
- const query2 = dedent `
574
- select name from users where status = 'active' OR status = 'pending' limit 10
575
- `;
576
- const { nudges: nudges2 } = await analyzer.analyze(query2);
577
- assert(!nudges2.some(n => n.kind === "CONSIDER_IN_INSTEAD_OF_MANY_ORS"));
578
- // Test IN clause (should NOT trigger nudge)
579
- const query3 = dedent `
580
- select name from users where status IN ('active', 'pending', 'trial') limit 10
581
- `;
582
- const { nudges: nudges3 } = await analyzer.analyze(query3);
583
- assert(!nudges3.some(n => n.kind === "CONSIDER_IN_INSTEAD_OF_MANY_ORS"));
584
- });
@@ -1,77 +0,0 @@
1
- export class PostgresQueryBuilder {
2
- query;
3
- commands = {};
4
- isIntrospection = false;
5
- explainFlags = [];
6
- _preamble = 0;
7
- constructor(query) {
8
- this.query = query;
9
- }
10
- get preamble() {
11
- return this._preamble;
12
- }
13
- static createIndex(definition, name) {
14
- if (name) {
15
- return new PostgresQueryBuilder(`create index "${name}" on ${definition};`);
16
- }
17
- return new PostgresQueryBuilder(`create index on ${definition};`);
18
- }
19
- enable(command, value = true) {
20
- const commandString = `enable_${command}`;
21
- if (value) {
22
- this.commands[commandString] = "on";
23
- }
24
- else {
25
- this.commands[commandString] = "off";
26
- }
27
- return this;
28
- }
29
- withQuery(query) {
30
- this.query = query;
31
- return this;
32
- }
33
- introspect() {
34
- this.isIntrospection = true;
35
- return this;
36
- }
37
- explain(flags) {
38
- this.explainFlags = flags;
39
- return this;
40
- }
41
- build() {
42
- let commands = this.generateSetCommands();
43
- commands += this.generateExplain().query;
44
- if (this.isIntrospection) {
45
- commands += " -- @qd_introspection";
46
- }
47
- return commands;
48
- }
49
- /** Return the "set a=b" parts of the command in the query separate from the explain select ... part */
50
- buildParts() {
51
- const commands = this.generateSetCommands();
52
- const explain = this.generateExplain();
53
- this._preamble = explain.preamble;
54
- if (this.isIntrospection) {
55
- explain.query += " -- @qd_introspection";
56
- }
57
- return { commands, query: explain.query };
58
- }
59
- generateSetCommands() {
60
- let commands = "";
61
- for (const key in this.commands) {
62
- const value = this.commands[key];
63
- commands += `set local ${key}=${value};\n`;
64
- }
65
- return commands;
66
- }
67
- generateExplain() {
68
- let query = "";
69
- if (this.explainFlags.length > 0) {
70
- query += `explain (${this.explainFlags.join(", ")}) `;
71
- }
72
- const semicolon = this.query.endsWith(";") ? "" : ";";
73
- const preamble = query.length;
74
- query += `${this.query}${semicolon}`;
75
- return { query, preamble };
76
- }
77
- }
@@ -1,20 +0,0 @@
1
- import { z } from "zod";
2
- export const PostgresVersion = z.string().brand("PostgresVersion");
3
- /**
4
- * Drops a disabled index. Rollsback if it fails for any reason
5
- * @returns Did dropping the index succeed?
6
- */
7
- export async function dropIndex(tx, index) {
8
- try {
9
- await tx.exec(`
10
- savepoint idx_drop;
11
- drop index if exists ${index} cascade;
12
- `);
13
- return true;
14
- }
15
- catch (error) {
16
- // no problem if droping the index fails. It should throw an error
17
- await tx.exec(`rollback to idx_drop`);
18
- return false;
19
- }
20
- }
@@ -1,12 +0,0 @@
1
- export function isIndexSupported(index) {
2
- return index.index_type === "btree";
3
- }
4
- /**
5
- * Doesn't necessarily decide whether the index can be dropped but can be
6
- * used to not even try dropping indexes that _definitely_ cannot be dropped
7
- */
8
- export function isIndexProbablyDroppable(index) {
9
- /* TODO: until we have a better solution, this is the best we have */
10
- /* The is_unique check is problematic only if the column is declared as unique */
11
- return !index.is_primary && !index.is_unique;
12
- }