5364 lines
146 KiB
TypeScript
5364 lines
146 KiB
TypeScript
import retry from 'async-retry';
|
|
import type Docker from 'dockerode';
|
|
import {
|
|
and,
|
|
arrayContained,
|
|
arrayContains,
|
|
arrayOverlaps,
|
|
asc,
|
|
avg,
|
|
avgDistinct,
|
|
count,
|
|
countDistinct,
|
|
eq,
|
|
type Equal,
|
|
exists,
|
|
getTableColumns,
|
|
gt,
|
|
gte,
|
|
ilike,
|
|
inArray,
|
|
is,
|
|
like,
|
|
lt,
|
|
max,
|
|
min,
|
|
not,
|
|
notInArray,
|
|
or,
|
|
SQL,
|
|
sql,
|
|
type SQLWrapper,
|
|
sum,
|
|
sumDistinct,
|
|
} from 'drizzle-orm';
|
|
import { drizzle, type GelJsDatabase } from 'drizzle-orm/gel';
|
|
import type { GelColumn } from 'drizzle-orm/gel-core';
|
|
import {
|
|
alias,
|
|
boolean,
|
|
dateDuration,
|
|
decimal,
|
|
duration,
|
|
except,
|
|
exceptAll,
|
|
foreignKey,
|
|
GelDialect,
|
|
GelPolicy,
|
|
gelPolicy,
|
|
gelSchema,
|
|
gelTable,
|
|
gelTableCreator,
|
|
getTableConfig,
|
|
integer,
|
|
intersect,
|
|
intersectAll,
|
|
json,
|
|
localDate,
|
|
localTime,
|
|
primaryKey,
|
|
relDuration,
|
|
text,
|
|
timestamp,
|
|
timestamptz,
|
|
union,
|
|
unionAll,
|
|
unique,
|
|
uniqueKeyName,
|
|
uuid as gelUuid,
|
|
} from 'drizzle-orm/gel-core';
|
|
import createClient, {
|
|
type Client,
|
|
DateDuration,
|
|
Duration,
|
|
LocalDate,
|
|
LocalDateTime,
|
|
LocalTime,
|
|
RelativeDuration,
|
|
} from 'gel';
|
|
import { v4 as uuidV4 } from 'uuid';
|
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, expectTypeOf, test, vi } from 'vitest';
|
|
import { Expect } from '~/utils';
|
|
import 'zx/globals';
|
|
import { TestCache, TestGlobalCache } from './cache';
|
|
import { createDockerDB } from './createInstance';
|
|
|
|
$.quiet = true;
|
|
|
|
const ENABLE_LOGGING = false;
|
|
|
|
let client: Client;
|
|
let db: GelJsDatabase;
|
|
let dbGlobalCached: GelJsDatabase;
|
|
let cachedDb: GelJsDatabase;
|
|
const tlsSecurity: string = 'insecure';
|
|
let dsn: string;
|
|
let container: Docker.Container | undefined;
|
|
|
|
function sleep(ms: number) {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
|
|
declare module 'vitest' {
|
|
interface TestContext {
|
|
gel: {
|
|
db: GelJsDatabase;
|
|
};
|
|
cachedGel: {
|
|
db: GelJsDatabase;
|
|
dbGlobalCached: GelJsDatabase;
|
|
};
|
|
}
|
|
}
|
|
|
|
const usersTable = gelTable('users', {
|
|
id1: integer('id1').notNull(),
|
|
name: text('name').notNull(),
|
|
verified: boolean('verified').notNull().default(false),
|
|
json: json('json').$type<string[]>(),
|
|
createdAt: timestamptz('created_at').notNull().defaultNow(),
|
|
});
|
|
|
|
const postsTable = gelTable('posts', {
|
|
id: integer().primaryKey(),
|
|
description: text().notNull(),
|
|
userId: integer('city_id').references(() => usersTable.id1),
|
|
});
|
|
|
|
const usersOnUpdate = gelTable('users_on_update', {
|
|
id1: integer('id1').notNull(),
|
|
name: text('name').notNull(),
|
|
updateCounter: integer('update_counter')
|
|
.default(sql`1`)
|
|
.$onUpdateFn(() => sql`update_counter + 1`),
|
|
updatedAt: timestamptz('updated_at').$onUpdate(() => new Date()),
|
|
alwaysNull: text('always_null')
|
|
.$type<string | null>()
|
|
.$onUpdate(() => null),
|
|
});
|
|
|
|
const citiesTable = gelTable('cities', {
|
|
id1: integer('id1').notNull(),
|
|
name: text('name').notNull(),
|
|
state: text('state'),
|
|
});
|
|
|
|
const cities2Table = gelTable('cities', {
|
|
id1: integer('id1').notNull(),
|
|
name: text('name').notNull(),
|
|
});
|
|
|
|
const users2Table = gelTable('some_new_users', {
|
|
id1: integer('id1').notNull(),
|
|
name: text('name').notNull(),
|
|
cityId: integer('cityId'),
|
|
});
|
|
|
|
const users3Table = gelTable('users3', {
|
|
id1: integer('id1'),
|
|
name: text('name').notNull(),
|
|
});
|
|
|
|
const coursesTable = gelTable('courses', {
|
|
id1: integer('id1').notNull(),
|
|
name: text('name').notNull(),
|
|
categoryId: integer('categoryId').references(() => courseCategoriesTable.id1),
|
|
});
|
|
|
|
const courseCategoriesTable = gelTable('course_categories', {
|
|
id1: integer('id1').notNull(),
|
|
name: text('name').notNull(),
|
|
});
|
|
|
|
const orders = gelTable('orders', {
|
|
id1: integer('id1'),
|
|
region: text('region').notNull(),
|
|
product: text('product')
|
|
.notNull()
|
|
.$default(() => 'random_string'),
|
|
amount: integer('amount').notNull(),
|
|
quantity: integer('quantity').notNull(),
|
|
});
|
|
|
|
const salEmp = gelTable('sal_emp', {
|
|
name: text('name'),
|
|
payByQuarter: integer('pay_by_quarter').array(),
|
|
});
|
|
|
|
const jsonTestTable = gelTable('jsontest', {
|
|
id1: integer('id1').primaryKey(),
|
|
json: json('json').$type<{ string: string; number: number }>(),
|
|
});
|
|
|
|
// To test aggregate functions
|
|
const aggregateTable = gelTable('aggregate_table', {
|
|
id1: integer('id1').notNull(),
|
|
name: text('name').notNull(),
|
|
a: integer('a'),
|
|
b: integer('b'),
|
|
c: integer('c'),
|
|
nullOnly: integer('nullOnly'),
|
|
});
|
|
|
|
// To test another schema and multischema
|
|
const mySchema = gelSchema('mySchema');
|
|
|
|
const usersMySchemaTable = mySchema.table('users', {
|
|
id1: integer('id1').notNull(),
|
|
name: text('name').notNull(),
|
|
verified: boolean('verified').notNull().default(false),
|
|
json: json('json').$type<string[]>(),
|
|
createdAt: timestamptz('created_at').notNull().defaultNow(),
|
|
});
|
|
|
|
beforeAll(async () => {
|
|
let connectionString;
|
|
if (process.env['GEL_CONNECTION_STRING']) {
|
|
connectionString = process.env['GEL_CONNECTION_STRING'];
|
|
} else {
|
|
const { connectionString: conStr, container: contrainerObj } = await createDockerDB();
|
|
connectionString = conStr;
|
|
container = contrainerObj;
|
|
}
|
|
await sleep(15 * 1000);
|
|
client = await retry(() => {
|
|
client = createClient({ dsn: connectionString, tlsSecurity: 'insecure' });
|
|
return client;
|
|
}, {
|
|
retries: 20,
|
|
factor: 1,
|
|
minTimeout: 250,
|
|
maxTimeout: 250,
|
|
randomize: false,
|
|
onRetry() {
|
|
client?.close();
|
|
},
|
|
});
|
|
db = drizzle(client, { logger: ENABLE_LOGGING });
|
|
cachedDb = drizzle(client, {
|
|
logger: ENABLE_LOGGING,
|
|
cache: new TestCache(),
|
|
});
|
|
dbGlobalCached = drizzle(client, {
|
|
logger: ENABLE_LOGGING,
|
|
cache: new TestGlobalCache(),
|
|
});
|
|
|
|
dsn = connectionString;
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await client?.close().catch(console.error);
|
|
await container?.stop().catch(console.error);
|
|
});
|
|
|
|
beforeEach((ctx) => {
|
|
ctx.gel = {
|
|
db,
|
|
};
|
|
ctx.cachedGel = {
|
|
db: cachedDb,
|
|
dbGlobalCached,
|
|
};
|
|
});
|
|
|
|
describe('some', async () => {
|
|
beforeEach(async (ctx) => {
|
|
await ctx.cachedGel.db.$cache?.invalidate({ tables: 'users' });
|
|
await ctx.cachedGel.dbGlobalCached.$cache?.invalidate({ tables: 'users' });
|
|
});
|
|
beforeAll(async () => {
|
|
await $`gel query "CREATE TYPE default::users {
|
|
create property id1: int16 {
|
|
create constraint exclusive;
|
|
};
|
|
create required property name: str;
|
|
create required property verified: bool {
|
|
SET default := false;
|
|
};
|
|
create PROPERTY json: json;
|
|
create required property created_at: datetime {
|
|
SET default := datetime_of_statement();
|
|
};
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::users_with_cities {
|
|
create property id1: int16 {
|
|
create constraint exclusive;
|
|
};
|
|
create required property name: str;
|
|
create required property cityId: int32;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::users_with_undefined {
|
|
create property id1: int16 {
|
|
create constraint exclusive;
|
|
};
|
|
create property name: str;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::users_insert_select {
|
|
create property id1: int16 {
|
|
create constraint exclusive;
|
|
};
|
|
create property name: str;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE MODULE mySchema;" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE mySchema::users {
|
|
create property id1: int16;
|
|
create required property name: str;
|
|
create required property verified: bool {
|
|
SET default := false;
|
|
};
|
|
create PROPERTY json: json;
|
|
create required property created_at: datetime {
|
|
SET default := datetime_of_statement();
|
|
};
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::orders {
|
|
CREATE PROPERTY id1 -> int16;
|
|
CREATE REQUIRED PROPERTY region -> str;
|
|
CREATE REQUIRED PROPERTY product -> str;
|
|
CREATE REQUIRED PROPERTY amount -> int64;
|
|
CREATE REQUIRED PROPERTY quantity -> int64;
|
|
};
|
|
" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::users_distinct {
|
|
create required property id1 -> int16;
|
|
create required property name -> str;
|
|
create required property age -> int16;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::users3 {
|
|
create property id1 -> int16;
|
|
create required property name -> str;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::cities {
|
|
create required property id1 -> int16;
|
|
create required property name -> str;
|
|
create property state -> str;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::courses {
|
|
create required property id1 -> int16;
|
|
create required property name -> str;
|
|
create property categoryId -> int16;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::course_categories {
|
|
create required property id1 -> int16;
|
|
create required property name -> str;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::jsontest {
|
|
create property id1 -> int16;
|
|
create required property json -> json;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::sal_emp {
|
|
create property name -> str;
|
|
create property pay_by_quarter -> array<int16>;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::some_new_users {
|
|
create required property id1 -> int16;
|
|
create required property name -> str;
|
|
create property cityId -> int32;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::aggregate_table {
|
|
create property id1: int16;
|
|
create required property name: str;
|
|
create property a: int16;
|
|
create property b: int16;
|
|
create property c: int16;
|
|
create PROPERTY nullOnly: int16;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::prefixed_users {
|
|
CREATE PROPERTY id1 -> int16;
|
|
CREATE REQUIRED PROPERTY name -> str;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::empty_insert_single {
|
|
CREATE PROPERTY id1 -> int16;
|
|
CREATE REQUIRED PROPERTY name -> str {
|
|
SET default := 'Dan';
|
|
};
|
|
CREATE PROPERTY state -> str;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::empty_insert_multiple {
|
|
CREATE PROPERTY id1 -> int16;
|
|
CREATE REQUIRED PROPERTY name -> str {
|
|
SET default := 'Dan';
|
|
};
|
|
CREATE PROPERTY state -> str;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::products {
|
|
CREATE PROPERTY id1 -> int16;
|
|
CREATE REQUIRED PROPERTY price -> decimal;
|
|
CREATE REQUIRED PROPERTY cheap -> bool {
|
|
SET default := false
|
|
};
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::myprefix_test_prefixed_table_with_unique_name {
|
|
create property id1 -> int16;
|
|
create required property name -> str;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::metric_entry {
|
|
create required property id1 -> uuid;
|
|
create required property createdAt -> datetime;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::users_transactions {
|
|
create required property id1 -> int16;
|
|
create required property balance -> int16;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::products_transactions {
|
|
create required property id1 -> int16;
|
|
create required property price -> int16;
|
|
create required property stock -> int16;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::users_transactions_rollback {
|
|
create required property id1 -> int16;
|
|
create required property balance -> int16;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::users_nested_transactions {
|
|
create required property id1 -> int16;
|
|
create required property balance -> int16;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::internal_staff {
|
|
create required property userId -> int16;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::custom_user {
|
|
create required property id1 -> int16;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::ticket {
|
|
create required property staffId -> int16;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::posts {
|
|
create required property id1 -> int16;
|
|
create property tags -> array<str>;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE dates_column {
|
|
create property datetimeColumn -> datetime;
|
|
create property local_datetimeColumn -> cal::local_datetime;
|
|
create property local_dateColumn -> cal::local_date;
|
|
create property local_timeColumn -> cal::local_time;
|
|
|
|
create property durationColumn -> duration;
|
|
create property relative_durationColumn -> cal::relative_duration;
|
|
create property dateDurationColumn -> cal::date_duration;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE users_with_insert {
|
|
create required property username -> str;
|
|
create required property admin -> bool;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE users_test_with_and_without_timezone {
|
|
create required property username -> str;
|
|
create required property admin -> bool;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::arrays_tests {
|
|
create property id1: int16 {
|
|
create constraint exclusive;
|
|
};
|
|
create property tags: array<str>;
|
|
create required property numbers: array<int32>;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::users_on_update {
|
|
create required property id1 -> int16;
|
|
create required property name -> str;
|
|
create property update_counter -> int16 {
|
|
SET default := 1
|
|
};
|
|
create property always_null -> str;
|
|
create property updated_at -> datetime;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::json_table {
|
|
create PROPERTY json: json;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::notifications {
|
|
create required property id1 -> int16;
|
|
create required property sentAt: datetime {
|
|
SET default := datetime_of_statement();
|
|
};
|
|
create property message -> str;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "CREATE TYPE default::user_notifications {
|
|
create required property userId -> int16;
|
|
create required property notificationId -> int16;
|
|
create property categoryId -> int16;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::users1 {
|
|
create required property id1: int16;
|
|
create required property name: str;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "CREATE TYPE default::users2 {
|
|
create required property id1: int16;
|
|
create required property name: str;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::count_test {
|
|
create required property id1: int16;
|
|
create required property name: str;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::users_with_names {
|
|
create required property id1: int16;
|
|
create required property firstName: str;
|
|
create required property lastName: str;
|
|
create required property admin: bool;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::users_with_age {
|
|
create required property id1: int16;
|
|
create required property name: str;
|
|
create required property age: int32;
|
|
create required property city: str;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
|
|
await $`gel query "CREATE TYPE default::users_on_update_sql {
|
|
create required property id1: int16;
|
|
create required property name: str;
|
|
create required property updated_at: datetime;
|
|
};" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await $`gel query "DELETE default::users;" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DELETE default::prefixed_users;" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DELETE default::some_new_users;" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DELETE default::orders;" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DELETE default::cities;" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DELETE default::users_on_update;" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DELETE default::aggregate_table;" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DELETE mySchema::users;" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DELETE default::count_test;" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DELETE default::users1;" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DELETE default::users2;" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DELETE default::jsontest;" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DELETE default::users_on_update_sql;" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await $`gel query "DROP TYPE default::users" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::users_with_cities" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::users_with_undefined " --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::users_insert_select" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE mySchema::users" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::orders" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::users_distinct" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::users3" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::cities" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::courses" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::course_categories" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::jsontest" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::sal_emp" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::some_new_users" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::aggregate_table" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::prefixed_users" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::empty_insert_single" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::empty_insert_multiple" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::products" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::myprefix_test_prefixed_table_with_unique_name" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::metric_entry" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::users_transactions" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::products_transactions" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::users_transactions_rollback" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::users_nested_transactions" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::internal_staff" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::custom_user" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::ticket" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::posts" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE dates_column" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE users_with_insert" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE users_test_with_and_without_timezone" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::arrays_tests" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::users_on_update" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::json_table" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::notifications" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::user_notifications" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::users1" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::users2" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::count_test" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::users_with_names" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP MODULE mySchema;" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE users_with_age;" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
await $`gel query "DROP TYPE default::users_on_update_sql;" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
|
});
|
|
|
|
async function setupSetOperationTest(db: GelJsDatabase) {
|
|
await db.insert(cities2Table).values([
|
|
{ id1: 1, name: 'New York' },
|
|
{ id1: 2, name: 'London' },
|
|
{ id1: 3, name: 'Tampa' },
|
|
]);
|
|
|
|
await db.insert(users2Table).values([
|
|
{ id1: 1, name: 'John', cityId: 1 },
|
|
{ id1: 2, name: 'Jane', cityId: 2 },
|
|
{ id1: 3, name: 'Jack', cityId: 3 },
|
|
{ id1: 4, name: 'Peter', cityId: 3 },
|
|
{ id1: 5, name: 'Ben', cityId: 2 },
|
|
{ id1: 6, name: 'Jill', cityId: 1 },
|
|
{ id1: 7, name: 'Mary', cityId: 2 },
|
|
{ id1: 8, name: 'Sally', cityId: 1 },
|
|
]);
|
|
}
|
|
|
|
async function setupAggregateFunctionsTest(db: GelJsDatabase) {
|
|
await db.insert(aggregateTable).values([
|
|
{ id1: 1, name: 'value 1', a: 5, b: 10, c: 20 },
|
|
{ id1: 2, name: 'value 1', a: 5, b: 20, c: 30 },
|
|
{ id1: 3, name: 'value 2', a: 10, b: 50, c: 60 },
|
|
{ id1: 4, name: 'value 3', a: 20, b: 20, c: null },
|
|
{ id1: 5, name: 'value 4', a: null, b: 90, c: 120 },
|
|
{ id1: 6, name: 'value 5', a: 80, b: 10, c: null },
|
|
{ id1: 7, name: 'value 6', a: null, b: null, c: 150 },
|
|
]);
|
|
}
|
|
|
|
test('table configs: unique third param', async () => {
|
|
const cities1Table = gelTable(
|
|
'cities1',
|
|
{
|
|
id: integer('id').primaryKey(),
|
|
name: text('name').notNull(),
|
|
state: text('state'),
|
|
},
|
|
(t) => ({
|
|
f: unique('custom_name').on(t.name, t.state).nullsNotDistinct(),
|
|
f1: unique('custom_name1').on(t.name, t.state),
|
|
}),
|
|
);
|
|
|
|
const tableConfig = getTableConfig(cities1Table);
|
|
|
|
expect(tableConfig.uniqueConstraints).toHaveLength(2);
|
|
|
|
expect(tableConfig.uniqueConstraints[0]?.name).toBe('custom_name');
|
|
expect(tableConfig.uniqueConstraints[0]?.nullsNotDistinct).toBe(true);
|
|
expect(tableConfig.uniqueConstraints[0]?.columns.map((t) => t.name)).toEqual(['name', 'state']);
|
|
|
|
expect(tableConfig.uniqueConstraints[1]?.name).toBe('custom_name1');
|
|
expect(tableConfig.uniqueConstraints[1]?.nullsNotDistinct).toBe(false);
|
|
expect(tableConfig.uniqueConstraints[1]?.columns.map((t) => t.name)).toEqual(['name', 'state']);
|
|
});
|
|
|
|
test('table configs: unique in column', async () => {
|
|
const cities1Table = gelTable('cities1', {
|
|
id: integer('id').primaryKey(),
|
|
name: text('name').notNull().unique(),
|
|
state: text('state').unique('custom'),
|
|
field: text('field').unique('custom_field', { nulls: 'not distinct' }),
|
|
});
|
|
|
|
const tableConfig = getTableConfig(cities1Table);
|
|
|
|
const columnName = tableConfig.columns.find((it) => it.name === 'name');
|
|
|
|
expect(columnName?.uniqueName).toBe(uniqueKeyName(cities1Table, [columnName!.name]));
|
|
expect(columnName?.isUnique).toBe(true);
|
|
|
|
const columnState = tableConfig.columns.find((it) => it.name === 'state');
|
|
expect(columnState?.uniqueName).toBe('custom');
|
|
expect(columnState?.isUnique).toBe(true);
|
|
|
|
const columnField = tableConfig.columns.find((it) => it.name === 'field');
|
|
expect(columnField?.uniqueName).toBe('custom_field');
|
|
expect(columnField?.isUnique).toBe(true);
|
|
expect(columnField?.uniqueType).toBe('not distinct');
|
|
});
|
|
|
|
test('table config: foreign keys name', async () => {
|
|
const table = gelTable(
|
|
'cities',
|
|
{
|
|
id1: integer('id1').primaryKey(),
|
|
name: text('name').notNull(),
|
|
state: text('state'),
|
|
},
|
|
(t) => ({
|
|
f: foreignKey({ foreignColumns: [t.id1], columns: [t.id1], name: 'custom_fk' }),
|
|
}),
|
|
);
|
|
|
|
const tableConfig = getTableConfig(table);
|
|
|
|
expect(tableConfig.foreignKeys).toHaveLength(1);
|
|
expect(tableConfig.foreignKeys[0]!.getName()).toBe('custom_fk');
|
|
});
|
|
|
|
test('table config: primary keys name', async () => {
|
|
const table = gelTable(
|
|
'cities',
|
|
{
|
|
id: integer('id').primaryKey(),
|
|
name: text('name').notNull(),
|
|
state: text('state'),
|
|
},
|
|
(t) => ({
|
|
f: primaryKey({ columns: [t.id, t.name], name: 'custom_pk' }),
|
|
}),
|
|
);
|
|
|
|
const tableConfig = getTableConfig(table);
|
|
|
|
expect(tableConfig.primaryKeys).toHaveLength(1);
|
|
expect(tableConfig.primaryKeys[0]!.getName()).toBe('custom_pk');
|
|
});
|
|
|
|
test('select all fields', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const now = Date.now();
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
|
|
|
const result = await db.select().from(usersTable);
|
|
|
|
expect(result[0]!.createdAt).toBeInstanceOf(Date);
|
|
// TODO 100 ms
|
|
expect(Math.abs(result[0]!.createdAt.getTime() - now)).toBeLessThan(500);
|
|
expect(result.map((it) => ({ ...it, id: undefined }))).toEqual([
|
|
{
|
|
id: undefined,
|
|
id1: 1,
|
|
name: 'John',
|
|
verified: false,
|
|
json: null,
|
|
createdAt: result[0]!.createdAt,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('select sql', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
|
const users = await db
|
|
.select({
|
|
name: sql`upper(${usersTable.name})`,
|
|
})
|
|
.from(usersTable);
|
|
|
|
expect(users.map((it) => ({ ...it, id: undefined }))).toStrictEqual([{ id: undefined, name: 'JOHN' }]);
|
|
});
|
|
|
|
test('select typed sql', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
|
|
|
const users = await db
|
|
.select({
|
|
name: sql<string>`upper(${usersTable.name})`,
|
|
})
|
|
.from(usersTable);
|
|
|
|
expect(users.map((it) => ({ ...it, id: undefined }))).toEqual([{ name: 'JOHN' }]);
|
|
});
|
|
|
|
test('select with empty array in inArray', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values([
|
|
{ id1: 1, name: 'John' },
|
|
{ id1: 2, name: 'Jane' },
|
|
{
|
|
id1: 3,
|
|
name: 'Jane',
|
|
},
|
|
]);
|
|
const result = await db
|
|
.select({
|
|
name: sql`upper(${usersTable.name})`,
|
|
})
|
|
.from(usersTable)
|
|
.where(inArray(usersTable.id1, []));
|
|
|
|
expect(result.map((it) => ({ ...it, id: undefined }))).toEqual([]);
|
|
});
|
|
|
|
test('select with empty array in notInArray', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values([
|
|
{ id1: 1, name: 'John' },
|
|
{ id1: 2, name: 'Jane' },
|
|
{
|
|
id1: 3,
|
|
name: 'Jane',
|
|
},
|
|
]);
|
|
const result = await db
|
|
.select({
|
|
name: sql`upper(${usersTable.name})`,
|
|
})
|
|
.from(usersTable)
|
|
.where(notInArray(usersTable.id1, []));
|
|
|
|
expect(result.map((it) => ({ ...it, id: undefined }))).toEqual([
|
|
{ name: 'JOHN' },
|
|
{ name: 'JANE' },
|
|
{
|
|
name: 'JANE',
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('$default function', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const insertedOrder = await db.insert(orders).values({ id1: 1, region: 'Ukraine', amount: 1, quantity: 1 })
|
|
.returning();
|
|
const selectedOrder = await db.select().from(orders);
|
|
|
|
expect(insertedOrder.map((it) => ({ ...it, id: undefined }))).toEqual([
|
|
{
|
|
id: undefined,
|
|
amount: 1,
|
|
id1: 1,
|
|
quantity: 1,
|
|
region: 'Ukraine',
|
|
product: 'random_string',
|
|
},
|
|
]);
|
|
|
|
expect(selectedOrder.map((it) => ({ ...it, id: undefined }))).toEqual([
|
|
{
|
|
id: undefined,
|
|
id1: 1,
|
|
amount: 1,
|
|
quantity: 1,
|
|
region: 'Ukraine',
|
|
product: 'random_string',
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('select distinct', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const usersDistinctTable = gelTable('users_distinct', {
|
|
id1: integer('id1').notNull(),
|
|
name: text('name').notNull(),
|
|
age: integer('age').notNull(),
|
|
});
|
|
|
|
await db.insert(usersDistinctTable).values([
|
|
{ id1: 1, name: 'John', age: 24 },
|
|
{ id1: 1, name: 'John', age: 24 },
|
|
{ id1: 2, name: 'John', age: 25 },
|
|
{ id1: 1, name: 'Jane', age: 24 },
|
|
{ id1: 1, name: 'Jane', age: 26 },
|
|
]);
|
|
|
|
const users1 = await db.selectDistinct().from(usersDistinctTable).orderBy(
|
|
usersDistinctTable.id1,
|
|
usersDistinctTable.name,
|
|
);
|
|
|
|
const users2 = await db.selectDistinctOn([usersDistinctTable.id1]).from(usersDistinctTable).orderBy(
|
|
usersDistinctTable.id1,
|
|
);
|
|
|
|
const users3 = await db.selectDistinctOn([usersDistinctTable.name], { name: usersDistinctTable.name }).from(
|
|
usersDistinctTable,
|
|
).orderBy(usersDistinctTable.name);
|
|
|
|
const users4 = await db.selectDistinctOn([usersDistinctTable.id1, usersDistinctTable.age]).from(
|
|
usersDistinctTable,
|
|
).orderBy(usersDistinctTable.id1, usersDistinctTable.age);
|
|
|
|
expect(users1).toEqual([
|
|
{ id1: 1, name: 'Jane', age: 24 },
|
|
{ id1: 1, name: 'Jane', age: 26 },
|
|
{ id1: 1, name: 'John', age: 24 },
|
|
{ id1: 2, name: 'John', age: 25 },
|
|
]);
|
|
|
|
expect(users2).toHaveLength(2);
|
|
expect(users2[0]?.id1).toBe(1);
|
|
expect(users2[1]?.id1).toBe(2);
|
|
|
|
expect(users3).toHaveLength(2);
|
|
expect(users3[0]?.name).toBe('Jane');
|
|
expect(users3[1]?.name).toBe('John');
|
|
|
|
expect(users4).toEqual([
|
|
{ id1: 1, name: 'John', age: 24 },
|
|
{ id1: 1, name: 'Jane', age: 26 },
|
|
{ id1: 2, name: 'John', age: 25 },
|
|
]);
|
|
});
|
|
|
|
test('insert returning sql', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const users = await db
|
|
.insert(usersTable)
|
|
.values({ id1: 1, name: 'John' })
|
|
.returning({
|
|
name: sql`upper(${usersTable.name})`,
|
|
});
|
|
|
|
expect(users).toEqual([{ name: 'JOHN' }]);
|
|
});
|
|
|
|
test('delete returning sql', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
|
const users = await db
|
|
.delete(usersTable)
|
|
.where(eq(usersTable.name, 'John'))
|
|
.returning({
|
|
name: sql`upper(${usersTable.name})`,
|
|
});
|
|
|
|
expect(users).toEqual([{ name: 'JOHN' }]);
|
|
});
|
|
|
|
test('update returning sql', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
|
const users = await db
|
|
.update(usersTable)
|
|
.set({ name: 'Jane' })
|
|
.where(eq(usersTable.name, 'John'))
|
|
.returning({
|
|
name: sql`upper(${usersTable.name})`,
|
|
});
|
|
|
|
expect(users).toEqual([{ name: 'JANE' }]);
|
|
});
|
|
|
|
test('update with returning all fields', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const now = Date.now();
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
|
const users = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')).returning();
|
|
|
|
expect(users[0]!.createdAt).toBeInstanceOf(Date);
|
|
expect(Math.abs(users[0]!.createdAt.getTime() - now)).toBeLessThan(500);
|
|
expect(users).toEqual([
|
|
{
|
|
id1: 1,
|
|
name: 'Jane',
|
|
verified: false,
|
|
json: null,
|
|
createdAt: users[0]!.createdAt,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('update with returning partial', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
|
const users = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')).returning({
|
|
id1: usersTable.id1,
|
|
name: usersTable.name,
|
|
});
|
|
|
|
expect(users).toEqual([{ id1: 1, name: 'Jane' }]);
|
|
});
|
|
|
|
test('delete with returning all fields', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const now = Date.now();
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
|
const users = await db.delete(usersTable).where(eq(usersTable.name, 'John')).returning();
|
|
|
|
expect(users[0]!.createdAt).toBeInstanceOf(Date);
|
|
expect(Math.abs(users[0]!.createdAt.getTime() - now)).toBeLessThan(500);
|
|
expect(users.map((it) => ({ ...it, id: undefined }))).toEqual([
|
|
{
|
|
name: 'John',
|
|
id1: 1,
|
|
id: undefined,
|
|
verified: false,
|
|
json: null,
|
|
createdAt: users[0]!.createdAt,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('delete with returning partial', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
|
const users = await db.delete(usersTable).where(eq(usersTable.name, 'John')).returning({
|
|
id1: usersTable.id1,
|
|
name: usersTable.name,
|
|
});
|
|
|
|
expect(users.map((it) => ({ ...it, id: undefined }))).toEqual([{ id1: 1, name: 'John' }]);
|
|
});
|
|
|
|
test('insert + select', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
|
const result = await db.select().from(usersTable);
|
|
expect(result).toEqual([
|
|
{
|
|
name: 'John',
|
|
id1: 1,
|
|
verified: false,
|
|
json: null,
|
|
createdAt: result[0]!.createdAt,
|
|
},
|
|
]);
|
|
|
|
await db.insert(usersTable).values({ id1: 2, name: 'Jane' });
|
|
const result2 = await db.select().from(usersTable);
|
|
expect(result2).toEqual([
|
|
{ id1: 1, name: 'John', verified: false, json: null, createdAt: result2[0]!.createdAt },
|
|
{ id1: 2, name: 'Jane', verified: false, json: null, createdAt: result2[1]!.createdAt },
|
|
]);
|
|
});
|
|
|
|
test('json insert', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: 'John', json: ['foo', 'bar'] });
|
|
const result = await db
|
|
.select({
|
|
id1: usersTable.id1,
|
|
name: usersTable.name,
|
|
json: usersTable.json,
|
|
})
|
|
.from(usersTable);
|
|
|
|
expect(result).toEqual([
|
|
{
|
|
id1: 1,
|
|
name: 'John',
|
|
json: ['foo', 'bar'],
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('insert with overridden default values', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: 'John', verified: true });
|
|
const result = await db.select().from(usersTable);
|
|
|
|
expect(result).toEqual([
|
|
{
|
|
id1: 1,
|
|
name: 'John',
|
|
verified: true,
|
|
json: null,
|
|
createdAt: result[0]!.createdAt,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('insert many', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values([
|
|
{ id1: 1, name: 'John' },
|
|
{ id1: 2, name: 'Bruce', json: ['foo', 'bar'], verified: true },
|
|
{ id1: 3, name: 'Jane' },
|
|
{ id1: 4, name: 'Austin', verified: true },
|
|
]);
|
|
const result = await db
|
|
.select({
|
|
name: usersTable.name,
|
|
json: usersTable.json,
|
|
verified: usersTable.verified,
|
|
})
|
|
.from(usersTable);
|
|
|
|
expect(result).toEqual([
|
|
{ name: 'John', json: null, verified: false },
|
|
{ name: 'Bruce', json: ['foo', 'bar'], verified: true },
|
|
{ name: 'Jane', json: null, verified: false },
|
|
{ name: 'Austin', json: null, verified: true },
|
|
]);
|
|
});
|
|
|
|
test('insert many with returning', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const result = await db
|
|
.insert(usersTable)
|
|
.values([
|
|
{ id1: 1, name: 'John' },
|
|
{ id1: 2, name: 'Bruce', json: ['foo', 'bar'] },
|
|
{ id1: 3, name: 'Jane' },
|
|
{
|
|
id1: 4,
|
|
name: 'Austin',
|
|
verified: true,
|
|
},
|
|
])
|
|
.returning({
|
|
name: usersTable.name,
|
|
json: usersTable.json,
|
|
verified: usersTable.verified,
|
|
});
|
|
|
|
expect(result).toEqual([
|
|
{ name: 'John', json: null, verified: false },
|
|
{ name: 'Bruce', json: ['foo', 'bar'], verified: false },
|
|
{ name: 'Jane', json: null, verified: false },
|
|
{ name: 'Austin', json: null, verified: true },
|
|
]);
|
|
});
|
|
|
|
test('select with group by as field', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values([
|
|
{ id1: 1, name: 'John' },
|
|
{ id1: 2, name: 'Jane' },
|
|
{
|
|
id1: 3,
|
|
name: 'Jane',
|
|
},
|
|
]);
|
|
|
|
const result = await db.select({ name: usersTable.name }).from(usersTable).groupBy(usersTable.name);
|
|
|
|
expect(result).toEqual([{ name: 'Jane' }, { name: 'John' }]);
|
|
});
|
|
|
|
test('select with exists', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values([
|
|
{ id1: 1, name: 'John' },
|
|
{ id1: 2, name: 'Jane' },
|
|
{
|
|
id1: 3,
|
|
name: 'Jane',
|
|
},
|
|
]);
|
|
|
|
const user = alias(usersTable, 'user');
|
|
const result = await db
|
|
.select({ name: usersTable.name })
|
|
.from(usersTable)
|
|
.where(
|
|
exists(
|
|
db
|
|
.select({ one: sql`1` })
|
|
.from(user)
|
|
.where(and(eq(usersTable.name, 'John'), eq(user.id1, usersTable.id1))),
|
|
),
|
|
);
|
|
|
|
expect(result).toEqual([{ name: 'John' }]);
|
|
});
|
|
|
|
test('select with group by as sql', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values([
|
|
{ id1: 1, name: 'John' },
|
|
{ id1: 2, name: 'Jane' },
|
|
{
|
|
id1: 3,
|
|
name: 'Jane',
|
|
},
|
|
]);
|
|
|
|
const result = await db
|
|
.select({ name: usersTable.name })
|
|
.from(usersTable)
|
|
.groupBy(sql`${usersTable.name}`);
|
|
|
|
expect(result).toEqual([{ name: 'Jane' }, { name: 'John' }]);
|
|
});
|
|
|
|
test.skip('select with group by as sql + column', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values([
|
|
{ id1: 1, name: 'John' },
|
|
{ id1: 2, name: 'Jane' },
|
|
{
|
|
id1: 3,
|
|
name: 'Jane',
|
|
},
|
|
]);
|
|
|
|
const result = await db
|
|
.select({ name: usersTable.name })
|
|
.from(usersTable)
|
|
.groupBy(sql`${usersTable.name}`, usersTable.id1);
|
|
|
|
expect(result).toEqual([{ name: 'Jane' }, { name: 'John' }, { name: 'Jane' }]);
|
|
});
|
|
|
|
test('select with group by as column + sql', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values([
|
|
{ id1: 1, name: 'John' },
|
|
{ id1: 2, name: 'Jane' },
|
|
{
|
|
id1: 3,
|
|
name: 'Jane',
|
|
},
|
|
]);
|
|
|
|
const result = await db
|
|
.select({ name: usersTable.name })
|
|
.from(usersTable)
|
|
.groupBy(usersTable.id1, sql`${usersTable.name}`);
|
|
|
|
expect(result).toEqual([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
|
|
});
|
|
|
|
test('select with group by complex query', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values([
|
|
{ id1: 1, name: 'John' },
|
|
{ id1: 2, name: 'Jane' },
|
|
{
|
|
id1: 3,
|
|
name: 'Jane',
|
|
},
|
|
]);
|
|
|
|
const result = await db
|
|
.select({ name: usersTable.name })
|
|
.from(usersTable)
|
|
.groupBy(usersTable.id1, sql`${usersTable.name}`)
|
|
.orderBy(asc(usersTable.name))
|
|
.limit(1);
|
|
|
|
expect(result).toEqual([{ name: 'Jane' }]);
|
|
});
|
|
|
|
test('build query', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const query = db.select({ id: usersTable.id1, name: usersTable.name }).from(usersTable).groupBy(
|
|
usersTable.id1,
|
|
usersTable.name,
|
|
).toSQL();
|
|
|
|
expect(query).toEqual({
|
|
sql: 'select "users"."id1", "users"."name" from "users" group by "users"."id1", "users"."name"',
|
|
params: [],
|
|
});
|
|
});
|
|
|
|
test('insert sql', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: sql`${'John'}` });
|
|
const result = await db.select({ name: usersTable.name }).from(usersTable);
|
|
expect(result).toEqual([{ name: 'John' }]);
|
|
});
|
|
|
|
test('partial join with alias', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
const customerAlias = alias(usersTable, 'customer');
|
|
|
|
await db.insert(usersTable).values([
|
|
{ id1: 10, name: 'Ivan' },
|
|
{ id1: 11, name: 'Hans' },
|
|
]);
|
|
const result = await db
|
|
.select({
|
|
user: {
|
|
id1: usersTable.id1,
|
|
name: usersTable.name,
|
|
},
|
|
customer: {
|
|
id1: customerAlias.id1,
|
|
name: customerAlias.name,
|
|
},
|
|
})
|
|
.from(usersTable)
|
|
.leftJoin(customerAlias, eq(customerAlias.id1, 11))
|
|
.where(eq(usersTable.id1, 10));
|
|
|
|
expect(result).toEqual([
|
|
{
|
|
user: { id1: 10, name: 'Ivan' },
|
|
customer: { id1: 11, name: 'Hans' },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('full join with alias', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const gelTable = gelTableCreator((name) => `prefixed_${name}`);
|
|
|
|
const users = gelTable('users', {
|
|
id1: integer('id1').primaryKey(),
|
|
name: text('name').notNull(),
|
|
});
|
|
|
|
const customers = alias(users, 'customer');
|
|
|
|
await db.insert(users).values([
|
|
{ id1: 10, name: 'Ivan' },
|
|
{ id1: 11, name: 'Hans' },
|
|
]);
|
|
const result = await db.select().from(users).leftJoin(customers, eq(customers.id1, 11)).where(
|
|
eq(users.id1, 10),
|
|
);
|
|
|
|
expect(result).toEqual([
|
|
{
|
|
users: {
|
|
id1: 10,
|
|
name: 'Ivan',
|
|
},
|
|
customer: {
|
|
id1: 11,
|
|
name: 'Hans',
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('select from alias', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const gelTable = gelTableCreator((name) => `prefixed_${name}`);
|
|
|
|
const users = gelTable('users', {
|
|
id1: integer('id1'),
|
|
name: text('name').notNull(),
|
|
});
|
|
|
|
const user = alias(users, 'user');
|
|
const customers = alias(users, 'customer');
|
|
|
|
await db.insert(users).values([
|
|
{ id1: 10, name: 'Ivan' },
|
|
{ id1: 11, name: 'Hans' },
|
|
]);
|
|
const result = await db.select().from(user).leftJoin(customers, eq(customers.id1, 11)).where(eq(user.id1, 10));
|
|
|
|
expect(result).toEqual([
|
|
{
|
|
user: {
|
|
id1: 10,
|
|
name: 'Ivan',
|
|
},
|
|
customer: {
|
|
id1: 11,
|
|
name: 'Hans',
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('insert with spaces', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: sql`'Jo h n'` });
|
|
const result = await db.select({ id1: usersTable.id1, name: usersTable.name }).from(usersTable);
|
|
|
|
expect(result).toEqual([{ id1: 1, name: 'Jo h n' }]);
|
|
});
|
|
|
|
test('prepared statement', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
|
const statement = db
|
|
.select({
|
|
name: usersTable.name,
|
|
})
|
|
.from(usersTable)
|
|
.prepare('statement1');
|
|
const result = await statement.execute();
|
|
|
|
expect(result).toEqual([{ name: 'John' }]);
|
|
});
|
|
|
|
test('insert: placeholders on columns with encoder', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const statement = db
|
|
.insert(usersTable)
|
|
.values({
|
|
id1: 1,
|
|
name: 'John',
|
|
json: sql.placeholder('json'),
|
|
})
|
|
.prepare('encoder_statement');
|
|
|
|
await statement.execute({ json: ['foo', 'bar'] });
|
|
|
|
const result = await db
|
|
.select({
|
|
json: usersTable.json,
|
|
})
|
|
.from(usersTable);
|
|
|
|
expect(result).toEqual([{ json: ['foo', 'bar'] }]);
|
|
});
|
|
|
|
test('prepared statement reuse', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const stmt = db
|
|
.insert(usersTable)
|
|
.values({
|
|
id1: sql.placeholder('id1'),
|
|
verified: true,
|
|
name: sql.placeholder('name'),
|
|
})
|
|
.prepare('stmt2');
|
|
|
|
for (let i = 1; i < 11; i++) {
|
|
await stmt.execute({ id1: i, name: `John ${i}` });
|
|
}
|
|
|
|
const result = await db
|
|
.select({
|
|
name: usersTable.name,
|
|
verified: usersTable.verified,
|
|
})
|
|
.from(usersTable);
|
|
|
|
expect(result).toEqual([
|
|
{ name: 'John 1', verified: true },
|
|
{ name: 'John 2', verified: true },
|
|
{ name: 'John 3', verified: true },
|
|
{ name: 'John 4', verified: true },
|
|
{ name: 'John 5', verified: true },
|
|
{ name: 'John 6', verified: true },
|
|
{ name: 'John 7', verified: true },
|
|
{ name: 'John 8', verified: true },
|
|
{ name: 'John 9', verified: true },
|
|
{ name: 'John 10', verified: true },
|
|
]);
|
|
});
|
|
|
|
test('prepared statement with placeholder in .where', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
|
const stmt = db
|
|
.select({
|
|
id1: usersTable.id1,
|
|
name: usersTable.name,
|
|
})
|
|
.from(usersTable)
|
|
.where(eq(usersTable.id1, sql.placeholder('id1')))
|
|
.prepare('stmt3');
|
|
const result = await stmt.execute({ id1: 1 });
|
|
|
|
expect(result).toEqual([{ id1: 1, name: 'John' }]);
|
|
});
|
|
|
|
test('prepared statement with placeholder in .limit', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
|
const stmt = db
|
|
.select({
|
|
id1: usersTable.id1,
|
|
name: usersTable.name,
|
|
})
|
|
.from(usersTable)
|
|
.where(eq(usersTable.id1, sql.placeholder('id1')))
|
|
.limit(sql.placeholder('limit'))
|
|
.prepare('stmt_limit');
|
|
|
|
const result = await stmt.execute({ id1: 1, limit: 1 });
|
|
|
|
expect(result).toEqual([{ id1: 1, name: 'John' }]);
|
|
expect(result).toHaveLength(1);
|
|
});
|
|
|
|
test('prepared statement with placeholder in .offset', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values([
|
|
{ id1: 1, name: 'John' },
|
|
{ id1: 2, name: 'John1' },
|
|
]);
|
|
const stmt = db
|
|
.select({
|
|
id1: usersTable.id1,
|
|
name: usersTable.name,
|
|
})
|
|
.from(usersTable)
|
|
.offset(sql.placeholder('offset'))
|
|
.prepare('stmt_offset');
|
|
|
|
const result = await stmt.execute({ offset: 1 });
|
|
|
|
expect(result).toEqual([{ id1: 2, name: 'John1' }]);
|
|
});
|
|
|
|
test('prepared statement built using $dynamic', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
function withLimitOffset(qb: any) {
|
|
return qb.limit(sql.placeholder('limit')).offset(sql.placeholder('offset'));
|
|
}
|
|
|
|
await db.insert(usersTable).values([
|
|
{ id1: 1, name: 'John' },
|
|
{ id1: 2, name: 'John1' },
|
|
]);
|
|
const stmt = db
|
|
.select({
|
|
id1: usersTable.id1,
|
|
name: usersTable.name,
|
|
})
|
|
.from(usersTable)
|
|
.$dynamic();
|
|
withLimitOffset(stmt).prepare('stmt_limit');
|
|
|
|
const result = await stmt.execute({ limit: 1, offset: 1 });
|
|
|
|
expect(result).toEqual([{ id1: 2, name: 'John1' }]);
|
|
expect(result).toHaveLength(1);
|
|
});
|
|
|
|
test('Query check: Insert all defaults in 1 row', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const users = gelTable('users', {
|
|
id: integer('id'),
|
|
name: text('name').default('Dan'),
|
|
state: text('state'),
|
|
});
|
|
|
|
const query = db.insert(users).values({}).toSQL();
|
|
|
|
expect(query).toEqual({
|
|
sql: 'insert into "users" ("id", "name", "state") values (default, default, default)',
|
|
params: [],
|
|
});
|
|
});
|
|
|
|
test('Query check: Insert all defaults in multiple rows', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const users = gelTable('users', {
|
|
id: integer('id'),
|
|
name: text('name').default('Dan'),
|
|
state: text('state').default('UA'),
|
|
});
|
|
|
|
const query = db.insert(users).values([{}, {}]).toSQL();
|
|
|
|
expect(query).toEqual({
|
|
sql:
|
|
'insert into "users" ("id", "name", "state") values (default, default, default), (default, default, default)',
|
|
params: [],
|
|
});
|
|
});
|
|
|
|
test('Insert all defaults in 1 row', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const users = gelTable('empty_insert_single', {
|
|
id1: integer('id1'),
|
|
name: text('name').default('Dan'),
|
|
state: text('state'),
|
|
});
|
|
|
|
await db.insert(users).values({});
|
|
|
|
const res = await db.select().from(users);
|
|
|
|
expect(res).toEqual([{ id1: null, name: 'Dan', state: null }]);
|
|
});
|
|
|
|
test('Insert all defaults in multiple rows', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const users = gelTable('empty_insert_multiple', {
|
|
id: integer('id'),
|
|
name: text('name').default('Dan'),
|
|
state: text('state'),
|
|
});
|
|
|
|
await db.insert(users).values([{}, {}]);
|
|
|
|
const res = await db.select().from(users);
|
|
|
|
expect(res.map((it) => ({ ...it, id: undefined }))).toEqual([
|
|
{ id: undefined, name: 'Dan', state: null },
|
|
{ id: undefined, name: 'Dan', state: null },
|
|
]);
|
|
});
|
|
|
|
// TODO not supported in gel
|
|
test.todo('build query insert with onConflict do update', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const query = db
|
|
.insert(usersTable)
|
|
.values({ id1: 1, name: 'John', json: ['foo', 'bar'] })
|
|
// .onConflictDoUpdate({ target: usersTable.id1, set: { name: 'John1' } })
|
|
.toSQL();
|
|
|
|
expect(query).toEqual({
|
|
sql:
|
|
'insert into "users" ("id1", "name", "verified", "json", "created_at") values ($1, $2, default, $3, default) on conflict ("id1") do update set "name" = $4',
|
|
params: [1, 'John', ['foo', 'bar'], 'John1'],
|
|
});
|
|
});
|
|
|
|
// TODO on conflict not supported in gel
|
|
test.todo('build query insert with onConflict do update / multiple columns', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const query = db
|
|
.insert(usersTable)
|
|
.values({ id1: 1, name: 'John', json: ['foo', 'bar'] })
|
|
// .onConflictDoUpdate({ target: [usersTable.id1, usersTable.name], set: { name: 'John1' } })
|
|
.toSQL();
|
|
|
|
expect(query).toEqual({
|
|
sql:
|
|
'insert into "users" ("id1", "name", "verified", "json", "created_at") values ($1, $2, default, $3, default) on conflict ("id1","name") do update set "name" = $4',
|
|
params: [1, 'John', ['foo', 'bar'], 'John1'],
|
|
});
|
|
});
|
|
|
|
// TODO on conflict not supported in gel
|
|
test.todo('build query insert with onConflict do nothing', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const query = db
|
|
.insert(usersTable)
|
|
.values({ id1: 1, name: 'John', json: ['foo', 'bar'] })
|
|
// .onConflictDoNothing()
|
|
.toSQL();
|
|
|
|
expect(query).toEqual({
|
|
sql:
|
|
'insert into "users" ("id1", "name", "verified", "json", "created_at") values ($1, $2, default, 32, default) on conflict do nothing',
|
|
params: [1, 'John', ['foo', 'bar']],
|
|
});
|
|
});
|
|
|
|
// TODO on conflict not supported
|
|
test.todo('build query insert with onConflict do nothing + target', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const query = db
|
|
.insert(usersTable)
|
|
.values({ id1: 1, name: 'John', json: ['foo', 'bar'] })
|
|
// .onConflictDoNothing({ target: usersTable.id1 })
|
|
.toSQL();
|
|
|
|
expect(query).toEqual({
|
|
sql:
|
|
'insert into "users" ("id1", "name", "verified", "json", "created_at") values ($1, $2, default, $3, default) on conflict ("id1") do nothing',
|
|
params: [1, 'John', ['foo', 'bar']],
|
|
});
|
|
});
|
|
|
|
// TODO on conflict not supported in gel
|
|
test.todo('insert with onConflict do update', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
|
|
|
await db
|
|
.insert(usersTable)
|
|
.values({ id1: 1, name: 'John' });
|
|
// .onConflictDoUpdate({ target: usersTable.id1, set: { name: 'John1' } });
|
|
|
|
const res = await db.select({ id1: usersTable.id1, name: usersTable.name }).from(usersTable).where(
|
|
eq(usersTable.id1, 1),
|
|
);
|
|
|
|
expect(res).toEqual([{ id1: 1, name: 'John1' }]);
|
|
});
|
|
|
|
// TODO on conflict does not supported
|
|
test.todo('insert with onConflict do nothing', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
|
|
|
// await db.insert(usersTable).values({ id1: 1, name: 'John' }).onConflictDoNothing();
|
|
|
|
const res = await db.select({ id1: usersTable.id1, name: usersTable.name }).from(usersTable).where(
|
|
eq(usersTable.id1, 1),
|
|
);
|
|
|
|
expect(res).toEqual([{ id1: 1, name: 'John' }]);
|
|
});
|
|
|
|
// TODO on conflict does not supported
|
|
test.todo('insert with onConflict do nothing + target', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
|
|
|
// await db.insert(usersTable).values({ id1: 1, name: 'John' }).onConflictDoNothing({
|
|
// target: usersTable.id1,
|
|
// });
|
|
|
|
const res = await db.select({ id1: usersTable.id1, name: usersTable.name }).from(usersTable).where(
|
|
eq(usersTable.id1, 1),
|
|
);
|
|
|
|
expect(res).toEqual([{ id1: 1, name: 'John' }]);
|
|
});
|
|
|
|
test('left join (flat object fields)', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const { id1: cityId } = await db
|
|
.insert(citiesTable)
|
|
.values([
|
|
{ id1: 1, name: 'Paris', state: 'Unknown' },
|
|
{ id1: 2, name: 'London', state: 'Unknown' },
|
|
])
|
|
.returning({ id1: citiesTable.id1 })
|
|
.then((rows) => rows[0]!);
|
|
|
|
await db.insert(users2Table).values([
|
|
{ id1: 1, name: 'John', cityId },
|
|
{ id1: 2, name: 'Jane', cityId },
|
|
]);
|
|
|
|
const res = await db
|
|
.select({
|
|
userId: users2Table.id1,
|
|
userName: users2Table.name,
|
|
cityId: citiesTable.id1,
|
|
cityName: citiesTable.name,
|
|
})
|
|
.from(users2Table)
|
|
.leftJoin(citiesTable, eq(users2Table.cityId, citiesTable.id1));
|
|
|
|
expect(res).toEqual([
|
|
{ userId: 1, userName: 'John', cityId, cityName: 'Paris' },
|
|
{ userId: 2, userName: 'Jane', cityId, cityName: 'Paris' },
|
|
]);
|
|
});
|
|
|
|
test('left join (grouped fields)', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const { id1: cityId } = await db
|
|
.insert(citiesTable)
|
|
.values([
|
|
{ id1: 1, name: 'Paris' },
|
|
{ id1: 2, name: 'London' },
|
|
])
|
|
.returning({ id1: citiesTable.id1 })
|
|
.then((rows) => rows[0]!);
|
|
|
|
await db.insert(users2Table).values([
|
|
{ id1: 1, name: 'John', cityId },
|
|
{ id1: 2, name: 'Jane', cityId },
|
|
]);
|
|
|
|
const res = await db
|
|
.select({
|
|
id: users2Table.id1,
|
|
user: {
|
|
name: users2Table.name,
|
|
nameUpper: sql<string>`upper(${users2Table.name})`,
|
|
},
|
|
city: {
|
|
id: citiesTable.id1,
|
|
name: citiesTable.name,
|
|
nameUpper: sql<string>`upper(${citiesTable.name})`,
|
|
},
|
|
})
|
|
.from(users2Table)
|
|
.leftJoin(citiesTable, eq(users2Table.cityId, citiesTable.id1));
|
|
|
|
expect(res).toEqual([
|
|
{
|
|
id: 1,
|
|
user: { name: 'John', nameUpper: 'JOHN' },
|
|
city: { id: cityId, name: 'Paris', nameUpper: 'PARIS' },
|
|
},
|
|
{
|
|
id: 2,
|
|
user: { name: 'Jane', nameUpper: 'JANE' },
|
|
city: { id: cityId, name: 'Paris', nameUpper: 'PARIS' },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('left join (all fields)', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const { id1: cityId } = await db
|
|
.insert(citiesTable)
|
|
.values([
|
|
{ id1: 1, name: 'Paris' },
|
|
{ id1: 2, name: 'London' },
|
|
])
|
|
.returning({ id1: citiesTable.id1 })
|
|
.then((rows) => rows[0]!);
|
|
|
|
await db.insert(users2Table).values([
|
|
{ id1: 1, name: 'John', cityId },
|
|
{ id1: 2, name: 'Jane', cityId },
|
|
]);
|
|
|
|
const res = await db.select().from(users2Table).leftJoin(citiesTable, eq(users2Table.cityId, citiesTable.id1));
|
|
|
|
expect(res).toEqual([
|
|
{
|
|
some_new_users: {
|
|
id1: 1,
|
|
name: 'John',
|
|
cityId,
|
|
},
|
|
cities: {
|
|
id1: cityId,
|
|
name: 'Paris',
|
|
state: null,
|
|
},
|
|
},
|
|
{
|
|
some_new_users: {
|
|
id1: 2,
|
|
name: 'Jane',
|
|
cityId,
|
|
},
|
|
cities: {
|
|
id1: cityId,
|
|
name: 'Paris',
|
|
state: null,
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('select from a many subquery', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(citiesTable)
|
|
.values([{ id1: 1, name: 'Paris' }, { id1: 2, name: 'London' }]);
|
|
|
|
await db.insert(users2Table).values([
|
|
{ id1: 1, name: 'John', cityId: 1 },
|
|
{ id1: 2, name: 'Jane', cityId: 2 },
|
|
{ id1: 3, name: 'Jack', cityId: 2 },
|
|
]);
|
|
|
|
const res = await db.select({
|
|
population: db.select({ count: count().as('count') }).from(users2Table).where(
|
|
eq(users2Table.cityId, citiesTable.id1),
|
|
).as(
|
|
'population',
|
|
),
|
|
name: citiesTable.name,
|
|
}).from(citiesTable);
|
|
|
|
expectTypeOf(res).toEqualTypeOf<{
|
|
population: number;
|
|
name: string;
|
|
}[]>();
|
|
|
|
expect(res).toStrictEqual([{
|
|
population: 1,
|
|
name: 'Paris',
|
|
}, {
|
|
population: 2,
|
|
name: 'London',
|
|
}]);
|
|
});
|
|
|
|
test('select from a one subquery', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(citiesTable)
|
|
.values([{ id1: 1, name: 'Paris' }, { id1: 2, name: 'London' }]);
|
|
|
|
await db.insert(users2Table).values([
|
|
{ id1: 1, name: 'John', cityId: 1 },
|
|
{ id1: 2, name: 'Jane', cityId: 2 },
|
|
{ id1: 3, name: 'Jack', cityId: 2 },
|
|
]);
|
|
|
|
const res = await db.select({
|
|
cityName: db.select({ name: citiesTable.name }).from(citiesTable).where(eq(users2Table.cityId, citiesTable.id1))
|
|
.as(
|
|
'cityName',
|
|
),
|
|
name: users2Table.name,
|
|
}).from(users2Table);
|
|
|
|
expectTypeOf(res).toEqualTypeOf<{
|
|
cityName: string;
|
|
name: string;
|
|
}[]>();
|
|
|
|
expect(res).toStrictEqual([{
|
|
cityName: 'Paris',
|
|
name: 'John',
|
|
}, {
|
|
cityName: 'London',
|
|
name: 'Jane',
|
|
}, {
|
|
cityName: 'London',
|
|
name: 'Jack',
|
|
}]);
|
|
});
|
|
|
|
test('join subquery', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(courseCategoriesTable).values([
|
|
{ id1: 1, name: 'Category 1' },
|
|
{ id1: 2, name: 'Category 2' },
|
|
{
|
|
id1: 3,
|
|
name: 'Category 3',
|
|
},
|
|
{ id1: 4, name: 'Category 4' },
|
|
]);
|
|
|
|
await db.insert(coursesTable).values([
|
|
{ id1: 1, name: 'Development', categoryId: 2 },
|
|
{ id1: 2, name: 'IT & Software', categoryId: 3 },
|
|
{ id1: 3, name: 'Marketing', categoryId: 4 },
|
|
{ id1: 4, name: 'Design', categoryId: 1 },
|
|
]);
|
|
|
|
const sq2 = db
|
|
.select({
|
|
categoryId: courseCategoriesTable.id1,
|
|
category: courseCategoriesTable.name,
|
|
total: sql<number>`count(${courseCategoriesTable.id1})`,
|
|
})
|
|
.from(courseCategoriesTable)
|
|
.groupBy(courseCategoriesTable.id1, courseCategoriesTable.name)
|
|
.as('sq2');
|
|
|
|
const res = await db
|
|
.select({
|
|
courseName: coursesTable.name,
|
|
categoryId: sq2.categoryId,
|
|
})
|
|
.from(coursesTable)
|
|
.leftJoin(sq2, eq(coursesTable.categoryId, sq2.categoryId))
|
|
.orderBy(coursesTable.name);
|
|
|
|
expect(res).toEqual([
|
|
{ courseName: 'Design', categoryId: 1 },
|
|
{ courseName: 'Development', categoryId: 2 },
|
|
{ courseName: 'IT & Software', categoryId: 3 },
|
|
{ courseName: 'Marketing', categoryId: 4 },
|
|
]);
|
|
});
|
|
|
|
test('with ... select', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(orders).values([
|
|
{ region: 'Europe', product: 'A', amount: 10, quantity: 1 },
|
|
{ region: 'Europe', product: 'A', amount: 20, quantity: 2 },
|
|
{ region: 'Europe', product: 'B', amount: 20, quantity: 2 },
|
|
{ region: 'Europe', product: 'B', amount: 30, quantity: 3 },
|
|
{ region: 'US', product: 'A', amount: 30, quantity: 3 },
|
|
{ region: 'US', product: 'A', amount: 40, quantity: 4 },
|
|
{ region: 'US', product: 'B', amount: 40, quantity: 4 },
|
|
{ region: 'US', product: 'B', amount: 50, quantity: 5 },
|
|
]);
|
|
|
|
const regionalSales = db.$with('regional_sales').as(
|
|
db
|
|
.select({
|
|
region: orders.region,
|
|
totalSales: sql<number>`sum(${orders.amount})`.as('total_sales'),
|
|
})
|
|
.from(orders)
|
|
.groupBy(orders.region),
|
|
);
|
|
|
|
const topRegions = db.$with('top_regions').as(
|
|
db
|
|
.select({
|
|
region: regionalSales.region,
|
|
})
|
|
.from(regionalSales)
|
|
.where(
|
|
gt(
|
|
regionalSales.totalSales,
|
|
db.select({ sales: sql`sum(${regionalSales.totalSales})/10` }).from(regionalSales),
|
|
),
|
|
),
|
|
);
|
|
|
|
const result1 = await db
|
|
.with(regionalSales, topRegions)
|
|
.select({
|
|
region: orders.region,
|
|
product: orders.product,
|
|
productUnits: sql<number>`sum(${orders.quantity})::int`,
|
|
productSales: sql<number>`sum(${orders.amount})::int`,
|
|
})
|
|
.from(orders)
|
|
.where(inArray(orders.region, db.select({ region: topRegions.region }).from(topRegions)))
|
|
.groupBy(orders.region, orders.product)
|
|
.orderBy(orders.region, orders.product);
|
|
const result2 = await db
|
|
.with(regionalSales, topRegions)
|
|
.selectDistinct({
|
|
region: orders.region,
|
|
product: orders.product,
|
|
productUnits: sql<number>`sum(${orders.quantity})::int`,
|
|
productSales: sql<number>`sum(${orders.amount})::int`,
|
|
})
|
|
.from(orders)
|
|
.where(inArray(orders.region, db.select({ region: topRegions.region }).from(topRegions)))
|
|
.groupBy(orders.region, orders.product)
|
|
.orderBy(orders.region, orders.product);
|
|
const result3 = await db
|
|
.with(regionalSales, topRegions)
|
|
.selectDistinctOn([orders.region], {
|
|
region: orders.region,
|
|
productUnits: sql<number>`sum(${orders.quantity})::int`,
|
|
productSales: sql<number>`sum(${orders.amount})::int`,
|
|
})
|
|
.from(orders)
|
|
.where(inArray(orders.region, db.select({ region: topRegions.region }).from(topRegions)))
|
|
.groupBy(orders.region)
|
|
.orderBy(orders.region);
|
|
|
|
expect(result1).toEqual([
|
|
{
|
|
region: 'Europe',
|
|
product: 'A',
|
|
productUnits: 3,
|
|
productSales: 30,
|
|
},
|
|
{
|
|
region: 'Europe',
|
|
product: 'B',
|
|
productUnits: 5,
|
|
productSales: 50,
|
|
},
|
|
{
|
|
region: 'US',
|
|
product: 'A',
|
|
productUnits: 7,
|
|
productSales: 70,
|
|
},
|
|
{
|
|
region: 'US',
|
|
product: 'B',
|
|
productUnits: 9,
|
|
productSales: 90,
|
|
},
|
|
]);
|
|
expect(result2).toEqual(result1);
|
|
expect(result3).toEqual([
|
|
{
|
|
region: 'Europe',
|
|
productUnits: 8,
|
|
productSales: 80,
|
|
},
|
|
{
|
|
region: 'US',
|
|
productUnits: 16,
|
|
productSales: 160,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('with ... update', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const products = gelTable('products', {
|
|
id1: integer('id1'),
|
|
price: decimal('price').notNull(),
|
|
cheap: boolean('cheap').notNull().default(false),
|
|
});
|
|
|
|
await db.insert(products).values([
|
|
{ id1: 1, price: '10.99' },
|
|
{ id1: 2, price: '25.85' },
|
|
{ id1: 3, price: '32.99' },
|
|
{ id1: 4, price: '2.50' },
|
|
{ id1: 5, price: '4.59' },
|
|
]);
|
|
|
|
const averagePrice = db.$with('average_price').as(
|
|
db
|
|
.select({
|
|
value: sql`avg(${products.price})`.as('value'),
|
|
})
|
|
.from(products),
|
|
);
|
|
|
|
const result = await db
|
|
.with(averagePrice)
|
|
.update(products)
|
|
.set({
|
|
cheap: true,
|
|
})
|
|
.where(lt(products.price, sql`(select * from ${averagePrice})`))
|
|
.returning({
|
|
id1: products.id1,
|
|
});
|
|
|
|
expect(result).toEqual([{ id1: 1 }, { id1: 4 }, { id1: 5 }]);
|
|
});
|
|
|
|
test('with ... insert', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const users = gelTable('users_with_insert', {
|
|
username: text('username').notNull(),
|
|
admin: boolean('admin').notNull(),
|
|
});
|
|
|
|
const userCount = db.$with('user_count').as(
|
|
db
|
|
.select({
|
|
value: sql`count(*)`.as('value'),
|
|
})
|
|
.from(users),
|
|
);
|
|
|
|
const result = await db
|
|
.with(userCount)
|
|
.insert(users)
|
|
.values([{ username: 'user1', admin: sql`((select * from ${userCount}) = 0)` }])
|
|
.returning({
|
|
admin: users.admin,
|
|
});
|
|
|
|
expect(result).toEqual([{ admin: true }]);
|
|
});
|
|
|
|
test('with ... delete', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(orders).values([
|
|
{ id1: 1, region: 'Europe', product: 'A', amount: 10, quantity: 1 },
|
|
{ id1: 2, region: 'Europe', product: 'A', amount: 20, quantity: 2 },
|
|
{ id1: 3, region: 'Europe', product: 'B', amount: 20, quantity: 2 },
|
|
{ id1: 4, region: 'Europe', product: 'B', amount: 30, quantity: 3 },
|
|
{ id1: 5, region: 'US', product: 'A', amount: 30, quantity: 3 },
|
|
{ id1: 6, region: 'US', product: 'A', amount: 40, quantity: 4 },
|
|
{ id1: 7, region: 'US', product: 'B', amount: 40, quantity: 4 },
|
|
{ id1: 8, region: 'US', product: 'B', amount: 50, quantity: 5 },
|
|
]);
|
|
|
|
const averageAmount = db.$with('average_amount').as(
|
|
db
|
|
.select({
|
|
value: sql`avg(${orders.amount})`.as('value'),
|
|
})
|
|
.from(orders),
|
|
);
|
|
|
|
const result = await db
|
|
.with(averageAmount)
|
|
.delete(orders)
|
|
.where(gt(orders.amount, sql`(select * from ${averageAmount})`))
|
|
.returning({
|
|
id1: orders.id1,
|
|
});
|
|
|
|
expect(result).toEqual([{ id1: 6 }, { id1: 7 }, { id1: 8 }]);
|
|
});
|
|
|
|
test('select from subquery sql', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(users3Table).values([
|
|
{ id1: 1, name: 'John' },
|
|
{ id1: 2, name: 'Jane' },
|
|
]);
|
|
|
|
const sq = db
|
|
.select({ name: sql<string>`${users3Table.name} || ' modified'`.as('name') })
|
|
.from(users3Table)
|
|
.as('sq');
|
|
|
|
const res = await db.select({ name: sq.name }).from(sq);
|
|
|
|
expect(res).toEqual([{ name: 'John modified' }, { name: 'Jane modified' }]);
|
|
});
|
|
|
|
test('select a field without joining its table', (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
expect(() => db.select({ name: users3Table.name }).from(usersTable).prepare('query')).toThrowError();
|
|
});
|
|
|
|
test('select all fields from subquery without alias', (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const sq = db.$with('sq').as(db.select({ name: sql<string>`upper(${users3Table.name})` }).from(users3Table));
|
|
|
|
expect(() => db.select().from(sq).prepare('query')).toThrowError();
|
|
});
|
|
|
|
test('select count()', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values([
|
|
{ id1: 1, name: 'John' },
|
|
{ id1: 2, name: 'Jane' },
|
|
]);
|
|
|
|
const res = await db.select({ count: sql`count(*)` }).from(usersTable);
|
|
|
|
expect(res).toEqual([{ count: 2 }]);
|
|
});
|
|
|
|
test('select count w/ custom mapper', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
function count(value: GelColumn | SQLWrapper): SQL<number>;
|
|
function count(value: GelColumn | SQLWrapper, alias: string): SQL.Aliased<number>;
|
|
function count(value: GelColumn | SQLWrapper, alias?: string): SQL<number> | SQL.Aliased<number> {
|
|
const result = sql`count(${value})`.mapWith(Number);
|
|
if (!alias) {
|
|
return result;
|
|
}
|
|
return result.as(alias);
|
|
}
|
|
|
|
await db.insert(usersTable).values([
|
|
{ id1: 1, name: 'John' },
|
|
{ id1: 2, name: 'Jane' },
|
|
]);
|
|
|
|
const res = await db.select({ count: count(sql`*`) }).from(usersTable);
|
|
|
|
expect(res).toEqual([{ count: 2 }]);
|
|
});
|
|
|
|
test('array types', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const values: (typeof salEmp.$inferSelect)[] = [
|
|
{
|
|
name: 'John',
|
|
payByQuarter: [10000, 10000, 10000, 10000],
|
|
},
|
|
{
|
|
name: 'Carol',
|
|
payByQuarter: [20000, 25000, 25000, 25000],
|
|
},
|
|
];
|
|
|
|
await db.insert(salEmp).values(values);
|
|
|
|
const res = await db.select().from(salEmp);
|
|
|
|
expect(res.map((it) => ({ ...it, id: undefined }))).toEqual(values);
|
|
});
|
|
|
|
test('select for ...', (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
{
|
|
const query = db.select().from(users3Table).for('update').toSQL();
|
|
|
|
expect(query.sql).toMatch(/ for update$/);
|
|
}
|
|
|
|
{
|
|
const query = db
|
|
.select()
|
|
.from(users2Table)
|
|
.for('update', { of: [users3Table, coursesTable] })
|
|
.toSQL();
|
|
|
|
expect(query.sql).toMatch(/ for update of "users3", "courses"$/);
|
|
}
|
|
|
|
{
|
|
const query = db.select().from(users3Table).for('no key update', { of: users3Table }).toSQL();
|
|
|
|
expect(query.sql).toMatch(/for no key update of "users3"$/);
|
|
}
|
|
|
|
{
|
|
const query = db.select().from(users3Table).for('no key update', { of: users3Table, skipLocked: true })
|
|
.toSQL();
|
|
|
|
expect(query.sql).toMatch(/ for no key update of "users3" skip locked$/);
|
|
}
|
|
|
|
{
|
|
const query = db.select().from(users3Table).for('share', { of: users3Table, noWait: true }).toSQL();
|
|
|
|
expect(query.sql).toMatch(/for share of "users3" nowait$/);
|
|
}
|
|
});
|
|
|
|
// TODO
|
|
// column "rel~1.0e3b7152-d977-11ef-a173-530b4c6088b1" must appear in the GROUP BY
|
|
test.todo('having', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(citiesTable).values([
|
|
{ id1: 1, name: 'London' },
|
|
{ id1: 2, name: 'Paris' },
|
|
{
|
|
id1: 3,
|
|
name: 'New York',
|
|
},
|
|
]);
|
|
|
|
await db.insert(users2Table).values([
|
|
{ id1: 1, name: 'John', cityId: 1 },
|
|
{ id1: 2, name: 'Jane', cityId: 1 },
|
|
{
|
|
id1: 3,
|
|
name: 'Jack',
|
|
cityId: 2,
|
|
},
|
|
]);
|
|
|
|
const result = await db
|
|
.select({
|
|
id1: citiesTable.id1,
|
|
name: sql<string>`upper(${citiesTable.name})`.as('upper_name'),
|
|
usersCount: sql<number>`count(${users2Table.id1})::int`.as('users_count'),
|
|
})
|
|
.from(citiesTable)
|
|
.leftJoin(users2Table, eq(users2Table.cityId, citiesTable.id1))
|
|
.where(({ name }) => sql`length(${name}) >= 3`)
|
|
.groupBy(citiesTable.id1)
|
|
.having(({ usersCount }) => sql`${usersCount} > 0`)
|
|
.orderBy(({ name }) => name);
|
|
|
|
expect(result).toEqual([
|
|
{
|
|
id1: 1,
|
|
name: 'LONDON',
|
|
usersCount: 2,
|
|
},
|
|
{
|
|
id1: 2,
|
|
name: 'PARIS',
|
|
usersCount: 1,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('select from raw sql', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const result = await db
|
|
.select({
|
|
id: sql<number>`id`,
|
|
name: sql<string>`name`,
|
|
})
|
|
.from(sql`(select 1 as id, 'John' as name) as users`);
|
|
|
|
Expect<Equal<{ id: number; name: string }[], typeof result>>;
|
|
expect(result).toEqual([{ id: 1, name: 'John' }]);
|
|
});
|
|
|
|
test('select from raw sql with joins', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const result = await db
|
|
.select({
|
|
id: sql<number>`users.id`,
|
|
name: sql<string>`users.name`,
|
|
userCity: sql<string>`users.city`,
|
|
cityName: sql<string>`cities.name`,
|
|
})
|
|
.from(sql`(select 1 as id, 'John' as name, 'New York' as city) as users`)
|
|
.leftJoin(sql`(select 1 as id, 'Paris' as name) as cities`, sql`cities.id = users.id`);
|
|
|
|
Expect<Equal<{ id: number; name: string; userCity: string; cityName: string }[], typeof result>>;
|
|
|
|
expect(result).toEqual([{ id: 1, name: 'John', userCity: 'New York', cityName: 'Paris' }]);
|
|
});
|
|
|
|
test('join on aliased sql from select', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const result = await db
|
|
.select({
|
|
userId: sql<number>`users.id`.as('userId'),
|
|
name: sql<string>`users.name`,
|
|
userCity: sql<string>`users.city`,
|
|
cityId: sql<number>`cities.id`.as('cityId'),
|
|
cityName: sql<string>`cities.name`,
|
|
})
|
|
.from(sql`(select 1 as id, 'John' as name, 'New York' as city) as users`)
|
|
.leftJoin(sql`(select 1 as id, 'Paris' as name) as cities`, (cols) => eq(cols.cityId, cols.userId));
|
|
|
|
Expect<
|
|
Equal<{ userId: number; name: string; userCity: string; cityId: number; cityName: string }[], typeof result>
|
|
>;
|
|
|
|
expect(result).toEqual([{ userId: 1, name: 'John', userCity: 'New York', cityId: 1, cityName: 'Paris' }]);
|
|
});
|
|
|
|
test('join on aliased sql from with clause', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const users = db.$with('users').as(
|
|
db
|
|
.select({
|
|
id: sql<number>`id`.as('userId'),
|
|
name: sql<string>`name`.as('userName'),
|
|
city: sql<string>`city`.as('city'),
|
|
})
|
|
.from(sql`(select 1 as id, 'John' as name, 'New York' as city) as users`),
|
|
);
|
|
|
|
const cities = db.$with('cities').as(
|
|
db
|
|
.select({
|
|
id: sql<number>`id`.as('cityId'),
|
|
name: sql<string>`name`.as('cityName'),
|
|
})
|
|
.from(sql`(select 1 as id, 'Paris' as name) as cities`),
|
|
);
|
|
|
|
const result = await db
|
|
.with(users, cities)
|
|
.select({
|
|
userId: users.id,
|
|
name: users.name,
|
|
userCity: users.city,
|
|
cityId: cities.id,
|
|
cityName: cities.name,
|
|
})
|
|
.from(users)
|
|
.leftJoin(cities, (cols) => eq(cols.cityId, cols.userId));
|
|
|
|
Expect<
|
|
Equal<{ userId: number; name: string; userCity: string; cityId: number; cityName: string }[], typeof result>
|
|
>;
|
|
|
|
expect(result).toEqual([{ userId: 1, name: 'John', userCity: 'New York', cityId: 1, cityName: 'Paris' }]);
|
|
});
|
|
|
|
test('prefixed table', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const gelTable = gelTableCreator((name) => `myprefix_${name}`);
|
|
|
|
const users = gelTable('test_prefixed_table_with_unique_name', {
|
|
id1: integer('id1').primaryKey(),
|
|
name: text('name').notNull(),
|
|
});
|
|
|
|
await db.insert(users).values({ id1: 1, name: 'John' });
|
|
|
|
const result = await db.select().from(users);
|
|
|
|
expect(result.map((it) => ({ ...it, id: undefined }))).toEqual([{ id1: 1, name: 'John' }]);
|
|
});
|
|
|
|
test('all date and time columns', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const table = gelTable('dates_column', {
|
|
datetimeColumn: timestamptz().notNull(),
|
|
local_datetimeColumn: timestamp().notNull(),
|
|
local_dateColumn: localDate().notNull(),
|
|
local_timeColumn: localTime().notNull(),
|
|
|
|
durationColumn: duration().notNull(),
|
|
relative_durationColumn: relDuration().notNull(),
|
|
dateDurationColumn: dateDuration().notNull(),
|
|
});
|
|
|
|
await db.insert(table).values({
|
|
datetimeColumn: new Date('2022-01-01T00:00:00.123Z'),
|
|
local_datetimeColumn: new LocalDateTime(2014, 2, 1, 4, 1, 6, 2, 0, 0),
|
|
local_dateColumn: new LocalDate(2013, 2, 1),
|
|
local_timeColumn: new LocalTime(12, 42, 2, 3, 1, 0),
|
|
durationColumn: new Duration(0, 0, 0, 0, 12, 3, 0, 0, 1, 3),
|
|
relative_durationColumn: new RelativeDuration(2014, 2, 1, 4, 1, 6, 2, 0, 0),
|
|
dateDurationColumn: new DateDuration(2032, 2, 1, 5),
|
|
});
|
|
|
|
const result = await db.select().from(table);
|
|
|
|
Expect<
|
|
Equal<
|
|
{
|
|
datetimeColumn: Date;
|
|
local_datetimeColumn: LocalDateTime;
|
|
local_dateColumn: LocalDate;
|
|
local_timeColumn: LocalTime;
|
|
durationColumn: Duration;
|
|
relative_durationColumn: RelativeDuration;
|
|
dateDurationColumn: DateDuration;
|
|
}[],
|
|
typeof result
|
|
>
|
|
>;
|
|
|
|
Expect<
|
|
Equal<
|
|
{
|
|
datetimeColumn: Date;
|
|
local_datetimeColumn: LocalDateTime;
|
|
local_dateColumn: LocalDate;
|
|
local_timeColumn: LocalTime;
|
|
durationColumn: Duration;
|
|
relative_durationColumn: RelativeDuration;
|
|
dateDurationColumn: DateDuration;
|
|
},
|
|
typeof table.$inferInsert
|
|
>
|
|
>;
|
|
});
|
|
|
|
test('orderBy with aliased column', (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const query = db
|
|
.select({
|
|
test: sql`something`.as('test'),
|
|
})
|
|
.from(users3Table)
|
|
.orderBy((fields) => fields.test)
|
|
.toSQL();
|
|
|
|
expect(query.sql).toBe('select something as "test" from "users3" order by "test"');
|
|
});
|
|
|
|
test('select from sql', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const metricEntry = gelTable('metric_entry', {
|
|
id1: gelUuid('id1').notNull(),
|
|
createdAt: timestamptz('created_at').notNull(),
|
|
});
|
|
|
|
const metricId = uuidV4();
|
|
|
|
const intervals = db.$with('intervals').as(
|
|
db
|
|
.select({
|
|
startTime: sql<string>`(date'2023-03-01'+ x * '1 day'::interval)`.as('start_time'),
|
|
endTime: sql<string>`(date'2023-03-01'+ (x+1) *'1 day'::interval)`.as('end_time'),
|
|
})
|
|
.from(sql`generate_series(0, 29, 1) as t(x)`),
|
|
);
|
|
|
|
const func = () =>
|
|
db
|
|
.with(intervals)
|
|
.select({
|
|
startTime: intervals.startTime,
|
|
endTime: intervals.endTime,
|
|
count: sql<number>`count(${metricEntry})`,
|
|
})
|
|
.from(metricEntry)
|
|
.rightJoin(
|
|
intervals,
|
|
and(
|
|
eq(metricEntry.id1, metricId),
|
|
gte(metricEntry.createdAt, intervals.startTime),
|
|
lt(metricEntry.createdAt, intervals.endTime),
|
|
),
|
|
)
|
|
.groupBy(intervals.startTime, intervals.endTime)
|
|
.orderBy(asc(intervals.startTime));
|
|
|
|
await expect(
|
|
(async () => {
|
|
func();
|
|
})(),
|
|
).resolves.not.toThrowError();
|
|
});
|
|
|
|
test('transaction', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const users = gelTable('users_transactions', {
|
|
id1: integer('id1').notNull(),
|
|
balance: integer('balance').notNull(),
|
|
});
|
|
const products = gelTable('products_transactions', {
|
|
id1: integer('id1').notNull(),
|
|
price: integer('price').notNull(),
|
|
stock: integer('stock').notNull(),
|
|
});
|
|
|
|
const user = await db
|
|
.insert(users)
|
|
.values({ id1: 1, balance: 100 })
|
|
.returning()
|
|
.then((rows) => rows[0]!);
|
|
const product = await db
|
|
.insert(products)
|
|
.values({ id1: 1, price: 10, stock: 10 })
|
|
.returning()
|
|
.then((rows) => rows[0]!);
|
|
|
|
await db.transaction(async (tx) => {
|
|
await tx
|
|
.update(users)
|
|
.set({ balance: user.balance - product.price })
|
|
.where(eq(users.id1, user.id1));
|
|
await tx
|
|
.update(products)
|
|
.set({ stock: product.stock - 1 })
|
|
.where(eq(products.id1, product.id1));
|
|
});
|
|
|
|
const result = await db.select().from(users);
|
|
|
|
expect(result).toEqual([{ id1: 1, balance: 90 }]);
|
|
});
|
|
|
|
test('transaction rollback', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const users = gelTable('users_transactions_rollback', {
|
|
id1: integer('id1').notNull(),
|
|
balance: integer('balance').notNull(),
|
|
});
|
|
|
|
await expect(
|
|
(async () => {
|
|
await db.transaction(async (tx) => {
|
|
await tx.insert(users).values({ id1: 1, balance: 100 });
|
|
tx.rollback();
|
|
});
|
|
})(),
|
|
).rejects.toThrowError(Error);
|
|
|
|
const result = await db.select().from(users);
|
|
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
test('join subquery with join', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const internalStaff = gelTable('internal_staff', {
|
|
userId: integer('userId').notNull(),
|
|
});
|
|
|
|
const customUser = gelTable('custom_user', {
|
|
id1: integer('id1').notNull(),
|
|
});
|
|
|
|
const ticket = gelTable('ticket', {
|
|
staffId: integer('staffId').notNull(),
|
|
});
|
|
|
|
await db.insert(internalStaff).values({ userId: 1 });
|
|
await db.insert(customUser).values({ id1: 1 });
|
|
await db.insert(ticket).values({ staffId: 1 });
|
|
|
|
const subq = db.select().from(internalStaff).leftJoin(customUser, eq(internalStaff.userId, customUser.id1)).as(
|
|
'internal_staff',
|
|
);
|
|
|
|
const mainQuery = await db.select().from(ticket).leftJoin(subq, eq(subq.internal_staff.userId, ticket.staffId));
|
|
|
|
expect(mainQuery).toEqual([
|
|
{
|
|
ticket: { staffId: 1 },
|
|
internal_staff: {
|
|
internal_staff: { userId: 1 },
|
|
custom_user: { id1: 1 },
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('table selection with single table', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const users = gelTable('users_with_cities', {
|
|
id1: integer('id1').notNull(),
|
|
name: text('name').notNull(),
|
|
cityId: integer('cityId').notNull(),
|
|
});
|
|
|
|
await db.insert(users).values({ id1: 1, name: 'John', cityId: 1 });
|
|
|
|
const result = await db.select({ users }).from(users);
|
|
|
|
expect(result).toEqual([{ users: { id1: 1, name: 'John', cityId: 1 } }]);
|
|
});
|
|
|
|
test('set null to json field', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const result = await db.insert(usersTable).values({ id1: 1, name: 'Alex', json: null }).returning();
|
|
|
|
expect(result.map((it) => ({ ...it, verified: undefined, createdAt: undefined }))).toEqual([
|
|
{
|
|
id1: 1,
|
|
name: 'Alex',
|
|
json: null,
|
|
verified: undefined,
|
|
createdAt: undefined,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('insert undefined', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const users = gelTable('users_with_undefined', {
|
|
id1: integer('id1').notNull(),
|
|
name: text('name'),
|
|
});
|
|
|
|
await expect(
|
|
(async () => {
|
|
await db.insert(users).values({ id1: 1, name: undefined });
|
|
})(),
|
|
).resolves.not.toThrowError();
|
|
});
|
|
|
|
test('update undefined', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const users = gelTable('users', {
|
|
id1: integer('id1').notNull(),
|
|
name: text('name'),
|
|
});
|
|
|
|
await expect(
|
|
(async () => {
|
|
await db.update(users).set({ name: undefined });
|
|
})(),
|
|
).rejects.toThrowError();
|
|
await expect(
|
|
(async () => {
|
|
db.update(users).set({ name: undefined });
|
|
})(),
|
|
).rejects.toThrowError();
|
|
});
|
|
|
|
test('array operators', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const posts = gelTable('posts', {
|
|
id1: integer('id1').notNull(),
|
|
tags: text('tags').array(),
|
|
});
|
|
|
|
await db.insert(posts).values([
|
|
{
|
|
id1: 1,
|
|
tags: ['ORM'],
|
|
},
|
|
{
|
|
id1: 2,
|
|
tags: ['Typescript'],
|
|
},
|
|
{
|
|
id1: 3,
|
|
tags: ['Typescript', 'ORM'],
|
|
},
|
|
{ id1: 4, tags: ['Typescript', 'Frontend', 'React'] },
|
|
{
|
|
id1: 5,
|
|
tags: ['Typescript', 'ORM', 'Database', 'Postgres'],
|
|
},
|
|
{
|
|
id1: 6,
|
|
tags: ['Java', 'Spring', 'OOP'],
|
|
},
|
|
]);
|
|
|
|
const contains = await db
|
|
.select({ id1: posts.id1 })
|
|
.from(posts)
|
|
.where(arrayContains(posts.tags, ['Typescript', 'ORM']));
|
|
const contained = await db
|
|
.select({ id1: posts.id1 })
|
|
.from(posts)
|
|
.where(arrayContained(posts.tags, ['Typescript', 'ORM']));
|
|
const overlaps = await db
|
|
.select({ id1: posts.id1 })
|
|
.from(posts)
|
|
.where(arrayOverlaps(posts.tags, ['Typescript', 'ORM']));
|
|
const withSubQuery = await db
|
|
.select({ id1: posts.id1 })
|
|
.from(posts)
|
|
.where(arrayContains(posts.tags, db.select({ tags: posts.tags }).from(posts).where(eq(posts.id1, 1))));
|
|
|
|
expect(contains).toEqual([{ id1: 3 }, { id1: 5 }]);
|
|
expect(contained).toEqual([{ id1: 1 }, { id1: 2 }, { id1: 3 }]);
|
|
expect(overlaps).toEqual([{ id1: 1 }, { id1: 2 }, { id1: 3 }, { id1: 4 }, { id1: 5 }]);
|
|
expect(withSubQuery).toEqual([{ id1: 1 }, { id1: 3 }, { id1: 5 }]);
|
|
});
|
|
|
|
test('set operations (union) from query builder with subquery', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await setupSetOperationTest(db);
|
|
|
|
const sq = db
|
|
.select({ id: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).as('sq');
|
|
|
|
const result = await db
|
|
.select({ id: cities2Table.id1, name: citiesTable.name })
|
|
.from(cities2Table).union(
|
|
db.select().from(sq),
|
|
).orderBy(asc(sql`name`)).limit(2).offset(1);
|
|
|
|
expect(result).toHaveLength(2);
|
|
|
|
expect(result).toEqual([
|
|
{ id: 3, name: 'Jack' },
|
|
{ id: 2, name: 'Jane' },
|
|
]);
|
|
|
|
await expect((async () => {
|
|
db
|
|
.select({ id: cities2Table.id1, name: citiesTable.name, name2: users2Table.name })
|
|
.from(cities2Table).union(
|
|
// @ts-expect-error
|
|
db
|
|
.select({ id: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table),
|
|
).orderBy(asc(sql`name`));
|
|
})()).rejects.toThrowError();
|
|
});
|
|
|
|
test('set operations (union) as function', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await setupSetOperationTest(db);
|
|
|
|
const result = await union(
|
|
db
|
|
.select({ id: cities2Table.id1, name: citiesTable.name })
|
|
.from(cities2Table).where(eq(citiesTable.id1, 1)),
|
|
db
|
|
.select({ id: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(eq(users2Table.id1, 1)),
|
|
db
|
|
.select({ id: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(eq(users2Table.id1, 1)),
|
|
).orderBy(asc(sql`name`)).limit(1).offset(1);
|
|
|
|
expect(result).toHaveLength(1);
|
|
|
|
expect(result).toEqual([
|
|
{ id: 1, name: 'New York' },
|
|
]);
|
|
|
|
await expect((async () => {
|
|
union(
|
|
db
|
|
.select({ name: citiesTable.name, id: cities2Table.id1 })
|
|
.from(cities2Table).where(eq(citiesTable.id1, 1)),
|
|
db
|
|
.select({ id: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(eq(users2Table.id1, 1)),
|
|
db
|
|
.select({ id: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(eq(users2Table.id1, 1)),
|
|
).orderBy(asc(sql`name`));
|
|
})()).rejects.toThrowError();
|
|
});
|
|
|
|
test('set operations (union all) from query builder', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await setupSetOperationTest(db);
|
|
|
|
const result = await db
|
|
.select({ id1: cities2Table.id1, name: citiesTable.name })
|
|
.from(cities2Table).limit(2).unionAll(
|
|
db
|
|
.select({ id1: cities2Table.id1, name: citiesTable.name })
|
|
.from(cities2Table).limit(2),
|
|
).orderBy(asc(sql`id1`));
|
|
|
|
expect(result).toHaveLength(4);
|
|
|
|
expect(result).toEqual([
|
|
{ id1: 1, name: 'New York' },
|
|
{ id1: 1, name: 'New York' },
|
|
{ id1: 2, name: 'London' },
|
|
{ id1: 2, name: 'London' },
|
|
]);
|
|
|
|
await expect((async () => {
|
|
db
|
|
.select({ id1: cities2Table.id1, name: citiesTable.name })
|
|
.from(cities2Table).limit(2).unionAll(
|
|
db
|
|
.select({ name: citiesTable.name, id1: cities2Table.id1 })
|
|
.from(cities2Table).limit(2),
|
|
).orderBy(asc(sql`id1`));
|
|
})()).rejects.toThrowError();
|
|
});
|
|
|
|
test('set operations (union all) as function', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await setupSetOperationTest(db);
|
|
|
|
const result = await unionAll(
|
|
db
|
|
.select({ id: cities2Table.id1, name: citiesTable.name })
|
|
.from(cities2Table).where(eq(citiesTable.id1, 1)),
|
|
db
|
|
.select({ id: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(eq(users2Table.id1, 1)),
|
|
db
|
|
.select({ id: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(eq(users2Table.id1, 1)),
|
|
);
|
|
|
|
expect(result).toHaveLength(3);
|
|
|
|
expect(result).toEqual([
|
|
{ id: 1, name: 'New York' },
|
|
{ id: 1, name: 'John' },
|
|
{ id: 1, name: 'John' },
|
|
]);
|
|
|
|
await expect((async () => {
|
|
unionAll(
|
|
db
|
|
.select({ id: cities2Table.id1, name: citiesTable.name })
|
|
.from(cities2Table).where(eq(citiesTable.id1, 1)),
|
|
db
|
|
.select({ name: users2Table.name, id: users2Table.id1 })
|
|
.from(users2Table).where(eq(users2Table.id1, 1)),
|
|
db
|
|
.select({ id: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(eq(users2Table.id1, 1)),
|
|
);
|
|
})()).rejects.toThrowError();
|
|
});
|
|
|
|
test('set operations (intersect) from query builder', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await setupSetOperationTest(db);
|
|
|
|
const result = await db
|
|
.select({ id: cities2Table.id1, name: citiesTable.name })
|
|
.from(cities2Table).intersect(
|
|
db
|
|
.select({ id: cities2Table.id1, name: citiesTable.name })
|
|
.from(cities2Table).where(gt(citiesTable.id1, 1)),
|
|
).orderBy(asc(sql`name`));
|
|
|
|
expect(result).toHaveLength(2);
|
|
|
|
expect(result).toEqual([
|
|
{ id: 2, name: 'London' },
|
|
{ id: 3, name: 'Tampa' },
|
|
]);
|
|
|
|
await expect((async () => {
|
|
db
|
|
.select({ id: cities2Table.id1, name: citiesTable.name })
|
|
.from(cities2Table).intersect(
|
|
// @ts-expect-error
|
|
db
|
|
.select({ id: cities2Table.id1, name: citiesTable.name, id2: cities2Table.id1 })
|
|
.from(cities2Table).where(gt(citiesTable.id1, 1)),
|
|
).orderBy(asc(sql`name`));
|
|
})()).rejects.toThrowError();
|
|
});
|
|
|
|
test('set operations (intersect) as function', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await setupSetOperationTest(db);
|
|
|
|
const result = await intersect(
|
|
db
|
|
.select({ id: cities2Table.id1, name: citiesTable.name })
|
|
.from(cities2Table).where(eq(citiesTable.id1, 1)),
|
|
db
|
|
.select({ id: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(eq(users2Table.id1, 1)),
|
|
db
|
|
.select({ id: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(eq(users2Table.id1, 1)),
|
|
);
|
|
|
|
expect(result).toHaveLength(0);
|
|
|
|
expect(result).toEqual([]);
|
|
|
|
await expect((async () => {
|
|
intersect(
|
|
db
|
|
.select({ id: cities2Table.id1, name: citiesTable.name })
|
|
.from(cities2Table).where(eq(citiesTable.id1, 1)),
|
|
db
|
|
.select({ id: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(eq(users2Table.id1, 1)),
|
|
db
|
|
.select({ name: users2Table.name, id: users2Table.id1 })
|
|
.from(users2Table).where(eq(users2Table.id1, 1)),
|
|
);
|
|
})()).rejects.toThrowError();
|
|
});
|
|
|
|
test('set operations (intersect all) from query builder', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await setupSetOperationTest(db);
|
|
|
|
const result = await db
|
|
.select({ id1: cities2Table.id1, name: citiesTable.name })
|
|
.from(cities2Table).limit(2).intersectAll(
|
|
db
|
|
.select({ id1: cities2Table.id1, name: citiesTable.name })
|
|
.from(cities2Table).limit(2),
|
|
).orderBy(asc(sql`id1`));
|
|
|
|
expect(result).toHaveLength(2);
|
|
|
|
expect(result).toEqual([
|
|
{ id1: 1, name: 'New York' },
|
|
{ id1: 2, name: 'London' },
|
|
]);
|
|
|
|
await expect((async () => {
|
|
db
|
|
.select({ id: cities2Table.id1, name: citiesTable.name })
|
|
.from(cities2Table).limit(2).intersectAll(
|
|
db
|
|
.select({ name: users2Table.name, id: users2Table.id1 })
|
|
.from(cities2Table).limit(2),
|
|
).orderBy(asc(sql`id`));
|
|
})()).rejects.toThrowError();
|
|
});
|
|
|
|
test('set operations (intersect all) as function', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await setupSetOperationTest(db);
|
|
|
|
const result = await intersectAll(
|
|
db
|
|
.select({ id: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(eq(users2Table.id1, 1)),
|
|
db
|
|
.select({ id: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(eq(users2Table.id1, 1)),
|
|
db
|
|
.select({ id: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(eq(users2Table.id1, 1)),
|
|
);
|
|
|
|
expect(result).toHaveLength(1);
|
|
|
|
expect(result).toEqual([
|
|
{ id: 1, name: 'John' },
|
|
]);
|
|
|
|
await expect((async () => {
|
|
intersectAll(
|
|
db
|
|
.select({ id: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(eq(users2Table.id1, 1)),
|
|
db
|
|
.select({ name: users2Table.name, id: users2Table.id1 })
|
|
.from(users2Table).where(eq(users2Table.id1, 1)),
|
|
db
|
|
.select({ id: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(eq(users2Table.id1, 1)),
|
|
);
|
|
})()).rejects.toThrowError();
|
|
});
|
|
|
|
test('set operations (except) from query builder', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await setupSetOperationTest(db);
|
|
|
|
const result = await db
|
|
.select()
|
|
.from(cities2Table).except(
|
|
db
|
|
.select()
|
|
.from(cities2Table).where(gt(citiesTable.id1, 1)),
|
|
);
|
|
|
|
expect(result).toHaveLength(1);
|
|
|
|
expect(result).toEqual([
|
|
{ id1: 1, name: 'New York' },
|
|
]);
|
|
|
|
await expect((async () => {
|
|
db
|
|
.select()
|
|
.from(cities2Table).except(
|
|
db
|
|
.select({ name: users2Table.name, id1: users2Table.id1 })
|
|
.from(cities2Table).where(gt(citiesTable.id1, 1)),
|
|
);
|
|
})()).rejects.toThrowError();
|
|
});
|
|
|
|
test('set operations (except) as function', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await setupSetOperationTest(db);
|
|
|
|
const result = await except(
|
|
db
|
|
.select({ id1: cities2Table.id1, name: citiesTable.name })
|
|
.from(cities2Table),
|
|
db
|
|
.select({ id1: cities2Table.id1, name: citiesTable.name })
|
|
.from(cities2Table).where(eq(citiesTable.id1, 1)),
|
|
db
|
|
.select({ id1: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(eq(users2Table.id1, 1)),
|
|
).orderBy(asc(sql`id1`));
|
|
|
|
expect(result).toHaveLength(2);
|
|
|
|
expect(result).toEqual([
|
|
{ id1: 2, name: 'London' },
|
|
{ id1: 3, name: 'Tampa' },
|
|
]);
|
|
|
|
await expect((async () => {
|
|
except(
|
|
db
|
|
.select({ id1: cities2Table.id1, name: citiesTable.name })
|
|
.from(cities2Table),
|
|
db
|
|
.select({ name: users2Table.name, id1: users2Table.id1 })
|
|
.from(cities2Table).where(eq(citiesTable.id1, 1)),
|
|
db
|
|
.select({ id1: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(eq(users2Table.id1, 1)),
|
|
).orderBy(asc(sql`id1`));
|
|
})()).rejects.toThrowError();
|
|
});
|
|
|
|
test('set operations (except all) from query builder', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await setupSetOperationTest(db);
|
|
|
|
const result = await db
|
|
.select()
|
|
.from(cities2Table).exceptAll(
|
|
db
|
|
.select({ id1: cities2Table.id1, name: citiesTable.name })
|
|
.from(cities2Table).where(eq(citiesTable.id1, 1)),
|
|
).orderBy(asc(sql`id1`));
|
|
|
|
expect(result).toHaveLength(2);
|
|
|
|
expect(result).toEqual([
|
|
{ id1: 2, name: 'London' },
|
|
{ id1: 3, name: 'Tampa' },
|
|
]);
|
|
|
|
await expect((async () => {
|
|
db
|
|
.select({ name: cities2Table.name, id1: cities2Table.id1 })
|
|
.from(cities2Table).exceptAll(
|
|
db
|
|
.select({ id1: cities2Table.id1, name: citiesTable.name })
|
|
.from(cities2Table).where(eq(citiesTable.id1, 1)),
|
|
).orderBy(asc(sql`id1`));
|
|
})()).rejects.toThrowError();
|
|
});
|
|
|
|
test('set operations (except all) as function', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await setupSetOperationTest(db);
|
|
|
|
const result = await exceptAll(
|
|
db
|
|
.select({ id1: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table),
|
|
db
|
|
.select({ id1: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(gt(users2Table.id1, 7)),
|
|
db
|
|
.select({ id1: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(eq(users2Table.id1, 1)),
|
|
).orderBy(asc(sql`id1`)).limit(5).offset(2);
|
|
|
|
expect(result).toHaveLength(4);
|
|
|
|
expect(result).toEqual([
|
|
{ id1: 4, name: 'Peter' },
|
|
{ id1: 5, name: 'Ben' },
|
|
{ id1: 6, name: 'Jill' },
|
|
{ id1: 7, name: 'Mary' },
|
|
]);
|
|
|
|
await expect((async () => {
|
|
exceptAll(
|
|
db
|
|
.select({ name: users2Table.name, id: users2Table.id1 })
|
|
.from(users2Table),
|
|
db
|
|
.select({ id: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(gt(users2Table.id1, 7)),
|
|
db
|
|
.select({ id: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(eq(users2Table.id1, 1)),
|
|
).orderBy(asc(sql`id`));
|
|
})()).rejects.toThrowError();
|
|
});
|
|
|
|
test('set operations (mixed) from query builder with subquery', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await setupSetOperationTest(db);
|
|
const sq = db
|
|
.select()
|
|
.from(cities2Table).where(gt(citiesTable.id1, 1)).as('sq');
|
|
|
|
const result = await db
|
|
.select()
|
|
.from(cities2Table).except(
|
|
({ unionAll }) =>
|
|
unionAll(
|
|
db.select().from(sq),
|
|
db.select().from(cities2Table).where(eq(citiesTable.id1, 2)),
|
|
),
|
|
);
|
|
|
|
expect(result).toHaveLength(1);
|
|
|
|
expect(result).toEqual([
|
|
{ id1: 1, name: 'New York' },
|
|
]);
|
|
|
|
await expect((async () => {
|
|
db
|
|
.select()
|
|
.from(cities2Table).except(
|
|
({ unionAll }) =>
|
|
unionAll(
|
|
db
|
|
.select({ name: cities2Table.name, id1: cities2Table.id1 })
|
|
.from(cities2Table).where(gt(citiesTable.id1, 1)),
|
|
db.select().from(cities2Table).where(eq(citiesTable.id1, 2)),
|
|
),
|
|
);
|
|
})()).rejects.toThrowError();
|
|
});
|
|
|
|
test('set operations (mixed all) as function', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await setupSetOperationTest(db);
|
|
|
|
const result = await union(
|
|
db
|
|
.select({ id1: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(eq(users2Table.id1, 1)),
|
|
except(
|
|
db
|
|
.select({ id1: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(gte(users2Table.id1, 5)),
|
|
db
|
|
.select({ id1: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(eq(users2Table.id1, 7)),
|
|
),
|
|
db
|
|
.select().from(cities2Table).where(gt(citiesTable.id1, 1)),
|
|
).orderBy(asc(sql`id1`));
|
|
|
|
expect(result).toHaveLength(6);
|
|
|
|
expect(result).toEqual([
|
|
{ id1: 1, name: 'John' },
|
|
{ id1: 2, name: 'London' },
|
|
{ id1: 3, name: 'Tampa' },
|
|
{ id1: 5, name: 'Ben' },
|
|
{ id1: 6, name: 'Jill' },
|
|
{ id1: 8, name: 'Sally' },
|
|
]);
|
|
|
|
await expect((async () => {
|
|
union(
|
|
db
|
|
.select({ id1: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(eq(users2Table.id1, 1)),
|
|
except(
|
|
db
|
|
.select({ id1: users2Table.id1, name: users2Table.name })
|
|
.from(users2Table).where(gte(users2Table.id1, 5)),
|
|
db
|
|
.select({ name: users2Table.name, id1: users2Table.id1 })
|
|
.from(users2Table).where(eq(users2Table.id1, 7)),
|
|
),
|
|
db
|
|
.select().from(cities2Table).where(gt(citiesTable.id1, 1)),
|
|
).orderBy(asc(sql`id`));
|
|
})()).rejects.toThrowError();
|
|
});
|
|
test('aggregate function: count', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
const table = aggregateTable;
|
|
await setupAggregateFunctionsTest(db);
|
|
|
|
const result1 = await db.select({ value: count() }).from(table);
|
|
const result2 = await db.select({ value: count(table.a) }).from(table);
|
|
const result3 = await db.select({ value: countDistinct(table.name) }).from(table);
|
|
|
|
expect(result1[0]?.value).toBe(7);
|
|
expect(result2[0]?.value).toBe(5);
|
|
expect(result3[0]?.value).toBe(6);
|
|
});
|
|
|
|
test('aggregate function: avg', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
const table = aggregateTable;
|
|
await setupAggregateFunctionsTest(db);
|
|
|
|
const result1 = await db.select({ value: avg(table.b) }).from(table);
|
|
const result2 = await db.select({ value: avg(table.nullOnly) }).from(table);
|
|
const result3 = await db.select({ value: avgDistinct(table.b) }).from(table);
|
|
|
|
expect(result1[0]?.value).toBe('33.3333333333333333');
|
|
expect(result2[0]?.value).toBeNull();
|
|
expect(result3[0]?.value).toBe('42.5000000000000000');
|
|
});
|
|
|
|
test('aggregate function: sum', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
const table = aggregateTable;
|
|
await setupAggregateFunctionsTest(db);
|
|
|
|
const result1 = await db.select({ value: sum(table.b) }).from(table);
|
|
const result2 = await db.select({ value: sum(table.nullOnly) }).from(table);
|
|
const result3 = await db.select({ value: sumDistinct(table.b) }).from(table);
|
|
|
|
expect(result1[0]?.value).toBe('200');
|
|
expect(result2[0]?.value).toBeNull();
|
|
expect(result3[0]?.value).toBe('170');
|
|
});
|
|
|
|
test('aggregate function: max', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
const table = aggregateTable;
|
|
await setupAggregateFunctionsTest(db);
|
|
|
|
const result1 = await db.select({ value: max(table.b) }).from(table);
|
|
const result2 = await db.select({ value: max(table.nullOnly) }).from(table);
|
|
|
|
expect(result1[0]?.value).toBe(90);
|
|
expect(result2[0]?.value).toBeNull();
|
|
});
|
|
|
|
test('aggregate function: min', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
const table = aggregateTable;
|
|
await setupAggregateFunctionsTest(db);
|
|
|
|
const result1 = await db.select({ value: min(table.b) }).from(table);
|
|
const result2 = await db.select({ value: min(table.nullOnly) }).from(table);
|
|
|
|
expect(result1[0]?.value).toBe(10);
|
|
expect(result2[0]?.value).toBeNull();
|
|
});
|
|
|
|
test('array mapping and parsing', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const arrays = gelTable('arrays_tests', {
|
|
id1: integer('id1').notNull(),
|
|
tags: text('tags').array(),
|
|
numbers: integer('numbers').notNull().array(),
|
|
});
|
|
|
|
await db.insert(arrays).values({
|
|
id1: 1,
|
|
tags: ['', 'b', 'c'],
|
|
numbers: [1, 2, 3],
|
|
});
|
|
|
|
const result = await db.select().from(arrays);
|
|
|
|
expect(result).toEqual([
|
|
{
|
|
id1: 1,
|
|
tags: ['', 'b', 'c'],
|
|
numbers: [1, 2, 3],
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('test $onUpdateFn and $onUpdate works as $default', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersOnUpdate).values([
|
|
{ id1: 1, name: 'John' },
|
|
{ id1: 2, name: 'Jane' },
|
|
{
|
|
id1: 3,
|
|
name: 'Jack',
|
|
},
|
|
{ id1: 4, name: 'Jill' },
|
|
]);
|
|
|
|
// const { updatedAt, ..._ } = getTableColumns(usersOnUpdate);
|
|
|
|
// const justDates = await db.select({ updatedAt }).from(usersOnUpdate).orderBy(asc(usersOnUpdate.id1));
|
|
|
|
const response = await db.select(getTableColumns(usersOnUpdate)).from(usersOnUpdate).orderBy(
|
|
asc(usersOnUpdate.id1),
|
|
);
|
|
|
|
expect(response.map((it) => ({ ...it, updatedAt: undefined }))).toEqual([
|
|
{ name: 'John', id1: 1, updateCounter: 1, alwaysNull: null, updatedAt: undefined },
|
|
{ name: 'Jane', id1: 2, updateCounter: 1, alwaysNull: null, updatedAt: undefined },
|
|
{ name: 'Jack', id1: 3, updateCounter: 1, alwaysNull: null, updatedAt: undefined },
|
|
{ name: 'Jill', id1: 4, updateCounter: 1, alwaysNull: null, updatedAt: undefined },
|
|
]);
|
|
|
|
// const msDelay = 250;
|
|
|
|
// for (const eachUser of justDates) {
|
|
// expect(eachUser.updatedAt!.valueOf()).toBeGreaterThan(Date.now() - msDelay);
|
|
// }
|
|
});
|
|
|
|
test('test $onUpdateFn and $onUpdate works updating', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersOnUpdate).values([
|
|
{ id1: 1, name: 'John', alwaysNull: 'this will be null after updating' },
|
|
{ id1: 2, name: 'Jane' },
|
|
{ id1: 3, name: 'Jack' },
|
|
{ id1: 4, name: 'Jill' },
|
|
]);
|
|
|
|
const { updatedAt, ...rest } = getTableColumns(usersOnUpdate);
|
|
await db.select({ updatedAt }).from(usersOnUpdate).orderBy(asc(usersOnUpdate.id1));
|
|
|
|
await db.update(usersOnUpdate).set({ name: 'Angel' }).where(eq(usersOnUpdate.id1, 1));
|
|
await db.update(usersOnUpdate).set({ updateCounter: null }).where(eq(usersOnUpdate.id1, 2));
|
|
|
|
// const justDates = await db.select({ updatedAt: usersOnUpdate.updatedAt }).from(usersOnUpdate).orderBy(
|
|
// asc(usersOnUpdate.id1),
|
|
// );
|
|
|
|
const response = await db.select({ ...rest }).from(usersOnUpdate).orderBy(
|
|
asc(usersOnUpdate.id1),
|
|
);
|
|
|
|
expect(response).toEqual([
|
|
{ name: 'Angel', id1: 1, updateCounter: 2, alwaysNull: null },
|
|
{ name: 'Jane', id1: 2, updateCounter: null, alwaysNull: null },
|
|
{ name: 'Jack', id1: 3, updateCounter: 1, alwaysNull: null },
|
|
{ name: 'Jill', id1: 4, updateCounter: 1, alwaysNull: null },
|
|
]);
|
|
// const msDelay = 500;
|
|
|
|
// for (const eachUser of justDates) {
|
|
// expect(eachUser.updatedAt!.valueOf()).toBeGreaterThan(Date.now() - msDelay);
|
|
// }
|
|
});
|
|
|
|
test('test if method with sql operators', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const users = gelTable('users_with_age', {
|
|
id1: integer('id1').notNull(),
|
|
name: text('name').notNull(),
|
|
age: integer('age').notNull(),
|
|
city: text('city').notNull(),
|
|
});
|
|
|
|
await db.insert(users).values([
|
|
{ id1: 1, name: 'John', age: 20, city: 'New York' },
|
|
{ id1: 2, name: 'Alice', age: 21, city: 'New York' },
|
|
{ id1: 3, name: 'Nick', age: 22, city: 'London' },
|
|
{ id1: 4, name: 'Lina', age: 23, city: 'London' },
|
|
]);
|
|
|
|
const condition1 = true;
|
|
|
|
const [result1] = await db.select().from(users).where(eq(users.id1, 1).if(condition1));
|
|
|
|
expect({ ...result1, id: undefined }).toEqual({
|
|
id1: 1,
|
|
name: 'John',
|
|
age: 20,
|
|
city: 'New York',
|
|
});
|
|
|
|
const condition2 = 1;
|
|
|
|
const [result2] = await db
|
|
.select()
|
|
.from(users)
|
|
.where(sql`${users.id1} = 1`.if(condition2));
|
|
|
|
expect({ ...result2, id: undefined }).toEqual({ id1: 1, name: 'John', age: 20, city: 'New York' });
|
|
|
|
const condition3 = 'non-empty string';
|
|
|
|
const result3 = await db
|
|
.select()
|
|
.from(users)
|
|
.where(or(eq(users.id1, 1).if(condition3), eq(users.id1, 2).if(condition3)));
|
|
|
|
expect(result3.map((it) => ({ ...it, id: undefined }))).toEqual([
|
|
{ id1: 1, name: 'John', age: 20, city: 'New York' },
|
|
{
|
|
id1: 2,
|
|
name: 'Alice',
|
|
age: 21,
|
|
city: 'New York',
|
|
},
|
|
]);
|
|
|
|
const condtition4 = false;
|
|
|
|
const result4 = await db.select().from(users).where(eq(users.id1, 1).if(condtition4));
|
|
|
|
expect(result4.map((it) => ({ ...it, id: undefined }))).toEqual([
|
|
{ id1: 1, name: 'John', age: 20, city: 'New York' },
|
|
{ id1: 2, name: 'Alice', age: 21, city: 'New York' },
|
|
{ id1: 3, name: 'Nick', age: 22, city: 'London' },
|
|
{ id1: 4, name: 'Lina', age: 23, city: 'London' },
|
|
]);
|
|
|
|
const condition5 = undefined;
|
|
|
|
const result5 = await db
|
|
.select()
|
|
.from(users)
|
|
.where(sql`${users.id1} = 1`.if(condition5));
|
|
|
|
expect(result5.map((it) => ({ ...it, id: undefined }))).toEqual([
|
|
{ id1: 1, name: 'John', age: 20, city: 'New York' },
|
|
{ id1: 2, name: 'Alice', age: 21, city: 'New York' },
|
|
{ id1: 3, name: 'Nick', age: 22, city: 'London' },
|
|
{ id1: 4, name: 'Lina', age: 23, city: 'London' },
|
|
]);
|
|
|
|
const condition6 = null;
|
|
|
|
const result6 = await db
|
|
.select()
|
|
.from(users)
|
|
.where(or(eq(users.id1, 1).if(condition6), eq(users.id1, 2).if(condition6)));
|
|
|
|
expect(result6.map((it) => ({ ...it, id: undefined }))).toEqual([
|
|
{ id1: 1, name: 'John', age: 20, city: 'New York' },
|
|
{ id1: 2, name: 'Alice', age: 21, city: 'New York' },
|
|
{ id1: 3, name: 'Nick', age: 22, city: 'London' },
|
|
{ id1: 4, name: 'Lina', age: 23, city: 'London' },
|
|
]);
|
|
|
|
const condition7 = {
|
|
term1: 0,
|
|
term2: 1,
|
|
};
|
|
|
|
const result7 = await db
|
|
.select()
|
|
.from(users)
|
|
.where(and(gt(users.age, 20).if(condition7.term1), eq(users.city, 'New York').if(condition7.term2)));
|
|
|
|
expect(result7.map((it) => ({ ...it, id: undefined }))).toEqual([
|
|
{ id1: 1, name: 'John', age: 20, city: 'New York' },
|
|
{ id1: 2, name: 'Alice', age: 21, city: 'New York' },
|
|
]);
|
|
|
|
const condition8 = {
|
|
term1: '',
|
|
term2: 'non-empty string',
|
|
};
|
|
|
|
const result8 = await db
|
|
.select()
|
|
.from(users)
|
|
.where(or(lt(users.age, 21).if(condition8.term1), eq(users.city, 'London').if(condition8.term2)));
|
|
|
|
expect(result8.map((it) => ({ ...it, id: undefined }))).toEqual([
|
|
{ id1: 3, name: 'Nick', age: 22, city: 'London' },
|
|
{ id1: 4, name: 'Lina', age: 23, city: 'London' },
|
|
]);
|
|
|
|
const condition9 = {
|
|
term1: 1,
|
|
term2: true,
|
|
};
|
|
|
|
const result9 = await db
|
|
.select()
|
|
.from(users)
|
|
.where(
|
|
and(
|
|
inArray(users.city, ['New York', 'London']).if(condition9.term1),
|
|
ilike(users.name, 'a%').if(condition9.term2),
|
|
),
|
|
);
|
|
|
|
expect(result9.map((it) => ({ ...it, id: undefined }))).toEqual([
|
|
{
|
|
id1: 2,
|
|
name: 'Alice',
|
|
age: 21,
|
|
city: 'New York',
|
|
},
|
|
]);
|
|
|
|
const condition10 = {
|
|
term1: 4,
|
|
term2: 19,
|
|
};
|
|
|
|
const result10 = await db
|
|
.select()
|
|
.from(users)
|
|
.where(
|
|
and(
|
|
sql`length(${users.name}) <= ${condition10.term1}`.if(condition10.term1),
|
|
gt(users.age, condition10.term2).if(condition10.term2 > 20),
|
|
),
|
|
);
|
|
|
|
expect(result10.map((it) => ({ ...it, id: undefined }))).toEqual([
|
|
{ id1: 1, name: 'John', age: 20, city: 'New York' },
|
|
{ id1: 3, name: 'Nick', age: 22, city: 'London' },
|
|
{ id1: 4, name: 'Lina', age: 23, city: 'London' },
|
|
]);
|
|
|
|
const condition11 = true;
|
|
|
|
const result11 = await db
|
|
.select()
|
|
.from(users)
|
|
.where(or(eq(users.city, 'New York'), gte(users.age, 22))!.if(condition11));
|
|
|
|
expect(result11.map((it) => ({ ...it, id: undefined }))).toEqual([
|
|
{ id1: 1, name: 'John', age: 20, city: 'New York' },
|
|
{ id1: 2, name: 'Alice', age: 21, city: 'New York' },
|
|
{ id1: 3, name: 'Nick', age: 22, city: 'London' },
|
|
{ id1: 4, name: 'Lina', age: 23, city: 'London' },
|
|
]);
|
|
|
|
const condition12 = false;
|
|
|
|
const result12 = await db
|
|
.select()
|
|
.from(users)
|
|
.where(and(eq(users.city, 'London'), gte(users.age, 23))!.if(condition12));
|
|
|
|
expect(result12.map((it) => ({ ...it, id: undefined }))).toEqual([
|
|
{ id1: 1, name: 'John', age: 20, city: 'New York' },
|
|
{ id1: 2, name: 'Alice', age: 21, city: 'New York' },
|
|
{ id1: 3, name: 'Nick', age: 22, city: 'London' },
|
|
{ id1: 4, name: 'Lina', age: 23, city: 'London' },
|
|
]);
|
|
|
|
const condition13 = true;
|
|
|
|
const result13 = await db
|
|
.select()
|
|
.from(users)
|
|
.where(sql`(city = 'New York' or age >= 22)`.if(condition13));
|
|
|
|
expect(result13.map((it) => ({ ...it, id: undefined }))).toEqual([
|
|
{ id1: 1, name: 'John', age: 20, city: 'New York' },
|
|
{ id1: 2, name: 'Alice', age: 21, city: 'New York' },
|
|
{ id1: 3, name: 'Nick', age: 22, city: 'London' },
|
|
{ id1: 4, name: 'Lina', age: 23, city: 'London' },
|
|
]);
|
|
|
|
const condition14 = false;
|
|
|
|
const result14 = await db
|
|
.select()
|
|
.from(users)
|
|
.where(sql`(city = 'London' and age >= 23)`.if(condition14));
|
|
|
|
expect(result14.map((it) => ({ ...it, id: undefined }))).toEqual([
|
|
{ id1: 1, name: 'John', age: 20, city: 'New York' },
|
|
{ id1: 2, name: 'Alice', age: 21, city: 'New York' },
|
|
{ id1: 3, name: 'Nick', age: 22, city: 'London' },
|
|
{ id1: 4, name: 'Lina', age: 23, city: 'London' },
|
|
]);
|
|
});
|
|
|
|
// MySchema tests
|
|
test('mySchema :: select all fields', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const now = Date.now();
|
|
|
|
await db.insert(usersMySchemaTable).values({ id1: 1, name: 'John' });
|
|
const result = await db.select().from(usersMySchemaTable);
|
|
|
|
expect(result[0]!.createdAt).toBeInstanceOf(Date);
|
|
expect(Math.abs(result[0]!.createdAt.getTime() - now)).toBeLessThan(500);
|
|
expect(result).toEqual([
|
|
{
|
|
id1: 1,
|
|
name: 'John',
|
|
verified: false,
|
|
json: null,
|
|
createdAt: result[0]!.createdAt,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('mySchema :: select sql', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersMySchemaTable).values({ id1: 1, name: 'John' });
|
|
const users = await db
|
|
.select({
|
|
name: sql`upper(${usersMySchemaTable.name})`,
|
|
})
|
|
.from(usersMySchemaTable);
|
|
|
|
expect(users).toEqual([{ name: 'JOHN' }]);
|
|
});
|
|
|
|
test('mySchema :: select typed sql', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersMySchemaTable).values({ id1: 1, name: 'John' });
|
|
const users = await db
|
|
.select({
|
|
name: sql<string>`upper(${usersMySchemaTable.name})`,
|
|
})
|
|
.from(usersMySchemaTable);
|
|
|
|
expect(users).toEqual([{ name: 'JOHN' }]);
|
|
});
|
|
|
|
test('mySchema :: select distinct', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersMySchemaTable).values([
|
|
{ id1: 1, name: 'John' },
|
|
{ id1: 1, name: 'John' },
|
|
{ id1: 2, name: 'John' },
|
|
{ id1: 1, name: 'Jane' },
|
|
]);
|
|
const users1 = await db.selectDistinct().from(usersMySchemaTable).orderBy(
|
|
usersMySchemaTable.id1,
|
|
usersMySchemaTable.name,
|
|
);
|
|
const users2 = await db.selectDistinctOn([usersMySchemaTable.id1]).from(usersMySchemaTable).orderBy(
|
|
usersMySchemaTable.id1,
|
|
);
|
|
const users3 = await db.selectDistinctOn([usersMySchemaTable.name], { name: usersMySchemaTable.name }).from(
|
|
usersMySchemaTable,
|
|
).orderBy(usersMySchemaTable.name);
|
|
|
|
expect(users1.map((it) => ({ ...it, id: undefined, createdAt: undefined }))).toEqual([
|
|
{ id1: 1, name: 'Jane', id: undefined, verified: false, json: null, createdAt: undefined },
|
|
{ id1: 1, name: 'John', id: undefined, verified: false, json: null, createdAt: undefined },
|
|
{ id1: 2, name: 'John', id: undefined, verified: false, json: null, createdAt: undefined },
|
|
]);
|
|
|
|
expect(users2).toHaveLength(2);
|
|
expect(users2[0]?.id1).toBe(1);
|
|
expect(users2[1]?.id1).toBe(2);
|
|
|
|
expect(users3).toHaveLength(2);
|
|
expect(users3[0]?.name).toBe('Jane');
|
|
expect(users3[1]?.name).toBe('John');
|
|
});
|
|
|
|
test('mySchema :: insert returning sql', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const users = await db
|
|
.insert(usersMySchemaTable)
|
|
.values({ id1: 1, name: 'John' })
|
|
.returning({
|
|
name: sql`upper(${usersMySchemaTable.name})`,
|
|
});
|
|
|
|
expect(users).toEqual([{ name: 'JOHN' }]);
|
|
});
|
|
|
|
test('mySchema :: delete returning sql', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersMySchemaTable).values({ id1: 1, name: 'John' });
|
|
const users = await db
|
|
.delete(usersMySchemaTable)
|
|
.where(eq(usersMySchemaTable.name, 'John'))
|
|
.returning({
|
|
name: sql`upper(${usersMySchemaTable.name})`,
|
|
});
|
|
|
|
expect(users).toEqual([{ name: 'JOHN' }]);
|
|
});
|
|
|
|
test('mySchema :: update with returning partial', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersMySchemaTable).values({ id1: 1, name: 'John' });
|
|
const users = await db.update(usersMySchemaTable).set({ name: 'Jane' }).where(
|
|
eq(usersMySchemaTable.name, 'John'),
|
|
)
|
|
.returning({
|
|
id1: usersMySchemaTable.id1,
|
|
name: usersMySchemaTable.name,
|
|
});
|
|
|
|
expect(users).toEqual([{ id1: 1, name: 'Jane' }]);
|
|
});
|
|
|
|
test('mySchema :: delete with returning all fields', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const now = Date.now();
|
|
|
|
await db.insert(usersMySchemaTable).values({ id1: 1, name: 'John' });
|
|
const users = await db.delete(usersMySchemaTable).where(eq(usersMySchemaTable.name, 'John')).returning();
|
|
|
|
expect(users[0]!.createdAt).toBeInstanceOf(Date);
|
|
expect(Math.abs(users[0]!.createdAt.getTime() - now)).toBeLessThan(500);
|
|
expect(users).toEqual([
|
|
{
|
|
id1: 1,
|
|
name: 'John',
|
|
verified: false,
|
|
json: null,
|
|
createdAt: users[0]!.createdAt,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('mySchema :: insert + select', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersMySchemaTable).values({ id1: 1, name: 'John' });
|
|
const result = await db.select().from(usersMySchemaTable);
|
|
expect(result).toEqual([
|
|
{
|
|
id1: 1,
|
|
name: 'John',
|
|
verified: false,
|
|
json: null,
|
|
createdAt: result[0]!.createdAt,
|
|
},
|
|
]);
|
|
|
|
await db.insert(usersMySchemaTable).values({ id1: 2, name: 'Jane' });
|
|
const result2 = await db.select().from(usersMySchemaTable);
|
|
expect(result2).toEqual([
|
|
{ id1: 1, name: 'John', verified: false, json: null, createdAt: result2[0]!.createdAt },
|
|
{ id1: 2, name: 'Jane', verified: false, json: null, createdAt: result2[1]!.createdAt },
|
|
]);
|
|
});
|
|
|
|
test('mySchema :: insert with overridden default values', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersMySchemaTable).values({ id1: 1, name: 'John', verified: true });
|
|
const result = await db.select().from(usersMySchemaTable);
|
|
|
|
expect(result).toEqual([
|
|
{
|
|
id1: 1,
|
|
name: 'John',
|
|
verified: true,
|
|
json: null,
|
|
createdAt: result[0]!.createdAt,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('mySchema :: insert many', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersMySchemaTable).values([
|
|
{ id1: 1, name: 'John' },
|
|
{
|
|
id1: 2,
|
|
name: 'Bruce',
|
|
json: ['foo', 'bar'],
|
|
},
|
|
{
|
|
id1: 3,
|
|
name: 'Jane',
|
|
},
|
|
{ id1: 4, name: 'Austin', verified: true },
|
|
]);
|
|
const result = await db
|
|
.select({
|
|
id1: usersMySchemaTable.id1,
|
|
name: usersMySchemaTable.name,
|
|
json: usersMySchemaTable.json,
|
|
verified: usersMySchemaTable.verified,
|
|
})
|
|
.from(usersMySchemaTable);
|
|
|
|
expect(result).toEqual([
|
|
{ id1: 1, name: 'John', json: null, verified: false },
|
|
{ id1: 2, name: 'Bruce', json: ['foo', 'bar'], verified: false },
|
|
{ id1: 3, name: 'Jane', json: null, verified: false },
|
|
{ id1: 4, name: 'Austin', json: null, verified: true },
|
|
]);
|
|
});
|
|
|
|
test('mySchema :: select with group by as field', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersMySchemaTable).values([
|
|
{ id1: 1, name: 'John' },
|
|
{ id1: 2, name: 'Jane' },
|
|
{
|
|
id1: 3,
|
|
name: 'Jane',
|
|
},
|
|
]);
|
|
|
|
const result = await db.select({ name: usersMySchemaTable.name }).from(usersMySchemaTable).groupBy(
|
|
usersMySchemaTable.name,
|
|
);
|
|
|
|
expect(result).toEqual([{ name: 'Jane' }, { name: 'John' }]);
|
|
});
|
|
|
|
test('mySchema :: select with group by as column + sql', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersMySchemaTable).values([
|
|
{ id1: 1, name: 'John' },
|
|
{ id1: 2, name: 'Jane' },
|
|
{
|
|
id1: 3,
|
|
name: 'Jane',
|
|
},
|
|
]);
|
|
|
|
const result = await db
|
|
.select({ name: usersMySchemaTable.name })
|
|
.from(usersMySchemaTable)
|
|
.groupBy(usersMySchemaTable.id1, sql`${usersMySchemaTable.name}`);
|
|
|
|
expect(result.sort((a, b) => a.name.localeCompare(b.name))).toEqual([
|
|
{ name: 'Jane' },
|
|
{ name: 'Jane' },
|
|
{
|
|
name: 'John',
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('mySchema :: build query', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const query = db.select({ id1: usersMySchemaTable.id1, name: usersMySchemaTable.name }).from(usersMySchemaTable)
|
|
.groupBy(usersMySchemaTable.id1, usersMySchemaTable.name).toSQL();
|
|
|
|
expect(query).toEqual({
|
|
sql:
|
|
'select "mySchema"."users"."id1", "mySchema"."users"."name" from "mySchema"."users" group by "mySchema"."users"."id1", "mySchema"."users"."name"',
|
|
params: [],
|
|
});
|
|
});
|
|
|
|
test('mySchema :: partial join with alias', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
const customerAlias = alias(usersMySchemaTable, 'customer');
|
|
|
|
await db.insert(usersMySchemaTable).values([
|
|
{ id1: 10, name: 'Ivan' },
|
|
{ id1: 11, name: 'Hans' },
|
|
]);
|
|
const result = await db
|
|
.select({
|
|
user: {
|
|
id1: usersMySchemaTable.id1,
|
|
name: usersMySchemaTable.name,
|
|
},
|
|
customer: {
|
|
id1: customerAlias.id1,
|
|
name: customerAlias.name,
|
|
},
|
|
})
|
|
.from(usersMySchemaTable)
|
|
.leftJoin(customerAlias, eq(customerAlias.id1, 11))
|
|
.where(eq(usersMySchemaTable.id1, 10));
|
|
|
|
expect(result).toEqual([
|
|
{
|
|
user: { id1: 10, name: 'Ivan' },
|
|
customer: { id1: 11, name: 'Hans' },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('mySchema :: insert with spaces', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersMySchemaTable).values({ id1: 1, name: sql`'Jo h n'` });
|
|
const result = await db.select({ id1: usersMySchemaTable.id1, name: usersMySchemaTable.name }).from(
|
|
usersMySchemaTable,
|
|
);
|
|
|
|
expect(result).toEqual([{ id1: 1, name: 'Jo h n' }]);
|
|
});
|
|
|
|
test('mySchema :: prepared statement with placeholder in .limit', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersMySchemaTable).values({ id1: 1, name: 'John' });
|
|
const stmt = db
|
|
.select({
|
|
id1: usersMySchemaTable.id1,
|
|
name: usersMySchemaTable.name,
|
|
})
|
|
.from(usersMySchemaTable)
|
|
.where(eq(usersMySchemaTable.id1, sql.placeholder('id1')))
|
|
.limit(sql.placeholder('limit'))
|
|
.prepare('mySchema_stmt_limit');
|
|
|
|
const result = await stmt.execute({ id1: 1, limit: 1 });
|
|
|
|
expect(result.map((it) => ({ ...it, id: undefined }))).toEqual([{ id1: 1, name: 'John' }]);
|
|
expect(result).toHaveLength(1);
|
|
});
|
|
|
|
// TODO on conflict does not supported in gel
|
|
test.todo('mySchema :: build query insert with onConflict do update / multiple columns', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const query = db
|
|
.insert(usersMySchemaTable)
|
|
.values({ id1: 1, name: 'John', json: ['foo', 'bar'] })
|
|
// .onConflictDoUpdate({ target: [usersMySchemaTable.id1, usersMySchemaTable.name], set: { name: 'John1' } })
|
|
.toSQL();
|
|
|
|
expect(query).toEqual({
|
|
sql:
|
|
'insert into "mySchema"."users" ("id1", "name", "verified", "json", "created_at") values ($1, $2, default, $3, default) on conflict ("id1","name") do update set "name" = $4',
|
|
params: [1, 'John', ['foo', 'bar'], 'John1'],
|
|
});
|
|
});
|
|
|
|
// TODO on conflict not supported in gel
|
|
test.todo('mySchema :: build query insert with onConflict do nothing + target', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const query = db
|
|
.insert(usersMySchemaTable)
|
|
.values({ id1: 1, name: 'John', json: ['foo', 'bar'] })
|
|
// .onConflictDoNothing({ target: usersMySchemaTable.id1 })
|
|
.toSQL();
|
|
|
|
expect(query).toEqual({
|
|
sql:
|
|
'insert into "mySchema"."users" ("id1", "name", "verified", "json", "created_at") values ($1, $2, default, $3, default) on conflict ("id1") do nothing',
|
|
params: [1, 'John', ['foo', 'bar']],
|
|
});
|
|
});
|
|
|
|
test('mySchema :: select from tables with same name from different schema using alias', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersMySchemaTable).values({ id1: 10, name: 'Ivan' });
|
|
await db.insert(usersTable).values({ id1: 11, name: 'Hans' });
|
|
|
|
const customerAlias = alias(usersTable, 'customer');
|
|
|
|
const result = await db.select().from(usersMySchemaTable).leftJoin(customerAlias, eq(customerAlias.id1, 11))
|
|
.where(eq(customerAlias.id1, 11));
|
|
|
|
expect(result).toEqual([
|
|
{
|
|
users: {
|
|
id1: 10,
|
|
name: 'Ivan',
|
|
verified: false,
|
|
json: null,
|
|
createdAt: result[0]!.users.createdAt,
|
|
},
|
|
customer: {
|
|
id1: 11,
|
|
name: 'Hans',
|
|
verified: false,
|
|
json: null,
|
|
createdAt: result[0]!.customer!.createdAt,
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('limit 0', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
|
const users = await db.select().from(usersTable).limit(0);
|
|
|
|
expect(users).toEqual([]);
|
|
});
|
|
|
|
test('limit -1', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(usersTable).values({ id1: 2, name: 'John' });
|
|
const users = await db.select().from(usersTable).limit(-1);
|
|
|
|
expect(users.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
test('Object keys as column names', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
// Tests the following:
|
|
// Column with required config
|
|
// Column with optional config without providing a value
|
|
// Column with optional config providing a value
|
|
// Column without config
|
|
const users = gelTable('users_with_names', {
|
|
id1: integer().notNull(),
|
|
firstName: text(),
|
|
lastName: text(),
|
|
admin: boolean(),
|
|
});
|
|
|
|
await db.insert(users).values([
|
|
{ id1: 1, firstName: 'John', lastName: 'Doe', admin: true },
|
|
{ id1: 2, firstName: 'Jane', lastName: 'Smith', admin: false },
|
|
]);
|
|
const result = await db.select({ id1: users.id1, firstName: users.firstName, lastName: users.lastName }).from(
|
|
users,
|
|
).where(eq(users.admin, true));
|
|
|
|
expect(result).toEqual([{ id1: 1, firstName: 'John', lastName: 'Doe' }]);
|
|
});
|
|
|
|
test('proper json handling', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const jsonTable = gelTable('json_table', {
|
|
json: json('json').$type<{ name: string; age: number }>(),
|
|
});
|
|
|
|
await db.insert(jsonTable).values({ json: { name: 'Tom', age: 75 } });
|
|
|
|
const result = await db.select().from(jsonTable);
|
|
|
|
const justNames = await db
|
|
.select({
|
|
name1: sql<string>`${jsonTable.json}->>'name'`.as('name1'),
|
|
name2: sql<string>`${jsonTable.json}->>'name'`.as('name2'),
|
|
})
|
|
.from(jsonTable);
|
|
|
|
expect(result).toStrictEqual([
|
|
{
|
|
json: { name: 'Tom', age: 75 },
|
|
},
|
|
]);
|
|
|
|
expect(justNames).toStrictEqual([
|
|
{
|
|
name1: 'Tom',
|
|
name2: 'Tom',
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('set json fields with objects and retrieve with the ->> operator', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const obj = { string: 'test', number: 123 };
|
|
const { string: testString, number: testNumber } = obj;
|
|
|
|
await db.insert(jsonTestTable).values({
|
|
id1: 1,
|
|
json: obj,
|
|
});
|
|
|
|
const result = await db
|
|
.select({
|
|
jsonStringField: sql<string>`${jsonTestTable.json}->>'string'`,
|
|
jsonNumberField: sql<string>`${jsonTestTable.json}->>'number'`,
|
|
})
|
|
.from(jsonTestTable);
|
|
|
|
expect(result).toStrictEqual([
|
|
{
|
|
jsonStringField: testString,
|
|
jsonNumberField: String(testNumber),
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('set json fields with objects and retrieve with the -> operator', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const obj = { string: 'test', number: 123 };
|
|
const { string: testString, number: testNumber } = obj;
|
|
|
|
await db.insert(jsonTestTable).values({ id1: 1, json: obj });
|
|
|
|
const result = await db
|
|
.select({
|
|
jsonStringField: sql<string>`${jsonTestTable.json}->'string'`,
|
|
jsonNumberField: sql<number>`${jsonTestTable.json}->'number'`,
|
|
})
|
|
.from(jsonTestTable);
|
|
|
|
expect(result).toStrictEqual([
|
|
{
|
|
jsonStringField: testString,
|
|
jsonNumberField: testNumber,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('set json fields with strings and retrieve with the -> operator', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const obj = { string: 'test', number: 123 };
|
|
const { string: testString, number: testNumber } = obj;
|
|
|
|
await db.insert(jsonTestTable).values({
|
|
id1: 1,
|
|
json: sql`${obj}`,
|
|
});
|
|
|
|
const result = await db
|
|
.select({
|
|
jsonStringField: sql<string>`${jsonTestTable.json}->'string'`,
|
|
jsonNumberField: sql<number>`${jsonTestTable.json}->'number'`,
|
|
})
|
|
.from(jsonTestTable);
|
|
|
|
expect(result).toStrictEqual([
|
|
{
|
|
jsonStringField: testString,
|
|
jsonNumberField: testNumber,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('cross join', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db
|
|
.insert(usersTable)
|
|
.values([
|
|
{ id1: 1, name: 'John' },
|
|
{ id1: 2, name: 'Jane' },
|
|
]);
|
|
|
|
await db
|
|
.insert(citiesTable)
|
|
.values([
|
|
{ id1: 1, name: 'Seattle' },
|
|
{ id1: 2, name: 'New York City' },
|
|
]);
|
|
|
|
const result = await db
|
|
.select({
|
|
user: usersTable.name,
|
|
city: citiesTable.name,
|
|
})
|
|
.from(usersTable)
|
|
.crossJoin(citiesTable)
|
|
.orderBy(usersTable.name, citiesTable.name);
|
|
|
|
expect(result).toStrictEqual([
|
|
{ city: 'New York City', user: 'Jane' },
|
|
{ city: 'Seattle', user: 'Jane' },
|
|
{ city: 'New York City', user: 'John' },
|
|
{ city: 'Seattle', user: 'John' },
|
|
]);
|
|
});
|
|
|
|
test('left join (lateral)', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db
|
|
.insert(citiesTable)
|
|
.values([{ id1: 1, name: 'Paris' }, { id1: 2, name: 'London' }])
|
|
.returning({ id: citiesTable.id1 });
|
|
|
|
await db.insert(users2Table).values([{ id1: 1, name: 'John', cityId: 1 }, { id1: 2, name: 'Jane' }]);
|
|
|
|
const sq = db
|
|
.select({
|
|
userId: users2Table.id1,
|
|
userName: users2Table.name,
|
|
cityId: users2Table.cityId,
|
|
})
|
|
.from(users2Table)
|
|
.where(eq(users2Table.cityId, citiesTable.id1))
|
|
.as('sq');
|
|
|
|
const res = await db
|
|
.select({
|
|
cityId: citiesTable.id1,
|
|
cityName: citiesTable.name,
|
|
userId: sq.userId,
|
|
userName: sq.userName,
|
|
})
|
|
.from(citiesTable)
|
|
.leftJoinLateral(sq, sql`true`);
|
|
|
|
expect(res).toStrictEqual([
|
|
{ cityId: 1, cityName: 'Paris', userId: 1, userName: 'John' },
|
|
{ cityId: 2, cityName: 'London', userId: null, userName: null },
|
|
]);
|
|
});
|
|
|
|
test('inner join (lateral)', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db
|
|
.insert(citiesTable)
|
|
.values([{ id1: 1, name: 'Paris' }, { id1: 2, name: 'London' }])
|
|
.returning({ id: citiesTable.id1 });
|
|
|
|
await db.insert(users2Table).values([{ id1: 1, name: 'John', cityId: 1 }, { id1: 2, name: 'Jane' }]);
|
|
|
|
const sq = db
|
|
.select({
|
|
userId: users2Table.id1,
|
|
userName: users2Table.name,
|
|
cityId: users2Table.cityId,
|
|
})
|
|
.from(users2Table)
|
|
.where(eq(users2Table.cityId, citiesTable.id1))
|
|
.as('sq');
|
|
|
|
const res = await db
|
|
.select({
|
|
cityId: citiesTable.id1,
|
|
cityName: citiesTable.name,
|
|
userId: sq.userId,
|
|
userName: sq.userName,
|
|
})
|
|
.from(citiesTable)
|
|
.innerJoinLateral(sq, sql`true`);
|
|
|
|
expect(res).toStrictEqual([
|
|
{ cityId: 1, cityName: 'Paris', userId: 1, userName: 'John' },
|
|
]);
|
|
});
|
|
|
|
test('cross join (lateral)', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db
|
|
.insert(citiesTable)
|
|
.values([{ id1: 1, name: 'Paris' }, { id1: 2, name: 'London' }, { id1: 3, name: 'Berlin' }]);
|
|
|
|
await db.insert(users2Table).values([{ id1: 1, name: 'John', cityId: 1 }, { id1: 2, name: 'Jane' }, {
|
|
id1: 3,
|
|
name: 'Patrick',
|
|
cityId: 2,
|
|
}]);
|
|
|
|
const sq = db
|
|
.select({
|
|
userId: users2Table.id1,
|
|
userName: users2Table.name,
|
|
cityId: users2Table.cityId,
|
|
})
|
|
.from(users2Table)
|
|
.where(not(like(citiesTable.name, 'L%')))
|
|
.as('sq');
|
|
|
|
const res = await db
|
|
.select({
|
|
cityId: citiesTable.id1,
|
|
cityName: citiesTable.name,
|
|
userId: sq.userId,
|
|
userName: sq.userName,
|
|
})
|
|
.from(citiesTable)
|
|
.crossJoinLateral(sq)
|
|
.orderBy(citiesTable.id1, sq.userId);
|
|
|
|
expect(res).toStrictEqual([
|
|
{
|
|
cityId: 1,
|
|
cityName: 'Paris',
|
|
userId: 1,
|
|
userName: 'John',
|
|
},
|
|
{
|
|
cityId: 1,
|
|
cityName: 'Paris',
|
|
userId: 2,
|
|
userName: 'Jane',
|
|
},
|
|
{
|
|
cityId: 1,
|
|
cityName: 'Paris',
|
|
userId: 3,
|
|
userName: 'Patrick',
|
|
},
|
|
{
|
|
cityId: 3,
|
|
cityName: 'Berlin',
|
|
userId: 1,
|
|
userName: 'John',
|
|
},
|
|
{
|
|
cityId: 3,
|
|
cityName: 'Berlin',
|
|
userId: 2,
|
|
userName: 'Jane',
|
|
},
|
|
{
|
|
cityId: 3,
|
|
cityName: 'Berlin',
|
|
userId: 3,
|
|
userName: 'Patrick',
|
|
},
|
|
]);
|
|
});
|
|
|
|
// TODO not supported yet
|
|
test.todo('update ... from', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(cities2Table).values([
|
|
{ id1: 1, name: 'New York City' },
|
|
{ id1: 2, name: 'Seattle' },
|
|
]);
|
|
await db.insert(users2Table).values([
|
|
{ id1: 1, name: 'John', cityId: 1 },
|
|
{ id1: 2, name: 'Jane', cityId: 2 },
|
|
]);
|
|
const result = await db
|
|
.update(users2Table)
|
|
.set({
|
|
cityId: cities2Table.id1,
|
|
})
|
|
.from(cities2Table)
|
|
.where(and(eq(cities2Table.name, 'Seattle'), eq(users2Table.name, 'John')))
|
|
.returning();
|
|
|
|
expect(result).toStrictEqual([
|
|
{
|
|
id1: 1,
|
|
name: 'John',
|
|
cityId: 2,
|
|
cities: {
|
|
id1: 2,
|
|
name: 'Seattle',
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
// TODO not supported yet
|
|
test.todo('update ... from with alias', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
await db.insert(cities2Table).values([
|
|
{ id1: 1, name: 'New York City' },
|
|
{ id1: 2, name: 'Seattle' },
|
|
]);
|
|
await db.insert(users2Table).values([
|
|
{ id1: 1, name: 'John', cityId: 1 },
|
|
{ id1: 2, name: 'Jane', cityId: 2 },
|
|
]);
|
|
|
|
const users = alias(users2Table, 'u');
|
|
const cities = alias(cities2Table, 'c');
|
|
const result = await db
|
|
.update(users)
|
|
.set({
|
|
cityId: cities.id1,
|
|
})
|
|
.from(cities)
|
|
.where(and(eq(cities.name, 'Seattle'), eq(users.name, 'John')))
|
|
.returning();
|
|
|
|
expect(result).toStrictEqual([
|
|
{
|
|
id1: 1,
|
|
name: 'John',
|
|
cityId: 2,
|
|
c: {
|
|
id1: 2,
|
|
name: 'Seattle',
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
// TODO not supported yet
|
|
// test.todo('update ... from with join', async (ctx) => {
|
|
// const { db } = ctx.gel;
|
|
|
|
// const states = gelTable('states', {
|
|
// id1: integer('id1').primaryKey(),
|
|
// name: text('name').notNull(),
|
|
// });
|
|
// const cities = gelTable('cities', {
|
|
// id1: integer('id1').primaryKey(),
|
|
// name: text('name').notNull(),
|
|
// stateId: integer('state_id').references(() => states.id1),
|
|
// });
|
|
// const users = gelTable('users', {
|
|
// id1: integer('id1').primaryKey(),
|
|
// name: text('name').notNull(),
|
|
// cityId: integer('city_id')
|
|
// .notNull()
|
|
// .references(() => cities.id1),
|
|
// });
|
|
|
|
// await db.execute(sql`drop table if exists "states" cascade`);
|
|
// await db.execute(sql`drop table if exists "cities" cascade`);
|
|
// await db.execute(sql`drop table if exists "users" cascade`);
|
|
// await db.execute(sql`
|
|
// create table "states" (
|
|
// "id" serial primary key,
|
|
// "name" text not null
|
|
// )
|
|
// `);
|
|
// await db.execute(sql`
|
|
// create table "cities" (
|
|
// "id" serial primary key,
|
|
// "name" text not null,
|
|
// "state_id" integer references "states"("id")
|
|
// )
|
|
// `);
|
|
// await db.execute(sql`
|
|
// create table "users" (
|
|
// "id" serial primary key,
|
|
// "name" text not null,
|
|
// "city_id" integer not null references "cities"("id")
|
|
// )
|
|
// `);
|
|
|
|
// await db.insert(states).values([{ id1: 1, name: 'New York' }, { id1: 2, name: 'Washington' }]);
|
|
// await db.insert(cities).values([
|
|
// { id1: 1, name: 'New York City', stateId: 1 },
|
|
// { id1: 2, name: 'Seattle', stateId: 2 },
|
|
// {
|
|
// id1: 2,
|
|
// name: 'London',
|
|
// },
|
|
// ]);
|
|
// await db.insert(users).values([
|
|
// { id1: 1, name: 'John', cityId: 1 },
|
|
// { id1: 2, name: 'Jane', cityId: 2 },
|
|
// { id1: 3, name: 'Jack', cityId: 3 },
|
|
// ]);
|
|
|
|
// const result1 = await db
|
|
// .update(users)
|
|
// .set({
|
|
// cityId: cities.id1,
|
|
// })
|
|
// .from(cities)
|
|
// .leftJoin(states, eq(cities.stateId, states.id1))
|
|
// .where(and(eq(cities.name, 'Seattle'), eq(users.name, 'John')))
|
|
// .returning();
|
|
// const result2 = await db
|
|
// .update(users)
|
|
// .set({
|
|
// cityId: cities.id1,
|
|
// })
|
|
// .from(cities)
|
|
// .leftJoin(states, eq(cities.stateId, states.id1))
|
|
// .where(and(eq(cities.name, 'London'), eq(users.name, 'Jack')))
|
|
// .returning();
|
|
|
|
// expect(result1).toStrictEqual([
|
|
// {
|
|
// id: 1,
|
|
// name: 'John',
|
|
// cityId: 2,
|
|
// cities: {
|
|
// id: 2,
|
|
// name: 'Seattle',
|
|
// stateId: 2,
|
|
// },
|
|
// states: {
|
|
// id: 2,
|
|
// name: 'Washington',
|
|
// },
|
|
// },
|
|
// ]);
|
|
// expect(result2).toStrictEqual([
|
|
// {
|
|
// id: 3,
|
|
// name: 'Jack',
|
|
// cityId: 3,
|
|
// cities: {
|
|
// id: 3,
|
|
// name: 'London',
|
|
// stateId: null,
|
|
// },
|
|
// states: null,
|
|
// },
|
|
// ]);
|
|
// });
|
|
|
|
test('insert into ... select', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const notifications = gelTable('notifications', {
|
|
id1: integer('id1').notNull(),
|
|
sentAt: timestamp('sentAt').notNull().defaultNow(),
|
|
message: text('message').notNull(),
|
|
});
|
|
const users = gelTable('users_insert_select', {
|
|
id1: integer('id1').notNull(),
|
|
name: text('name').notNull(),
|
|
});
|
|
const userNotications = gelTable('user_notifications', {
|
|
userId: integer('userId').notNull(),
|
|
notificationId: integer('notificationId').notNull(),
|
|
});
|
|
|
|
const newNotification = await db
|
|
.insert(notifications)
|
|
.values({ id1: 1, message: 'You are one of the 3 lucky winners!' })
|
|
.returning({ id1: notifications.id1 })
|
|
.then((result) => result[0]);
|
|
await db.insert(users).values([
|
|
{ id1: 1, name: 'Alice' },
|
|
{ id1: 2, name: 'Bob' },
|
|
{ id1: 3, name: 'Charlie' },
|
|
{
|
|
id1: 4,
|
|
name: 'David',
|
|
},
|
|
{
|
|
id1: 5,
|
|
name: 'Eve',
|
|
},
|
|
]);
|
|
|
|
const sentNotifications = await db
|
|
.insert(userNotications)
|
|
.select(
|
|
db
|
|
.select({
|
|
userId: users.id1,
|
|
notificationId: sql`${newNotification!.id1}`.as('notification_id'),
|
|
})
|
|
.from(users)
|
|
.where(inArray(users.name, ['Alice', 'Charlie', 'Eve']))
|
|
.orderBy(asc(users.id1)),
|
|
)
|
|
.returning();
|
|
|
|
expect(sentNotifications).toStrictEqual([
|
|
{ userId: 1, notificationId: newNotification!.id1 },
|
|
{ userId: 3, notificationId: newNotification!.id1 },
|
|
{ userId: 5, notificationId: newNotification!.id1 },
|
|
]);
|
|
});
|
|
|
|
test('insert into ... select with keys in different order', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const users1 = gelTable('users1', {
|
|
id1: integer('id1').notNull(),
|
|
name: text('name').notNull(),
|
|
});
|
|
const users2 = gelTable('users2', {
|
|
id1: integer('id1').notNull(),
|
|
name: text('name').notNull(),
|
|
});
|
|
|
|
expect(() =>
|
|
db.insert(users1).select(
|
|
db
|
|
.select({
|
|
name: users2.name,
|
|
id1: users2.id1,
|
|
})
|
|
.from(users2),
|
|
)
|
|
).toThrowError();
|
|
});
|
|
|
|
test('policy', () => {
|
|
{
|
|
const policy = gelPolicy('test policy');
|
|
|
|
expect(is(policy, GelPolicy)).toBe(true);
|
|
expect(policy.name).toBe('test policy');
|
|
}
|
|
|
|
{
|
|
const policy = gelPolicy('test policy', {
|
|
as: 'permissive',
|
|
for: 'all',
|
|
to: 'public',
|
|
using: sql`1=1`,
|
|
withCheck: sql`1=1`,
|
|
});
|
|
|
|
expect(is(policy, GelPolicy)).toBe(true);
|
|
expect(policy.name).toBe('test policy');
|
|
expect(policy.as).toBe('permissive');
|
|
expect(policy.for).toBe('all');
|
|
expect(policy.to).toBe('public');
|
|
const dialect = new GelDialect();
|
|
expect(is(policy.using, SQL)).toBe(true);
|
|
expect(dialect.sqlToQuery(policy.using!).sql).toBe('1=1');
|
|
expect(is(policy.withCheck, SQL)).toBe(true);
|
|
expect(dialect.sqlToQuery(policy.withCheck!).sql).toBe('1=1');
|
|
}
|
|
|
|
{
|
|
const policy = gelPolicy('test policy', {
|
|
to: 'custom value',
|
|
});
|
|
|
|
expect(policy.to).toBe('custom value');
|
|
}
|
|
|
|
{
|
|
const p1 = gelPolicy('test policy');
|
|
const p2 = gelPolicy('test policy 2', {
|
|
as: 'permissive',
|
|
for: 'all',
|
|
to: 'public',
|
|
using: sql`1=1`,
|
|
withCheck: sql`1=1`,
|
|
});
|
|
const table = gelTable(
|
|
'table_with_policy',
|
|
{
|
|
id: integer('id').primaryKey(),
|
|
name: text('name').notNull(),
|
|
},
|
|
() => ({
|
|
p1,
|
|
p2,
|
|
}),
|
|
);
|
|
const config = getTableConfig(table);
|
|
expect(config.policies).toHaveLength(2);
|
|
expect(config.policies[0]).toBe(p1);
|
|
expect(config.policies[1]).toBe(p2);
|
|
}
|
|
});
|
|
|
|
test('Enable RLS function', () => {
|
|
const usersWithRLS = gelTable('users', {
|
|
id: integer(),
|
|
}).enableRLS();
|
|
|
|
const config1 = getTableConfig(usersWithRLS);
|
|
|
|
const usersNoRLS = gelTable('users', {
|
|
id: integer(),
|
|
});
|
|
|
|
const config2 = getTableConfig(usersNoRLS);
|
|
|
|
expect(config1.enableRLS).toBeTruthy();
|
|
expect(config2.enableRLS).toBeFalsy();
|
|
});
|
|
|
|
test('test $onUpdateFn and $onUpdate works with sql value', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const users = gelTable('users_on_update_sql', {
|
|
id: integer('id1').notNull(),
|
|
name: text('name').notNull(),
|
|
updatedAt: timestamptz('updated_at').notNull().$onUpdate(() => sql`now()`),
|
|
});
|
|
|
|
const insertResp = await db.insert(users).values({
|
|
id: 1,
|
|
name: 'John',
|
|
}).returning({
|
|
updatedAt: users.updatedAt,
|
|
});
|
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
|
|
const now = Date.now();
|
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
const updateResp = await db.update(users).set({
|
|
name: 'John',
|
|
}).returning({
|
|
updatedAt: users.updatedAt,
|
|
});
|
|
|
|
expect(new Date(insertResp[0]?.updatedAt.toISOString() ?? 0).getTime()).lessThan(now);
|
|
expect(new Date(updateResp[0]?.updatedAt.toISOString() ?? 0).getTime()).greaterThan(now);
|
|
});
|
|
|
|
test('$count separate', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const countTestTable = gelTable('count_test', {
|
|
id1: integer('id1').notNull(),
|
|
name: text('name').notNull(),
|
|
});
|
|
|
|
await db.insert(countTestTable).values([
|
|
{ id1: 1, name: 'First' },
|
|
{ id1: 2, name: 'Second' },
|
|
{ id1: 3, name: 'Third' },
|
|
{ id1: 4, name: 'Fourth' },
|
|
]);
|
|
|
|
const count = await db.$count(countTestTable);
|
|
|
|
expect(count).toStrictEqual(4);
|
|
});
|
|
|
|
test('$count embedded', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const countTestTable = gelTable('count_test', {
|
|
id1: integer('id1').notNull(),
|
|
name: text('name').notNull(),
|
|
});
|
|
|
|
await db.insert(countTestTable).values([
|
|
{ id1: 1, name: 'First' },
|
|
{ id1: 2, name: 'Second' },
|
|
{ id1: 3, name: 'Third' },
|
|
{ id1: 4, name: 'Fourth' },
|
|
]);
|
|
|
|
const count = await db
|
|
.select({
|
|
count: db.$count(countTestTable),
|
|
})
|
|
.from(countTestTable);
|
|
|
|
expect(count).toStrictEqual([{ count: 4 }, { count: 4 }, { count: 4 }, { count: 4 }]);
|
|
});
|
|
|
|
test('$count separate reuse', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const countTestTable = gelTable('count_test', {
|
|
id1: integer('id1').notNull(),
|
|
name: text('name').notNull(),
|
|
});
|
|
|
|
await db.insert(countTestTable).values([
|
|
{ id1: 1, name: 'First' },
|
|
{ id1: 2, name: 'Second' },
|
|
{ id1: 3, name: 'Third' },
|
|
{ id1: 4, name: 'Fourth' },
|
|
]);
|
|
|
|
const count = db.$count(countTestTable);
|
|
|
|
const count1 = await count;
|
|
|
|
await db.insert(countTestTable).values({ id1: 5, name: 'fifth' });
|
|
|
|
const count2 = await count;
|
|
|
|
await db.insert(countTestTable).values({ id1: 6, name: 'sixth' });
|
|
|
|
const count3 = await count;
|
|
|
|
expect(count1).toStrictEqual(4);
|
|
expect(count2).toStrictEqual(5);
|
|
expect(count3).toStrictEqual(6);
|
|
});
|
|
|
|
test('$count embedded reuse', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const countTestTable = gelTable('count_test', {
|
|
id1: integer('id1').notNull(),
|
|
name: text('name').notNull(),
|
|
});
|
|
|
|
await db.insert(countTestTable).values([
|
|
{ id1: 1, name: 'First' },
|
|
{ id1: 2, name: 'Second' },
|
|
{ id1: 3, name: 'Third' },
|
|
{ id1: 4, name: 'Fourth' },
|
|
]);
|
|
|
|
const count = db
|
|
.select({
|
|
count: db.$count(countTestTable),
|
|
})
|
|
.from(countTestTable);
|
|
|
|
const count1 = await count;
|
|
|
|
await db.insert(countTestTable).values({ id1: 5, name: 'fifth' });
|
|
|
|
const count2 = await count;
|
|
|
|
await db.insert(countTestTable).values({ id1: 6, name: 'sixth' });
|
|
|
|
const count3 = await count;
|
|
|
|
expect(count1).toStrictEqual([{ count: 4 }, { count: 4 }, { count: 4 }, { count: 4 }]);
|
|
expect(count2).toStrictEqual([{ count: 5 }, { count: 5 }, { count: 5 }, { count: 5 }, { count: 5 }]);
|
|
expect(count3).toStrictEqual([
|
|
{ count: 6 },
|
|
{ count: 6 },
|
|
{ count: 6 },
|
|
{ count: 6 },
|
|
{ count: 6 },
|
|
{
|
|
count: 6,
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('$count separate with filters', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const countTestTable = gelTable('count_test', {
|
|
id1: integer('id1').notNull(),
|
|
name: text('name').notNull(),
|
|
});
|
|
|
|
await db.insert(countTestTable).values([
|
|
{ id1: 1, name: 'First' },
|
|
{ id1: 2, name: 'Second' },
|
|
{ id1: 3, name: 'Third' },
|
|
{ id1: 4, name: 'Fourth' },
|
|
]);
|
|
|
|
const count = await db.$count(countTestTable, gt(countTestTable.id1, 1));
|
|
|
|
expect(count).toStrictEqual(3);
|
|
});
|
|
|
|
test('$count embedded with filters', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const countTestTable = gelTable('count_test', {
|
|
id1: integer('id1').notNull(),
|
|
name: text('name').notNull(),
|
|
});
|
|
|
|
await db.insert(countTestTable).values([
|
|
{ id1: 1, name: 'First' },
|
|
{ id1: 2, name: 'Second' },
|
|
{ id1: 3, name: 'Third' },
|
|
{ id1: 4, name: 'Fourth' },
|
|
]);
|
|
|
|
const count = await db
|
|
.select({
|
|
count: db.$count(countTestTable, gt(countTestTable.id1, 1)),
|
|
})
|
|
.from(countTestTable);
|
|
|
|
expect(count).toStrictEqual([{ count: 3 }, { count: 3 }, { count: 3 }, { count: 3 }]);
|
|
});
|
|
|
|
// TODO
|
|
test.todo('insert multiple rows into table with generated identity column', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
|
|
const identityColumnsTable = gelTable('identity_columns_table', {
|
|
id: integer('id').generatedAlwaysAsIdentity(),
|
|
id1: integer('id1').generatedByDefaultAsIdentity(),
|
|
name: text('name').notNull(),
|
|
});
|
|
|
|
// not passing identity columns
|
|
await db.execute(sql`drop table if exists ${identityColumnsTable}`);
|
|
await db.execute(
|
|
sql`create table ${identityColumnsTable} ("id" integer generated always as identity, "id1" integer generated by default as identity, "name" text)`,
|
|
);
|
|
|
|
let result = await db
|
|
.insert(identityColumnsTable)
|
|
.values([{ name: 'John' }, { name: 'Jane' }, { name: 'Bob' }])
|
|
.returning();
|
|
|
|
expect(result).toEqual([
|
|
{ id: 1, id1: 1, name: 'John' },
|
|
{ id: 2, id1: 2, name: 'Jane' },
|
|
{ id: 3, id1: 3, name: 'Bob' },
|
|
]);
|
|
|
|
// passing generated by default as identity column
|
|
await db.execute(sql`drop table if exists ${identityColumnsTable}`);
|
|
await db.execute(
|
|
sql`create table ${identityColumnsTable} ("id" integer generated always as identity, "id1" integer generated by default as identity, "name" text)`,
|
|
);
|
|
|
|
result = await db
|
|
.insert(identityColumnsTable)
|
|
.values([
|
|
{ name: 'John', id1: 3 },
|
|
{ name: 'Jane', id1: 5 },
|
|
{ name: 'Bob', id1: 5 },
|
|
])
|
|
.returning();
|
|
|
|
expect(result).toEqual([
|
|
{ id: 1, id1: 3, name: 'John' },
|
|
{ id: 2, id1: 5, name: 'Jane' },
|
|
{ id: 3, id1: 5, name: 'Bob' },
|
|
]);
|
|
|
|
// passing all identity columns
|
|
await db.execute(sql`drop table if exists ${identityColumnsTable}`);
|
|
await db.execute(
|
|
sql`create table ${identityColumnsTable} ("id" integer generated always as identity, "id1" integer generated by default as identity, "name" text)`,
|
|
);
|
|
|
|
result = await db
|
|
.insert(identityColumnsTable)
|
|
.overridingSystemValue()
|
|
.values([
|
|
{ name: 'John', id: 2, id1: 3 },
|
|
{ name: 'Jane', id: 4, id1: 5 },
|
|
{ name: 'Bob', id: 4, id1: 5 },
|
|
])
|
|
.returning();
|
|
|
|
expect(result).toEqual([
|
|
{ id: 2, id1: 3, name: 'John' },
|
|
{ id: 4, id1: 5, name: 'Jane' },
|
|
{ id: 4, id1: 5, name: 'Bob' },
|
|
]);
|
|
});
|
|
|
|
test('insert via db.execute + select via db.execute', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
await db.execute(
|
|
sql`insert into ${usersTable} (${sql.identifier(usersTable.id1.name)},${
|
|
sql.identifier(usersTable.name.name)
|
|
}) values (1, ${'John'})`,
|
|
);
|
|
|
|
const result = await db.execute<{ id1: number; name: string }>(
|
|
sql`select id1, name from "users"`,
|
|
);
|
|
expect(result).toEqual([{ id1: 1, name: 'John' }]);
|
|
});
|
|
|
|
test('insert via db.execute + returning', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
const inserted = await db.execute<{ id1: number; name: string }>(
|
|
sql`insert into ${usersTable} (${
|
|
sql.identifier(
|
|
usersTable.id1.name,
|
|
)
|
|
}, ${
|
|
sql.identifier(
|
|
usersTable.name.name,
|
|
)
|
|
}) values (1, ${'John'}) returning ${usersTable.id1}, ${usersTable.name}`,
|
|
);
|
|
expect(inserted).toEqual([{ id1: 1, name: 'John' }]);
|
|
});
|
|
|
|
test('insert via db.execute w/ query builder', async (ctx) => {
|
|
const { db } = ctx.gel;
|
|
const inserted = await db.execute<Pick<typeof usersTable.$inferSelect, 'id1' | 'name'>>(
|
|
db
|
|
.insert(usersTable)
|
|
.values({ id1: 1, name: 'John' })
|
|
.returning({ id1: usersTable.id1, name: usersTable.name }),
|
|
);
|
|
expect(inserted).toEqual([{ id1: 1, name: 'John' }]);
|
|
});
|
|
|
|
test('test force invalidate', async (ctx) => {
|
|
const { db } = ctx.cachedGel;
|
|
|
|
const spyInvalidate = vi.spyOn(db.$cache, 'invalidate');
|
|
await db.$cache?.invalidate({ tables: 'users' });
|
|
expect(spyInvalidate).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test('default global config - no cache should be hit', async (ctx) => {
|
|
const { db } = ctx.cachedGel;
|
|
|
|
// @ts-expect-error
|
|
const spyPut = vi.spyOn(db.$cache, 'put');
|
|
// @ts-expect-error
|
|
const spyGet = vi.spyOn(db.$cache, 'get');
|
|
// @ts-expect-error
|
|
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
|
|
|
await db.select().from(usersTable);
|
|
|
|
expect(spyPut).toHaveBeenCalledTimes(0);
|
|
expect(spyGet).toHaveBeenCalledTimes(0);
|
|
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
|
});
|
|
|
|
test('default global config + enable cache on select: get, put', async (ctx) => {
|
|
const { db } = ctx.cachedGel;
|
|
|
|
// @ts-expect-error
|
|
const spyPut = vi.spyOn(db.$cache, 'put');
|
|
// @ts-expect-error
|
|
const spyGet = vi.spyOn(db.$cache, 'get');
|
|
// @ts-expect-error
|
|
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
|
|
|
await db.select().from(usersTable).$withCache();
|
|
|
|
expect(spyPut).toHaveBeenCalledTimes(1);
|
|
expect(spyGet).toHaveBeenCalledTimes(1);
|
|
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
|
});
|
|
|
|
test('default global config + enable cache on select + write: get, put, onMutate', async (ctx) => {
|
|
const { db } = ctx.cachedGel;
|
|
|
|
// @ts-expect-error
|
|
const spyPut = vi.spyOn(db.$cache, 'put');
|
|
// @ts-expect-error
|
|
const spyGet = vi.spyOn(db.$cache, 'get');
|
|
// @ts-expect-error
|
|
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
|
|
|
await db.select().from(usersTable).$withCache({ config: { ex: 1 } });
|
|
|
|
expect(spyPut).toHaveBeenCalledTimes(1);
|
|
expect(spyGet).toHaveBeenCalledTimes(1);
|
|
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
|
|
|
spyPut.mockClear();
|
|
spyGet.mockClear();
|
|
spyInvalidate.mockClear();
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
|
|
|
expect(spyPut).toHaveBeenCalledTimes(0);
|
|
expect(spyGet).toHaveBeenCalledTimes(0);
|
|
expect(spyInvalidate).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test('default global config + enable cache on select + disable invalidate: get, put', async (ctx) => {
|
|
const { db } = ctx.cachedGel;
|
|
|
|
// @ts-expect-error
|
|
const spyPut = vi.spyOn(db.$cache, 'put');
|
|
// @ts-expect-error
|
|
const spyGet = vi.spyOn(db.$cache, 'get');
|
|
// @ts-expect-error
|
|
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
|
|
|
await db.select().from(usersTable).$withCache({ tag: 'custom', autoInvalidate: false, config: { ex: 1 } });
|
|
|
|
expect(spyPut).toHaveBeenCalledTimes(1);
|
|
expect(spyGet).toHaveBeenCalledTimes(1);
|
|
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
|
|
|
// invalidate force
|
|
await db.$cache?.invalidate({ tags: ['custom'] });
|
|
});
|
|
|
|
test('global: true + disable cache', async (ctx) => {
|
|
const { dbGlobalCached: db } = ctx.cachedGel;
|
|
|
|
// @ts-expect-error
|
|
const spyPut = vi.spyOn(db.$cache, 'put');
|
|
// @ts-expect-error
|
|
const spyGet = vi.spyOn(db.$cache, 'get');
|
|
// @ts-expect-error
|
|
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
|
|
|
await db.select().from(usersTable).$withCache(false);
|
|
|
|
expect(spyPut).toHaveBeenCalledTimes(0);
|
|
expect(spyGet).toHaveBeenCalledTimes(0);
|
|
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
|
});
|
|
|
|
test('global: true - cache should be hit', async (ctx) => {
|
|
const { dbGlobalCached: db } = ctx.cachedGel;
|
|
|
|
// @ts-expect-error
|
|
const spyPut = vi.spyOn(db.$cache, 'put');
|
|
// @ts-expect-error
|
|
const spyGet = vi.spyOn(db.$cache, 'get');
|
|
// @ts-expect-error
|
|
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
|
|
|
await db.select().from(usersTable);
|
|
|
|
expect(spyPut).toHaveBeenCalledTimes(1);
|
|
expect(spyGet).toHaveBeenCalledTimes(1);
|
|
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
|
});
|
|
|
|
test('global: true - cache: false on select - no cache hit', async (ctx) => {
|
|
const { dbGlobalCached: db } = ctx.cachedGel;
|
|
|
|
// @ts-expect-error
|
|
const spyPut = vi.spyOn(db.$cache, 'put');
|
|
// @ts-expect-error
|
|
const spyGet = vi.spyOn(db.$cache, 'get');
|
|
// @ts-expect-error
|
|
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
|
|
|
await db.select().from(usersTable).$withCache(false);
|
|
|
|
expect(spyPut).toHaveBeenCalledTimes(0);
|
|
expect(spyGet).toHaveBeenCalledTimes(0);
|
|
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
|
});
|
|
|
|
test('global: true - disable invalidate - cache hit + no invalidate', async (ctx) => {
|
|
const { dbGlobalCached: db } = ctx.cachedGel;
|
|
|
|
// @ts-expect-error
|
|
const spyPut = vi.spyOn(db.$cache, 'put');
|
|
// @ts-expect-error
|
|
const spyGet = vi.spyOn(db.$cache, 'get');
|
|
// @ts-expect-error
|
|
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
|
|
|
await db.select().from(usersTable).$withCache({ autoInvalidate: false });
|
|
|
|
expect(spyPut).toHaveBeenCalledTimes(1);
|
|
expect(spyGet).toHaveBeenCalledTimes(1);
|
|
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
|
|
|
spyPut.mockClear();
|
|
spyGet.mockClear();
|
|
spyInvalidate.mockClear();
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
|
|
|
expect(spyPut).toHaveBeenCalledTimes(0);
|
|
expect(spyGet).toHaveBeenCalledTimes(0);
|
|
expect(spyInvalidate).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test('global: true - with custom tag', async (ctx) => {
|
|
const { dbGlobalCached: db } = ctx.cachedGel;
|
|
|
|
// @ts-expect-error
|
|
const spyPut = vi.spyOn(db.$cache, 'put');
|
|
// @ts-expect-error
|
|
const spyGet = vi.spyOn(db.$cache, 'get');
|
|
// @ts-expect-error
|
|
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
|
|
|
await db.select().from(usersTable).$withCache({ tag: 'custom', autoInvalidate: false });
|
|
|
|
expect(spyPut).toHaveBeenCalledTimes(1);
|
|
expect(spyGet).toHaveBeenCalledTimes(1);
|
|
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
|
|
|
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
|
|
|
// invalidate force
|
|
await db.$cache?.invalidate({ tags: ['custom'] });
|
|
});
|
|
|
|
// check select used tables
|
|
test('check simple select used tables', (ctx) => {
|
|
const { db } = ctx.cachedGel;
|
|
|
|
// @ts-expect-error
|
|
expect(db.select().from(usersTable).getUsedTables()).toStrictEqual(['users']);
|
|
// @ts-expect-error
|
|
expect(db.select().from(sql`${usersTable}`).getUsedTables()).toStrictEqual(['users']);
|
|
});
|
|
// check select+join used tables
|
|
test('select+join', (ctx) => {
|
|
const { db } = ctx.cachedGel;
|
|
|
|
// @ts-expect-error
|
|
expect(db.select().from(usersTable).leftJoin(postsTable, eq(usersTable.id, postsTable.userId)).getUsedTables())
|
|
.toStrictEqual(['users', 'posts']);
|
|
expect(
|
|
// @ts-expect-error
|
|
db.select().from(sql`${usersTable}`).leftJoin(postsTable, eq(usersTable.id, postsTable.userId)).getUsedTables(),
|
|
).toStrictEqual(['users', 'posts']);
|
|
});
|
|
// check select+2join used tables
|
|
test('select+2joins', (ctx) => {
|
|
const { db } = ctx.cachedGel;
|
|
|
|
expect(
|
|
db.select().from(usersTable).leftJoin(
|
|
postsTable,
|
|
eq(usersTable.id1, postsTable.userId),
|
|
).leftJoin(
|
|
alias(postsTable, 'post2'),
|
|
eq(usersTable.id1, postsTable.userId),
|
|
)
|
|
// @ts-expect-error
|
|
.getUsedTables(),
|
|
)
|
|
.toStrictEqual(['users', 'posts']);
|
|
expect(
|
|
db.select().from(sql`${usersTable}`).leftJoin(postsTable, eq(usersTable.id1, postsTable.userId)).leftJoin(
|
|
alias(postsTable, 'post2'),
|
|
eq(usersTable.id1, postsTable.userId),
|
|
// @ts-expect-error
|
|
).getUsedTables(),
|
|
).toStrictEqual(['users', 'posts']);
|
|
});
|
|
// select subquery used tables
|
|
test('select+join', (ctx) => {
|
|
const { db } = ctx.cachedGel;
|
|
|
|
const sq = db.select().from(usersTable).where(eq(usersTable.id1, 42)).as('sq');
|
|
|
|
// @ts-expect-error
|
|
expect(db.select().from(sq).getUsedTables()).toStrictEqual(['users']);
|
|
});
|
|
});
|