@xata.io/drizzle 0.0.0-alpha.vbaa467c → 0.0.0-alpha.vbae8d27060d2a4d1064936766420bd236bfa7e73
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/.turbo/turbo-build.log +16 -5
- package/CHANGELOG.md +115 -2
- package/README.md +3 -0
- package/dist/index.cjs +85 -122
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +44 -65
- package/dist/index.mjs +83 -122
- package/dist/index.mjs.map +1 -1
- package/dist/pg.cjs +201 -0
- package/dist/pg.cjs.map +1 -0
- package/dist/pg.d.ts +58 -0
- package/dist/pg.mjs +195 -0
- package/dist/pg.mjs.map +1 -0
- package/package.json +12 -4
- package/test/core.schema.ts +93 -0
- package/test/core.test.ts +3892 -0
- package/test/drizzle.test.ts +6293 -0
- package/test/relational.schema.ts +85 -0
- package/test/relational.test.ts +6293 -0
- package/test/schema.ts +85 -0
@@ -0,0 +1,3892 @@
|
|
1
|
+
import { HostProvider, parseProviderString, XataApiClient } from '@xata.io/client';
|
2
|
+
import 'dotenv/config';
|
3
|
+
import {
|
4
|
+
and,
|
5
|
+
arrayContained,
|
6
|
+
arrayContains,
|
7
|
+
arrayOverlaps,
|
8
|
+
asc,
|
9
|
+
avg,
|
10
|
+
avgDistinct,
|
11
|
+
count,
|
12
|
+
countDistinct,
|
13
|
+
eq,
|
14
|
+
exists,
|
15
|
+
gt,
|
16
|
+
gte,
|
17
|
+
inArray,
|
18
|
+
lt,
|
19
|
+
max,
|
20
|
+
min,
|
21
|
+
name,
|
22
|
+
placeholder,
|
23
|
+
type SQL,
|
24
|
+
sql,
|
25
|
+
type SQLWrapper,
|
26
|
+
sum,
|
27
|
+
sumDistinct,
|
28
|
+
TransactionRollbackError
|
29
|
+
} from 'drizzle-orm';
|
30
|
+
import {
|
31
|
+
alias,
|
32
|
+
boolean,
|
33
|
+
char,
|
34
|
+
cidr,
|
35
|
+
date,
|
36
|
+
except,
|
37
|
+
exceptAll,
|
38
|
+
foreignKey,
|
39
|
+
getMaterializedViewConfig,
|
40
|
+
getTableConfig,
|
41
|
+
getViewConfig,
|
42
|
+
inet,
|
43
|
+
integer,
|
44
|
+
intersect,
|
45
|
+
intersectAll,
|
46
|
+
interval,
|
47
|
+
jsonb,
|
48
|
+
macaddr,
|
49
|
+
macaddr8,
|
50
|
+
numeric,
|
51
|
+
type PgColumn,
|
52
|
+
pgEnum,
|
53
|
+
pgMaterializedView,
|
54
|
+
pgTable,
|
55
|
+
pgTableCreator,
|
56
|
+
pgView,
|
57
|
+
primaryKey,
|
58
|
+
serial,
|
59
|
+
text,
|
60
|
+
time,
|
61
|
+
timestamp,
|
62
|
+
union,
|
63
|
+
unionAll,
|
64
|
+
unique,
|
65
|
+
uniqueKeyName,
|
66
|
+
uuid as pgUuid,
|
67
|
+
varchar
|
68
|
+
} from 'drizzle-orm/pg-core';
|
69
|
+
import { Client } from 'pg';
|
70
|
+
import { afterAll, afterEach, assert, beforeAll, beforeEach, describe, test } from 'vitest';
|
71
|
+
import { drizzle as drizzlePg, type XataDatabase } from '../src/pg';
|
72
|
+
import { migrate } from '../src/pg/migrator';
|
73
|
+
import * as schema from './core.schema';
|
74
|
+
import { v4 as uuid } from 'uuid';
|
75
|
+
|
76
|
+
const {
|
77
|
+
usersTable,
|
78
|
+
cities2Table,
|
79
|
+
users2Table,
|
80
|
+
aggregateTable,
|
81
|
+
orders,
|
82
|
+
_tictactoe,
|
83
|
+
citiesTable,
|
84
|
+
courseCategoriesTable,
|
85
|
+
coursesTable,
|
86
|
+
network,
|
87
|
+
salEmp,
|
88
|
+
usersMigratorTable
|
89
|
+
} = schema;
|
90
|
+
|
91
|
+
const ENABLE_LOGGING = false;
|
92
|
+
|
93
|
+
declare module 'vitest' {
|
94
|
+
export interface TestContext {
|
95
|
+
db2: XataDatabase<typeof schema>;
|
96
|
+
client?: Client;
|
97
|
+
branch: string;
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
const apiKey = process.env.XATA_API_KEY ?? '';
|
102
|
+
if (apiKey === '') throw new Error('XATA_API_KEY environment variable is not set');
|
103
|
+
|
104
|
+
const workspace = (process.env.XATA_WORKSPACE ?? '').split('-').pop() ?? '';
|
105
|
+
if (workspace === '') throw new Error('XATA_WORKSPACE environment variable is not set');
|
106
|
+
|
107
|
+
const host = parseProviderString(process.env.XATA_API_PROVIDER) ?? 'production';
|
108
|
+
|
109
|
+
// TODO: Branches for pgroll only work in some regions for now
|
110
|
+
// const region = process.env.XATA_REGION || 'us-east-1';
|
111
|
+
const region =
|
112
|
+
host === 'production' ? 'us-east-1' : host === 'staging' ? 'eu-west-1' : process.env.XATA_REGION || 'us-east-1';
|
113
|
+
|
114
|
+
const database = `drizzle-test-${Math.random().toString(36).substring(7)}`;
|
115
|
+
|
116
|
+
const api = new XataApiClient({ apiKey, host, clientName: 'sdk-tests' });
|
117
|
+
|
118
|
+
function getDomain(host: HostProvider) {
|
119
|
+
switch (host) {
|
120
|
+
case 'production':
|
121
|
+
return 'xata.sh';
|
122
|
+
case 'staging':
|
123
|
+
return 'staging-xata.dev';
|
124
|
+
case 'dev':
|
125
|
+
return 'dev-xata.dev';
|
126
|
+
case 'local':
|
127
|
+
return 'localhost:6001';
|
128
|
+
default:
|
129
|
+
return host.workspaces;
|
130
|
+
}
|
131
|
+
}
|
132
|
+
|
133
|
+
function getDrizzleClient(type: string, branch: string) {
|
134
|
+
if (type === 'pg') {
|
135
|
+
const client = new Client({
|
136
|
+
connectionString: `postgresql://${workspace}:${apiKey}@${region}.sql.${getDomain(
|
137
|
+
host
|
138
|
+
)}:5432/${database}:${branch}`,
|
139
|
+
ssl: true
|
140
|
+
});
|
141
|
+
|
142
|
+
return { db: drizzlePg(client, { schema, logger: ENABLE_LOGGING }), client };
|
143
|
+
} else {
|
144
|
+
throw new Error(`Unknown type: ${type}`);
|
145
|
+
}
|
146
|
+
}
|
147
|
+
|
148
|
+
async function setupSetOperationTest(db: XataDatabase<typeof schema>) {
|
149
|
+
await db.execute(sql`drop table if exists users2`);
|
150
|
+
await db.execute(sql`drop table if exists cities`);
|
151
|
+
await db.execute(
|
152
|
+
sql`
|
153
|
+
create table cities (
|
154
|
+
id serial primary key,
|
155
|
+
name text not null
|
156
|
+
)
|
157
|
+
`
|
158
|
+
);
|
159
|
+
await db.execute(
|
160
|
+
sql`
|
161
|
+
create table users2 (
|
162
|
+
id serial primary key,
|
163
|
+
name text not null,
|
164
|
+
city_id integer references cities(id)
|
165
|
+
)
|
166
|
+
`
|
167
|
+
);
|
168
|
+
|
169
|
+
await db.insert(cities2Table).values([
|
170
|
+
{ id: 1, name: 'New York' },
|
171
|
+
{ id: 2, name: 'London' },
|
172
|
+
{ id: 3, name: 'Tampa' }
|
173
|
+
]);
|
174
|
+
|
175
|
+
await db.insert(users2Table).values([
|
176
|
+
{ id: 1, name: 'John', cityId: 1 },
|
177
|
+
{ id: 2, name: 'Jane', cityId: 2 },
|
178
|
+
{ id: 3, name: 'Jack', cityId: 3 },
|
179
|
+
{ id: 4, name: 'Peter', cityId: 3 },
|
180
|
+
{ id: 5, name: 'Ben', cityId: 2 },
|
181
|
+
{ id: 6, name: 'Jill', cityId: 1 },
|
182
|
+
{ id: 7, name: 'Mary', cityId: 2 },
|
183
|
+
{ id: 8, name: 'Sally', cityId: 1 }
|
184
|
+
]);
|
185
|
+
}
|
186
|
+
|
187
|
+
async function setupAggregateFunctionsTest(db: XataDatabase<typeof schema>) {
|
188
|
+
await db.execute(sql`drop table if exists "aggregate_table"`);
|
189
|
+
await db.execute(
|
190
|
+
sql`
|
191
|
+
create table "aggregate_table" (
|
192
|
+
"id" serial not null,
|
193
|
+
"name" text not null,
|
194
|
+
"a" integer,
|
195
|
+
"b" integer,
|
196
|
+
"c" integer,
|
197
|
+
"null_only" integer
|
198
|
+
);
|
199
|
+
`
|
200
|
+
);
|
201
|
+
await db.insert(aggregateTable).values([
|
202
|
+
{ name: 'value 1', a: 5, b: 10, c: 20 },
|
203
|
+
{ name: 'value 1', a: 5, b: 20, c: 30 },
|
204
|
+
{ name: 'value 2', a: 10, b: 50, c: 60 },
|
205
|
+
{ name: 'value 3', a: 20, b: 20, c: null },
|
206
|
+
{ name: 'value 4', a: null, b: 90, c: 120 },
|
207
|
+
{ name: 'value 5', a: 80, b: 10, c: null },
|
208
|
+
{ name: 'value 6', a: null, b: null, c: 150 }
|
209
|
+
]);
|
210
|
+
}
|
211
|
+
|
212
|
+
describe.concurrent.each([{ type: 'pg' }])('Drizzle core $type', ({ type }) => {
|
213
|
+
beforeAll(async () => {
|
214
|
+
await api.database.createDatabase({
|
215
|
+
workspace,
|
216
|
+
database,
|
217
|
+
data: { region, branchName: 'main' },
|
218
|
+
headers: { 'X-Features': 'feat-pgroll-migrations=1' }
|
219
|
+
});
|
220
|
+
|
221
|
+
await waitForReplication();
|
222
|
+
|
223
|
+
// For now, run the migrations via wire protocol
|
224
|
+
const { client, db } = getDrizzleClient('pg', 'main');
|
225
|
+
await client?.connect();
|
226
|
+
|
227
|
+
await db.execute(sql`drop schema public cascade`);
|
228
|
+
await db.execute(sql`create schema public`);
|
229
|
+
await db.execute(
|
230
|
+
sql`
|
231
|
+
create table users (
|
232
|
+
id serial primary key,
|
233
|
+
name text not null,
|
234
|
+
verified boolean not null default false,
|
235
|
+
jsonb jsonb,
|
236
|
+
created_at timestamptz not null default now()
|
237
|
+
)
|
238
|
+
`
|
239
|
+
);
|
240
|
+
await db.execute(
|
241
|
+
sql`
|
242
|
+
create table cities (
|
243
|
+
id serial primary key,
|
244
|
+
name text not null,
|
245
|
+
state char(2)
|
246
|
+
)
|
247
|
+
`
|
248
|
+
);
|
249
|
+
await db.execute(
|
250
|
+
sql`
|
251
|
+
create table users2 (
|
252
|
+
id serial primary key,
|
253
|
+
name text not null,
|
254
|
+
city_id integer references cities(id)
|
255
|
+
)
|
256
|
+
`
|
257
|
+
);
|
258
|
+
await db.execute(
|
259
|
+
sql`
|
260
|
+
create table course_categories (
|
261
|
+
id serial primary key,
|
262
|
+
name text not null
|
263
|
+
)
|
264
|
+
`
|
265
|
+
);
|
266
|
+
await db.execute(
|
267
|
+
sql`
|
268
|
+
create table courses (
|
269
|
+
id serial primary key,
|
270
|
+
name text not null,
|
271
|
+
category_id integer references course_categories(id)
|
272
|
+
)
|
273
|
+
`
|
274
|
+
);
|
275
|
+
await db.execute(
|
276
|
+
sql`
|
277
|
+
create table orders (
|
278
|
+
id serial primary key,
|
279
|
+
region text not null,
|
280
|
+
product text not null,
|
281
|
+
amount integer not null,
|
282
|
+
quantity integer not null
|
283
|
+
)
|
284
|
+
`
|
285
|
+
);
|
286
|
+
await db.execute(
|
287
|
+
sql`
|
288
|
+
create table network_table (
|
289
|
+
inet inet not null,
|
290
|
+
cidr cidr not null,
|
291
|
+
macaddr macaddr not null,
|
292
|
+
macaddr8 macaddr8 not null
|
293
|
+
)
|
294
|
+
`
|
295
|
+
);
|
296
|
+
await db.execute(
|
297
|
+
sql`
|
298
|
+
create table sal_emp (
|
299
|
+
name text not null,
|
300
|
+
pay_by_quarter integer[] not null,
|
301
|
+
schedule text[][] not null
|
302
|
+
)
|
303
|
+
`
|
304
|
+
);
|
305
|
+
await db.execute(
|
306
|
+
sql`
|
307
|
+
create table tictactoe (
|
308
|
+
squares integer[3][3] not null
|
309
|
+
)
|
310
|
+
`
|
311
|
+
);
|
312
|
+
|
313
|
+
await client?.end();
|
314
|
+
});
|
315
|
+
|
316
|
+
afterAll(async () => {
|
317
|
+
await api.database.deleteDatabase({ workspace, database });
|
318
|
+
});
|
319
|
+
|
320
|
+
beforeEach(async (ctx) => {
|
321
|
+
ctx.branch = `test-${Math.random().toString(36).substring(7)}`;
|
322
|
+
await api.branches.createBranch({ workspace, database, region, branch: ctx.branch, from: 'main' });
|
323
|
+
|
324
|
+
const { db, client } = getDrizzleClient(type, ctx.branch);
|
325
|
+
await client?.connect();
|
326
|
+
|
327
|
+
ctx.db2 = db;
|
328
|
+
ctx.client = client;
|
329
|
+
});
|
330
|
+
|
331
|
+
afterEach(async (ctx) => {
|
332
|
+
await ctx.client?.end();
|
333
|
+
await api.branches.deleteBranch({ workspace, database, region, branch: ctx.branch });
|
334
|
+
});
|
335
|
+
|
336
|
+
/*
|
337
|
+
[Find Many] One relation users+posts
|
338
|
+
*/
|
339
|
+
|
340
|
+
test('table configs: unique third param', async (ctx) => {
|
341
|
+
const cities1Table = pgTable(
|
342
|
+
'cities1',
|
343
|
+
{
|
344
|
+
id: serial('id').primaryKey(),
|
345
|
+
name: text('name').notNull(),
|
346
|
+
state: char('state', { length: 2 })
|
347
|
+
},
|
348
|
+
(t) => ({
|
349
|
+
f: unique('custom_name').on(t.name, t.state).nullsNotDistinct(),
|
350
|
+
f1: unique('custom_name1').on(t.name, t.state)
|
351
|
+
})
|
352
|
+
);
|
353
|
+
|
354
|
+
const tableConfig = getTableConfig(cities1Table);
|
355
|
+
|
356
|
+
ctx.expect(tableConfig.uniqueConstraints.length === 2);
|
357
|
+
|
358
|
+
ctx.expect(tableConfig.uniqueConstraints[0]?.name === 'custom_name');
|
359
|
+
ctx.expect(tableConfig.uniqueConstraints[0]?.nullsNotDistinct);
|
360
|
+
assert.equal(
|
361
|
+
tableConfig.uniqueConstraints[0]?.columns.map((t) => t.name),
|
362
|
+
['name', 'state']
|
363
|
+
);
|
364
|
+
|
365
|
+
ctx.expect(tableConfig.uniqueConstraints[1]?.name, 'custom_name1');
|
366
|
+
ctx.expect(!tableConfig.uniqueConstraints[1]?.nullsNotDistinct);
|
367
|
+
assert.equal(
|
368
|
+
tableConfig.uniqueConstraints[0]?.columns.map((t) => t.name),
|
369
|
+
['name', 'state']
|
370
|
+
);
|
371
|
+
});
|
372
|
+
|
373
|
+
test('table configs: unique in column', async (ctx) => {
|
374
|
+
const cities1Table = pgTable('cities1', {
|
375
|
+
id: serial('id').primaryKey(),
|
376
|
+
name: text('name').notNull().unique(),
|
377
|
+
state: char('state', { length: 2 }).unique('custom'),
|
378
|
+
field: char('field', { length: 2 }).unique('custom_field', { nulls: 'not distinct' })
|
379
|
+
});
|
380
|
+
|
381
|
+
const tableConfig = getTableConfig(cities1Table);
|
382
|
+
|
383
|
+
const columnName = tableConfig.columns.find((it) => it.name === 'name');
|
384
|
+
ctx.expect(columnName?.uniqueName === uniqueKeyName(cities1Table, [columnName!.name]));
|
385
|
+
ctx.expect(columnName?.isUnique);
|
386
|
+
|
387
|
+
const columnState = tableConfig.columns.find((it) => it.name === 'state');
|
388
|
+
ctx.expect(columnState?.uniqueName === 'custom');
|
389
|
+
ctx.expect(columnState?.isUnique);
|
390
|
+
|
391
|
+
const columnField = tableConfig.columns.find((it) => it.name === 'field');
|
392
|
+
ctx.expect(columnField?.uniqueName === 'custom_field');
|
393
|
+
ctx.expect(columnField?.isUnique);
|
394
|
+
ctx.expect(columnField?.uniqueType === 'not distinct');
|
395
|
+
});
|
396
|
+
|
397
|
+
test('table config: foreign keys name', async (ctx) => {
|
398
|
+
const table = pgTable(
|
399
|
+
'cities',
|
400
|
+
{
|
401
|
+
id: serial('id').primaryKey(),
|
402
|
+
name: text('name').notNull(),
|
403
|
+
state: text('state')
|
404
|
+
},
|
405
|
+
(t) => ({
|
406
|
+
f: foreignKey({ foreignColumns: [t.id], columns: [t.id], name: 'custom_fk' })
|
407
|
+
})
|
408
|
+
);
|
409
|
+
|
410
|
+
const tableConfig = getTableConfig(table);
|
411
|
+
|
412
|
+
ctx.expect(tableConfig.foreignKeys.length === 1);
|
413
|
+
ctx.expect(tableConfig.foreignKeys[0]!.getName() === 'custom_fk');
|
414
|
+
});
|
415
|
+
|
416
|
+
test('table config: primary keys name', async (ctx) => {
|
417
|
+
const table = pgTable(
|
418
|
+
'cities',
|
419
|
+
{
|
420
|
+
id: serial('id').primaryKey(),
|
421
|
+
name: text('name').notNull(),
|
422
|
+
state: text('state')
|
423
|
+
},
|
424
|
+
(t) => ({
|
425
|
+
f: primaryKey({ columns: [t.id, t.name], name: 'custom_pk' })
|
426
|
+
})
|
427
|
+
);
|
428
|
+
|
429
|
+
const tableConfig = getTableConfig(table);
|
430
|
+
|
431
|
+
ctx.expect(tableConfig.primaryKeys.length === 1);
|
432
|
+
ctx.expect(tableConfig.primaryKeys[0]!.getName() === 'custom_pk');
|
433
|
+
});
|
434
|
+
|
435
|
+
test('select all fields', async (ctx) => {
|
436
|
+
const { db2: db } = ctx;
|
437
|
+
|
438
|
+
const now = Date.now();
|
439
|
+
|
440
|
+
await db.insert(usersTable).values({ name: 'John' });
|
441
|
+
const result = await db.select().from(usersTable);
|
442
|
+
|
443
|
+
ctx.expect(result[0]!.createdAt instanceof Date);
|
444
|
+
ctx.expect(Math.abs(result[0]!.createdAt.getTime() - now) < 100);
|
445
|
+
assert.equal(result, [{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result[0]!.createdAt }]);
|
446
|
+
});
|
447
|
+
|
448
|
+
test('select sql', async (ctx) => {
|
449
|
+
const { db2: db } = ctx;
|
450
|
+
|
451
|
+
await db.insert(usersTable).values({ name: 'John' });
|
452
|
+
const users = await db
|
453
|
+
.select({
|
454
|
+
name: sql`upper(${usersTable.name})`
|
455
|
+
})
|
456
|
+
.from(usersTable);
|
457
|
+
|
458
|
+
assert.equal(users, [{ name: 'JOHN' }]);
|
459
|
+
});
|
460
|
+
|
461
|
+
test('select typed sql', async (ctx) => {
|
462
|
+
const { db2: db } = ctx;
|
463
|
+
|
464
|
+
await db.insert(usersTable).values({ name: 'John' });
|
465
|
+
|
466
|
+
const users = await db
|
467
|
+
.select({
|
468
|
+
name: sql<string>`upper(${usersTable.name})`
|
469
|
+
})
|
470
|
+
.from(usersTable);
|
471
|
+
|
472
|
+
assert.equal(users, [{ name: 'JOHN' }]);
|
473
|
+
});
|
474
|
+
|
475
|
+
test('$default function', async (ctx) => {
|
476
|
+
const { db2: db } = ctx;
|
477
|
+
|
478
|
+
const insertedOrder = await db
|
479
|
+
.insert(orders)
|
480
|
+
.values({ id: 1, region: 'Ukraine', amount: 1, quantity: 1 })
|
481
|
+
.returning();
|
482
|
+
const selectedOrder = await db.select().from(orders);
|
483
|
+
|
484
|
+
assert.equal(insertedOrder, [
|
485
|
+
{
|
486
|
+
id: 1,
|
487
|
+
amount: 1,
|
488
|
+
quantity: 1,
|
489
|
+
region: 'Ukraine',
|
490
|
+
product: 'random_string'
|
491
|
+
}
|
492
|
+
]);
|
493
|
+
|
494
|
+
assert.equal(selectedOrder, [
|
495
|
+
{
|
496
|
+
id: 1,
|
497
|
+
amount: 1,
|
498
|
+
quantity: 1,
|
499
|
+
region: 'Ukraine',
|
500
|
+
product: 'random_string'
|
501
|
+
}
|
502
|
+
]);
|
503
|
+
});
|
504
|
+
|
505
|
+
test('select distinct', async (ctx) => {
|
506
|
+
const { db2: db } = ctx;
|
507
|
+
|
508
|
+
const usersDistinctTable = pgTable('users_distinct', {
|
509
|
+
id: integer('id').notNull(),
|
510
|
+
name: text('name').notNull(),
|
511
|
+
age: integer('age').notNull()
|
512
|
+
});
|
513
|
+
|
514
|
+
await db.execute(sql`drop table if exists ${usersDistinctTable}`);
|
515
|
+
await db.execute(sql`create table ${usersDistinctTable} (id integer, name text, age integer)`);
|
516
|
+
|
517
|
+
await db.insert(usersDistinctTable).values([
|
518
|
+
{ id: 1, name: 'John', age: 24 },
|
519
|
+
{ id: 1, name: 'John', age: 24 },
|
520
|
+
{ id: 2, name: 'John', age: 25 },
|
521
|
+
{ id: 1, name: 'Jane', age: 24 },
|
522
|
+
{ id: 1, name: 'Jane', age: 26 }
|
523
|
+
]);
|
524
|
+
const users1 = await db
|
525
|
+
.selectDistinct()
|
526
|
+
.from(usersDistinctTable)
|
527
|
+
.orderBy(usersDistinctTable.id, usersDistinctTable.name);
|
528
|
+
const users2 = await db
|
529
|
+
.selectDistinctOn([usersDistinctTable.id])
|
530
|
+
.from(usersDistinctTable)
|
531
|
+
.orderBy(usersDistinctTable.id);
|
532
|
+
const users3 = await db
|
533
|
+
.selectDistinctOn([usersDistinctTable.name], { name: usersDistinctTable.name })
|
534
|
+
.from(usersDistinctTable)
|
535
|
+
.orderBy(usersDistinctTable.name);
|
536
|
+
const users4 = await db
|
537
|
+
.selectDistinctOn([usersDistinctTable.id, usersDistinctTable.age])
|
538
|
+
.from(usersDistinctTable)
|
539
|
+
.orderBy(usersDistinctTable.id, usersDistinctTable.age);
|
540
|
+
|
541
|
+
await db.execute(sql`drop table ${usersDistinctTable}`);
|
542
|
+
|
543
|
+
assert.equal(users1, [
|
544
|
+
{ id: 1, name: 'Jane', age: 24 },
|
545
|
+
{ id: 1, name: 'Jane', age: 26 },
|
546
|
+
{ id: 1, name: 'John', age: 24 },
|
547
|
+
{ id: 2, name: 'John', age: 25 }
|
548
|
+
]);
|
549
|
+
|
550
|
+
assert.equal(users2.length, 2);
|
551
|
+
assert.equal(users2[0]?.id, 1);
|
552
|
+
assert.equal(users2[1]?.id, 2);
|
553
|
+
|
554
|
+
assert.equal(users3.length, 2);
|
555
|
+
assert.equal(users3[0]?.name, 'Jane');
|
556
|
+
assert.equal(users3[1]?.name, 'John');
|
557
|
+
|
558
|
+
assert.equal(users4, [
|
559
|
+
{ id: 1, name: 'John', age: 24 },
|
560
|
+
{ id: 1, name: 'Jane', age: 26 },
|
561
|
+
{ id: 2, name: 'John', age: 25 }
|
562
|
+
]);
|
563
|
+
});
|
564
|
+
|
565
|
+
test('insert returning sql', async (ctx) => {
|
566
|
+
const { db2: db } = ctx;
|
567
|
+
|
568
|
+
const users = await db
|
569
|
+
.insert(usersTable)
|
570
|
+
.values({ name: 'John' })
|
571
|
+
.returning({
|
572
|
+
name: sql`upper(${usersTable.name})`
|
573
|
+
});
|
574
|
+
|
575
|
+
assert.equal(users, [{ name: 'JOHN' }]);
|
576
|
+
});
|
577
|
+
|
578
|
+
test('delete returning sql', async (ctx) => {
|
579
|
+
const { db2: db } = ctx;
|
580
|
+
|
581
|
+
await db.insert(usersTable).values({ name: 'John' });
|
582
|
+
const users = await db
|
583
|
+
.delete(usersTable)
|
584
|
+
.where(eq(usersTable.name, 'John'))
|
585
|
+
.returning({
|
586
|
+
name: sql`upper(${usersTable.name})`
|
587
|
+
});
|
588
|
+
|
589
|
+
assert.equal(users, [{ name: 'JOHN' }]);
|
590
|
+
});
|
591
|
+
|
592
|
+
test('update returning sql', async (ctx) => {
|
593
|
+
const { db2: db } = ctx;
|
594
|
+
|
595
|
+
await db.insert(usersTable).values({ name: 'John' });
|
596
|
+
const users = await db
|
597
|
+
.update(usersTable)
|
598
|
+
.set({ name: 'Jane' })
|
599
|
+
.where(eq(usersTable.name, 'John'))
|
600
|
+
.returning({
|
601
|
+
name: sql`upper(${usersTable.name})`
|
602
|
+
});
|
603
|
+
|
604
|
+
assert.equal(users, [{ name: 'JANE' }]);
|
605
|
+
});
|
606
|
+
|
607
|
+
test('update with returning all fields', async (ctx) => {
|
608
|
+
const { db2: db } = ctx;
|
609
|
+
|
610
|
+
const now = Date.now();
|
611
|
+
|
612
|
+
await db.insert(usersTable).values({ name: 'John' });
|
613
|
+
const users = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')).returning();
|
614
|
+
|
615
|
+
ctx.expect(users[0]!.createdAt instanceof Date);
|
616
|
+
ctx.expect(Math.abs(users[0]!.createdAt.getTime() - now) < 100);
|
617
|
+
assert.equal(users, [{ id: 1, name: 'Jane', verified: false, jsonb: null, createdAt: users[0]!.createdAt }]);
|
618
|
+
});
|
619
|
+
|
620
|
+
test('update with returning partial', async (ctx) => {
|
621
|
+
const { db2: db } = ctx;
|
622
|
+
|
623
|
+
await db.insert(usersTable).values({ name: 'John' });
|
624
|
+
const users = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')).returning({
|
625
|
+
id: usersTable.id,
|
626
|
+
name: usersTable.name
|
627
|
+
});
|
628
|
+
|
629
|
+
assert.equal(users, [{ id: 1, name: 'Jane' }]);
|
630
|
+
});
|
631
|
+
|
632
|
+
test('delete with returning all fields', async (ctx) => {
|
633
|
+
const { db2: db } = ctx;
|
634
|
+
|
635
|
+
const now = Date.now();
|
636
|
+
|
637
|
+
await db.insert(usersTable).values({ name: 'John' });
|
638
|
+
const users = await db.delete(usersTable).where(eq(usersTable.name, 'John')).returning();
|
639
|
+
|
640
|
+
ctx.expect(users[0]!.createdAt instanceof Date);
|
641
|
+
ctx.expect(Math.abs(users[0]!.createdAt.getTime() - now) < 100);
|
642
|
+
assert.equal(users, [{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: users[0]!.createdAt }]);
|
643
|
+
});
|
644
|
+
|
645
|
+
test('delete with returning partial', async (ctx) => {
|
646
|
+
const { db2: db } = ctx;
|
647
|
+
|
648
|
+
await db.insert(usersTable).values({ name: 'John' });
|
649
|
+
const users = await db.delete(usersTable).where(eq(usersTable.name, 'John')).returning({
|
650
|
+
id: usersTable.id,
|
651
|
+
name: usersTable.name
|
652
|
+
});
|
653
|
+
|
654
|
+
assert.equal(users, [{ id: 1, name: 'John' }]);
|
655
|
+
});
|
656
|
+
|
657
|
+
test('insert + select', async (ctx) => {
|
658
|
+
const { db2: db } = ctx;
|
659
|
+
|
660
|
+
await db.insert(usersTable).values({ name: 'John' });
|
661
|
+
const result = await db.select().from(usersTable);
|
662
|
+
assert.equal(result, [{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result[0]!.createdAt }]);
|
663
|
+
|
664
|
+
await db.insert(usersTable).values({ name: 'Jane' });
|
665
|
+
const result2 = await db.select().from(usersTable);
|
666
|
+
assert.equal(result2, [
|
667
|
+
{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result2[0]!.createdAt },
|
668
|
+
{ id: 2, name: 'Jane', verified: false, jsonb: null, createdAt: result2[1]!.createdAt }
|
669
|
+
]);
|
670
|
+
});
|
671
|
+
|
672
|
+
test('json insert', async (ctx) => {
|
673
|
+
const { db2: db } = ctx;
|
674
|
+
|
675
|
+
await db.insert(usersTable).values({ name: 'John', jsonb: ['foo', 'bar'] });
|
676
|
+
const result = await db
|
677
|
+
.select({
|
678
|
+
id: usersTable.id,
|
679
|
+
name: usersTable.name,
|
680
|
+
jsonb: usersTable.jsonb
|
681
|
+
})
|
682
|
+
.from(usersTable);
|
683
|
+
|
684
|
+
assert.equal(result, [{ id: 1, name: 'John', jsonb: ['foo', 'bar'] }]);
|
685
|
+
});
|
686
|
+
|
687
|
+
test('char insert', async (ctx) => {
|
688
|
+
const { db2: db } = ctx;
|
689
|
+
|
690
|
+
await db.insert(citiesTable).values({ name: 'Austin', state: 'TX' });
|
691
|
+
const result = await db
|
692
|
+
.select({ id: citiesTable.id, name: citiesTable.name, state: citiesTable.state })
|
693
|
+
.from(citiesTable);
|
694
|
+
|
695
|
+
assert.equal(result, [{ id: 1, name: 'Austin', state: 'TX' }]);
|
696
|
+
});
|
697
|
+
|
698
|
+
test('char update', async (ctx) => {
|
699
|
+
const { db2: db } = ctx;
|
700
|
+
|
701
|
+
await db.insert(citiesTable).values({ name: 'Austin', state: 'TX' });
|
702
|
+
await db.update(citiesTable).set({ name: 'Atlanta', state: 'GA' }).where(eq(citiesTable.id, 1));
|
703
|
+
const result = await db
|
704
|
+
.select({ id: citiesTable.id, name: citiesTable.name, state: citiesTable.state })
|
705
|
+
.from(citiesTable);
|
706
|
+
|
707
|
+
assert.equal(result, [{ id: 1, name: 'Atlanta', state: 'GA' }]);
|
708
|
+
});
|
709
|
+
|
710
|
+
test('char delete', async (ctx) => {
|
711
|
+
const { db2: db } = ctx;
|
712
|
+
|
713
|
+
await db.insert(citiesTable).values({ name: 'Austin', state: 'TX' });
|
714
|
+
await db.delete(citiesTable).where(eq(citiesTable.state, 'TX'));
|
715
|
+
const result = await db
|
716
|
+
.select({ id: citiesTable.id, name: citiesTable.name, state: citiesTable.state })
|
717
|
+
.from(citiesTable);
|
718
|
+
|
719
|
+
assert.equal(result, []);
|
720
|
+
});
|
721
|
+
|
722
|
+
test('insert with overridden default values', async (ctx) => {
|
723
|
+
const { db2: db } = ctx;
|
724
|
+
|
725
|
+
await db.insert(usersTable).values({ name: 'John', verified: true });
|
726
|
+
const result = await db.select().from(usersTable);
|
727
|
+
|
728
|
+
assert.equal(result, [{ id: 1, name: 'John', verified: true, jsonb: null, createdAt: result[0]!.createdAt }]);
|
729
|
+
});
|
730
|
+
|
731
|
+
test('insert many', async (ctx) => {
|
732
|
+
const { db2: db } = ctx;
|
733
|
+
|
734
|
+
await db
|
735
|
+
.insert(usersTable)
|
736
|
+
.values([
|
737
|
+
{ name: 'John' },
|
738
|
+
{ name: 'Bruce', jsonb: ['foo', 'bar'] },
|
739
|
+
{ name: 'Jane' },
|
740
|
+
{ name: 'Austin', verified: true }
|
741
|
+
]);
|
742
|
+
const result = await db
|
743
|
+
.select({
|
744
|
+
id: usersTable.id,
|
745
|
+
name: usersTable.name,
|
746
|
+
jsonb: usersTable.jsonb,
|
747
|
+
verified: usersTable.verified
|
748
|
+
})
|
749
|
+
.from(usersTable);
|
750
|
+
|
751
|
+
assert.equal(result, [
|
752
|
+
{ id: 1, name: 'John', jsonb: null, verified: false },
|
753
|
+
{ id: 2, name: 'Bruce', jsonb: ['foo', 'bar'], verified: false },
|
754
|
+
{ id: 3, name: 'Jane', jsonb: null, verified: false },
|
755
|
+
{ id: 4, name: 'Austin', jsonb: null, verified: true }
|
756
|
+
]);
|
757
|
+
});
|
758
|
+
|
759
|
+
test('insert many with returning', async (ctx) => {
|
760
|
+
const { db2: db } = ctx;
|
761
|
+
|
762
|
+
const result = await db
|
763
|
+
.insert(usersTable)
|
764
|
+
.values([
|
765
|
+
{ name: 'John' },
|
766
|
+
{ name: 'Bruce', jsonb: ['foo', 'bar'] },
|
767
|
+
{ name: 'Jane' },
|
768
|
+
{ name: 'Austin', verified: true }
|
769
|
+
])
|
770
|
+
.returning({
|
771
|
+
id: usersTable.id,
|
772
|
+
name: usersTable.name,
|
773
|
+
jsonb: usersTable.jsonb,
|
774
|
+
verified: usersTable.verified
|
775
|
+
});
|
776
|
+
|
777
|
+
assert.equal(result, [
|
778
|
+
{ id: 1, name: 'John', jsonb: null, verified: false },
|
779
|
+
{ id: 2, name: 'Bruce', jsonb: ['foo', 'bar'], verified: false },
|
780
|
+
{ id: 3, name: 'Jane', jsonb: null, verified: false },
|
781
|
+
{ id: 4, name: 'Austin', jsonb: null, verified: true }
|
782
|
+
]);
|
783
|
+
});
|
784
|
+
|
785
|
+
test('select with group by as field', async (ctx) => {
|
786
|
+
const { db2: db } = ctx;
|
787
|
+
|
788
|
+
await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
|
789
|
+
|
790
|
+
const result = await db.select({ name: usersTable.name }).from(usersTable).groupBy(usersTable.name);
|
791
|
+
|
792
|
+
assert.equal(result, [{ name: 'Jane' }, { name: 'John' }]);
|
793
|
+
});
|
794
|
+
|
795
|
+
test('select with exists', async (ctx) => {
|
796
|
+
const { db2: db } = ctx;
|
797
|
+
|
798
|
+
await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
|
799
|
+
|
800
|
+
const user = alias(usersTable, 'user');
|
801
|
+
const result = await db
|
802
|
+
.select({ name: usersTable.name })
|
803
|
+
.from(usersTable)
|
804
|
+
.where(
|
805
|
+
exists(
|
806
|
+
db
|
807
|
+
.select({ one: sql`1` })
|
808
|
+
.from(user)
|
809
|
+
.where(and(eq(usersTable.name, 'John'), eq(user.id, usersTable.id)))
|
810
|
+
)
|
811
|
+
);
|
812
|
+
|
813
|
+
assert.equal(result, [{ name: 'John' }]);
|
814
|
+
});
|
815
|
+
|
816
|
+
test('select with group by as sql', async (ctx) => {
|
817
|
+
const { db2: db } = ctx;
|
818
|
+
|
819
|
+
await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
|
820
|
+
|
821
|
+
const result = await db
|
822
|
+
.select({ name: usersTable.name })
|
823
|
+
.from(usersTable)
|
824
|
+
.groupBy(sql`${usersTable.name}`);
|
825
|
+
|
826
|
+
assert.equal(result, [{ name: 'Jane' }, { name: 'John' }]);
|
827
|
+
});
|
828
|
+
|
829
|
+
test('select with group by as sql + column', async (ctx) => {
|
830
|
+
const { db2: db } = ctx;
|
831
|
+
|
832
|
+
await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
|
833
|
+
|
834
|
+
const result = await db
|
835
|
+
.select({ name: usersTable.name })
|
836
|
+
.from(usersTable)
|
837
|
+
.groupBy(sql`${usersTable.name}`, usersTable.id);
|
838
|
+
|
839
|
+
assert.equal(result, [{ name: 'Jane' }, { name: 'Jane' }, { name: 'John' }]);
|
840
|
+
});
|
841
|
+
|
842
|
+
test('select with group by as column + sql', async (ctx) => {
|
843
|
+
const { db2: db } = ctx;
|
844
|
+
|
845
|
+
await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
|
846
|
+
|
847
|
+
const result = await db
|
848
|
+
.select({ name: usersTable.name })
|
849
|
+
.from(usersTable)
|
850
|
+
.groupBy(usersTable.id, sql`${usersTable.name}`);
|
851
|
+
|
852
|
+
assert.equal(result, [{ name: 'Jane' }, { name: 'Jane' }, { name: 'John' }]);
|
853
|
+
});
|
854
|
+
|
855
|
+
test('select with group by complex query', async (ctx) => {
|
856
|
+
const { db2: db } = ctx;
|
857
|
+
|
858
|
+
await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
|
859
|
+
|
860
|
+
const result = await db
|
861
|
+
.select({ name: usersTable.name })
|
862
|
+
.from(usersTable)
|
863
|
+
.groupBy(usersTable.id, sql`${usersTable.name}`)
|
864
|
+
.orderBy(asc(usersTable.name))
|
865
|
+
.limit(1);
|
866
|
+
|
867
|
+
assert.equal(result, [{ name: 'Jane' }]);
|
868
|
+
});
|
869
|
+
|
870
|
+
test('build query', async (ctx) => {
|
871
|
+
const { db2: db } = ctx;
|
872
|
+
|
873
|
+
const query = db
|
874
|
+
.select({ id: usersTable.id, name: usersTable.name })
|
875
|
+
.from(usersTable)
|
876
|
+
.groupBy(usersTable.id, usersTable.name)
|
877
|
+
.toSQL();
|
878
|
+
|
879
|
+
assert.equal(query, {
|
880
|
+
sql: 'select "id", "name" from "users" group by "users"."id", "users"."name"',
|
881
|
+
params: []
|
882
|
+
});
|
883
|
+
});
|
884
|
+
|
885
|
+
test('insert sql', async (ctx) => {
|
886
|
+
const { db2: db } = ctx;
|
887
|
+
|
888
|
+
await db.insert(usersTable).values({ name: sql`${'John'}` });
|
889
|
+
const result = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable);
|
890
|
+
assert.equal(result, [{ id: 1, name: 'John' }]);
|
891
|
+
});
|
892
|
+
|
893
|
+
test('partial join with alias', async (ctx) => {
|
894
|
+
const { db2: db } = ctx;
|
895
|
+
const customerAlias = alias(usersTable, 'customer');
|
896
|
+
|
897
|
+
await db.insert(usersTable).values([
|
898
|
+
{ id: 10, name: 'Ivan' },
|
899
|
+
{ id: 11, name: 'Hans' }
|
900
|
+
]);
|
901
|
+
const result = await db
|
902
|
+
.select({
|
903
|
+
user: {
|
904
|
+
id: usersTable.id,
|
905
|
+
name: usersTable.name
|
906
|
+
},
|
907
|
+
customer: {
|
908
|
+
id: customerAlias.id,
|
909
|
+
name: customerAlias.name
|
910
|
+
}
|
911
|
+
})
|
912
|
+
.from(usersTable)
|
913
|
+
.leftJoin(customerAlias, eq(customerAlias.id, 11))
|
914
|
+
.where(eq(usersTable.id, 10));
|
915
|
+
|
916
|
+
assert.equal(result, [
|
917
|
+
{
|
918
|
+
user: { id: 10, name: 'Ivan' },
|
919
|
+
customer: { id: 11, name: 'Hans' }
|
920
|
+
}
|
921
|
+
]);
|
922
|
+
});
|
923
|
+
|
924
|
+
test('full join with alias', async (ctx) => {
|
925
|
+
const { db2: db } = ctx;
|
926
|
+
|
927
|
+
const pgTable = pgTableCreator((name) => `prefixed_${name}`);
|
928
|
+
|
929
|
+
const users = pgTable('users', {
|
930
|
+
id: serial('id').primaryKey(),
|
931
|
+
name: text('name').notNull()
|
932
|
+
});
|
933
|
+
|
934
|
+
await db.execute(sql`drop table if exists ${users}`);
|
935
|
+
await db.execute(sql`create table ${users} (id serial primary key, name text not null)`);
|
936
|
+
|
937
|
+
const customers = alias(users, 'customer');
|
938
|
+
|
939
|
+
await db.insert(users).values([
|
940
|
+
{ id: 10, name: 'Ivan' },
|
941
|
+
{ id: 11, name: 'Hans' }
|
942
|
+
]);
|
943
|
+
const result = await db.select().from(users).leftJoin(customers, eq(customers.id, 11)).where(eq(users.id, 10));
|
944
|
+
|
945
|
+
assert.equal(result, [
|
946
|
+
{
|
947
|
+
users: {
|
948
|
+
id: 10,
|
949
|
+
name: 'Ivan'
|
950
|
+
},
|
951
|
+
customer: {
|
952
|
+
id: 11,
|
953
|
+
name: 'Hans'
|
954
|
+
}
|
955
|
+
}
|
956
|
+
]);
|
957
|
+
|
958
|
+
await db.execute(sql`drop table ${users}`);
|
959
|
+
});
|
960
|
+
|
961
|
+
test('select from alias', async (ctx) => {
|
962
|
+
const { db2: db } = ctx;
|
963
|
+
|
964
|
+
const pgTable = pgTableCreator((name) => `prefixed_${name}`);
|
965
|
+
|
966
|
+
const users = pgTable('users', {
|
967
|
+
id: serial('id').primaryKey(),
|
968
|
+
name: text('name').notNull()
|
969
|
+
});
|
970
|
+
|
971
|
+
await db.execute(sql`drop table if exists ${users}`);
|
972
|
+
await db.execute(sql`create table ${users} (id serial primary key, name text not null)`);
|
973
|
+
|
974
|
+
const user = alias(users, 'user');
|
975
|
+
const customers = alias(users, 'customer');
|
976
|
+
|
977
|
+
await db.insert(users).values([
|
978
|
+
{ id: 10, name: 'Ivan' },
|
979
|
+
{ id: 11, name: 'Hans' }
|
980
|
+
]);
|
981
|
+
const result = await db.select().from(user).leftJoin(customers, eq(customers.id, 11)).where(eq(user.id, 10));
|
982
|
+
|
983
|
+
assert.equal(result, [
|
984
|
+
{
|
985
|
+
user: {
|
986
|
+
id: 10,
|
987
|
+
name: 'Ivan'
|
988
|
+
},
|
989
|
+
customer: {
|
990
|
+
id: 11,
|
991
|
+
name: 'Hans'
|
992
|
+
}
|
993
|
+
}
|
994
|
+
]);
|
995
|
+
|
996
|
+
await db.execute(sql`drop table ${users}`);
|
997
|
+
});
|
998
|
+
|
999
|
+
test('insert with spaces', async (ctx) => {
|
1000
|
+
const { db2: db } = ctx;
|
1001
|
+
|
1002
|
+
await db.insert(usersTable).values({ name: sql`'Jo h n'` });
|
1003
|
+
const result = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable);
|
1004
|
+
|
1005
|
+
assert.equal(result, [{ id: 1, name: 'Jo h n' }]);
|
1006
|
+
});
|
1007
|
+
|
1008
|
+
test('prepared statement', async (ctx) => {
|
1009
|
+
const { db2: db } = ctx;
|
1010
|
+
|
1011
|
+
await db.insert(usersTable).values({ name: 'John' });
|
1012
|
+
const statement = db
|
1013
|
+
.select({
|
1014
|
+
id: usersTable.id,
|
1015
|
+
name: usersTable.name
|
1016
|
+
})
|
1017
|
+
.from(usersTable)
|
1018
|
+
.prepare('statement1');
|
1019
|
+
const result = await statement.execute();
|
1020
|
+
|
1021
|
+
assert.equal(result, [{ id: 1, name: 'John' }]);
|
1022
|
+
});
|
1023
|
+
|
1024
|
+
test('prepared statement reuse', async (ctx) => {
|
1025
|
+
const { db2: db } = ctx;
|
1026
|
+
|
1027
|
+
const stmt = db
|
1028
|
+
.insert(usersTable)
|
1029
|
+
.values({
|
1030
|
+
verified: true,
|
1031
|
+
name: placeholder('name')
|
1032
|
+
})
|
1033
|
+
.prepare('stmt2');
|
1034
|
+
|
1035
|
+
for (let i = 0; i < 10; i++) {
|
1036
|
+
await stmt.execute({ name: `John ${i}` });
|
1037
|
+
}
|
1038
|
+
|
1039
|
+
const result = await db
|
1040
|
+
.select({
|
1041
|
+
id: usersTable.id,
|
1042
|
+
name: usersTable.name,
|
1043
|
+
verified: usersTable.verified
|
1044
|
+
})
|
1045
|
+
.from(usersTable);
|
1046
|
+
|
1047
|
+
assert.equal(result, [
|
1048
|
+
{ id: 1, name: 'John 0', verified: true },
|
1049
|
+
{ id: 2, name: 'John 1', verified: true },
|
1050
|
+
{ id: 3, name: 'John 2', verified: true },
|
1051
|
+
{ id: 4, name: 'John 3', verified: true },
|
1052
|
+
{ id: 5, name: 'John 4', verified: true },
|
1053
|
+
{ id: 6, name: 'John 5', verified: true },
|
1054
|
+
{ id: 7, name: 'John 6', verified: true },
|
1055
|
+
{ id: 8, name: 'John 7', verified: true },
|
1056
|
+
{ id: 9, name: 'John 8', verified: true },
|
1057
|
+
{ id: 10, name: 'John 9', verified: true }
|
1058
|
+
]);
|
1059
|
+
});
|
1060
|
+
|
1061
|
+
test('prepared statement with placeholder in .where', async (ctx) => {
|
1062
|
+
const { db2: db } = ctx;
|
1063
|
+
|
1064
|
+
await db.insert(usersTable).values({ name: 'John' });
|
1065
|
+
const stmt = db
|
1066
|
+
.select({
|
1067
|
+
id: usersTable.id,
|
1068
|
+
name: usersTable.name
|
1069
|
+
})
|
1070
|
+
.from(usersTable)
|
1071
|
+
.where(eq(usersTable.id, placeholder('id')))
|
1072
|
+
.prepare('stmt3');
|
1073
|
+
const result = await stmt.execute({ id: 1 });
|
1074
|
+
|
1075
|
+
assert.equal(result, [{ id: 1, name: 'John' }]);
|
1076
|
+
});
|
1077
|
+
|
1078
|
+
test('prepared statement with placeholder in .limit', async (ctx) => {
|
1079
|
+
const { db2: db } = ctx;
|
1080
|
+
|
1081
|
+
await db.insert(usersTable).values({ name: 'John' });
|
1082
|
+
const stmt = db
|
1083
|
+
.select({
|
1084
|
+
id: usersTable.id,
|
1085
|
+
name: usersTable.name
|
1086
|
+
})
|
1087
|
+
.from(usersTable)
|
1088
|
+
.where(eq(usersTable.id, placeholder('id')))
|
1089
|
+
.limit(placeholder('limit'))
|
1090
|
+
.prepare('stmt_limit');
|
1091
|
+
|
1092
|
+
const result = await stmt.execute({ id: 1, limit: 1 });
|
1093
|
+
|
1094
|
+
assert.equal(result, [{ id: 1, name: 'John' }]);
|
1095
|
+
ctx.expect(result.length === 1);
|
1096
|
+
});
|
1097
|
+
|
1098
|
+
test('prepared statement with placeholder in .offset', async (ctx) => {
|
1099
|
+
const { db2: db } = ctx;
|
1100
|
+
|
1101
|
+
await db.insert(usersTable).values([{ name: 'John' }, { name: 'John1' }]);
|
1102
|
+
const stmt = db
|
1103
|
+
.select({
|
1104
|
+
id: usersTable.id,
|
1105
|
+
name: usersTable.name
|
1106
|
+
})
|
1107
|
+
.from(usersTable)
|
1108
|
+
.offset(placeholder('offset'))
|
1109
|
+
.prepare('stmt_offset');
|
1110
|
+
|
1111
|
+
const result = await stmt.execute({ offset: 1 });
|
1112
|
+
|
1113
|
+
assert.equal(result, [{ id: 2, name: 'John1' }]);
|
1114
|
+
});
|
1115
|
+
|
1116
|
+
// TODO change tests to new structure
|
1117
|
+
test('migrator : default migration strategy', async (ctx) => {
|
1118
|
+
const { db2: db } = ctx;
|
1119
|
+
|
1120
|
+
await db.execute(sql`drop table if exists all_columns`);
|
1121
|
+
await db.execute(sql`drop table if exists users12`);
|
1122
|
+
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
1123
|
+
|
1124
|
+
await migrate(db, { migrationsFolder: './drizzle2/pg' });
|
1125
|
+
|
1126
|
+
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
1127
|
+
|
1128
|
+
const result = await db.select().from(usersMigratorTable);
|
1129
|
+
|
1130
|
+
assert.equal(result, [{ id: 1, name: 'John', email: 'email' }]);
|
1131
|
+
|
1132
|
+
await db.execute(sql`drop table all_columns`);
|
1133
|
+
await db.execute(sql`drop table users12`);
|
1134
|
+
await db.execute(sql`drop table "drizzle"."__drizzle_migrations"`);
|
1135
|
+
});
|
1136
|
+
|
1137
|
+
test('migrator : migrate with custom schema', async (ctx) => {
|
1138
|
+
const { db2: db } = ctx;
|
1139
|
+
const customSchema = randomString();
|
1140
|
+
await db.execute(sql`drop table if exists all_columns`);
|
1141
|
+
await db.execute(sql`drop table if exists users12`);
|
1142
|
+
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
1143
|
+
|
1144
|
+
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsSchema: customSchema });
|
1145
|
+
|
1146
|
+
// test if the custom migrations table was created
|
1147
|
+
const { rowCount } = await db.execute(sql`select * from ${sql.identifier(customSchema)}."__drizzle_migrations";`);
|
1148
|
+
ctx.expect(rowCount !== null && rowCount > 0);
|
1149
|
+
|
1150
|
+
// test if the migrated table are working as expected
|
1151
|
+
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
1152
|
+
const result = await db.select().from(usersMigratorTable);
|
1153
|
+
assert.equal(result, [{ id: 1, name: 'John', email: 'email' }]);
|
1154
|
+
|
1155
|
+
await db.execute(sql`drop table all_columns`);
|
1156
|
+
await db.execute(sql`drop table users12`);
|
1157
|
+
await db.execute(sql`drop table ${sql.identifier(customSchema)}."__drizzle_migrations"`);
|
1158
|
+
});
|
1159
|
+
|
1160
|
+
test('migrator : migrate with custom table', async (ctx) => {
|
1161
|
+
const { db2: db } = ctx;
|
1162
|
+
const customTable = randomString();
|
1163
|
+
await db.execute(sql`drop table if exists all_columns`);
|
1164
|
+
await db.execute(sql`drop table if exists users12`);
|
1165
|
+
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
1166
|
+
|
1167
|
+
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsTable: customTable });
|
1168
|
+
|
1169
|
+
// test if the custom migrations table was created
|
1170
|
+
const { rowCount } = await db.execute(sql`select * from "drizzle".${sql.identifier(customTable)};`);
|
1171
|
+
ctx.expect(rowCount !== null && rowCount > 0);
|
1172
|
+
|
1173
|
+
// test if the migrated table are working as expected
|
1174
|
+
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
1175
|
+
const result = await db.select().from(usersMigratorTable);
|
1176
|
+
assert.equal(result, [{ id: 1, name: 'John', email: 'email' }]);
|
1177
|
+
|
1178
|
+
await db.execute(sql`drop table all_columns`);
|
1179
|
+
await db.execute(sql`drop table users12`);
|
1180
|
+
await db.execute(sql`drop table "drizzle".${sql.identifier(customTable)}`);
|
1181
|
+
});
|
1182
|
+
|
1183
|
+
test('migrator : migrate with custom table and custom schema', async (ctx) => {
|
1184
|
+
const { db2: db } = ctx;
|
1185
|
+
const customTable = randomString();
|
1186
|
+
const customSchema = randomString();
|
1187
|
+
await db.execute(sql`drop table if exists all_columns`);
|
1188
|
+
await db.execute(sql`drop table if exists users12`);
|
1189
|
+
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
1190
|
+
|
1191
|
+
await migrate(db, {
|
1192
|
+
migrationsFolder: './drizzle2/pg',
|
1193
|
+
migrationsTable: customTable,
|
1194
|
+
migrationsSchema: customSchema
|
1195
|
+
});
|
1196
|
+
|
1197
|
+
// test if the custom migrations table was created
|
1198
|
+
const { rowCount } = await db.execute(
|
1199
|
+
sql`select * from ${sql.identifier(customSchema)}.${sql.identifier(customTable)};`
|
1200
|
+
);
|
1201
|
+
ctx.expect(rowCount !== null && rowCount > 0);
|
1202
|
+
|
1203
|
+
// test if the migrated table are working as expected
|
1204
|
+
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
1205
|
+
const result = await db.select().from(usersMigratorTable);
|
1206
|
+
assert.equal(result, [{ id: 1, name: 'John', email: 'email' }]);
|
1207
|
+
|
1208
|
+
await db.execute(sql`drop table all_columns`);
|
1209
|
+
await db.execute(sql`drop table users12`);
|
1210
|
+
await db.execute(sql`drop table ${sql.identifier(customSchema)}.${sql.identifier(customTable)}`);
|
1211
|
+
});
|
1212
|
+
|
1213
|
+
test('insert via db.execute + select via db.execute', async (ctx) => {
|
1214
|
+
const { db2: db } = ctx;
|
1215
|
+
|
1216
|
+
await db.execute(sql`insert into ${usersTable} (${name(usersTable.name.name)}) values (${'John'})`);
|
1217
|
+
|
1218
|
+
const result = await db.execute<{ id: number; name: string }>(sql`select id, name from "users"`);
|
1219
|
+
assert.equal(result.rows, [{ id: 1, name: 'John' }]);
|
1220
|
+
});
|
1221
|
+
|
1222
|
+
test('insert via db.execute + returning', async (ctx) => {
|
1223
|
+
const { db2: db } = ctx;
|
1224
|
+
|
1225
|
+
const inserted = await db.execute<{ id: number; name: string }>(
|
1226
|
+
sql`insert into ${usersTable} (${name(usersTable.name.name)}) values (${'John'}) returning ${usersTable.id}, ${
|
1227
|
+
usersTable.name
|
1228
|
+
}`
|
1229
|
+
);
|
1230
|
+
assert.equal(inserted.rows, [{ id: 1, name: 'John' }]);
|
1231
|
+
});
|
1232
|
+
|
1233
|
+
test('insert via db.execute w/ query builder', async (ctx) => {
|
1234
|
+
const { db2: db } = ctx;
|
1235
|
+
|
1236
|
+
const inserted = await db.execute<Pick<typeof usersTable.$inferSelect, 'id' | 'name'>>(
|
1237
|
+
db.insert(usersTable).values({ name: 'John' }).returning({ id: usersTable.id, name: usersTable.name })
|
1238
|
+
);
|
1239
|
+
assert.equal(inserted.rows, [{ id: 1, name: 'John' }]);
|
1240
|
+
});
|
1241
|
+
|
1242
|
+
test('Query check: Insert all defaults in 1 row', async (ctx) => {
|
1243
|
+
const { db2: db } = ctx;
|
1244
|
+
|
1245
|
+
const users = pgTable('users', {
|
1246
|
+
id: serial('id').primaryKey(),
|
1247
|
+
name: text('name').default('Dan'),
|
1248
|
+
state: text('state')
|
1249
|
+
});
|
1250
|
+
|
1251
|
+
const query = db.insert(users).values({}).toSQL();
|
1252
|
+
|
1253
|
+
assert.equal(query, {
|
1254
|
+
sql: 'insert into "users" ("id", "name", "state") values (default, default, default)',
|
1255
|
+
params: []
|
1256
|
+
});
|
1257
|
+
});
|
1258
|
+
|
1259
|
+
test('Query check: Insert all defaults in multiple rows', async (ctx) => {
|
1260
|
+
const { db2: db } = ctx;
|
1261
|
+
|
1262
|
+
const users = pgTable('users', {
|
1263
|
+
id: serial('id').primaryKey(),
|
1264
|
+
name: text('name').default('Dan'),
|
1265
|
+
state: text('state').default('UA')
|
1266
|
+
});
|
1267
|
+
|
1268
|
+
const query = db.insert(users).values([{}, {}]).toSQL();
|
1269
|
+
|
1270
|
+
assert.equal(query, {
|
1271
|
+
sql: 'insert into "users" ("id", "name", "state") values (default, default, default), (default, default, default)',
|
1272
|
+
params: []
|
1273
|
+
});
|
1274
|
+
});
|
1275
|
+
|
1276
|
+
test('Insert all defaults in 1 row', async (ctx) => {
|
1277
|
+
const { db2: db } = ctx;
|
1278
|
+
|
1279
|
+
const users = pgTable('empty_insert_single', {
|
1280
|
+
id: serial('id').primaryKey(),
|
1281
|
+
name: text('name').default('Dan'),
|
1282
|
+
state: text('state')
|
1283
|
+
});
|
1284
|
+
|
1285
|
+
await db.execute(sql`drop table if exists ${users}`);
|
1286
|
+
|
1287
|
+
await db.execute(sql`create table ${users} (id serial primary key, name text default 'Dan', state text)`);
|
1288
|
+
|
1289
|
+
await db.insert(users).values({});
|
1290
|
+
|
1291
|
+
const res = await db.select().from(users);
|
1292
|
+
|
1293
|
+
assert.equal(res, [{ id: 1, name: 'Dan', state: null }]);
|
1294
|
+
});
|
1295
|
+
|
1296
|
+
test('Insert all defaults in multiple rows', async (ctx) => {
|
1297
|
+
const { db2: db } = ctx;
|
1298
|
+
|
1299
|
+
const users = pgTable('empty_insert_multiple', {
|
1300
|
+
id: serial('id').primaryKey(),
|
1301
|
+
name: text('name').default('Dan'),
|
1302
|
+
state: text('state')
|
1303
|
+
});
|
1304
|
+
|
1305
|
+
await db.execute(sql`drop table if exists ${users}`);
|
1306
|
+
|
1307
|
+
await db.execute(sql`create table ${users} (id serial primary key, name text default 'Dan', state text)`);
|
1308
|
+
|
1309
|
+
await db.insert(users).values([{}, {}]);
|
1310
|
+
|
1311
|
+
const res = await db.select().from(users);
|
1312
|
+
|
1313
|
+
assert.equal(res, [
|
1314
|
+
{ id: 1, name: 'Dan', state: null },
|
1315
|
+
{ id: 2, name: 'Dan', state: null }
|
1316
|
+
]);
|
1317
|
+
});
|
1318
|
+
|
1319
|
+
test('build query insert with onConflict do update', async (ctx) => {
|
1320
|
+
const { db2: db } = ctx;
|
1321
|
+
|
1322
|
+
const query = db
|
1323
|
+
.insert(usersTable)
|
1324
|
+
.values({ name: 'John', jsonb: ['foo', 'bar'] })
|
1325
|
+
.onConflictDoUpdate({ target: usersTable.id, set: { name: 'John1' } })
|
1326
|
+
.toSQL();
|
1327
|
+
|
1328
|
+
assert.equal(query, {
|
1329
|
+
sql: 'insert into "users" ("id", "name", "verified", "jsonb", "created_at") values (default, $1, default, $2, default) on conflict ("id") do update set "name" = $3',
|
1330
|
+
params: ['John', '["foo","bar"]', 'John1']
|
1331
|
+
});
|
1332
|
+
});
|
1333
|
+
|
1334
|
+
test('build query insert with onConflict do update / multiple columns', async (ctx) => {
|
1335
|
+
const { db2: db } = ctx;
|
1336
|
+
|
1337
|
+
const query = db
|
1338
|
+
.insert(usersTable)
|
1339
|
+
.values({ name: 'John', jsonb: ['foo', 'bar'] })
|
1340
|
+
.onConflictDoUpdate({ target: [usersTable.id, usersTable.name], set: { name: 'John1' } })
|
1341
|
+
.toSQL();
|
1342
|
+
|
1343
|
+
assert.equal(query, {
|
1344
|
+
sql: 'insert into "users" ("id", "name", "verified", "jsonb", "created_at") values (default, $1, default, $2, default) on conflict ("id","name") do update set "name" = $3',
|
1345
|
+
params: ['John', '["foo","bar"]', 'John1']
|
1346
|
+
});
|
1347
|
+
});
|
1348
|
+
|
1349
|
+
test('build query insert with onConflict do nothing', async (ctx) => {
|
1350
|
+
const { db2: db } = ctx;
|
1351
|
+
|
1352
|
+
const query = db
|
1353
|
+
.insert(usersTable)
|
1354
|
+
.values({ name: 'John', jsonb: ['foo', 'bar'] })
|
1355
|
+
.onConflictDoNothing()
|
1356
|
+
.toSQL();
|
1357
|
+
|
1358
|
+
assert.equal(query, {
|
1359
|
+
sql: 'insert into "users" ("id", "name", "verified", "jsonb", "created_at") values (default, $1, default, $2, default) on conflict do nothing',
|
1360
|
+
params: ['John', '["foo","bar"]']
|
1361
|
+
});
|
1362
|
+
});
|
1363
|
+
|
1364
|
+
test('build query insert with onConflict do nothing + target', async (ctx) => {
|
1365
|
+
const { db2: db } = ctx;
|
1366
|
+
|
1367
|
+
const query = db
|
1368
|
+
.insert(usersTable)
|
1369
|
+
.values({ name: 'John', jsonb: ['foo', 'bar'] })
|
1370
|
+
.onConflictDoNothing({ target: usersTable.id })
|
1371
|
+
.toSQL();
|
1372
|
+
|
1373
|
+
assert.equal(query, {
|
1374
|
+
sql: 'insert into "users" ("id", "name", "verified", "jsonb", "created_at") values (default, $1, default, $2, default) on conflict ("id") do nothing',
|
1375
|
+
params: ['John', '["foo","bar"]']
|
1376
|
+
});
|
1377
|
+
});
|
1378
|
+
|
1379
|
+
test('insert with onConflict do update', async (ctx) => {
|
1380
|
+
const { db2: db } = ctx;
|
1381
|
+
|
1382
|
+
await db.insert(usersTable).values({ name: 'John' });
|
1383
|
+
|
1384
|
+
await db
|
1385
|
+
.insert(usersTable)
|
1386
|
+
.values({ id: 1, name: 'John' })
|
1387
|
+
.onConflictDoUpdate({ target: usersTable.id, set: { name: 'John1' } });
|
1388
|
+
|
1389
|
+
const res = await db
|
1390
|
+
.select({ id: usersTable.id, name: usersTable.name })
|
1391
|
+
.from(usersTable)
|
1392
|
+
.where(eq(usersTable.id, 1));
|
1393
|
+
|
1394
|
+
assert.equal(res, [{ id: 1, name: 'John1' }]);
|
1395
|
+
});
|
1396
|
+
|
1397
|
+
test('insert with onConflict do nothing', async (ctx) => {
|
1398
|
+
const { db2: db } = ctx;
|
1399
|
+
|
1400
|
+
await db.insert(usersTable).values({ name: 'John' });
|
1401
|
+
|
1402
|
+
await db.insert(usersTable).values({ id: 1, name: 'John' }).onConflictDoNothing();
|
1403
|
+
|
1404
|
+
const res = await db
|
1405
|
+
.select({ id: usersTable.id, name: usersTable.name })
|
1406
|
+
.from(usersTable)
|
1407
|
+
.where(eq(usersTable.id, 1));
|
1408
|
+
|
1409
|
+
assert.equal(res, [{ id: 1, name: 'John' }]);
|
1410
|
+
});
|
1411
|
+
|
1412
|
+
test('insert with onConflict do nothing + target', async (ctx) => {
|
1413
|
+
const { db2: db } = ctx;
|
1414
|
+
|
1415
|
+
await db.insert(usersTable).values({ name: 'John' });
|
1416
|
+
|
1417
|
+
await db.insert(usersTable).values({ id: 1, name: 'John' }).onConflictDoNothing({ target: usersTable.id });
|
1418
|
+
|
1419
|
+
const res = await db
|
1420
|
+
.select({ id: usersTable.id, name: usersTable.name })
|
1421
|
+
.from(usersTable)
|
1422
|
+
.where(eq(usersTable.id, 1));
|
1423
|
+
|
1424
|
+
assert.equal(res, [{ id: 1, name: 'John' }]);
|
1425
|
+
});
|
1426
|
+
|
1427
|
+
test('left join (flat object fields)', async (ctx) => {
|
1428
|
+
const { db2: db } = ctx;
|
1429
|
+
|
1430
|
+
const { id: cityId } = await db
|
1431
|
+
.insert(citiesTable)
|
1432
|
+
.values([{ name: 'Paris' }, { name: 'London' }])
|
1433
|
+
.returning({ id: citiesTable.id })
|
1434
|
+
.then((rows) => rows[0]!);
|
1435
|
+
|
1436
|
+
await db.insert(users2Table).values([{ name: 'John', cityId }, { name: 'Jane' }]);
|
1437
|
+
|
1438
|
+
const res = await db
|
1439
|
+
.select({
|
1440
|
+
userId: users2Table.id,
|
1441
|
+
userName: users2Table.name,
|
1442
|
+
cityId: citiesTable.id,
|
1443
|
+
cityName: citiesTable.name
|
1444
|
+
})
|
1445
|
+
.from(users2Table)
|
1446
|
+
.leftJoin(citiesTable, eq(users2Table.cityId, citiesTable.id));
|
1447
|
+
|
1448
|
+
assert.equal(res, [
|
1449
|
+
{ userId: 1, userName: 'John', cityId, cityName: 'Paris' },
|
1450
|
+
{ userId: 2, userName: 'Jane', cityId: null, cityName: null }
|
1451
|
+
]);
|
1452
|
+
});
|
1453
|
+
|
1454
|
+
test('left join (grouped fields)', async (ctx) => {
|
1455
|
+
const { db2: db } = ctx;
|
1456
|
+
|
1457
|
+
const { id: cityId } = await db
|
1458
|
+
.insert(citiesTable)
|
1459
|
+
.values([{ name: 'Paris' }, { name: 'London' }])
|
1460
|
+
.returning({ id: citiesTable.id })
|
1461
|
+
.then((rows) => rows[0]!);
|
1462
|
+
|
1463
|
+
await db.insert(users2Table).values([{ name: 'John', cityId }, { name: 'Jane' }]);
|
1464
|
+
|
1465
|
+
const res = await db
|
1466
|
+
.select({
|
1467
|
+
id: users2Table.id,
|
1468
|
+
user: {
|
1469
|
+
name: users2Table.name,
|
1470
|
+
nameUpper: sql<string>`upper(${users2Table.name})`
|
1471
|
+
},
|
1472
|
+
city: {
|
1473
|
+
id: citiesTable.id,
|
1474
|
+
name: citiesTable.name,
|
1475
|
+
nameUpper: sql<string>`upper(${citiesTable.name})`
|
1476
|
+
}
|
1477
|
+
})
|
1478
|
+
.from(users2Table)
|
1479
|
+
.leftJoin(citiesTable, eq(users2Table.cityId, citiesTable.id));
|
1480
|
+
|
1481
|
+
assert.equal(res, [
|
1482
|
+
{
|
1483
|
+
id: 1,
|
1484
|
+
user: { name: 'John', nameUpper: 'JOHN' },
|
1485
|
+
city: { id: cityId, name: 'Paris', nameUpper: 'PARIS' }
|
1486
|
+
},
|
1487
|
+
{
|
1488
|
+
id: 2,
|
1489
|
+
user: { name: 'Jane', nameUpper: 'JANE' },
|
1490
|
+
city: null
|
1491
|
+
}
|
1492
|
+
]);
|
1493
|
+
});
|
1494
|
+
|
1495
|
+
test('left join (all fields)', async (ctx) => {
|
1496
|
+
const { db2: db } = ctx;
|
1497
|
+
|
1498
|
+
const { id: cityId } = await db
|
1499
|
+
.insert(citiesTable)
|
1500
|
+
.values([{ name: 'Paris' }, { name: 'London' }])
|
1501
|
+
.returning({ id: citiesTable.id })
|
1502
|
+
.then((rows) => rows[0]!);
|
1503
|
+
|
1504
|
+
await db.insert(users2Table).values([{ name: 'John', cityId }, { name: 'Jane' }]);
|
1505
|
+
|
1506
|
+
const res = await db.select().from(users2Table).leftJoin(citiesTable, eq(users2Table.cityId, citiesTable.id));
|
1507
|
+
|
1508
|
+
assert.equal(res, [
|
1509
|
+
{
|
1510
|
+
users2: {
|
1511
|
+
id: 1,
|
1512
|
+
name: 'John',
|
1513
|
+
cityId
|
1514
|
+
},
|
1515
|
+
cities: {
|
1516
|
+
id: cityId,
|
1517
|
+
name: 'Paris',
|
1518
|
+
state: null
|
1519
|
+
}
|
1520
|
+
},
|
1521
|
+
{
|
1522
|
+
users2: {
|
1523
|
+
id: 2,
|
1524
|
+
name: 'Jane',
|
1525
|
+
cityId: null
|
1526
|
+
},
|
1527
|
+
cities: null
|
1528
|
+
}
|
1529
|
+
]);
|
1530
|
+
});
|
1531
|
+
|
1532
|
+
test('join subquery', async (ctx) => {
|
1533
|
+
const { db2: db } = ctx;
|
1534
|
+
|
1535
|
+
await db
|
1536
|
+
.insert(courseCategoriesTable)
|
1537
|
+
.values([{ name: 'Category 1' }, { name: 'Category 2' }, { name: 'Category 3' }, { name: 'Category 4' }]);
|
1538
|
+
|
1539
|
+
await db.insert(coursesTable).values([
|
1540
|
+
{ name: 'Development', categoryId: 2 },
|
1541
|
+
{ name: 'IT & Software', categoryId: 3 },
|
1542
|
+
{ name: 'Marketing', categoryId: 4 },
|
1543
|
+
{ name: 'Design', categoryId: 1 }
|
1544
|
+
]);
|
1545
|
+
|
1546
|
+
const sq2 = db
|
1547
|
+
.select({
|
1548
|
+
categoryId: courseCategoriesTable.id,
|
1549
|
+
category: courseCategoriesTable.name,
|
1550
|
+
total: sql<number>`count(${courseCategoriesTable.id})`
|
1551
|
+
})
|
1552
|
+
.from(courseCategoriesTable)
|
1553
|
+
.groupBy(courseCategoriesTable.id, courseCategoriesTable.name)
|
1554
|
+
.as('sq2');
|
1555
|
+
|
1556
|
+
const res = await db
|
1557
|
+
.select({
|
1558
|
+
courseName: coursesTable.name,
|
1559
|
+
categoryId: sq2.categoryId
|
1560
|
+
})
|
1561
|
+
.from(coursesTable)
|
1562
|
+
.leftJoin(sq2, eq(coursesTable.categoryId, sq2.categoryId))
|
1563
|
+
.orderBy(coursesTable.name);
|
1564
|
+
|
1565
|
+
assert.equal(res, [
|
1566
|
+
{ courseName: 'Design', categoryId: 1 },
|
1567
|
+
{ courseName: 'Development', categoryId: 2 },
|
1568
|
+
{ courseName: 'IT & Software', categoryId: 3 },
|
1569
|
+
{ courseName: 'Marketing', categoryId: 4 }
|
1570
|
+
]);
|
1571
|
+
});
|
1572
|
+
|
1573
|
+
test('with ... select', async (ctx) => {
|
1574
|
+
const { db2: db } = ctx;
|
1575
|
+
|
1576
|
+
await db.insert(orders).values([
|
1577
|
+
{ region: 'Europe', product: 'A', amount: 10, quantity: 1 },
|
1578
|
+
{ region: 'Europe', product: 'A', amount: 20, quantity: 2 },
|
1579
|
+
{ region: 'Europe', product: 'B', amount: 20, quantity: 2 },
|
1580
|
+
{ region: 'Europe', product: 'B', amount: 30, quantity: 3 },
|
1581
|
+
{ region: 'US', product: 'A', amount: 30, quantity: 3 },
|
1582
|
+
{ region: 'US', product: 'A', amount: 40, quantity: 4 },
|
1583
|
+
{ region: 'US', product: 'B', amount: 40, quantity: 4 },
|
1584
|
+
{ region: 'US', product: 'B', amount: 50, quantity: 5 }
|
1585
|
+
]);
|
1586
|
+
|
1587
|
+
const regionalSales = db.$with('regional_sales').as(
|
1588
|
+
db
|
1589
|
+
.select({
|
1590
|
+
region: orders.region,
|
1591
|
+
totalSales: sql<number>`sum(${orders.amount})`.as('total_sales')
|
1592
|
+
})
|
1593
|
+
.from(orders)
|
1594
|
+
.groupBy(orders.region)
|
1595
|
+
);
|
1596
|
+
|
1597
|
+
const topRegions = db.$with('top_regions').as(
|
1598
|
+
db
|
1599
|
+
.select({
|
1600
|
+
region: regionalSales.region
|
1601
|
+
})
|
1602
|
+
.from(regionalSales)
|
1603
|
+
.where(
|
1604
|
+
gt(
|
1605
|
+
regionalSales.totalSales,
|
1606
|
+
db.select({ sales: sql`sum(${regionalSales.totalSales})/10` }).from(regionalSales)
|
1607
|
+
)
|
1608
|
+
)
|
1609
|
+
);
|
1610
|
+
|
1611
|
+
const result1 = await db
|
1612
|
+
.with(regionalSales, topRegions)
|
1613
|
+
.select({
|
1614
|
+
region: orders.region,
|
1615
|
+
product: orders.product,
|
1616
|
+
productUnits: sql<number>`sum(${orders.quantity})::int`,
|
1617
|
+
productSales: sql<number>`sum(${orders.amount})::int`
|
1618
|
+
})
|
1619
|
+
.from(orders)
|
1620
|
+
.where(inArray(orders.region, db.select({ region: topRegions.region }).from(topRegions)))
|
1621
|
+
.groupBy(orders.region, orders.product)
|
1622
|
+
.orderBy(orders.region, orders.product);
|
1623
|
+
const result2 = await db
|
1624
|
+
.with(regionalSales, topRegions)
|
1625
|
+
.selectDistinct({
|
1626
|
+
region: orders.region,
|
1627
|
+
product: orders.product,
|
1628
|
+
productUnits: sql<number>`sum(${orders.quantity})::int`,
|
1629
|
+
productSales: sql<number>`sum(${orders.amount})::int`
|
1630
|
+
})
|
1631
|
+
.from(orders)
|
1632
|
+
.where(inArray(orders.region, db.select({ region: topRegions.region }).from(topRegions)))
|
1633
|
+
.groupBy(orders.region, orders.product)
|
1634
|
+
.orderBy(orders.region, orders.product);
|
1635
|
+
const result3 = await db
|
1636
|
+
.with(regionalSales, topRegions)
|
1637
|
+
.selectDistinctOn([orders.region], {
|
1638
|
+
region: orders.region,
|
1639
|
+
productUnits: sql<number>`sum(${orders.quantity})::int`,
|
1640
|
+
productSales: sql<number>`sum(${orders.amount})::int`
|
1641
|
+
})
|
1642
|
+
.from(orders)
|
1643
|
+
.where(inArray(orders.region, db.select({ region: topRegions.region }).from(topRegions)))
|
1644
|
+
.groupBy(orders.region)
|
1645
|
+
.orderBy(orders.region);
|
1646
|
+
|
1647
|
+
assert.equal(result1, [
|
1648
|
+
{
|
1649
|
+
region: 'Europe',
|
1650
|
+
product: 'A',
|
1651
|
+
productUnits: 3,
|
1652
|
+
productSales: 30
|
1653
|
+
},
|
1654
|
+
{
|
1655
|
+
region: 'Europe',
|
1656
|
+
product: 'B',
|
1657
|
+
productUnits: 5,
|
1658
|
+
productSales: 50
|
1659
|
+
},
|
1660
|
+
{
|
1661
|
+
region: 'US',
|
1662
|
+
product: 'A',
|
1663
|
+
productUnits: 7,
|
1664
|
+
productSales: 70
|
1665
|
+
},
|
1666
|
+
{
|
1667
|
+
region: 'US',
|
1668
|
+
product: 'B',
|
1669
|
+
productUnits: 9,
|
1670
|
+
productSales: 90
|
1671
|
+
}
|
1672
|
+
]);
|
1673
|
+
assert.equal(result2, result1);
|
1674
|
+
assert.equal(result3, [
|
1675
|
+
{
|
1676
|
+
region: 'Europe',
|
1677
|
+
productUnits: 8,
|
1678
|
+
productSales: 80
|
1679
|
+
},
|
1680
|
+
{
|
1681
|
+
region: 'US',
|
1682
|
+
productUnits: 16,
|
1683
|
+
productSales: 160
|
1684
|
+
}
|
1685
|
+
]);
|
1686
|
+
});
|
1687
|
+
|
1688
|
+
test('with ... update', async (ctx) => {
|
1689
|
+
const { db2: db } = ctx;
|
1690
|
+
|
1691
|
+
const products = pgTable('products', {
|
1692
|
+
id: serial('id').primaryKey(),
|
1693
|
+
price: numeric('price').notNull(),
|
1694
|
+
cheap: boolean('cheap').notNull().default(false)
|
1695
|
+
});
|
1696
|
+
|
1697
|
+
await db.execute(sql`drop table if exists ${products}`);
|
1698
|
+
await db.execute(sql`
|
1699
|
+
create table ${products} (
|
1700
|
+
id serial primary key,
|
1701
|
+
price numeric not null,
|
1702
|
+
cheap boolean not null default false
|
1703
|
+
)
|
1704
|
+
`);
|
1705
|
+
|
1706
|
+
await db
|
1707
|
+
.insert(products)
|
1708
|
+
.values([{ price: '10.99' }, { price: '25.85' }, { price: '32.99' }, { price: '2.50' }, { price: '4.59' }]);
|
1709
|
+
|
1710
|
+
const averagePrice = db.$with('average_price').as(
|
1711
|
+
db
|
1712
|
+
.select({
|
1713
|
+
value: sql`avg(${products.price})`.as('value')
|
1714
|
+
})
|
1715
|
+
.from(products)
|
1716
|
+
);
|
1717
|
+
|
1718
|
+
const result = await db
|
1719
|
+
.with(averagePrice)
|
1720
|
+
.update(products)
|
1721
|
+
.set({
|
1722
|
+
cheap: true
|
1723
|
+
})
|
1724
|
+
.where(lt(products.price, sql`(select * from ${averagePrice})`))
|
1725
|
+
.returning({
|
1726
|
+
id: products.id
|
1727
|
+
});
|
1728
|
+
|
1729
|
+
assert.equal(result, [{ id: 1 }, { id: 4 }, { id: 5 }]);
|
1730
|
+
});
|
1731
|
+
|
1732
|
+
test('with ... insert', async (ctx) => {
|
1733
|
+
const { db2: db } = ctx;
|
1734
|
+
|
1735
|
+
const users = pgTable('users', {
|
1736
|
+
username: text('username').notNull(),
|
1737
|
+
admin: boolean('admin').notNull()
|
1738
|
+
});
|
1739
|
+
|
1740
|
+
await db.execute(sql`drop table if exists ${users}`);
|
1741
|
+
await db.execute(sql`create table ${users} (username text not null, admin boolean not null default false)`);
|
1742
|
+
|
1743
|
+
const userCount = db.$with('user_count').as(
|
1744
|
+
db
|
1745
|
+
.select({
|
1746
|
+
value: sql`count(*)`.as('value')
|
1747
|
+
})
|
1748
|
+
.from(users)
|
1749
|
+
);
|
1750
|
+
|
1751
|
+
const result = await db
|
1752
|
+
.with(userCount)
|
1753
|
+
.insert(users)
|
1754
|
+
.values([{ username: 'user1', admin: sql`((select * from ${userCount}) = 0)` }])
|
1755
|
+
.returning({
|
1756
|
+
admin: users.admin
|
1757
|
+
});
|
1758
|
+
|
1759
|
+
assert.equal(result, [{ admin: true }]);
|
1760
|
+
});
|
1761
|
+
|
1762
|
+
test('with ... delete', async (ctx) => {
|
1763
|
+
const { db2: db } = ctx;
|
1764
|
+
|
1765
|
+
await db.insert(orders).values([
|
1766
|
+
{ region: 'Europe', product: 'A', amount: 10, quantity: 1 },
|
1767
|
+
{ region: 'Europe', product: 'A', amount: 20, quantity: 2 },
|
1768
|
+
{ region: 'Europe', product: 'B', amount: 20, quantity: 2 },
|
1769
|
+
{ region: 'Europe', product: 'B', amount: 30, quantity: 3 },
|
1770
|
+
{ region: 'US', product: 'A', amount: 30, quantity: 3 },
|
1771
|
+
{ region: 'US', product: 'A', amount: 40, quantity: 4 },
|
1772
|
+
{ region: 'US', product: 'B', amount: 40, quantity: 4 },
|
1773
|
+
{ region: 'US', product: 'B', amount: 50, quantity: 5 }
|
1774
|
+
]);
|
1775
|
+
|
1776
|
+
const averageAmount = db.$with('average_amount').as(
|
1777
|
+
db
|
1778
|
+
.select({
|
1779
|
+
value: sql`avg(${orders.amount})`.as('value')
|
1780
|
+
})
|
1781
|
+
.from(orders)
|
1782
|
+
);
|
1783
|
+
|
1784
|
+
const result = await db
|
1785
|
+
.with(averageAmount)
|
1786
|
+
.delete(orders)
|
1787
|
+
.where(gt(orders.amount, sql`(select * from ${averageAmount})`))
|
1788
|
+
.returning({
|
1789
|
+
id: orders.id
|
1790
|
+
});
|
1791
|
+
|
1792
|
+
assert.equal(result, [{ id: 6 }, { id: 7 }, { id: 8 }]);
|
1793
|
+
});
|
1794
|
+
|
1795
|
+
test('select from subquery sql', async (ctx) => {
|
1796
|
+
const { db2: db } = ctx;
|
1797
|
+
|
1798
|
+
await db.insert(users2Table).values([{ name: 'John' }, { name: 'Jane' }]);
|
1799
|
+
|
1800
|
+
const sq = db
|
1801
|
+
.select({ name: sql<string>`${users2Table.name} || ' modified'`.as('name') })
|
1802
|
+
.from(users2Table)
|
1803
|
+
.as('sq');
|
1804
|
+
|
1805
|
+
const res = await db.select({ name: sq.name }).from(sq);
|
1806
|
+
|
1807
|
+
assert.equal(res, [{ name: 'John modified' }, { name: 'Jane modified' }]);
|
1808
|
+
});
|
1809
|
+
|
1810
|
+
test('select a field without joining its table', (ctx) => {
|
1811
|
+
const { db2: db } = ctx;
|
1812
|
+
|
1813
|
+
ctx.expect(() => db.select({ name: users2Table.name }).from(usersTable).prepare('query')).toThrowError();
|
1814
|
+
});
|
1815
|
+
|
1816
|
+
test('select all fields from subquery without alias', (ctx) => {
|
1817
|
+
const { db2: db } = ctx;
|
1818
|
+
|
1819
|
+
const sq = db.$with('sq').as(db.select({ name: sql<string>`upper(${users2Table.name})` }).from(users2Table));
|
1820
|
+
|
1821
|
+
ctx.expect(() => db.select().from(sq).prepare('query')).toThrowError();
|
1822
|
+
});
|
1823
|
+
|
1824
|
+
test('select count()', async (ctx) => {
|
1825
|
+
const { db2: db } = ctx;
|
1826
|
+
|
1827
|
+
await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }]);
|
1828
|
+
|
1829
|
+
const res = await db.select({ count: sql`count(*)` }).from(usersTable);
|
1830
|
+
|
1831
|
+
assert.equal(res, [{ count: '2' }]);
|
1832
|
+
});
|
1833
|
+
|
1834
|
+
test('select count w/ custom mapper', async (ctx) => {
|
1835
|
+
const { db2: db } = ctx;
|
1836
|
+
|
1837
|
+
function count(value: PgColumn | SQLWrapper): SQL<number>;
|
1838
|
+
function count(value: PgColumn | SQLWrapper, alias: string): SQL.Aliased<number>;
|
1839
|
+
function count(value: PgColumn | SQLWrapper, alias?: string): SQL<number> | SQL.Aliased<number> {
|
1840
|
+
const result = sql`count(${value})`.mapWith(Number);
|
1841
|
+
if (!alias) {
|
1842
|
+
return result;
|
1843
|
+
}
|
1844
|
+
return result.as(alias);
|
1845
|
+
}
|
1846
|
+
|
1847
|
+
await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }]);
|
1848
|
+
|
1849
|
+
const res = await db.select({ count: count(sql`*`) }).from(usersTable);
|
1850
|
+
|
1851
|
+
assert.equal(res, [{ count: 2 }]);
|
1852
|
+
});
|
1853
|
+
|
1854
|
+
test('network types', async (ctx) => {
|
1855
|
+
const { db2: db } = ctx;
|
1856
|
+
|
1857
|
+
const value: typeof network.$inferSelect = {
|
1858
|
+
inet: '127.0.0.1',
|
1859
|
+
cidr: '192.168.100.128/25',
|
1860
|
+
macaddr: '08:00:2b:01:02:03',
|
1861
|
+
macaddr8: '08:00:2b:01:02:03:04:05'
|
1862
|
+
};
|
1863
|
+
|
1864
|
+
await db.insert(network).values(value);
|
1865
|
+
|
1866
|
+
const res = await db.select().from(network);
|
1867
|
+
|
1868
|
+
assert.equal(res, [value]);
|
1869
|
+
});
|
1870
|
+
|
1871
|
+
test('array types', async (ctx) => {
|
1872
|
+
const { db2: db } = ctx;
|
1873
|
+
|
1874
|
+
const values: (typeof salEmp.$inferSelect)[] = [
|
1875
|
+
{
|
1876
|
+
name: 'John',
|
1877
|
+
payByQuarter: [10000, 10000, 10000, 10000],
|
1878
|
+
schedule: [
|
1879
|
+
['meeting', 'lunch'],
|
1880
|
+
['training', 'presentation']
|
1881
|
+
]
|
1882
|
+
},
|
1883
|
+
{
|
1884
|
+
name: 'Carol',
|
1885
|
+
payByQuarter: [20000, 25000, 25000, 25000],
|
1886
|
+
schedule: [
|
1887
|
+
['breakfast', 'consulting'],
|
1888
|
+
['meeting', 'lunch']
|
1889
|
+
]
|
1890
|
+
}
|
1891
|
+
];
|
1892
|
+
|
1893
|
+
await db.insert(salEmp).values(values);
|
1894
|
+
|
1895
|
+
const res = await db.select().from(salEmp);
|
1896
|
+
|
1897
|
+
assert.equal(res, values);
|
1898
|
+
});
|
1899
|
+
|
1900
|
+
test('select for ...', (ctx) => {
|
1901
|
+
const { db2: db } = ctx;
|
1902
|
+
|
1903
|
+
{
|
1904
|
+
const query = db.select().from(users2Table).for('update').toSQL();
|
1905
|
+
|
1906
|
+
ctx.expect(query.sql).toMatch(/ for update$/);
|
1907
|
+
}
|
1908
|
+
|
1909
|
+
{
|
1910
|
+
const query = db
|
1911
|
+
.select()
|
1912
|
+
.from(users2Table)
|
1913
|
+
.for('update', { of: [users2Table, coursesTable] })
|
1914
|
+
.toSQL();
|
1915
|
+
|
1916
|
+
ctx.expect(query.sql).toMatch(/ for update of "users2", "courses"$/);
|
1917
|
+
}
|
1918
|
+
|
1919
|
+
{
|
1920
|
+
const query = db.select().from(users2Table).for('no key update', { of: users2Table }).toSQL();
|
1921
|
+
|
1922
|
+
ctx.expect(query.sql).toMatch(/for no key update of "users2"$/);
|
1923
|
+
}
|
1924
|
+
|
1925
|
+
{
|
1926
|
+
const query = db.select().from(users2Table).for('no key update', { of: users2Table, skipLocked: true }).toSQL();
|
1927
|
+
|
1928
|
+
ctx.expect(query.sql).toMatch(/ for no key update of "users2" skip locked$/);
|
1929
|
+
}
|
1930
|
+
|
1931
|
+
{
|
1932
|
+
const query = db.select().from(users2Table).for('share', { of: users2Table, noWait: true }).toSQL();
|
1933
|
+
|
1934
|
+
ctx.expect(query.sql).toMatch(/for share of "users2" no wait$/);
|
1935
|
+
}
|
1936
|
+
});
|
1937
|
+
|
1938
|
+
test('having', async (ctx) => {
|
1939
|
+
const { db2: db } = ctx;
|
1940
|
+
|
1941
|
+
await db.insert(citiesTable).values([{ name: 'London' }, { name: 'Paris' }, { name: 'New York' }]);
|
1942
|
+
|
1943
|
+
await db.insert(users2Table).values([
|
1944
|
+
{ name: 'John', cityId: 1 },
|
1945
|
+
{ name: 'Jane', cityId: 1 },
|
1946
|
+
{
|
1947
|
+
name: 'Jack',
|
1948
|
+
cityId: 2
|
1949
|
+
}
|
1950
|
+
]);
|
1951
|
+
|
1952
|
+
const result = await db
|
1953
|
+
.select({
|
1954
|
+
id: citiesTable.id,
|
1955
|
+
name: sql<string>`upper(${citiesTable.name})`.as('upper_name'),
|
1956
|
+
usersCount: sql<number>`count(${users2Table.id})::int`.as('users_count')
|
1957
|
+
})
|
1958
|
+
.from(citiesTable)
|
1959
|
+
.leftJoin(users2Table, eq(users2Table.cityId, citiesTable.id))
|
1960
|
+
.where(({ name }) => sql`length(${name}) >= 3`)
|
1961
|
+
.groupBy(citiesTable.id)
|
1962
|
+
.having(({ usersCount }) => sql`${usersCount} > 0`)
|
1963
|
+
.orderBy(({ name }) => name);
|
1964
|
+
|
1965
|
+
assert.equal(result, [
|
1966
|
+
{
|
1967
|
+
id: 1,
|
1968
|
+
name: 'LONDON',
|
1969
|
+
usersCount: 2
|
1970
|
+
},
|
1971
|
+
{
|
1972
|
+
id: 2,
|
1973
|
+
name: 'PARIS',
|
1974
|
+
usersCount: 1
|
1975
|
+
}
|
1976
|
+
]);
|
1977
|
+
});
|
1978
|
+
|
1979
|
+
test('view', async (ctx) => {
|
1980
|
+
const { db2: db } = ctx;
|
1981
|
+
|
1982
|
+
const newYorkers1 = pgView('new_yorkers').as((qb) =>
|
1983
|
+
qb.select().from(users2Table).where(eq(users2Table.cityId, 1))
|
1984
|
+
);
|
1985
|
+
|
1986
|
+
const newYorkers2 = pgView('new_yorkers', {
|
1987
|
+
id: serial('id').primaryKey(),
|
1988
|
+
name: text('name').notNull(),
|
1989
|
+
cityId: integer('city_id').notNull()
|
1990
|
+
}).as(sql`select * from ${users2Table} where ${eq(users2Table.cityId, 1)}`);
|
1991
|
+
|
1992
|
+
const newYorkers3 = pgView('new_yorkers', {
|
1993
|
+
id: serial('id').primaryKey(),
|
1994
|
+
name: text('name').notNull(),
|
1995
|
+
cityId: integer('city_id').notNull()
|
1996
|
+
}).existing();
|
1997
|
+
|
1998
|
+
await db.execute(sql`create view ${newYorkers1} as ${getViewConfig(newYorkers1).query}`);
|
1999
|
+
|
2000
|
+
await db.insert(citiesTable).values([{ name: 'New York' }, { name: 'Paris' }]);
|
2001
|
+
|
2002
|
+
await db.insert(users2Table).values([
|
2003
|
+
{ name: 'John', cityId: 1 },
|
2004
|
+
{ name: 'Jane', cityId: 1 },
|
2005
|
+
{ name: 'Jack', cityId: 2 }
|
2006
|
+
]);
|
2007
|
+
|
2008
|
+
{
|
2009
|
+
const result = await db.select().from(newYorkers1);
|
2010
|
+
assert.equal(result, [
|
2011
|
+
{ id: 1, name: 'John', cityId: 1 },
|
2012
|
+
{ id: 2, name: 'Jane', cityId: 1 }
|
2013
|
+
]);
|
2014
|
+
}
|
2015
|
+
|
2016
|
+
{
|
2017
|
+
const result = await db.select().from(newYorkers2);
|
2018
|
+
assert.equal(result, [
|
2019
|
+
{ id: 1, name: 'John', cityId: 1 },
|
2020
|
+
{ id: 2, name: 'Jane', cityId: 1 }
|
2021
|
+
]);
|
2022
|
+
}
|
2023
|
+
|
2024
|
+
{
|
2025
|
+
const result = await db.select().from(newYorkers3);
|
2026
|
+
assert.equal(result, [
|
2027
|
+
{ id: 1, name: 'John', cityId: 1 },
|
2028
|
+
{ id: 2, name: 'Jane', cityId: 1 }
|
2029
|
+
]);
|
2030
|
+
}
|
2031
|
+
|
2032
|
+
{
|
2033
|
+
const result = await db.select({ name: newYorkers1.name }).from(newYorkers1);
|
2034
|
+
assert.equal(result, [{ name: 'John' }, { name: 'Jane' }]);
|
2035
|
+
}
|
2036
|
+
|
2037
|
+
await db.execute(sql`drop view ${newYorkers1}`);
|
2038
|
+
});
|
2039
|
+
|
2040
|
+
test('materialized view', async (ctx) => {
|
2041
|
+
const { db2: db } = ctx;
|
2042
|
+
|
2043
|
+
const newYorkers1 = pgMaterializedView('new_yorkers').as((qb) =>
|
2044
|
+
qb.select().from(users2Table).where(eq(users2Table.cityId, 1))
|
2045
|
+
);
|
2046
|
+
|
2047
|
+
const newYorkers2 = pgMaterializedView('new_yorkers', {
|
2048
|
+
id: serial('id').primaryKey(),
|
2049
|
+
name: text('name').notNull(),
|
2050
|
+
cityId: integer('city_id').notNull()
|
2051
|
+
}).as(sql`select * from ${users2Table} where ${eq(users2Table.cityId, 1)}`);
|
2052
|
+
|
2053
|
+
const newYorkers3 = pgMaterializedView('new_yorkers', {
|
2054
|
+
id: serial('id').primaryKey(),
|
2055
|
+
name: text('name').notNull(),
|
2056
|
+
cityId: integer('city_id').notNull()
|
2057
|
+
}).existing();
|
2058
|
+
|
2059
|
+
await db.execute(sql`create materialized view ${newYorkers1} as ${getMaterializedViewConfig(newYorkers1).query}`);
|
2060
|
+
|
2061
|
+
await db.insert(citiesTable).values([{ name: 'New York' }, { name: 'Paris' }]);
|
2062
|
+
|
2063
|
+
await db.insert(users2Table).values([
|
2064
|
+
{ name: 'John', cityId: 1 },
|
2065
|
+
{ name: 'Jane', cityId: 1 },
|
2066
|
+
{ name: 'Jack', cityId: 2 }
|
2067
|
+
]);
|
2068
|
+
|
2069
|
+
{
|
2070
|
+
const result = await db.select().from(newYorkers1);
|
2071
|
+
assert.equal(result, []);
|
2072
|
+
}
|
2073
|
+
|
2074
|
+
await db.refreshMaterializedView(newYorkers1);
|
2075
|
+
|
2076
|
+
{
|
2077
|
+
const result = await db.select().from(newYorkers1);
|
2078
|
+
assert.equal(result, [
|
2079
|
+
{ id: 1, name: 'John', cityId: 1 },
|
2080
|
+
{ id: 2, name: 'Jane', cityId: 1 }
|
2081
|
+
]);
|
2082
|
+
}
|
2083
|
+
|
2084
|
+
{
|
2085
|
+
const result = await db.select().from(newYorkers2);
|
2086
|
+
assert.equal(result, [
|
2087
|
+
{ id: 1, name: 'John', cityId: 1 },
|
2088
|
+
{ id: 2, name: 'Jane', cityId: 1 }
|
2089
|
+
]);
|
2090
|
+
}
|
2091
|
+
|
2092
|
+
{
|
2093
|
+
const result = await db.select().from(newYorkers3);
|
2094
|
+
assert.equal(result, [
|
2095
|
+
{ id: 1, name: 'John', cityId: 1 },
|
2096
|
+
{ id: 2, name: 'Jane', cityId: 1 }
|
2097
|
+
]);
|
2098
|
+
}
|
2099
|
+
|
2100
|
+
{
|
2101
|
+
const result = await db.select({ name: newYorkers1.name }).from(newYorkers1);
|
2102
|
+
assert.equal(result, [{ name: 'John' }, { name: 'Jane' }]);
|
2103
|
+
}
|
2104
|
+
|
2105
|
+
await db.execute(sql`drop materialized view ${newYorkers1}`);
|
2106
|
+
});
|
2107
|
+
|
2108
|
+
// TODO: copy to SQLite and MySQL, add to docs
|
2109
|
+
test('select from raw sql', async (ctx) => {
|
2110
|
+
const { db2: db } = ctx;
|
2111
|
+
|
2112
|
+
const result = await db
|
2113
|
+
.select({
|
2114
|
+
id: sql<number>`id`,
|
2115
|
+
name: sql<string>`name`
|
2116
|
+
})
|
2117
|
+
.from(sql`(select 1 as id, 'John' as name) as users`);
|
2118
|
+
|
2119
|
+
Expect<Equal<{ id: number; name: string }[], typeof result>>;
|
2120
|
+
|
2121
|
+
assert.equal(result, [{ id: 1, name: 'John' }]);
|
2122
|
+
});
|
2123
|
+
|
2124
|
+
test('select from raw sql with joins', async (ctx) => {
|
2125
|
+
const { db2: db } = ctx;
|
2126
|
+
|
2127
|
+
const result = await db
|
2128
|
+
.select({
|
2129
|
+
id: sql<number>`users.id`,
|
2130
|
+
name: sql<string>`users.name`,
|
2131
|
+
userCity: sql<string>`users.city`,
|
2132
|
+
cityName: sql<string>`cities.name`
|
2133
|
+
})
|
2134
|
+
.from(sql`(select 1 as id, 'John' as name, 'New York' as city) as users`)
|
2135
|
+
.leftJoin(sql`(select 1 as id, 'Paris' as name) as cities`, sql`cities.id = users.id`);
|
2136
|
+
|
2137
|
+
Expect<Equal<{ id: number; name: string; userCity: string; cityName: string }[], typeof result>>;
|
2138
|
+
|
2139
|
+
assert.equal(result, [{ id: 1, name: 'John', userCity: 'New York', cityName: 'Paris' }]);
|
2140
|
+
});
|
2141
|
+
|
2142
|
+
test('join on aliased sql from select', async (ctx) => {
|
2143
|
+
const { db2: db } = ctx;
|
2144
|
+
|
2145
|
+
const result = await db
|
2146
|
+
.select({
|
2147
|
+
userId: sql<number>`users.id`.as('userId'),
|
2148
|
+
name: sql<string>`users.name`,
|
2149
|
+
userCity: sql<string>`users.city`,
|
2150
|
+
cityId: sql<number>`cities.id`.as('cityId'),
|
2151
|
+
cityName: sql<string>`cities.name`
|
2152
|
+
})
|
2153
|
+
.from(sql`(select 1 as id, 'John' as name, 'New York' as city) as users`)
|
2154
|
+
.leftJoin(sql`(select 1 as id, 'Paris' as name) as cities`, (cols) => eq(cols.cityId, cols.userId));
|
2155
|
+
|
2156
|
+
Expect<
|
2157
|
+
Equal<{ userId: number; name: string; userCity: string; cityId: number; cityName: string }[], typeof result>
|
2158
|
+
>;
|
2159
|
+
|
2160
|
+
assert.equal(result, [{ userId: 1, name: 'John', userCity: 'New York', cityId: 1, cityName: 'Paris' }]);
|
2161
|
+
});
|
2162
|
+
|
2163
|
+
test('join on aliased sql from with clause', async (ctx) => {
|
2164
|
+
const { db2: db } = ctx;
|
2165
|
+
|
2166
|
+
const users = db.$with('users').as(
|
2167
|
+
db
|
2168
|
+
.select({
|
2169
|
+
id: sql<number>`id`.as('userId'),
|
2170
|
+
name: sql<string>`name`.as('userName'),
|
2171
|
+
city: sql<string>`city`.as('city')
|
2172
|
+
})
|
2173
|
+
.from(sql`(select 1 as id, 'John' as name, 'New York' as city) as users`)
|
2174
|
+
);
|
2175
|
+
|
2176
|
+
const cities = db.$with('cities').as(
|
2177
|
+
db
|
2178
|
+
.select({
|
2179
|
+
id: sql<number>`id`.as('cityId'),
|
2180
|
+
name: sql<string>`name`.as('cityName')
|
2181
|
+
})
|
2182
|
+
.from(sql`(select 1 as id, 'Paris' as name) as cities`)
|
2183
|
+
);
|
2184
|
+
|
2185
|
+
const result = await db
|
2186
|
+
.with(users, cities)
|
2187
|
+
.select({
|
2188
|
+
userId: users.id,
|
2189
|
+
name: users.name,
|
2190
|
+
userCity: users.city,
|
2191
|
+
cityId: cities.id,
|
2192
|
+
cityName: cities.name
|
2193
|
+
})
|
2194
|
+
.from(users)
|
2195
|
+
.leftJoin(cities, (cols) => eq(cols.cityId, cols.userId));
|
2196
|
+
|
2197
|
+
Expect<
|
2198
|
+
Equal<{ userId: number; name: string; userCity: string; cityId: number; cityName: string }[], typeof result>
|
2199
|
+
>;
|
2200
|
+
|
2201
|
+
assert.equal(result, [{ userId: 1, name: 'John', userCity: 'New York', cityId: 1, cityName: 'Paris' }]);
|
2202
|
+
});
|
2203
|
+
|
2204
|
+
test('prefixed table', async (ctx) => {
|
2205
|
+
const { db2: db } = ctx;
|
2206
|
+
|
2207
|
+
const pgTable = pgTableCreator((name) => `myprefix_${name}`);
|
2208
|
+
|
2209
|
+
const users = pgTable('test_prefixed_table_with_unique_name', {
|
2210
|
+
id: integer('id').primaryKey(),
|
2211
|
+
name: text('name').notNull()
|
2212
|
+
});
|
2213
|
+
|
2214
|
+
await db.execute(sql`drop table if exists ${users}`);
|
2215
|
+
|
2216
|
+
await db.execute(
|
2217
|
+
sql`create table myprefix_test_prefixed_table_with_unique_name (id integer not null primary key, name text not null)`
|
2218
|
+
);
|
2219
|
+
|
2220
|
+
await db.insert(users).values({ id: 1, name: 'John' });
|
2221
|
+
|
2222
|
+
const result = await db.select().from(users);
|
2223
|
+
|
2224
|
+
assert.equal(result, [{ id: 1, name: 'John' }]);
|
2225
|
+
|
2226
|
+
await db.execute(sql`drop table ${users}`);
|
2227
|
+
});
|
2228
|
+
|
2229
|
+
test('select from enum', async (ctx) => {
|
2230
|
+
const { db2: db } = ctx;
|
2231
|
+
|
2232
|
+
const muscleEnum = pgEnum('muscle', [
|
2233
|
+
'abdominals',
|
2234
|
+
'hamstrings',
|
2235
|
+
'adductors',
|
2236
|
+
'quadriceps',
|
2237
|
+
'biceps',
|
2238
|
+
'shoulders',
|
2239
|
+
'chest',
|
2240
|
+
'middle_back',
|
2241
|
+
'calves',
|
2242
|
+
'glutes',
|
2243
|
+
'lower_back',
|
2244
|
+
'lats',
|
2245
|
+
'triceps',
|
2246
|
+
'traps',
|
2247
|
+
'forearms',
|
2248
|
+
'neck',
|
2249
|
+
'abductors'
|
2250
|
+
]);
|
2251
|
+
|
2252
|
+
const forceEnum = pgEnum('force', ['isometric', 'isotonic', 'isokinetic']);
|
2253
|
+
|
2254
|
+
const levelEnum = pgEnum('level', ['beginner', 'intermediate', 'advanced']);
|
2255
|
+
|
2256
|
+
const mechanicEnum = pgEnum('mechanic', ['compound', 'isolation']);
|
2257
|
+
|
2258
|
+
const equipmentEnum = pgEnum('equipment', ['barbell', 'dumbbell', 'bodyweight', 'machine', 'cable', 'kettlebell']);
|
2259
|
+
|
2260
|
+
const categoryEnum = pgEnum('category', ['upper_body', 'lower_body', 'full_body']);
|
2261
|
+
|
2262
|
+
const exercises = pgTable('exercises', {
|
2263
|
+
id: serial('id').primaryKey(),
|
2264
|
+
name: varchar('name').notNull(),
|
2265
|
+
force: forceEnum('force'),
|
2266
|
+
level: levelEnum('level'),
|
2267
|
+
mechanic: mechanicEnum('mechanic'),
|
2268
|
+
equipment: equipmentEnum('equipment'),
|
2269
|
+
instructions: text('instructions'),
|
2270
|
+
category: categoryEnum('category'),
|
2271
|
+
primaryMuscles: muscleEnum('primary_muscles').array(),
|
2272
|
+
secondaryMuscles: muscleEnum('secondary_muscles').array(),
|
2273
|
+
createdAt: timestamp('created_at')
|
2274
|
+
.notNull()
|
2275
|
+
.default(sql`now()`),
|
2276
|
+
updatedAt: timestamp('updated_at')
|
2277
|
+
.notNull()
|
2278
|
+
.default(sql`now()`)
|
2279
|
+
});
|
2280
|
+
|
2281
|
+
await db.execute(sql`drop table if exists ${exercises}`);
|
2282
|
+
await db.execute(sql`drop type if exists ${name(muscleEnum.enumName)}`);
|
2283
|
+
await db.execute(sql`drop type if exists ${name(forceEnum.enumName)}`);
|
2284
|
+
await db.execute(sql`drop type if exists ${name(levelEnum.enumName)}`);
|
2285
|
+
await db.execute(sql`drop type if exists ${name(mechanicEnum.enumName)}`);
|
2286
|
+
await db.execute(sql`drop type if exists ${name(equipmentEnum.enumName)}`);
|
2287
|
+
await db.execute(sql`drop type if exists ${name(categoryEnum.enumName)}`);
|
2288
|
+
|
2289
|
+
await db.execute(
|
2290
|
+
sql`create type ${name(
|
2291
|
+
muscleEnum.enumName
|
2292
|
+
)} as enum ('abdominals', 'hamstrings', 'adductors', 'quadriceps', 'biceps', 'shoulders', 'chest', 'middle_back', 'calves', 'glutes', 'lower_back', 'lats', 'triceps', 'traps', 'forearms', 'neck', 'abductors')`
|
2293
|
+
);
|
2294
|
+
await db.execute(sql`create type ${name(forceEnum.enumName)} as enum ('isometric', 'isotonic', 'isokinetic')`);
|
2295
|
+
await db.execute(sql`create type ${name(levelEnum.enumName)} as enum ('beginner', 'intermediate', 'advanced')`);
|
2296
|
+
await db.execute(sql`create type ${name(mechanicEnum.enumName)} as enum ('compound', 'isolation')`);
|
2297
|
+
await db.execute(
|
2298
|
+
sql`create type ${name(
|
2299
|
+
equipmentEnum.enumName
|
2300
|
+
)} as enum ('barbell', 'dumbbell', 'bodyweight', 'machine', 'cable', 'kettlebell')`
|
2301
|
+
);
|
2302
|
+
await db.execute(sql`create type ${name(categoryEnum.enumName)} as enum ('upper_body', 'lower_body', 'full_body')`);
|
2303
|
+
await db.execute(sql`
|
2304
|
+
create table ${exercises} (
|
2305
|
+
id serial primary key,
|
2306
|
+
name varchar not null,
|
2307
|
+
force force,
|
2308
|
+
level level,
|
2309
|
+
mechanic mechanic,
|
2310
|
+
equipment equipment,
|
2311
|
+
instructions text,
|
2312
|
+
category category,
|
2313
|
+
primary_muscles muscle[],
|
2314
|
+
secondary_muscles muscle[],
|
2315
|
+
created_at timestamp not null default now(),
|
2316
|
+
updated_at timestamp not null default now()
|
2317
|
+
)
|
2318
|
+
`);
|
2319
|
+
|
2320
|
+
await db.insert(exercises).values({
|
2321
|
+
name: 'Bench Press',
|
2322
|
+
force: 'isotonic',
|
2323
|
+
level: 'beginner',
|
2324
|
+
mechanic: 'compound',
|
2325
|
+
equipment: 'barbell',
|
2326
|
+
instructions:
|
2327
|
+
'Lie on your back on a flat bench. Grasp the barbell with an overhand grip, slightly wider than shoulder width. Unrack the barbell and hold it over you with your arms locked. Lower the barbell to your chest. Press the barbell back to the starting position.',
|
2328
|
+
category: 'upper_body',
|
2329
|
+
primaryMuscles: ['chest', 'triceps'],
|
2330
|
+
secondaryMuscles: ['shoulders', 'traps']
|
2331
|
+
});
|
2332
|
+
|
2333
|
+
const result = await db.select().from(exercises);
|
2334
|
+
|
2335
|
+
assert.equal(result, [
|
2336
|
+
{
|
2337
|
+
id: 1,
|
2338
|
+
name: 'Bench Press',
|
2339
|
+
force: 'isotonic',
|
2340
|
+
level: 'beginner',
|
2341
|
+
mechanic: 'compound',
|
2342
|
+
equipment: 'barbell',
|
2343
|
+
instructions:
|
2344
|
+
'Lie on your back on a flat bench. Grasp the barbell with an overhand grip, slightly wider than shoulder width. Unrack the barbell and hold it over you with your arms locked. Lower the barbell to your chest. Press the barbell back to the starting position.',
|
2345
|
+
category: 'upper_body',
|
2346
|
+
primaryMuscles: ['chest', 'triceps'],
|
2347
|
+
secondaryMuscles: ['shoulders', 'traps'],
|
2348
|
+
createdAt: result[0]!.createdAt,
|
2349
|
+
updatedAt: result[0]!.updatedAt
|
2350
|
+
}
|
2351
|
+
]);
|
2352
|
+
|
2353
|
+
await db.execute(sql`drop table ${exercises}`);
|
2354
|
+
await db.execute(sql`drop type ${name(muscleEnum.enumName)}`);
|
2355
|
+
await db.execute(sql`drop type ${name(forceEnum.enumName)}`);
|
2356
|
+
await db.execute(sql`drop type ${name(levelEnum.enumName)}`);
|
2357
|
+
await db.execute(sql`drop type ${name(mechanicEnum.enumName)}`);
|
2358
|
+
await db.execute(sql`drop type ${name(equipmentEnum.enumName)}`);
|
2359
|
+
await db.execute(sql`drop type ${name(categoryEnum.enumName)}`);
|
2360
|
+
});
|
2361
|
+
|
2362
|
+
test('all date and time columns', async (ctx) => {
|
2363
|
+
const { db2: db } = ctx;
|
2364
|
+
|
2365
|
+
const table = pgTable('all_columns', {
|
2366
|
+
id: serial('id').primaryKey(),
|
2367
|
+
dateString: date('date_string', { mode: 'string' }).notNull(),
|
2368
|
+
time: time('time', { precision: 3 }).notNull(),
|
2369
|
+
datetime: timestamp('datetime').notNull(),
|
2370
|
+
datetimeWTZ: timestamp('datetime_wtz', { withTimezone: true }).notNull(),
|
2371
|
+
datetimeString: timestamp('datetime_string', { mode: 'string' }).notNull(),
|
2372
|
+
datetimeFullPrecision: timestamp('datetime_full_precision', { precision: 6, mode: 'string' }).notNull(),
|
2373
|
+
datetimeWTZString: timestamp('datetime_wtz_string', { withTimezone: true, mode: 'string' }).notNull(),
|
2374
|
+
interval: interval('interval').notNull()
|
2375
|
+
});
|
2376
|
+
|
2377
|
+
await db.execute(sql`drop table if exists ${table}`);
|
2378
|
+
|
2379
|
+
await db.execute(sql`
|
2380
|
+
create table ${table} (
|
2381
|
+
id serial primary key,
|
2382
|
+
date_string date not null,
|
2383
|
+
time time(3) not null,
|
2384
|
+
datetime timestamp not null,
|
2385
|
+
datetime_wtz timestamp with time zone not null,
|
2386
|
+
datetime_string timestamp not null,
|
2387
|
+
datetime_full_precision timestamp(6) not null,
|
2388
|
+
datetime_wtz_string timestamp with time zone not null,
|
2389
|
+
interval interval not null
|
2390
|
+
)
|
2391
|
+
`);
|
2392
|
+
|
2393
|
+
const someDatetime = new Date('2022-01-01T00:00:00.123Z');
|
2394
|
+
const fullPrecision = '2022-01-01T00:00:00.123456Z';
|
2395
|
+
const someTime = '23:23:12.432';
|
2396
|
+
|
2397
|
+
await db.insert(table).values({
|
2398
|
+
dateString: '2022-01-01',
|
2399
|
+
time: someTime,
|
2400
|
+
datetime: someDatetime,
|
2401
|
+
datetimeWTZ: someDatetime,
|
2402
|
+
datetimeString: '2022-01-01T00:00:00.123Z',
|
2403
|
+
datetimeFullPrecision: fullPrecision,
|
2404
|
+
datetimeWTZString: '2022-01-01T00:00:00.123Z',
|
2405
|
+
interval: '1 day'
|
2406
|
+
});
|
2407
|
+
|
2408
|
+
const result = await db.select().from(table);
|
2409
|
+
|
2410
|
+
Expect<
|
2411
|
+
Equal<
|
2412
|
+
{
|
2413
|
+
id: number;
|
2414
|
+
dateString: string;
|
2415
|
+
time: string;
|
2416
|
+
datetime: Date;
|
2417
|
+
datetimeWTZ: Date;
|
2418
|
+
datetimeString: string;
|
2419
|
+
datetimeFullPrecision: string;
|
2420
|
+
datetimeWTZString: string;
|
2421
|
+
interval: string;
|
2422
|
+
}[],
|
2423
|
+
typeof result
|
2424
|
+
>
|
2425
|
+
>;
|
2426
|
+
|
2427
|
+
Expect<
|
2428
|
+
Equal<
|
2429
|
+
{
|
2430
|
+
dateString: string;
|
2431
|
+
time: string;
|
2432
|
+
datetime: Date;
|
2433
|
+
datetimeWTZ: Date;
|
2434
|
+
datetimeString: string;
|
2435
|
+
datetimeFullPrecision: string;
|
2436
|
+
datetimeWTZString: string;
|
2437
|
+
interval: string;
|
2438
|
+
id?: number | undefined;
|
2439
|
+
},
|
2440
|
+
typeof table.$inferInsert
|
2441
|
+
>
|
2442
|
+
>;
|
2443
|
+
|
2444
|
+
assert.equal(result, [
|
2445
|
+
{
|
2446
|
+
id: 1,
|
2447
|
+
dateString: '2022-01-01',
|
2448
|
+
time: someTime,
|
2449
|
+
datetime: someDatetime,
|
2450
|
+
datetimeWTZ: someDatetime,
|
2451
|
+
datetimeString: '2022-01-01 00:00:00.123',
|
2452
|
+
datetimeFullPrecision: fullPrecision.replace('T', ' ').replace('Z', ''),
|
2453
|
+
datetimeWTZString: '2022-01-01 00:00:00.123+00',
|
2454
|
+
interval: '1 day'
|
2455
|
+
}
|
2456
|
+
]);
|
2457
|
+
|
2458
|
+
await db.execute(sql`drop table if exists ${table}`);
|
2459
|
+
});
|
2460
|
+
|
2461
|
+
test('all date and time columns with timezone second case mode date', async (ctx) => {
|
2462
|
+
const { db2: db } = ctx;
|
2463
|
+
|
2464
|
+
const table = pgTable('all_columns', {
|
2465
|
+
id: serial('id').primaryKey(),
|
2466
|
+
timestamp: timestamp('timestamp_string', { mode: 'date', withTimezone: true, precision: 3 }).notNull()
|
2467
|
+
});
|
2468
|
+
|
2469
|
+
await db.execute(sql`drop table if exists ${table}`);
|
2470
|
+
|
2471
|
+
await db.execute(sql`
|
2472
|
+
create table ${table} (
|
2473
|
+
id serial primary key,
|
2474
|
+
timestamp_string timestamp(3) with time zone not null
|
2475
|
+
)
|
2476
|
+
`);
|
2477
|
+
|
2478
|
+
const insertedDate = new Date();
|
2479
|
+
|
2480
|
+
// 1. Insert date as new date
|
2481
|
+
await db.insert(table).values([{ timestamp: insertedDate }]);
|
2482
|
+
|
2483
|
+
// 2, Select as date and check that timezones are the same
|
2484
|
+
// There is no way to check timezone in Date object, as it is always represented internally in UTC
|
2485
|
+
const result = await db.select().from(table);
|
2486
|
+
|
2487
|
+
assert.equal(result, [{ id: 1, timestamp: insertedDate }]);
|
2488
|
+
|
2489
|
+
// 3. Compare both dates
|
2490
|
+
assert.equal(insertedDate.getTime(), result[0]?.timestamp.getTime());
|
2491
|
+
|
2492
|
+
await db.execute(sql`drop table if exists ${table}`);
|
2493
|
+
});
|
2494
|
+
|
2495
|
+
test('all date and time columns with timezone third case mode date', async (ctx) => {
|
2496
|
+
const { db2: db } = ctx;
|
2497
|
+
|
2498
|
+
const table = pgTable('all_columns', {
|
2499
|
+
id: serial('id').primaryKey(),
|
2500
|
+
timestamp: timestamp('timestamp_string', { mode: 'date', withTimezone: true, precision: 3 }).notNull()
|
2501
|
+
});
|
2502
|
+
|
2503
|
+
await db.execute(sql`drop table if exists ${table}`);
|
2504
|
+
|
2505
|
+
await db.execute(sql`
|
2506
|
+
create table ${table} (
|
2507
|
+
id serial primary key,
|
2508
|
+
timestamp_string timestamp(3) with time zone not null
|
2509
|
+
)
|
2510
|
+
`);
|
2511
|
+
|
2512
|
+
const insertedDate = new Date('2022-01-01 20:00:00.123-04'); // used different time zones, internally is still UTC
|
2513
|
+
const insertedDate2 = new Date('2022-01-02 04:00:00.123+04'); // They are both the same date in different time zones
|
2514
|
+
|
2515
|
+
// 1. Insert date as new dates with different time zones
|
2516
|
+
await db.insert(table).values([{ timestamp: insertedDate }, { timestamp: insertedDate2 }]);
|
2517
|
+
|
2518
|
+
// 2, Select and compare both dates
|
2519
|
+
const result = await db.select().from(table);
|
2520
|
+
|
2521
|
+
assert.equal(result[0]?.timestamp.getTime(), result[1]?.timestamp.getTime());
|
2522
|
+
|
2523
|
+
await db.execute(sql`drop table if exists ${table}`);
|
2524
|
+
});
|
2525
|
+
|
2526
|
+
test('all date and time columns without timezone first case mode string', async (ctx) => {
|
2527
|
+
const { db2: db } = ctx;
|
2528
|
+
|
2529
|
+
const table = pgTable('all_columns', {
|
2530
|
+
id: serial('id').primaryKey(),
|
2531
|
+
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).notNull()
|
2532
|
+
});
|
2533
|
+
|
2534
|
+
await db.execute(sql`drop table if exists ${table}`);
|
2535
|
+
|
2536
|
+
await db.execute(sql`
|
2537
|
+
create table ${table} (
|
2538
|
+
id serial primary key,
|
2539
|
+
timestamp_string timestamp(6) not null
|
2540
|
+
)
|
2541
|
+
`);
|
2542
|
+
|
2543
|
+
// 1. Insert date in string format without timezone in it
|
2544
|
+
await db.insert(table).values([{ timestamp: '2022-01-01 02:00:00.123456' }]);
|
2545
|
+
|
2546
|
+
// 2, Select in string format and check that values are the same
|
2547
|
+
const result = await db.select().from(table);
|
2548
|
+
|
2549
|
+
assert.equal(result, [{ id: 1, timestamp: '2022-01-01 02:00:00.123456' }]);
|
2550
|
+
|
2551
|
+
// 3. Select as raw query and check that values are the same
|
2552
|
+
const result2 = await db.execute<{
|
2553
|
+
id: number;
|
2554
|
+
timestamp_string: string;
|
2555
|
+
}>(sql`select * from ${table}`);
|
2556
|
+
|
2557
|
+
assert.equal(result2.rows, [{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
|
2558
|
+
|
2559
|
+
await db.execute(sql`drop table if exists ${table}`);
|
2560
|
+
});
|
2561
|
+
|
2562
|
+
test('all date and time columns without timezone second case mode string', async (ctx) => {
|
2563
|
+
const { db2: db } = ctx;
|
2564
|
+
|
2565
|
+
const table = pgTable('all_columns', {
|
2566
|
+
id: serial('id').primaryKey(),
|
2567
|
+
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).notNull()
|
2568
|
+
});
|
2569
|
+
|
2570
|
+
await db.execute(sql`drop table if exists ${table}`);
|
2571
|
+
|
2572
|
+
await db.execute(sql`
|
2573
|
+
create table ${table} (
|
2574
|
+
id serial primary key,
|
2575
|
+
timestamp_string timestamp(6) not null
|
2576
|
+
)
|
2577
|
+
`);
|
2578
|
+
|
2579
|
+
// 1. Insert date in string format with timezone in it
|
2580
|
+
await db.insert(table).values([{ timestamp: '2022-01-01T02:00:00.123456-02' }]);
|
2581
|
+
|
2582
|
+
// 2, Select as raw query and check that values are the same
|
2583
|
+
const result = await db.execute<{
|
2584
|
+
id: number;
|
2585
|
+
timestamp_string: string;
|
2586
|
+
}>(sql`select * from ${table}`);
|
2587
|
+
|
2588
|
+
assert.equal(result.rows, [{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
|
2589
|
+
|
2590
|
+
await db.execute(sql`drop table if exists ${table}`);
|
2591
|
+
});
|
2592
|
+
|
2593
|
+
test('all date and time columns without timezone third case mode date', async (ctx) => {
|
2594
|
+
const { db2: db } = ctx;
|
2595
|
+
|
2596
|
+
const table = pgTable('all_columns', {
|
2597
|
+
id: serial('id').primaryKey(),
|
2598
|
+
timestamp: timestamp('timestamp_string', { mode: 'date', precision: 3 }).notNull()
|
2599
|
+
});
|
2600
|
+
|
2601
|
+
await db.execute(sql`drop table if exists ${table}`);
|
2602
|
+
|
2603
|
+
await db.execute(sql`
|
2604
|
+
create table ${table} (
|
2605
|
+
id serial primary key,
|
2606
|
+
timestamp_string timestamp(3) not null
|
2607
|
+
)
|
2608
|
+
`);
|
2609
|
+
|
2610
|
+
const insertedDate = new Date('2022-01-01 20:00:00.123+04');
|
2611
|
+
|
2612
|
+
// 1. Insert date as new date
|
2613
|
+
await db.insert(table).values([{ timestamp: insertedDate }]);
|
2614
|
+
|
2615
|
+
// 2, Select as raw query as string
|
2616
|
+
const result = await db.execute<{
|
2617
|
+
id: number;
|
2618
|
+
timestamp_string: string;
|
2619
|
+
}>(sql`select * from ${table}`);
|
2620
|
+
|
2621
|
+
// 3. Compare both dates using orm mapping - Need to add 'Z' to tell JS that it is UTC
|
2622
|
+
assert.equal(new Date(result.rows[0]!.timestamp_string + 'Z').getTime(), insertedDate.getTime());
|
2623
|
+
|
2624
|
+
await db.execute(sql`drop table if exists ${table}`);
|
2625
|
+
});
|
2626
|
+
|
2627
|
+
test('test mode string for timestamp with timezone', async (ctx) => {
|
2628
|
+
const { db2: db } = ctx;
|
2629
|
+
|
2630
|
+
const table = pgTable('all_columns', {
|
2631
|
+
id: serial('id').primaryKey(),
|
2632
|
+
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).notNull()
|
2633
|
+
});
|
2634
|
+
|
2635
|
+
await db.execute(sql`drop table if exists ${table}`);
|
2636
|
+
|
2637
|
+
await db.execute(sql`
|
2638
|
+
create table ${table} (
|
2639
|
+
id serial primary key,
|
2640
|
+
timestamp_string timestamp(6) with time zone not null
|
2641
|
+
)
|
2642
|
+
`);
|
2643
|
+
|
2644
|
+
const timestampString = '2022-01-01 00:00:00.123456-0200';
|
2645
|
+
|
2646
|
+
// 1. Insert date in string format with timezone in it
|
2647
|
+
await db.insert(table).values([{ timestamp: timestampString }]);
|
2648
|
+
|
2649
|
+
// 2. Select date in string format and check that the values are the same
|
2650
|
+
const result = await db.select().from(table);
|
2651
|
+
|
2652
|
+
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
|
2653
|
+
assert.equal(result, [{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
|
2654
|
+
|
2655
|
+
// 3. Select as raw query and checke that values are the same
|
2656
|
+
const result2 = await db.execute<{
|
2657
|
+
id: number;
|
2658
|
+
timestamp_string: string;
|
2659
|
+
}>(sql`select * from ${table}`);
|
2660
|
+
|
2661
|
+
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
|
2662
|
+
assert.equal(result2.rows, [{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
|
2663
|
+
|
2664
|
+
await db.execute(sql`drop table if exists ${table}`);
|
2665
|
+
});
|
2666
|
+
|
2667
|
+
test('test mode date for timestamp with timezone', async (ctx) => {
|
2668
|
+
const { db2: db } = ctx;
|
2669
|
+
|
2670
|
+
const table = pgTable('all_columns', {
|
2671
|
+
id: serial('id').primaryKey(),
|
2672
|
+
timestamp: timestamp('timestamp_string', { mode: 'date', withTimezone: true, precision: 3 }).notNull()
|
2673
|
+
});
|
2674
|
+
|
2675
|
+
await db.execute(sql`drop table if exists ${table}`);
|
2676
|
+
|
2677
|
+
await db.execute(sql`
|
2678
|
+
create table ${table} (
|
2679
|
+
id serial primary key,
|
2680
|
+
timestamp_string timestamp(3) with time zone not null
|
2681
|
+
)
|
2682
|
+
`);
|
2683
|
+
|
2684
|
+
const timestampString = new Date('2022-01-01 00:00:00.456-0200');
|
2685
|
+
|
2686
|
+
// 1. Insert date in string format with timezone in it
|
2687
|
+
await db.insert(table).values([{ timestamp: timestampString }]);
|
2688
|
+
|
2689
|
+
// 2. Select date in string format and check that the values are the same
|
2690
|
+
const result = await db.select().from(table);
|
2691
|
+
|
2692
|
+
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
|
2693
|
+
assert.equal(result, [{ id: 1, timestamp: timestampString }]);
|
2694
|
+
|
2695
|
+
// 3. Select as raw query and checke that values are the same
|
2696
|
+
const result2 = await db.execute<{
|
2697
|
+
id: number;
|
2698
|
+
timestamp_string: string;
|
2699
|
+
}>(sql`select * from ${table}`);
|
2700
|
+
|
2701
|
+
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
|
2702
|
+
assert.equal(result2.rows, [{ id: 1, timestamp_string: '2022-01-01 02:00:00.456+00' }]);
|
2703
|
+
|
2704
|
+
await db.execute(sql`drop table if exists ${table}`);
|
2705
|
+
});
|
2706
|
+
|
2707
|
+
test('test mode string for timestamp with timezone in UTC timezone', async (ctx) => {
|
2708
|
+
const { db2: db } = ctx;
|
2709
|
+
|
2710
|
+
// get current timezone from db
|
2711
|
+
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
|
2712
|
+
|
2713
|
+
// set timezone to UTC
|
2714
|
+
await db.execute(sql`set time zone 'UTC'`);
|
2715
|
+
|
2716
|
+
const table = pgTable('all_columns', {
|
2717
|
+
id: serial('id').primaryKey(),
|
2718
|
+
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).notNull()
|
2719
|
+
});
|
2720
|
+
|
2721
|
+
await db.execute(sql`drop table if exists ${table}`);
|
2722
|
+
|
2723
|
+
await db.execute(sql`
|
2724
|
+
create table ${table} (
|
2725
|
+
id serial primary key,
|
2726
|
+
timestamp_string timestamp(6) with time zone not null
|
2727
|
+
)
|
2728
|
+
`);
|
2729
|
+
|
2730
|
+
const timestampString = '2022-01-01 00:00:00.123456-0200';
|
2731
|
+
|
2732
|
+
// 1. Insert date in string format with timezone in it
|
2733
|
+
await db.insert(table).values([{ timestamp: timestampString }]);
|
2734
|
+
|
2735
|
+
// 2. Select date in string format and check that the values are the same
|
2736
|
+
const result = await db.select().from(table);
|
2737
|
+
|
2738
|
+
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
|
2739
|
+
assert.equal(result, [{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
|
2740
|
+
|
2741
|
+
// 3. Select as raw query and checke that values are the same
|
2742
|
+
const result2 = await db.execute<{
|
2743
|
+
id: number;
|
2744
|
+
timestamp_string: string;
|
2745
|
+
}>(sql`select * from ${table}`);
|
2746
|
+
|
2747
|
+
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
|
2748
|
+
assert.equal(result2.rows, [{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
|
2749
|
+
|
2750
|
+
await db.execute(sql`set time zone '${sql.raw(timezone.rows[0]!.TimeZone)}'`);
|
2751
|
+
|
2752
|
+
await db.execute(sql`drop table if exists ${table}`);
|
2753
|
+
});
|
2754
|
+
|
2755
|
+
test('test mode string for timestamp with timezone in different timezone', async (ctx) => {
|
2756
|
+
const { db2: db } = ctx;
|
2757
|
+
|
2758
|
+
// get current timezone from db
|
2759
|
+
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
|
2760
|
+
|
2761
|
+
// set timezone to HST (UTC - 10)
|
2762
|
+
await db.execute(sql`set time zone 'HST'`);
|
2763
|
+
|
2764
|
+
const table = pgTable('all_columns', {
|
2765
|
+
id: serial('id').primaryKey(),
|
2766
|
+
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).notNull()
|
2767
|
+
});
|
2768
|
+
|
2769
|
+
await db.execute(sql`drop table if exists ${table}`);
|
2770
|
+
|
2771
|
+
await db.execute(sql`
|
2772
|
+
create table ${table} (
|
2773
|
+
id serial primary key,
|
2774
|
+
timestamp_string timestamp(6) with time zone not null
|
2775
|
+
)
|
2776
|
+
`);
|
2777
|
+
|
2778
|
+
const timestampString = '2022-01-01 00:00:00.123456-1000';
|
2779
|
+
|
2780
|
+
// 1. Insert date in string format with timezone in it
|
2781
|
+
await db.insert(table).values([{ timestamp: timestampString }]);
|
2782
|
+
|
2783
|
+
// 2. Select date in string format and check that the values are the same
|
2784
|
+
const result = await db.select().from(table);
|
2785
|
+
|
2786
|
+
assert.equal(result, [{ id: 1, timestamp: '2022-01-01 00:00:00.123456-10' }]);
|
2787
|
+
|
2788
|
+
// 3. Select as raw query and checke that values are the same
|
2789
|
+
const result2 = await db.execute<{
|
2790
|
+
id: number;
|
2791
|
+
timestamp_string: string;
|
2792
|
+
}>(sql`select * from ${table}`);
|
2793
|
+
|
2794
|
+
assert.equal(result2.rows, [{ id: 1, timestamp_string: '2022-01-01 00:00:00.123456-10' }]);
|
2795
|
+
|
2796
|
+
await db.execute(sql`set time zone '${sql.raw(timezone.rows[0]!.TimeZone)}'`);
|
2797
|
+
|
2798
|
+
await db.execute(sql`drop table if exists ${table}`);
|
2799
|
+
});
|
2800
|
+
|
2801
|
+
test('orderBy with aliased column', (ctx) => {
|
2802
|
+
const { db2: db } = ctx;
|
2803
|
+
|
2804
|
+
const query = db
|
2805
|
+
.select({
|
2806
|
+
test: sql`something`.as('test')
|
2807
|
+
})
|
2808
|
+
.from(users2Table)
|
2809
|
+
.orderBy((fields) => fields.test)
|
2810
|
+
.toSQL();
|
2811
|
+
|
2812
|
+
assert.equal(query.sql, 'select something as "test" from "users2" order by "test"');
|
2813
|
+
});
|
2814
|
+
|
2815
|
+
test('select from sql', async (ctx) => {
|
2816
|
+
const { db2: db } = ctx;
|
2817
|
+
|
2818
|
+
const metricEntry = pgTable('metric_entry', {
|
2819
|
+
id: pgUuid('id').notNull(),
|
2820
|
+
createdAt: timestamp('created_at').notNull()
|
2821
|
+
});
|
2822
|
+
|
2823
|
+
await db.execute(sql`drop table if exists ${metricEntry}`);
|
2824
|
+
await db.execute(sql`create table ${metricEntry} (id uuid not null, created_at timestamp not null)`);
|
2825
|
+
|
2826
|
+
const metricId = uuid();
|
2827
|
+
|
2828
|
+
const intervals = db.$with('intervals').as(
|
2829
|
+
db
|
2830
|
+
.select({
|
2831
|
+
startTime: sql<string>`(date'2023-03-01'+ x * '1 day'::interval)`.as('start_time'),
|
2832
|
+
endTime: sql<string>`(date'2023-03-01'+ (x+1) *'1 day'::interval)`.as('end_time')
|
2833
|
+
})
|
2834
|
+
.from(sql`generate_series(0, 29, 1) as t(x)`)
|
2835
|
+
);
|
2836
|
+
|
2837
|
+
ctx
|
2838
|
+
.expect(() =>
|
2839
|
+
db
|
2840
|
+
.with(intervals)
|
2841
|
+
.select({
|
2842
|
+
startTime: intervals.startTime,
|
2843
|
+
endTime: intervals.endTime,
|
2844
|
+
count: sql<number>`count(${metricEntry})`
|
2845
|
+
})
|
2846
|
+
.from(metricEntry)
|
2847
|
+
.rightJoin(
|
2848
|
+
intervals,
|
2849
|
+
and(
|
2850
|
+
eq(metricEntry.id, metricId),
|
2851
|
+
gte(metricEntry.createdAt, intervals.startTime),
|
2852
|
+
lt(metricEntry.createdAt, intervals.endTime)
|
2853
|
+
)
|
2854
|
+
)
|
2855
|
+
.groupBy(intervals.startTime, intervals.endTime)
|
2856
|
+
.orderBy(asc(intervals.startTime))
|
2857
|
+
)
|
2858
|
+
.not.toThrowError();
|
2859
|
+
});
|
2860
|
+
|
2861
|
+
test('timestamp timezone', async (ctx) => {
|
2862
|
+
const { db2: db } = ctx;
|
2863
|
+
|
2864
|
+
const usersTableWithAndWithoutTimezone = pgTable('users_test_with_and_without_timezone', {
|
2865
|
+
id: serial('id').primaryKey(),
|
2866
|
+
name: text('name').notNull(),
|
2867
|
+
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
2868
|
+
updatedAt: timestamp('updated_at', { withTimezone: false }).notNull().defaultNow()
|
2869
|
+
});
|
2870
|
+
|
2871
|
+
await db.execute(sql`drop table if exists ${usersTableWithAndWithoutTimezone}`);
|
2872
|
+
|
2873
|
+
await db.execute(
|
2874
|
+
sql`
|
2875
|
+
create table users_test_with_and_without_timezone (
|
2876
|
+
id serial not null primary key,
|
2877
|
+
name text not null,
|
2878
|
+
created_at timestamptz not null default now(),
|
2879
|
+
updated_at timestamp not null default now()
|
2880
|
+
)
|
2881
|
+
`
|
2882
|
+
);
|
2883
|
+
|
2884
|
+
const date = new Date(Date.parse('2020-01-01T00:00:00+04:00'));
|
2885
|
+
|
2886
|
+
await db.insert(usersTableWithAndWithoutTimezone).values({ name: 'With default times' });
|
2887
|
+
await db.insert(usersTableWithAndWithoutTimezone).values({
|
2888
|
+
name: 'Without default times',
|
2889
|
+
createdAt: date,
|
2890
|
+
updatedAt: date
|
2891
|
+
});
|
2892
|
+
const users = await db.select().from(usersTableWithAndWithoutTimezone);
|
2893
|
+
|
2894
|
+
// check that the timestamps are set correctly for default times
|
2895
|
+
ctx.expect(Math.abs(users[0]!.updatedAt.getTime() - Date.now()) < 2000);
|
2896
|
+
ctx.expect(Math.abs(users[0]!.createdAt.getTime() - Date.now()) < 2000);
|
2897
|
+
|
2898
|
+
// check that the timestamps are set correctly for non default times
|
2899
|
+
ctx.expect(Math.abs(users[1]!.updatedAt.getTime() - date.getTime()) < 2000);
|
2900
|
+
ctx.expect(Math.abs(users[1]!.createdAt.getTime() - date.getTime()) < 2000);
|
2901
|
+
});
|
2902
|
+
|
2903
|
+
test('transaction', async (ctx) => {
|
2904
|
+
const { db2: db } = ctx;
|
2905
|
+
|
2906
|
+
const users = pgTable('users_transactions', {
|
2907
|
+
id: serial('id').primaryKey(),
|
2908
|
+
balance: integer('balance').notNull()
|
2909
|
+
});
|
2910
|
+
const products = pgTable('products_transactions', {
|
2911
|
+
id: serial('id').primaryKey(),
|
2912
|
+
price: integer('price').notNull(),
|
2913
|
+
stock: integer('stock').notNull()
|
2914
|
+
});
|
2915
|
+
|
2916
|
+
await db.execute(sql`drop table if exists ${users}`);
|
2917
|
+
await db.execute(sql`drop table if exists ${products}`);
|
2918
|
+
|
2919
|
+
await db.execute(sql`create table users_transactions (id serial not null primary key, balance integer not null)`);
|
2920
|
+
await db.execute(
|
2921
|
+
sql`create table products_transactions (id serial not null primary key, price integer not null, stock integer not null)`
|
2922
|
+
);
|
2923
|
+
|
2924
|
+
const user = await db
|
2925
|
+
.insert(users)
|
2926
|
+
.values({ balance: 100 })
|
2927
|
+
.returning()
|
2928
|
+
.then((rows) => rows[0]!);
|
2929
|
+
const product = await db
|
2930
|
+
.insert(products)
|
2931
|
+
.values({ price: 10, stock: 10 })
|
2932
|
+
.returning()
|
2933
|
+
.then((rows) => rows[0]!);
|
2934
|
+
|
2935
|
+
await db.transaction(async (tx) => {
|
2936
|
+
await tx
|
2937
|
+
.update(users)
|
2938
|
+
.set({ balance: user.balance - product.price })
|
2939
|
+
.where(eq(users.id, user.id));
|
2940
|
+
await tx
|
2941
|
+
.update(products)
|
2942
|
+
.set({ stock: product.stock - 1 })
|
2943
|
+
.where(eq(products.id, product.id));
|
2944
|
+
});
|
2945
|
+
|
2946
|
+
const result = await db.select().from(users);
|
2947
|
+
|
2948
|
+
assert.equal(result, [{ id: 1, balance: 90 }]);
|
2949
|
+
|
2950
|
+
await db.execute(sql`drop table ${users}`);
|
2951
|
+
await db.execute(sql`drop table ${products}`);
|
2952
|
+
});
|
2953
|
+
|
2954
|
+
test('transaction rollback', async (ctx) => {
|
2955
|
+
const { db2: db } = ctx;
|
2956
|
+
|
2957
|
+
const users = pgTable('users_transactions_rollback', {
|
2958
|
+
id: serial('id').primaryKey(),
|
2959
|
+
balance: integer('balance').notNull()
|
2960
|
+
});
|
2961
|
+
|
2962
|
+
await db.execute(sql`drop table if exists ${users}`);
|
2963
|
+
|
2964
|
+
await db.execute(
|
2965
|
+
sql`create table users_transactions_rollback (id serial not null primary key, balance integer not null)`
|
2966
|
+
);
|
2967
|
+
|
2968
|
+
ctx
|
2969
|
+
.expect(
|
2970
|
+
async () =>
|
2971
|
+
await db.transaction(async (tx) => {
|
2972
|
+
await tx.insert(users).values({ balance: 100 });
|
2973
|
+
tx.rollback();
|
2974
|
+
})
|
2975
|
+
)
|
2976
|
+
.toThrowError(TransactionRollbackError);
|
2977
|
+
|
2978
|
+
const result = await db.select().from(users);
|
2979
|
+
|
2980
|
+
assert.equal(result, []);
|
2981
|
+
|
2982
|
+
await db.execute(sql`drop table ${users}`);
|
2983
|
+
});
|
2984
|
+
|
2985
|
+
test('nested transaction', async (ctx) => {
|
2986
|
+
const { db2: db } = ctx;
|
2987
|
+
|
2988
|
+
const users = pgTable('users_nested_transactions', {
|
2989
|
+
id: serial('id').primaryKey(),
|
2990
|
+
balance: integer('balance').notNull()
|
2991
|
+
});
|
2992
|
+
|
2993
|
+
await db.execute(sql`drop table if exists ${users}`);
|
2994
|
+
|
2995
|
+
await db.execute(
|
2996
|
+
sql`create table users_nested_transactions (id serial not null primary key, balance integer not null)`
|
2997
|
+
);
|
2998
|
+
|
2999
|
+
await db.transaction(async (tx) => {
|
3000
|
+
await tx.insert(users).values({ balance: 100 });
|
3001
|
+
|
3002
|
+
await tx.transaction(async (tx) => {
|
3003
|
+
await tx.update(users).set({ balance: 200 });
|
3004
|
+
});
|
3005
|
+
});
|
3006
|
+
|
3007
|
+
const result = await db.select().from(users);
|
3008
|
+
|
3009
|
+
assert.equal(result, [{ id: 1, balance: 200 }]);
|
3010
|
+
|
3011
|
+
await db.execute(sql`drop table ${users}`);
|
3012
|
+
});
|
3013
|
+
|
3014
|
+
test('nested transaction rollback', async (ctx) => {
|
3015
|
+
const { db2: db } = ctx;
|
3016
|
+
|
3017
|
+
const users = pgTable('users_nested_transactions_rollback', {
|
3018
|
+
id: serial('id').primaryKey(),
|
3019
|
+
balance: integer('balance').notNull()
|
3020
|
+
});
|
3021
|
+
|
3022
|
+
await db.execute(sql`drop table if exists ${users}`);
|
3023
|
+
|
3024
|
+
await db.execute(
|
3025
|
+
sql`create table users_nested_transactions_rollback (id serial not null primary key, balance integer not null)`
|
3026
|
+
);
|
3027
|
+
|
3028
|
+
await db.transaction(async (tx) => {
|
3029
|
+
await tx.insert(users).values({ balance: 100 });
|
3030
|
+
|
3031
|
+
ctx
|
3032
|
+
.expect(
|
3033
|
+
async () =>
|
3034
|
+
await tx.transaction(async (tx) => {
|
3035
|
+
await tx.update(users).set({ balance: 200 });
|
3036
|
+
tx.rollback();
|
3037
|
+
})
|
3038
|
+
)
|
3039
|
+
.toThrowError(TransactionRollbackError);
|
3040
|
+
});
|
3041
|
+
|
3042
|
+
const result = await db.select().from(users);
|
3043
|
+
|
3044
|
+
assert.equal(result, [{ id: 1, balance: 100 }]);
|
3045
|
+
|
3046
|
+
await db.execute(sql`drop table ${users}`);
|
3047
|
+
});
|
3048
|
+
|
3049
|
+
test('join subquery with join', async (ctx) => {
|
3050
|
+
const { db2: db } = ctx;
|
3051
|
+
|
3052
|
+
const internalStaff = pgTable('internal_staff', {
|
3053
|
+
userId: integer('user_id').notNull()
|
3054
|
+
});
|
3055
|
+
|
3056
|
+
const customUser = pgTable('custom_user', {
|
3057
|
+
id: integer('id').notNull()
|
3058
|
+
});
|
3059
|
+
|
3060
|
+
const ticket = pgTable('ticket', {
|
3061
|
+
staffId: integer('staff_id').notNull()
|
3062
|
+
});
|
3063
|
+
|
3064
|
+
await db.execute(sql`drop table if exists ${internalStaff}`);
|
3065
|
+
await db.execute(sql`drop table if exists ${customUser}`);
|
3066
|
+
await db.execute(sql`drop table if exists ${ticket}`);
|
3067
|
+
|
3068
|
+
await db.execute(sql`create table internal_staff (user_id integer not null)`);
|
3069
|
+
await db.execute(sql`create table custom_user (id integer not null)`);
|
3070
|
+
await db.execute(sql`create table ticket (staff_id integer not null)`);
|
3071
|
+
|
3072
|
+
await db.insert(internalStaff).values({ userId: 1 });
|
3073
|
+
await db.insert(customUser).values({ id: 1 });
|
3074
|
+
await db.insert(ticket).values({ staffId: 1 });
|
3075
|
+
|
3076
|
+
const subq = db
|
3077
|
+
.select()
|
3078
|
+
.from(internalStaff)
|
3079
|
+
.leftJoin(customUser, eq(internalStaff.userId, customUser.id))
|
3080
|
+
.as('internal_staff');
|
3081
|
+
|
3082
|
+
const mainQuery = await db.select().from(ticket).leftJoin(subq, eq(subq.internal_staff.userId, ticket.staffId));
|
3083
|
+
|
3084
|
+
assert.equal(mainQuery, [
|
3085
|
+
{
|
3086
|
+
ticket: { staffId: 1 },
|
3087
|
+
internal_staff: {
|
3088
|
+
internal_staff: { userId: 1 },
|
3089
|
+
custom_user: { id: 1 }
|
3090
|
+
}
|
3091
|
+
}
|
3092
|
+
]);
|
3093
|
+
|
3094
|
+
await db.execute(sql`drop table ${internalStaff}`);
|
3095
|
+
await db.execute(sql`drop table ${customUser}`);
|
3096
|
+
await db.execute(sql`drop table ${ticket}`);
|
3097
|
+
});
|
3098
|
+
|
3099
|
+
test('subquery with view', async (ctx) => {
|
3100
|
+
const { db2: db } = ctx;
|
3101
|
+
|
3102
|
+
const users = pgTable('users_subquery_view', {
|
3103
|
+
id: serial('id').primaryKey(),
|
3104
|
+
name: text('name').notNull(),
|
3105
|
+
cityId: integer('city_id').notNull()
|
3106
|
+
});
|
3107
|
+
|
3108
|
+
const newYorkers = pgView('new_yorkers').as((qb) => qb.select().from(users).where(eq(users.cityId, 1)));
|
3109
|
+
|
3110
|
+
await db.execute(sql`drop table if exists ${users}`);
|
3111
|
+
await db.execute(sql`drop view if exists ${newYorkers}`);
|
3112
|
+
|
3113
|
+
await db.execute(
|
3114
|
+
sql`create table ${users} (id serial not null primary key, name text not null, city_id integer not null)`
|
3115
|
+
);
|
3116
|
+
await db.execute(sql`create view ${newYorkers} as select * from ${users} where city_id = 1`);
|
3117
|
+
|
3118
|
+
await db.insert(users).values([
|
3119
|
+
{ name: 'John', cityId: 1 },
|
3120
|
+
{ name: 'Jane', cityId: 2 },
|
3121
|
+
{ name: 'Jack', cityId: 1 },
|
3122
|
+
{ name: 'Jill', cityId: 2 }
|
3123
|
+
]);
|
3124
|
+
|
3125
|
+
const sq = db.$with('sq').as(db.select().from(newYorkers));
|
3126
|
+
const result = await db.with(sq).select().from(sq);
|
3127
|
+
|
3128
|
+
assert.equal(result, [
|
3129
|
+
{ id: 1, name: 'John', cityId: 1 },
|
3130
|
+
{ id: 3, name: 'Jack', cityId: 1 }
|
3131
|
+
]);
|
3132
|
+
|
3133
|
+
await db.execute(sql`drop view ${newYorkers}`);
|
3134
|
+
await db.execute(sql`drop table ${users}`);
|
3135
|
+
});
|
3136
|
+
|
3137
|
+
test('join view as subquery', async (ctx) => {
|
3138
|
+
const { db2: db } = ctx;
|
3139
|
+
|
3140
|
+
const users = pgTable('users_join_view', {
|
3141
|
+
id: serial('id').primaryKey(),
|
3142
|
+
name: text('name').notNull(),
|
3143
|
+
cityId: integer('city_id').notNull()
|
3144
|
+
});
|
3145
|
+
|
3146
|
+
const newYorkers = pgView('new_yorkers').as((qb) => qb.select().from(users).where(eq(users.cityId, 1)));
|
3147
|
+
|
3148
|
+
await db.execute(sql`drop table if exists ${users}`);
|
3149
|
+
await db.execute(sql`drop view if exists ${newYorkers}`);
|
3150
|
+
|
3151
|
+
await db.execute(
|
3152
|
+
sql`create table ${users} (id serial not null primary key, name text not null, city_id integer not null)`
|
3153
|
+
);
|
3154
|
+
await db.execute(sql`create view ${newYorkers} as select * from ${users} where city_id = 1`);
|
3155
|
+
|
3156
|
+
await db.insert(users).values([
|
3157
|
+
{ name: 'John', cityId: 1 },
|
3158
|
+
{ name: 'Jane', cityId: 2 },
|
3159
|
+
{ name: 'Jack', cityId: 1 },
|
3160
|
+
{ name: 'Jill', cityId: 2 }
|
3161
|
+
]);
|
3162
|
+
|
3163
|
+
const sq = db.select().from(newYorkers).as('new_yorkers_sq');
|
3164
|
+
|
3165
|
+
const result = await db.select().from(users).leftJoin(sq, eq(users.id, sq.id));
|
3166
|
+
|
3167
|
+
assert.equal(result, [
|
3168
|
+
{
|
3169
|
+
users_join_view: { id: 1, name: 'John', cityId: 1 },
|
3170
|
+
new_yorkers_sq: { id: 1, name: 'John', cityId: 1 }
|
3171
|
+
},
|
3172
|
+
{
|
3173
|
+
users_join_view: { id: 2, name: 'Jane', cityId: 2 },
|
3174
|
+
new_yorkers_sq: null
|
3175
|
+
},
|
3176
|
+
{
|
3177
|
+
users_join_view: { id: 3, name: 'Jack', cityId: 1 },
|
3178
|
+
new_yorkers_sq: { id: 3, name: 'Jack', cityId: 1 }
|
3179
|
+
},
|
3180
|
+
{
|
3181
|
+
users_join_view: { id: 4, name: 'Jill', cityId: 2 },
|
3182
|
+
new_yorkers_sq: null
|
3183
|
+
}
|
3184
|
+
]);
|
3185
|
+
|
3186
|
+
await db.execute(sql`drop view ${newYorkers}`);
|
3187
|
+
await db.execute(sql`drop table ${users}`);
|
3188
|
+
});
|
3189
|
+
|
3190
|
+
test('table selection with single table', async (ctx) => {
|
3191
|
+
const { db2: db } = ctx;
|
3192
|
+
|
3193
|
+
const users = pgTable('users', {
|
3194
|
+
id: serial('id').primaryKey(),
|
3195
|
+
name: text('name').notNull(),
|
3196
|
+
cityId: integer('city_id').notNull()
|
3197
|
+
});
|
3198
|
+
|
3199
|
+
await db.execute(sql`drop table if exists ${users}`);
|
3200
|
+
|
3201
|
+
await db.execute(
|
3202
|
+
sql`create table ${users} (id serial not null primary key, name text not null, city_id integer not null)`
|
3203
|
+
);
|
3204
|
+
|
3205
|
+
await db.insert(users).values({ name: 'John', cityId: 1 });
|
3206
|
+
|
3207
|
+
const result = await db.select({ users }).from(users);
|
3208
|
+
|
3209
|
+
assert.equal(result, [{ users: { id: 1, name: 'John', cityId: 1 } }]);
|
3210
|
+
|
3211
|
+
await db.execute(sql`drop table ${users}`);
|
3212
|
+
});
|
3213
|
+
|
3214
|
+
test('set null to jsonb field', async (ctx) => {
|
3215
|
+
const { db2: db } = ctx;
|
3216
|
+
|
3217
|
+
const users = pgTable('users', {
|
3218
|
+
id: serial('id').primaryKey(),
|
3219
|
+
jsonb: jsonb('jsonb')
|
3220
|
+
});
|
3221
|
+
|
3222
|
+
await db.execute(sql`drop table if exists ${users}`);
|
3223
|
+
|
3224
|
+
await db.execute(sql`create table ${users} (id serial not null primary key, jsonb jsonb)`);
|
3225
|
+
|
3226
|
+
const result = await db.insert(users).values({ jsonb: null }).returning();
|
3227
|
+
|
3228
|
+
assert.equal(result, [{ id: 1, jsonb: null }]);
|
3229
|
+
|
3230
|
+
await db.execute(sql`drop table ${users}`);
|
3231
|
+
});
|
3232
|
+
|
3233
|
+
test('insert undefined', async (ctx) => {
|
3234
|
+
const { db2: db } = ctx;
|
3235
|
+
|
3236
|
+
const users = pgTable('users', {
|
3237
|
+
id: serial('id').primaryKey(),
|
3238
|
+
name: text('name')
|
3239
|
+
});
|
3240
|
+
|
3241
|
+
await db.execute(sql`drop table if exists ${users}`);
|
3242
|
+
|
3243
|
+
await db.execute(sql`create table ${users} (id serial not null primary key, name text)`);
|
3244
|
+
|
3245
|
+
ctx.expect(async () => await db.insert(users).values({ name: undefined })).not.toThrowError();
|
3246
|
+
|
3247
|
+
await db.execute(sql`drop table ${users}`);
|
3248
|
+
});
|
3249
|
+
|
3250
|
+
test('update undefined', async (ctx) => {
|
3251
|
+
const { db2: db } = ctx;
|
3252
|
+
|
3253
|
+
const users = pgTable('users', {
|
3254
|
+
id: serial('id').primaryKey(),
|
3255
|
+
name: text('name')
|
3256
|
+
});
|
3257
|
+
|
3258
|
+
await db.execute(sql`drop table if exists ${users}`);
|
3259
|
+
|
3260
|
+
await db.execute(sql`create table ${users} (id serial not null primary key, name text)`);
|
3261
|
+
|
3262
|
+
ctx.expect(async () => await db.update(users).set({ name: undefined })).toThrowError();
|
3263
|
+
ctx.expect(async () => await db.update(users).set({ id: 1, name: undefined })).not.toThrowError();
|
3264
|
+
|
3265
|
+
await db.execute(sql`drop table ${users}`);
|
3266
|
+
});
|
3267
|
+
|
3268
|
+
test('array operators', async (ctx) => {
|
3269
|
+
const { db2: db } = ctx;
|
3270
|
+
|
3271
|
+
const posts = pgTable('posts', {
|
3272
|
+
id: serial('id').primaryKey(),
|
3273
|
+
tags: text('tags').array()
|
3274
|
+
});
|
3275
|
+
|
3276
|
+
await db.execute(sql`drop table if exists ${posts}`);
|
3277
|
+
|
3278
|
+
await db.execute(sql`create table ${posts} (id serial primary key, tags text[])`);
|
3279
|
+
|
3280
|
+
await db.insert(posts).values([
|
3281
|
+
{
|
3282
|
+
tags: ['ORM']
|
3283
|
+
},
|
3284
|
+
{
|
3285
|
+
tags: ['Typescript']
|
3286
|
+
},
|
3287
|
+
{
|
3288
|
+
tags: ['Typescript', 'ORM']
|
3289
|
+
},
|
3290
|
+
{
|
3291
|
+
tags: ['Typescript', 'Frontend', 'React']
|
3292
|
+
},
|
3293
|
+
{
|
3294
|
+
tags: ['Typescript', 'ORM', 'Database', 'Postgres']
|
3295
|
+
},
|
3296
|
+
{
|
3297
|
+
tags: ['Java', 'Spring', 'OOP']
|
3298
|
+
}
|
3299
|
+
]);
|
3300
|
+
|
3301
|
+
const contains = await db
|
3302
|
+
.select({ id: posts.id })
|
3303
|
+
.from(posts)
|
3304
|
+
.where(arrayContains(posts.tags, ['Typescript', 'ORM']));
|
3305
|
+
const contained = await db
|
3306
|
+
.select({ id: posts.id })
|
3307
|
+
.from(posts)
|
3308
|
+
.where(arrayContained(posts.tags, ['Typescript', 'ORM']));
|
3309
|
+
const overlaps = await db
|
3310
|
+
.select({ id: posts.id })
|
3311
|
+
.from(posts)
|
3312
|
+
.where(arrayOverlaps(posts.tags, ['Typescript', 'ORM']));
|
3313
|
+
const withSubQuery = await db
|
3314
|
+
.select({ id: posts.id })
|
3315
|
+
.from(posts)
|
3316
|
+
.where(arrayContains(posts.tags, db.select({ tags: posts.tags }).from(posts).where(eq(posts.id, 1))));
|
3317
|
+
|
3318
|
+
assert.equal(contains, [{ id: 3 }, { id: 5 }]);
|
3319
|
+
assert.equal(contained, [{ id: 1 }, { id: 2 }, { id: 3 }]);
|
3320
|
+
assert.equal(overlaps, [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }]);
|
3321
|
+
assert.equal(withSubQuery, [{ id: 1 }, { id: 3 }, { id: 5 }]);
|
3322
|
+
});
|
3323
|
+
|
3324
|
+
test('set operations (union) from query builder with subquery', async (ctx) => {
|
3325
|
+
const { db2: db } = ctx;
|
3326
|
+
|
3327
|
+
await setupSetOperationTest(db);
|
3328
|
+
|
3329
|
+
const sq = db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).as('sq');
|
3330
|
+
|
3331
|
+
const result = await db
|
3332
|
+
.select({ id: cities2Table.id, name: citiesTable.name })
|
3333
|
+
.from(cities2Table)
|
3334
|
+
.union(db.select().from(sq))
|
3335
|
+
.orderBy(asc(sql`name`))
|
3336
|
+
.limit(2)
|
3337
|
+
.offset(1);
|
3338
|
+
|
3339
|
+
ctx.expect(result.length === 2);
|
3340
|
+
|
3341
|
+
assert.equal(result, [
|
3342
|
+
{ id: 3, name: 'Jack' },
|
3343
|
+
{ id: 2, name: 'Jane' }
|
3344
|
+
]);
|
3345
|
+
|
3346
|
+
ctx
|
3347
|
+
.expect(() => {
|
3348
|
+
db.select({ id: cities2Table.id, name: citiesTable.name, name2: users2Table.name })
|
3349
|
+
.from(cities2Table)
|
3350
|
+
.union(
|
3351
|
+
// @ts-expect-error
|
3352
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table)
|
3353
|
+
)
|
3354
|
+
.orderBy(asc(sql`name`));
|
3355
|
+
})
|
3356
|
+
.toThrowError();
|
3357
|
+
});
|
3358
|
+
|
3359
|
+
test('set operations (union) as function', async (ctx) => {
|
3360
|
+
const { db2: db } = ctx;
|
3361
|
+
|
3362
|
+
await setupSetOperationTest(db);
|
3363
|
+
|
3364
|
+
const result = await union(
|
3365
|
+
db.select({ id: cities2Table.id, name: citiesTable.name }).from(cities2Table).where(eq(citiesTable.id, 1)),
|
3366
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1)),
|
3367
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1))
|
3368
|
+
)
|
3369
|
+
.orderBy(asc(sql`name`))
|
3370
|
+
.limit(1)
|
3371
|
+
.offset(1);
|
3372
|
+
|
3373
|
+
ctx.expect(result.length === 1);
|
3374
|
+
|
3375
|
+
assert.equal(result, [{ id: 1, name: 'New York' }]);
|
3376
|
+
|
3377
|
+
ctx
|
3378
|
+
.expect(() => {
|
3379
|
+
union(
|
3380
|
+
db.select({ name: citiesTable.name, id: cities2Table.id }).from(cities2Table).where(eq(citiesTable.id, 1)),
|
3381
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1)),
|
3382
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1))
|
3383
|
+
).orderBy(asc(sql`name`));
|
3384
|
+
})
|
3385
|
+
.toThrowError();
|
3386
|
+
});
|
3387
|
+
|
3388
|
+
test('set operations (union all) from query builder', async (ctx) => {
|
3389
|
+
const { db2: db } = ctx;
|
3390
|
+
|
3391
|
+
await setupSetOperationTest(db);
|
3392
|
+
|
3393
|
+
const result = await db
|
3394
|
+
.select({ id: cities2Table.id, name: citiesTable.name })
|
3395
|
+
.from(cities2Table)
|
3396
|
+
.limit(2)
|
3397
|
+
.unionAll(db.select({ id: cities2Table.id, name: citiesTable.name }).from(cities2Table).limit(2))
|
3398
|
+
.orderBy(asc(sql`id`));
|
3399
|
+
|
3400
|
+
ctx.expect(result.length === 4);
|
3401
|
+
|
3402
|
+
assert.equal(result, [
|
3403
|
+
{ id: 1, name: 'New York' },
|
3404
|
+
{ id: 1, name: 'New York' },
|
3405
|
+
{ id: 2, name: 'London' },
|
3406
|
+
{ id: 2, name: 'London' }
|
3407
|
+
]);
|
3408
|
+
|
3409
|
+
ctx
|
3410
|
+
.expect(() => {
|
3411
|
+
db.select({ id: cities2Table.id, name: citiesTable.name })
|
3412
|
+
.from(cities2Table)
|
3413
|
+
.limit(2)
|
3414
|
+
.unionAll(db.select({ name: citiesTable.name, id: cities2Table.id }).from(cities2Table).limit(2))
|
3415
|
+
.orderBy(asc(sql`id`));
|
3416
|
+
})
|
3417
|
+
.toThrowError();
|
3418
|
+
});
|
3419
|
+
|
3420
|
+
test('set operations (union all) as function', async (ctx) => {
|
3421
|
+
const { db2: db } = ctx;
|
3422
|
+
|
3423
|
+
await setupSetOperationTest(db);
|
3424
|
+
|
3425
|
+
const result = await unionAll(
|
3426
|
+
db.select({ id: cities2Table.id, name: citiesTable.name }).from(cities2Table).where(eq(citiesTable.id, 1)),
|
3427
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1)),
|
3428
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1))
|
3429
|
+
);
|
3430
|
+
|
3431
|
+
ctx.expect(result.length === 3);
|
3432
|
+
|
3433
|
+
assert.equal(result, [
|
3434
|
+
{ id: 1, name: 'New York' },
|
3435
|
+
{ id: 1, name: 'John' },
|
3436
|
+
{ id: 1, name: 'John' }
|
3437
|
+
]);
|
3438
|
+
|
3439
|
+
ctx
|
3440
|
+
.expect(() => {
|
3441
|
+
unionAll(
|
3442
|
+
db.select({ id: cities2Table.id, name: citiesTable.name }).from(cities2Table).where(eq(citiesTable.id, 1)),
|
3443
|
+
db.select({ name: users2Table.name, id: users2Table.id }).from(users2Table).where(eq(users2Table.id, 1)),
|
3444
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1))
|
3445
|
+
);
|
3446
|
+
})
|
3447
|
+
.toThrowError();
|
3448
|
+
});
|
3449
|
+
|
3450
|
+
test('set operations (intersect) from query builder', async (ctx) => {
|
3451
|
+
const { db2: db } = ctx;
|
3452
|
+
|
3453
|
+
await setupSetOperationTest(db);
|
3454
|
+
|
3455
|
+
const result = await db
|
3456
|
+
.select({ id: cities2Table.id, name: citiesTable.name })
|
3457
|
+
.from(cities2Table)
|
3458
|
+
.intersect(
|
3459
|
+
db.select({ id: cities2Table.id, name: citiesTable.name }).from(cities2Table).where(gt(citiesTable.id, 1))
|
3460
|
+
)
|
3461
|
+
.orderBy(asc(sql`name`));
|
3462
|
+
|
3463
|
+
ctx.expect(result.length === 2);
|
3464
|
+
|
3465
|
+
assert.equal(result, [
|
3466
|
+
{ id: 2, name: 'London' },
|
3467
|
+
{ id: 3, name: 'Tampa' }
|
3468
|
+
]);
|
3469
|
+
|
3470
|
+
ctx
|
3471
|
+
.expect(() => {
|
3472
|
+
db.select({ id: cities2Table.id, name: citiesTable.name })
|
3473
|
+
.from(cities2Table)
|
3474
|
+
.intersect(
|
3475
|
+
// @ts-expect-error
|
3476
|
+
db
|
3477
|
+
.select({ id: cities2Table.id, name: citiesTable.name, id2: cities2Table.id })
|
3478
|
+
.from(cities2Table)
|
3479
|
+
.where(gt(citiesTable.id, 1))
|
3480
|
+
)
|
3481
|
+
.orderBy(asc(sql`name`));
|
3482
|
+
})
|
3483
|
+
.toThrowError();
|
3484
|
+
});
|
3485
|
+
|
3486
|
+
test('set operations (intersect) as function', async (ctx) => {
|
3487
|
+
const { db2: db } = ctx;
|
3488
|
+
|
3489
|
+
await setupSetOperationTest(db);
|
3490
|
+
|
3491
|
+
const result = await intersect(
|
3492
|
+
db.select({ id: cities2Table.id, name: citiesTable.name }).from(cities2Table).where(eq(citiesTable.id, 1)),
|
3493
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1)),
|
3494
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1))
|
3495
|
+
);
|
3496
|
+
|
3497
|
+
ctx.expect(result.length === 0);
|
3498
|
+
|
3499
|
+
assert.equal(result, []);
|
3500
|
+
|
3501
|
+
ctx
|
3502
|
+
.expect(() => {
|
3503
|
+
intersect(
|
3504
|
+
db.select({ id: cities2Table.id, name: citiesTable.name }).from(cities2Table).where(eq(citiesTable.id, 1)),
|
3505
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1)),
|
3506
|
+
db.select({ name: users2Table.name, id: users2Table.id }).from(users2Table).where(eq(users2Table.id, 1))
|
3507
|
+
);
|
3508
|
+
})
|
3509
|
+
.toThrowError();
|
3510
|
+
});
|
3511
|
+
|
3512
|
+
test('set operations (intersect all) from query builder', async (ctx) => {
|
3513
|
+
const { db2: db } = ctx;
|
3514
|
+
|
3515
|
+
await setupSetOperationTest(db);
|
3516
|
+
|
3517
|
+
const result = await db
|
3518
|
+
.select({ id: cities2Table.id, name: citiesTable.name })
|
3519
|
+
.from(cities2Table)
|
3520
|
+
.limit(2)
|
3521
|
+
.intersectAll(db.select({ id: cities2Table.id, name: citiesTable.name }).from(cities2Table).limit(2))
|
3522
|
+
.orderBy(asc(sql`id`));
|
3523
|
+
|
3524
|
+
ctx.expect(result.length === 2);
|
3525
|
+
|
3526
|
+
assert.equal(result, [
|
3527
|
+
{ id: 1, name: 'New York' },
|
3528
|
+
{ id: 2, name: 'London' }
|
3529
|
+
]);
|
3530
|
+
|
3531
|
+
ctx
|
3532
|
+
.expect(() => {
|
3533
|
+
db.select({ id: cities2Table.id, name: citiesTable.name })
|
3534
|
+
.from(cities2Table)
|
3535
|
+
.limit(2)
|
3536
|
+
.intersectAll(db.select({ name: users2Table.name, id: users2Table.id }).from(cities2Table).limit(2))
|
3537
|
+
.orderBy(asc(sql`id`));
|
3538
|
+
})
|
3539
|
+
.toThrowError();
|
3540
|
+
});
|
3541
|
+
|
3542
|
+
test('set operations (intersect all) as function', async (ctx) => {
|
3543
|
+
const { db2: db } = ctx;
|
3544
|
+
|
3545
|
+
await setupSetOperationTest(db);
|
3546
|
+
|
3547
|
+
const result = await intersectAll(
|
3548
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1)),
|
3549
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1)),
|
3550
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1))
|
3551
|
+
);
|
3552
|
+
|
3553
|
+
ctx.expect(result.length === 1);
|
3554
|
+
|
3555
|
+
assert.equal(result, [{ id: 1, name: 'John' }]);
|
3556
|
+
|
3557
|
+
ctx
|
3558
|
+
.expect(() => {
|
3559
|
+
intersectAll(
|
3560
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1)),
|
3561
|
+
db.select({ name: users2Table.name, id: users2Table.id }).from(users2Table).where(eq(users2Table.id, 1)),
|
3562
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1))
|
3563
|
+
);
|
3564
|
+
})
|
3565
|
+
.toThrowError();
|
3566
|
+
});
|
3567
|
+
|
3568
|
+
test('set operations (except) from query builder', async (ctx) => {
|
3569
|
+
const { db2: db } = ctx;
|
3570
|
+
|
3571
|
+
await setupSetOperationTest(db);
|
3572
|
+
|
3573
|
+
const result = await db
|
3574
|
+
.select()
|
3575
|
+
.from(cities2Table)
|
3576
|
+
.except(db.select().from(cities2Table).where(gt(citiesTable.id, 1)));
|
3577
|
+
|
3578
|
+
ctx.expect(result.length === 1);
|
3579
|
+
|
3580
|
+
assert.equal(result, [{ id: 1, name: 'New York' }]);
|
3581
|
+
|
3582
|
+
ctx
|
3583
|
+
.expect(() => {
|
3584
|
+
db.select()
|
3585
|
+
.from(cities2Table)
|
3586
|
+
.except(
|
3587
|
+
db.select({ name: users2Table.name, id: users2Table.id }).from(cities2Table).where(gt(citiesTable.id, 1))
|
3588
|
+
);
|
3589
|
+
})
|
3590
|
+
.toThrowError();
|
3591
|
+
});
|
3592
|
+
|
3593
|
+
test('set operations (except) as function', async (ctx) => {
|
3594
|
+
const { db2: db } = ctx;
|
3595
|
+
|
3596
|
+
await setupSetOperationTest(db);
|
3597
|
+
|
3598
|
+
const result = await except(
|
3599
|
+
db.select({ id: cities2Table.id, name: citiesTable.name }).from(cities2Table),
|
3600
|
+
db.select({ id: cities2Table.id, name: citiesTable.name }).from(cities2Table).where(eq(citiesTable.id, 1)),
|
3601
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1))
|
3602
|
+
).orderBy(asc(sql`id`));
|
3603
|
+
|
3604
|
+
ctx.expect(result.length === 2);
|
3605
|
+
|
3606
|
+
assert.equal(result, [
|
3607
|
+
{ id: 2, name: 'London' },
|
3608
|
+
{ id: 3, name: 'Tampa' }
|
3609
|
+
]);
|
3610
|
+
|
3611
|
+
ctx
|
3612
|
+
.expect(() => {
|
3613
|
+
except(
|
3614
|
+
db.select({ id: cities2Table.id, name: citiesTable.name }).from(cities2Table),
|
3615
|
+
db.select({ name: users2Table.name, id: users2Table.id }).from(cities2Table).where(eq(citiesTable.id, 1)),
|
3616
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1))
|
3617
|
+
).orderBy(asc(sql`id`));
|
3618
|
+
})
|
3619
|
+
.toThrowError();
|
3620
|
+
});
|
3621
|
+
|
3622
|
+
test('set operations (except all) from query builder', async (ctx) => {
|
3623
|
+
const { db2: db } = ctx;
|
3624
|
+
|
3625
|
+
await setupSetOperationTest(db);
|
3626
|
+
|
3627
|
+
const result = await db
|
3628
|
+
.select()
|
3629
|
+
.from(cities2Table)
|
3630
|
+
.exceptAll(
|
3631
|
+
db.select({ id: cities2Table.id, name: citiesTable.name }).from(cities2Table).where(eq(citiesTable.id, 1))
|
3632
|
+
)
|
3633
|
+
.orderBy(asc(sql`id`));
|
3634
|
+
|
3635
|
+
ctx.expect(result.length === 2);
|
3636
|
+
|
3637
|
+
assert.equal(result, [
|
3638
|
+
{ id: 2, name: 'London' },
|
3639
|
+
{ id: 3, name: 'Tampa' }
|
3640
|
+
]);
|
3641
|
+
|
3642
|
+
ctx
|
3643
|
+
.expect(() => {
|
3644
|
+
db.select({ name: cities2Table.name, id: cities2Table.id })
|
3645
|
+
.from(cities2Table)
|
3646
|
+
.exceptAll(
|
3647
|
+
db.select({ id: cities2Table.id, name: citiesTable.name }).from(cities2Table).where(eq(citiesTable.id, 1))
|
3648
|
+
)
|
3649
|
+
.orderBy(asc(sql`id`));
|
3650
|
+
})
|
3651
|
+
.toThrowError();
|
3652
|
+
});
|
3653
|
+
|
3654
|
+
test('set operations (except all) as function', async (ctx) => {
|
3655
|
+
const { db2: db } = ctx;
|
3656
|
+
|
3657
|
+
await setupSetOperationTest(db);
|
3658
|
+
|
3659
|
+
const result = await exceptAll(
|
3660
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table),
|
3661
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(gt(users2Table.id, 7)),
|
3662
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1))
|
3663
|
+
)
|
3664
|
+
.orderBy(asc(sql`id`))
|
3665
|
+
.limit(5)
|
3666
|
+
.offset(2);
|
3667
|
+
|
3668
|
+
ctx.expect(result.length === 4);
|
3669
|
+
|
3670
|
+
assert.equal(result, [
|
3671
|
+
{ id: 4, name: 'Peter' },
|
3672
|
+
{ id: 5, name: 'Ben' },
|
3673
|
+
{ id: 6, name: 'Jill' },
|
3674
|
+
{ id: 7, name: 'Mary' }
|
3675
|
+
]);
|
3676
|
+
|
3677
|
+
ctx
|
3678
|
+
.expect(() => {
|
3679
|
+
exceptAll(
|
3680
|
+
db.select({ name: users2Table.name, id: users2Table.id }).from(users2Table),
|
3681
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(gt(users2Table.id, 7)),
|
3682
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1))
|
3683
|
+
).orderBy(asc(sql`id`));
|
3684
|
+
})
|
3685
|
+
.toThrowError();
|
3686
|
+
});
|
3687
|
+
|
3688
|
+
test('set operations (mixed) from query builder with subquery', async (ctx) => {
|
3689
|
+
const { db2: db } = ctx;
|
3690
|
+
|
3691
|
+
await setupSetOperationTest(db);
|
3692
|
+
const sq = db.select().from(cities2Table).where(gt(citiesTable.id, 1)).as('sq');
|
3693
|
+
|
3694
|
+
const result = await db
|
3695
|
+
.select()
|
3696
|
+
.from(cities2Table)
|
3697
|
+
.except(({ unionAll }) =>
|
3698
|
+
unionAll(db.select().from(sq), db.select().from(cities2Table).where(eq(citiesTable.id, 2)))
|
3699
|
+
);
|
3700
|
+
|
3701
|
+
ctx.expect(result.length === 1);
|
3702
|
+
|
3703
|
+
assert.equal(result, [{ id: 1, name: 'New York' }]);
|
3704
|
+
|
3705
|
+
ctx
|
3706
|
+
.expect(() => {
|
3707
|
+
db.select()
|
3708
|
+
.from(cities2Table)
|
3709
|
+
.except(({ unionAll }) =>
|
3710
|
+
unionAll(
|
3711
|
+
db
|
3712
|
+
.select({ name: cities2Table.name, id: cities2Table.id })
|
3713
|
+
.from(cities2Table)
|
3714
|
+
.where(gt(citiesTable.id, 1)),
|
3715
|
+
db.select().from(cities2Table).where(eq(citiesTable.id, 2))
|
3716
|
+
)
|
3717
|
+
);
|
3718
|
+
})
|
3719
|
+
.toThrowError();
|
3720
|
+
});
|
3721
|
+
|
3722
|
+
test('set operations (mixed all) as function', async (ctx) => {
|
3723
|
+
const { db2: db } = ctx;
|
3724
|
+
|
3725
|
+
await setupSetOperationTest(db);
|
3726
|
+
|
3727
|
+
const result = await union(
|
3728
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1)),
|
3729
|
+
except(
|
3730
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(gte(users2Table.id, 5)),
|
3731
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 7))
|
3732
|
+
),
|
3733
|
+
db.select().from(cities2Table).where(gt(citiesTable.id, 1))
|
3734
|
+
).orderBy(asc(sql`id`));
|
3735
|
+
|
3736
|
+
ctx.expect(result.length === 6);
|
3737
|
+
|
3738
|
+
assert.equal(result, [
|
3739
|
+
{ id: 1, name: 'John' },
|
3740
|
+
{ id: 2, name: 'London' },
|
3741
|
+
{ id: 3, name: 'Tampa' },
|
3742
|
+
{ id: 5, name: 'Ben' },
|
3743
|
+
{ id: 6, name: 'Jill' },
|
3744
|
+
{ id: 8, name: 'Sally' }
|
3745
|
+
]);
|
3746
|
+
|
3747
|
+
ctx
|
3748
|
+
.expect(() => {
|
3749
|
+
union(
|
3750
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(eq(users2Table.id, 1)),
|
3751
|
+
except(
|
3752
|
+
db.select({ id: users2Table.id, name: users2Table.name }).from(users2Table).where(gte(users2Table.id, 5)),
|
3753
|
+
db.select({ name: users2Table.name, id: users2Table.id }).from(users2Table).where(eq(users2Table.id, 7))
|
3754
|
+
),
|
3755
|
+
db.select().from(cities2Table).where(gt(citiesTable.id, 1))
|
3756
|
+
).orderBy(asc(sql`id`));
|
3757
|
+
})
|
3758
|
+
.toThrowError();
|
3759
|
+
});
|
3760
|
+
|
3761
|
+
test('aggregate function: count', async (ctx) => {
|
3762
|
+
const { db2: db } = ctx;
|
3763
|
+
const table = aggregateTable;
|
3764
|
+
await setupAggregateFunctionsTest(db);
|
3765
|
+
|
3766
|
+
const result1 = await db.select({ value: count() }).from(table);
|
3767
|
+
const result2 = await db.select({ value: count(table.a) }).from(table);
|
3768
|
+
const result3 = await db.select({ value: countDistinct(table.name) }).from(table);
|
3769
|
+
|
3770
|
+
assert.equal(result1[0]?.value, 7);
|
3771
|
+
assert.equal(result2[0]?.value, 5);
|
3772
|
+
assert.equal(result3[0]?.value, 6);
|
3773
|
+
});
|
3774
|
+
|
3775
|
+
test('aggregate function: avg', async (ctx) => {
|
3776
|
+
const { db2: db } = ctx;
|
3777
|
+
const table = aggregateTable;
|
3778
|
+
await setupAggregateFunctionsTest(db);
|
3779
|
+
|
3780
|
+
const result1 = await db.select({ value: avg(table.b) }).from(table);
|
3781
|
+
const result2 = await db.select({ value: avg(table.nullOnly) }).from(table);
|
3782
|
+
const result3 = await db.select({ value: avgDistinct(table.b) }).from(table);
|
3783
|
+
|
3784
|
+
assert.equal(result1[0]?.value, '33.3333333333333333');
|
3785
|
+
assert.equal(result2[0]?.value, null);
|
3786
|
+
assert.equal(result3[0]?.value, '42.5000000000000000');
|
3787
|
+
});
|
3788
|
+
|
3789
|
+
test('aggregate function: sum', async (ctx) => {
|
3790
|
+
const { db2: db } = ctx;
|
3791
|
+
const table = aggregateTable;
|
3792
|
+
await setupAggregateFunctionsTest(db);
|
3793
|
+
|
3794
|
+
const result1 = await db.select({ value: sum(table.b) }).from(table);
|
3795
|
+
const result2 = await db.select({ value: sum(table.nullOnly) }).from(table);
|
3796
|
+
const result3 = await db.select({ value: sumDistinct(table.b) }).from(table);
|
3797
|
+
|
3798
|
+
assert.equal(result1[0]?.value, '200');
|
3799
|
+
assert.equal(result2[0]?.value, null);
|
3800
|
+
assert.equal(result3[0]?.value, '170');
|
3801
|
+
});
|
3802
|
+
|
3803
|
+
test('aggregate function: max', async (ctx) => {
|
3804
|
+
const { db2: db } = ctx;
|
3805
|
+
const table = aggregateTable;
|
3806
|
+
await setupAggregateFunctionsTest(db);
|
3807
|
+
|
3808
|
+
const result1 = await db.select({ value: max(table.b) }).from(table);
|
3809
|
+
const result2 = await db.select({ value: max(table.nullOnly) }).from(table);
|
3810
|
+
|
3811
|
+
assert.equal(result1[0]?.value, 90);
|
3812
|
+
assert.equal(result2[0]?.value, null);
|
3813
|
+
});
|
3814
|
+
|
3815
|
+
test('aggregate function: min', async (ctx) => {
|
3816
|
+
const { db2: db } = ctx;
|
3817
|
+
const table = aggregateTable;
|
3818
|
+
await setupAggregateFunctionsTest(db);
|
3819
|
+
|
3820
|
+
const result1 = await db.select({ value: min(table.b) }).from(table);
|
3821
|
+
const result2 = await db.select({ value: min(table.nullOnly) }).from(table);
|
3822
|
+
|
3823
|
+
assert.equal(result1[0]?.value, 10);
|
3824
|
+
assert.equal(result2[0]?.value, null);
|
3825
|
+
});
|
3826
|
+
|
3827
|
+
test('array mapping and parsing', async (ctx) => {
|
3828
|
+
const { db2: db } = ctx;
|
3829
|
+
|
3830
|
+
const arrays = pgTable('arrays_tests', {
|
3831
|
+
id: serial('id').primaryKey(),
|
3832
|
+
tags: text('tags').array(),
|
3833
|
+
nested: text('nested').array().array(),
|
3834
|
+
numbers: integer('numbers').notNull().array()
|
3835
|
+
});
|
3836
|
+
|
3837
|
+
await db.execute(sql`drop table if exists ${arrays}`);
|
3838
|
+
await db.execute(sql`
|
3839
|
+
create table ${arrays} (
|
3840
|
+
id serial primary key,
|
3841
|
+
tags text[],
|
3842
|
+
nested text[][],
|
3843
|
+
numbers integer[]
|
3844
|
+
)
|
3845
|
+
`);
|
3846
|
+
|
3847
|
+
await db.insert(arrays).values({
|
3848
|
+
tags: ['', 'b', 'c'],
|
3849
|
+
nested: [
|
3850
|
+
['1', ''],
|
3851
|
+
['3', '\\a']
|
3852
|
+
],
|
3853
|
+
numbers: [1, 2, 3]
|
3854
|
+
});
|
3855
|
+
|
3856
|
+
const result = await db.select().from(arrays);
|
3857
|
+
|
3858
|
+
assert.equal(result, [
|
3859
|
+
{
|
3860
|
+
id: 1,
|
3861
|
+
tags: ['', 'b', 'c'],
|
3862
|
+
nested: [
|
3863
|
+
['1', ''],
|
3864
|
+
['3', '\\a']
|
3865
|
+
],
|
3866
|
+
numbers: [1, 2, 3]
|
3867
|
+
}
|
3868
|
+
]);
|
3869
|
+
|
3870
|
+
await db.execute(sql`drop table ${arrays}`);
|
3871
|
+
});
|
3872
|
+
});
|
3873
|
+
|
3874
|
+
const randomString = () => {
|
3875
|
+
return Array.from({ length: 10 }, () => 'abcdefghijklmnopqrstuvwxyz'[Math.floor(Math.random() * 26)]).join('');
|
3876
|
+
};
|
3877
|
+
|
3878
|
+
async function waitForReplication(): Promise<void> {
|
3879
|
+
try {
|
3880
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
3881
|
+
await api.branches.getBranchList({ workspace, database, region });
|
3882
|
+
} catch (error) {
|
3883
|
+
console.log(`Replication not ready yet, retrying...`);
|
3884
|
+
return await waitForReplication();
|
3885
|
+
}
|
3886
|
+
}
|
3887
|
+
|
3888
|
+
// shut up eslint you cannot possibly comprehend what's happening here
|
3889
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
|
3890
|
+
export function Expect<T extends true>() {}
|
3891
|
+
|
3892
|
+
export type Equal<X, Y extends X> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false;
|