-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
83f5e6f
commit 702c4c0
Showing
2 changed files
with
369 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
import {expect} from 'chai' | ||
import {DeleteResult, InsertResult, UpdateResult, sql} from 'kysely' | ||
import {jsonArrayFrom as jsonArrayFromMssql} from 'kysely/helpers/mssql' | ||
import {jsonArrayFrom as jsonArrayFromMySQL} from 'kysely/helpers/mysql' | ||
import {jsonArrayFrom as jsonArrayFromPostgres} from 'kysely/helpers/postgres' | ||
import {jsonArrayFrom as jsonArrayFromSQLite} from 'kysely/helpers/sqlite' | ||
import {omit} from 'lodash' | ||
import {SUPPORTED_DIALECTS} from '../../src/supported-dialects.js' | ||
import {PersonEntity} from './entity/Person.js' | ||
import {DEFAULT_DATA_SET, PerDialect, TestContext, initTest, seedDatabase} from './test-setup.js' | ||
|
||
for (const dialect of SUPPORTED_DIALECTS) { | ||
describe(`KyselyTypeORMDialect: ${dialect}`, () => { | ||
let ctx: TestContext | ||
|
||
const jsonArrayFrom = { | ||
'better-sqlite3': jsonArrayFromSQLite, | ||
mssql: jsonArrayFromMssql, | ||
mysql: jsonArrayFromMySQL, | ||
postgres: jsonArrayFromPostgres, | ||
sqlite: jsonArrayFromSQLite, | ||
}[dialect] as typeof jsonArrayFromMySQL | ||
|
||
beforeEach(async function () { | ||
ctx = await initTest(this, dialect) | ||
await seedDatabase(ctx) | ||
}) | ||
|
||
afterEach(async () => { | ||
await ctx.typeORMDataSource.dropDatabase() | ||
}) | ||
|
||
after(async () => { | ||
await ctx.kysely.destroy() | ||
}) | ||
|
||
it('should be able to perform select queries', async () => { | ||
const ormPeople = await PersonEntity.find({ | ||
select: { | ||
firstName: true, | ||
gender: true, | ||
lastName: true, | ||
maritalStatus: true, | ||
middleName: true, | ||
pets: { | ||
name: true, | ||
species: true, | ||
toys: true, | ||
}, | ||
}, | ||
relations: ['pets', 'pets.toys'], | ||
order: {id: 1}, | ||
}) | ||
|
||
expect(ormPeople).to.have.lengthOf(DEFAULT_DATA_SET.length) | ||
|
||
const normalizedOrmPeople = ormPeople.map((ormPerson) => | ||
omit({...ormPerson, pets: ormPerson.pets.map((pet) => omit(pet, ['id', 'owner', 'ownerId']))}, ['id']), | ||
) | ||
|
||
expect(normalizedOrmPeople).to.deep.equal(DEFAULT_DATA_SET) | ||
|
||
const queryBuilderPeople = await ctx.kysely | ||
.selectFrom('person') | ||
.select((eb) => [ | ||
'firstName', | ||
'gender', | ||
'lastName', | ||
'maritalStatus', | ||
'middleName', | ||
jsonArrayFrom( | ||
eb | ||
.selectFrom('pet') | ||
.whereRef('pet.ownerId', '=', 'person.id') | ||
.select(['pet.name', 'pet.species', sql`'[]'`.as('toys')]), | ||
).as('pets'), | ||
]) | ||
.execute() | ||
|
||
expect(queryBuilderPeople).to.deep.equal(normalizedOrmPeople) | ||
}) | ||
|
||
it('should be able to perform insert queries', async () => { | ||
const result = await ctx.kysely.insertInto('person').values({gender: 'female'}).executeTakeFirstOrThrow() | ||
|
||
expect(result).to.deep.equal( | ||
( | ||
{ | ||
'better-sqlite3': {insertId: BigInt(DEFAULT_DATA_SET.length + 1), numInsertedOrUpdatedRows: BigInt(1)}, | ||
mssql: {insertId: undefined, numInsertedOrUpdatedRows: BigInt(1)}, | ||
mysql: {insertId: BigInt(DEFAULT_DATA_SET.length + 1), numInsertedOrUpdatedRows: BigInt(1)}, | ||
postgres: {insertId: undefined, numInsertedOrUpdatedRows: BigInt(1)}, | ||
sqlite: {insertId: undefined, numInsertedOrUpdatedRows: undefined}, | ||
} satisfies PerDialect<{[K in keyof InsertResult]: InsertResult[K]}> | ||
)[dialect], | ||
) | ||
}) | ||
|
||
if (dialect === 'postgres' || dialect === 'sqlite') { | ||
it('should be able to perform insert queries with returning', async () => { | ||
const result = await ctx.kysely | ||
.insertInto('person') | ||
.values({gender: 'female'}) | ||
.returning('id') | ||
.executeTakeFirst() | ||
|
||
expect(result).to.deep.equal({id: DEFAULT_DATA_SET.length + 1}) | ||
}) | ||
} | ||
|
||
it('should be able to perform update queries', async () => { | ||
const result = await ctx.kysely | ||
.updateTable('person') | ||
.set({maritalStatus: 'widowed'}) | ||
.where('id', '=', 1) | ||
.executeTakeFirstOrThrow() | ||
|
||
expect(result).to.deep.equal( | ||
( | ||
{ | ||
'better-sqlite3': {numChangedRows: undefined, numUpdatedRows: BigInt(1)}, | ||
mssql: {numChangedRows: undefined, numUpdatedRows: BigInt(1)}, | ||
mysql: {numChangedRows: BigInt(1), numUpdatedRows: BigInt(1)}, | ||
postgres: {numChangedRows: undefined, numUpdatedRows: BigInt(1)}, | ||
sqlite: {numChangedRows: undefined, numUpdatedRows: BigInt(0)}, | ||
} satisfies PerDialect<{[K in keyof UpdateResult]: UpdateResult[K]}> | ||
)[dialect], | ||
) | ||
}) | ||
|
||
if (dialect === 'postgres' || dialect === 'sqlite') { | ||
it('should be able to perform update queries with returning', async () => { | ||
const result = await ctx.kysely | ||
.updateTable('person') | ||
.set({maritalStatus: 'widowed'}) | ||
.where('id', '=', 1) | ||
.returning(['gender']) | ||
.executeTakeFirstOrThrow() | ||
|
||
expect(result).to.deep.equal({gender: DEFAULT_DATA_SET[0].gender}) | ||
}) | ||
} | ||
|
||
it('should be able to perform delete queries', async () => { | ||
const result = await ctx.kysely.deleteFrom('person').where('id', '=', 1).executeTakeFirstOrThrow() | ||
|
||
expect(result).to.deep.equal( | ||
( | ||
{ | ||
'better-sqlite3': {numDeletedRows: BigInt(1)}, | ||
mssql: {numDeletedRows: BigInt(1)}, | ||
mysql: {numDeletedRows: BigInt(1)}, | ||
postgres: {numDeletedRows: BigInt(1)}, | ||
sqlite: {numDeletedRows: BigInt(0)}, | ||
} satisfies PerDialect<{[K in keyof DeleteResult]: DeleteResult[K]}> | ||
)[dialect], | ||
) | ||
}) | ||
|
||
if (dialect === 'postgres' || dialect === 'sqlite') { | ||
it('should be able to perform delete queries with returning', async () => { | ||
const result = await ctx.kysely | ||
.deleteFrom('person') | ||
.where('id', '=', 1) | ||
.returning('gender') | ||
.executeTakeFirstOrThrow() | ||
|
||
expect(result).to.deep.equal({gender: DEFAULT_DATA_SET[0].gender}) | ||
}) | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
import { | ||
CamelCasePlugin, | ||
Kysely, | ||
MssqlAdapter, | ||
MssqlIntrospector, | ||
MssqlQueryCompiler, | ||
MysqlAdapter, | ||
MysqlIntrospector, | ||
MysqlQueryCompiler, | ||
ParseJSONResultsPlugin, | ||
PostgresAdapter, | ||
PostgresIntrospector, | ||
PostgresQueryCompiler, | ||
SqliteAdapter, | ||
SqliteIntrospector, | ||
SqliteQueryCompiler, | ||
type KyselyPlugin, | ||
} from 'kysely' | ||
import 'reflect-metadata' | ||
import {DataSource, type DataSourceOptions, type DeepPartial} from 'typeorm' | ||
import {SnakeNamingStrategy} from 'typeorm-naming-strategies' | ||
import {KyselyTypeORMDialect, type KyselifyEntity, type KyselySubDialect, type KyselyTypeORMDialectConfig} from '../..' | ||
import type {SupportedDialect} from '../../src/supported-dialects.js' | ||
import {PersonEntity} from './entity/Person' | ||
import {PetEntity} from './entity/Pet' | ||
import {ToyEntity} from './entity/Toy' | ||
|
||
export type Person = KyselifyEntity<PersonEntity> | ||
export type Pet = KyselifyEntity<PetEntity> | ||
export type Toy = KyselifyEntity<ToyEntity> | ||
|
||
export interface Database { | ||
person: Person | ||
pet: Pet | ||
toy: Toy | ||
} | ||
|
||
export interface TestContext { | ||
kysely: Kysely<Database> | ||
typeORMDataSource: DataSource | ||
} | ||
|
||
export type PerDialect<T> = Record<SupportedDialect, T> | ||
|
||
const TEST_INIT_TIMEOUT = 5 * 60 * 1_000 | ||
|
||
export const PLUGINS: KyselyPlugin[] = [new ParseJSONResultsPlugin(), new CamelCasePlugin()] | ||
|
||
const POOL_SIZE = 10 | ||
|
||
const sqliteSubDialect = { | ||
createAdapter: () => new SqliteAdapter(), | ||
createIntrospector: (db) => new SqliteIntrospector(db), | ||
createQueryCompiler: () => new SqliteQueryCompiler(), | ||
} satisfies KyselySubDialect | ||
|
||
const BASE_DATA_SOURCE_OPTIONS = { | ||
entities: [PersonEntity, PetEntity, ToyEntity], | ||
logging: false, | ||
// logging: 'all', | ||
namingStrategy: new SnakeNamingStrategy(), | ||
} satisfies Omit<DataSourceOptions, 'type'> | ||
|
||
export const CONFIGS: PerDialect< | ||
Omit<KyselyTypeORMDialectConfig, 'typeORMDataSource'> & { | ||
typeORMDataSourceOptions: DataSourceOptions | ||
} | ||
> = { | ||
'better-sqlite3': { | ||
kyselySubDialect: sqliteSubDialect, | ||
typeORMDataSourceOptions: { | ||
...BASE_DATA_SOURCE_OPTIONS, | ||
database: ':memory:', | ||
type: 'better-sqlite3', | ||
}, | ||
}, | ||
mssql: { | ||
kyselySubDialect: { | ||
createAdapter: () => new MssqlAdapter(), | ||
createIntrospector: (db) => new MssqlIntrospector(db), | ||
createQueryCompiler: () => new MssqlQueryCompiler(), | ||
}, | ||
typeORMDataSourceOptions: { | ||
...BASE_DATA_SOURCE_OPTIONS, | ||
database: 'kysely_test', | ||
host: 'localhost', | ||
password: 'KyselyTest0', | ||
pool: {min: 0, max: POOL_SIZE}, | ||
port: 21433, | ||
type: 'mssql', | ||
username: 'sa', | ||
options: { | ||
trustServerCertificate: true, | ||
useUTC: true, | ||
}, | ||
}, | ||
}, | ||
mysql: { | ||
kyselySubDialect: { | ||
createAdapter: () => new MysqlAdapter(), | ||
createIntrospector: (db) => new MysqlIntrospector(db), | ||
createQueryCompiler: () => new MysqlQueryCompiler(), | ||
}, | ||
typeORMDataSourceOptions: { | ||
...BASE_DATA_SOURCE_OPTIONS, | ||
bigNumberStrings: true, | ||
database: 'kysely_test', | ||
host: 'localhost', | ||
password: 'kysely', | ||
poolSize: POOL_SIZE, | ||
port: 3308, | ||
supportBigNumbers: true, | ||
type: 'mysql', | ||
username: 'kysely', | ||
}, | ||
}, | ||
postgres: { | ||
kyselySubDialect: { | ||
createAdapter: () => new PostgresAdapter(), | ||
createIntrospector: (db) => new PostgresIntrospector(db), | ||
createQueryCompiler: () => new PostgresQueryCompiler(), | ||
}, | ||
typeORMDataSourceOptions: { | ||
...BASE_DATA_SOURCE_OPTIONS, | ||
database: 'kysely_test', | ||
host: 'localhost', | ||
poolSize: POOL_SIZE, | ||
port: 5434, | ||
type: 'postgres', | ||
username: 'kysely', | ||
useUTC: true, | ||
}, | ||
}, | ||
sqlite: { | ||
kyselySubDialect: sqliteSubDialect, | ||
typeORMDataSourceOptions: { | ||
...BASE_DATA_SOURCE_OPTIONS, | ||
database: ':memory:', | ||
type: 'sqlite', | ||
}, | ||
}, | ||
} | ||
|
||
export async function initTest(ctx: Mocha.Context, dialect: SupportedDialect): Promise<TestContext> { | ||
const config = CONFIGS[dialect] | ||
|
||
const typeORMDataSource = new DataSource(config.typeORMDataSourceOptions) | ||
|
||
ctx.timeout(TEST_INIT_TIMEOUT) | ||
await typeORMDataSource.initialize() | ||
|
||
await typeORMDataSource.synchronize(true) | ||
|
||
const kysely = new Kysely<Database>({ | ||
dialect: new KyselyTypeORMDialect({ | ||
kyselySubDialect: config.kyselySubDialect, | ||
shouldDestroyDataSource: true, | ||
typeORMDataSource, | ||
}), | ||
plugins: PLUGINS, | ||
}) | ||
|
||
return {kysely, typeORMDataSource} | ||
} | ||
|
||
export const DEFAULT_DATA_SET = [ | ||
{ | ||
firstName: 'Jennifer', | ||
middleName: null, | ||
lastName: 'Aniston', | ||
gender: 'female', | ||
pets: [{name: 'Catto', species: 'cat', toys: []}], | ||
maritalStatus: 'divorced', | ||
}, | ||
{ | ||
firstName: 'Arnold', | ||
middleName: null, | ||
lastName: 'Schwarzenegger', | ||
gender: 'male', | ||
pets: [{name: 'Doggo', species: 'dog', toys: []}], | ||
maritalStatus: 'divorced', | ||
}, | ||
{ | ||
firstName: 'Sylvester', | ||
middleName: 'Rocky', | ||
lastName: 'Stallone', | ||
gender: 'male', | ||
pets: [{name: 'Hammo', species: 'hamster', toys: []}], | ||
maritalStatus: 'married', | ||
}, | ||
] as const satisfies DeepPartial<PersonEntity>[] | ||
|
||
export async function seedDatabase(_ctx: TestContext): Promise<void> { | ||
for (const datum of DEFAULT_DATA_SET) { | ||
await PersonEntity.create(datum).save() | ||
} | ||
} |