├── slim-ef.png ├── .prettierrc ├── tests ├── seeder │ ├── slim_ef_test.db │ └── sqlite-seed.sql ├── db-set-style │ ├── initialization.test.ts │ ├── remove-person.ts │ ├── update-person.test.ts │ ├── add-person.test.ts │ └── add-agencies-trips.test.ts ├── entities │ ├── agency.ts │ ├── trip.ts │ └── person.ts ├── tsconfig.json ├── linq-query │ ├── distinct.test.ts │ ├── sql-functions.test.ts │ ├── logical.test.ts │ ├── simple.test.ts │ └── select.test.ts ├── repo-style │ └── repos.ts ├── unit-of-work.ts ├── transactions │ └── transactions.test.ts └── db-context.ts ├── nodemon.json ├── src ├── specification │ ├── exception.ts │ ├── index.ts │ ├── specification.interface.ts │ ├── utils.ts │ ├── base.specification.ts │ └── specification-evaluator.ts ├── repository │ ├── exception.ts │ ├── index.ts │ ├── _internal.interface.ts │ ├── utilis.ts │ ├── repository.decorator.ts │ ├── interfaces.ts │ └── db-set.ts ├── uow │ ├── index.ts │ ├── options-builder.ts │ ├── metadata-proxy.ts │ ├── _internal.interface.ts │ ├── model-builder.ts │ ├── metadata.ts │ ├── interfaces.ts │ └── db-context.ts ├── repo.ts └── index.ts ├── tslint.json ├── .vscode └── launch.json ├── LICENSE ├── .gitignore ├── package.json ├── tsconfig.json ├── mysql2sqlite.sh └── README.md /slim-ef.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bugMaker-237/slim-ef/HEAD/slim-ef.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "none", 4 | "arrowParens": "avoid" 5 | } 6 | -------------------------------------------------------------------------------- /tests/seeder/slim_ef_test.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bugMaker-237/slim-ef/HEAD/tests/seeder/slim_ef_test.db -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": ".ts,.js", 4 | "ignore": [], 5 | "exec": "ts-node ./src/index.ts" 6 | } 7 | -------------------------------------------------------------------------------- /src/specification/exception.ts: -------------------------------------------------------------------------------- 1 | export class SQLQuerySpecificationException extends Error { 2 | constructor(message?: string) { 3 | super(message); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/repository/exception.ts: -------------------------------------------------------------------------------- 1 | export class EmptySetException extends Error { 2 | constructor(message?: string) { 3 | super(message || 'Object not found'); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/uow/index.ts: -------------------------------------------------------------------------------- 1 | export { DbContext } from './db-context'; 2 | export * from './interfaces'; 3 | export { UnitOfWork } from './db-context'; 4 | 5 | export { DbContextModelBuilder } from './model-builder'; 6 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-config-prettier"], 3 | "rules": { 4 | "one-variable-per-declaration": false, 5 | "indent": { 6 | "options": "spaces" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/repository/index.ts: -------------------------------------------------------------------------------- 1 | export { DbSet } from './db-set'; 2 | export { DbSet as GenericRepository } from './db-set'; 3 | export * from './interfaces'; 4 | export { IDbSet as IGenericRepository } from './interfaces'; 5 | export * from './repository.decorator'; 6 | -------------------------------------------------------------------------------- /src/repo.ts: -------------------------------------------------------------------------------- 1 | export { 2 | GenericRepository, 3 | IGenericRepository, 4 | QueryRefiner, 5 | PrimitiveType, 6 | IQueryable, 7 | ExpressionResult 8 | } from './repository'; 9 | export { DbContext, IDbContext } from './uow'; 10 | 11 | export * from './repository/utilis'; 12 | -------------------------------------------------------------------------------- /src/specification/index.ts: -------------------------------------------------------------------------------- 1 | export { SQLQuerySpecificationEvaluator } from './specification-evaluator'; 2 | export { 3 | IQuerySpecificationEvaluator, 4 | FunctionQueryType, 5 | QuerySpecificationEvaluatorConstructor, 6 | FieldsSelector, 7 | CriteriaExpression 8 | } from './specification.interface'; 9 | -------------------------------------------------------------------------------- /tests/db-set-style/initialization.test.ts: -------------------------------------------------------------------------------- 1 | import { FakeDBContext } from '../db-context'; 2 | 3 | describe('Iniitialization', () => { 4 | it('Should register dbset', () => { 5 | // Arrange 6 | const context = new FakeDBContext(); 7 | 8 | // Assert 9 | expect(context.persons).toBeDefined(); 10 | expect(context.agencies).toBeDefined(); 11 | expect(context.trips).toBeDefined(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/entities/agency.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, OneToMany, PrimaryColumn, PrimaryGeneratedColumn } from 'typeorm'; 2 | import { Trip } from './trip'; 3 | 4 | @Entity() 5 | export class Agency { 6 | @PrimaryGeneratedColumn() 7 | id: number; 8 | @Column() 9 | name: string; 10 | 11 | @Column() 12 | phone: string; 13 | 14 | @Column({ 15 | nullable: true 16 | }) 17 | email: string; 18 | 19 | @OneToMany(() => Trip, (p) => p.agency) 20 | trips?: Trip[]; 21 | } 22 | -------------------------------------------------------------------------------- /src/repository/_internal.interface.ts: -------------------------------------------------------------------------------- 1 | import { ISpecification } from '../specification/specification.interface'; 2 | import { IDbSet, IQueryable } from './interfaces'; 3 | 4 | export type DeepPartial = { 5 | [P in keyof T]?: T[P] extends (infer U)[] 6 | ? DeepPartial[] 7 | : T[P] extends ReadonlyArray 8 | ? ReadonlyArray> 9 | : DeepPartial; 10 | }; 11 | 12 | export interface IInternalDbSet extends IDbSet { 13 | asSpecification(): ISpecification; 14 | fromSpecification(spec: ISpecification): IQueryable; 15 | } 16 | -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "target": "es2017", 5 | "module": "commonjs", 6 | "allowJs": true, 7 | "declaration": true, 8 | "sourceMap": true, 9 | "outDir": "lib", 10 | "rootDir": "../", 11 | "moduleResolution": "node", 12 | "esModuleInterop": true, 13 | "experimentalDecorators": true, 14 | "emitDecoratorMetadata": true, 15 | "resolveJsonModule": true, 16 | "skipLibCheck": true, 17 | "forceConsistentCasingInFileNames": true 18 | }, 19 | "include": ["./*"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | DbSet, 3 | IDbSet, 4 | DbSetEntity, 5 | QueryRefiner, 6 | PrimitiveType, 7 | IQueryable, 8 | GenericRepository, 9 | ExpressionResult, 10 | DeepPartial, 11 | EntityRepository 12 | } from './repository'; 13 | 14 | export { 15 | DbContext, 16 | IDbContext, 17 | ISavedTransaction, 18 | DbContextModelBuilder, 19 | IDbContextOptionsBuilder, 20 | ILogger, 21 | ILoggerCategoryName, 22 | ILoggerFactory, 23 | IUnitOfWork, 24 | UnitOfWork, 25 | QueryInitializer 26 | } from './uow'; 27 | 28 | export * from './repository/utilis'; 29 | 30 | export * from './specification/index'; 31 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Debug tests single run", 9 | "type": "node", 10 | "request": "launch", 11 | "env": { "CI": "true" }, 12 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/jest", 13 | "args": ["test", "--runInBand", "--no-cache"], 14 | "cwd": "${workspaceRoot}", 15 | "console": "integratedTerminal" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tests/entities/trip.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Column, 3 | Entity, 4 | ManyToOne, 5 | OneToMany, 6 | PrimaryColumn, 7 | PrimaryGeneratedColumn 8 | } from 'typeorm'; 9 | import { Agency } from './agency'; 10 | import { Person } from './person'; 11 | 12 | @Entity() 13 | export class Trip { 14 | @PrimaryGeneratedColumn('uuid') 15 | id: string; 16 | 17 | @ManyToOne(() => Agency, (c) => c.trips) 18 | agency: Agency; 19 | 20 | @Column() 21 | agencyId: number; 22 | 23 | @Column() 24 | departureDate: Date; 25 | 26 | @Column({ nullable: true }) 27 | estimatedArrivalDate: Date; 28 | 29 | @OneToMany(() => Person, (p) => p.trip) 30 | passengers: Person[]; 31 | } 32 | -------------------------------------------------------------------------------- /src/repository/utilis.ts: -------------------------------------------------------------------------------- 1 | import { DeepPartial } from './_internal.interface'; 2 | 3 | function __patch(entityShema: new () => T, entity: DeepPartial): T { 4 | const t = new entityShema(); 5 | if (t.constructor !== entity.constructor) { 6 | Object.assign(t, entity); 7 | return t; 8 | } 9 | return entity as T; 10 | } 11 | 12 | export function patch(entityShema: new () => T) { 13 | return (entity: DeepPartial): T => { 14 | return __patch(entityShema, entity); 15 | }; 16 | } 17 | export const With = patch; 18 | export function patchM(entityShema: new () => T) { 19 | return (...entities: DeepPartial[]): T[] => { 20 | return entities.map(e => __patch(entityShema, e)); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /tests/db-set-style/remove-person.ts: -------------------------------------------------------------------------------- 1 | import { With } from '../../src'; 2 | import { FakeDBContext } from '../db-context'; 3 | import { Person } from '../entities/person'; 4 | 5 | describe('Remove person', () => { 6 | it('Should remove person', async () => { 7 | // Arrange 8 | const context = new FakeDBContext(); 9 | const personId = '015c5549-3bbb-31ef-b2d5-402c6a9f746e'; 10 | 11 | // Act 12 | const person = await context.persons.find(personId); 13 | context.remove(person); 14 | await context.saveChanges(); 15 | 16 | const personIsThere = await context.persons.find(personId); 17 | context.dispose(); 18 | 19 | // Assert 20 | expect(personIsThere).toBeUndefined(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/entities/person.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Column, 3 | Entity, 4 | ManyToOne, 5 | PrimaryColumn, 6 | PrimaryGeneratedColumn 7 | } from 'typeorm'; 8 | import { Trip } from './trip'; 9 | 10 | @Entity() 11 | export class Person { 12 | @PrimaryGeneratedColumn('uuid') 13 | id: string; 14 | 15 | @ManyToOne(() => Trip, c => c.passengers) 16 | trip?: Trip; 17 | 18 | @Column({ 19 | nullable: true 20 | }) 21 | tripId?: string; 22 | 23 | @Column() 24 | firstname: string; 25 | 26 | @Column() 27 | lastname: string; 28 | 29 | @Column() 30 | phone: string; 31 | 32 | @Column() 33 | IDNumber: number; 34 | 35 | @Column({ 36 | nullable: true 37 | }) 38 | email: string; 39 | 40 | @Column({ 41 | default: false 42 | }) 43 | willTravel: boolean; 44 | } 45 | -------------------------------------------------------------------------------- /tests/linq-query/distinct.test.ts: -------------------------------------------------------------------------------- 1 | import { FakeDBContext } from '../db-context'; 2 | import { Agency } from '../entities/agency'; 3 | 4 | describe('Select test', () => { 5 | it('Should select 116 agencyIds', async () => { 6 | const context = new FakeDBContext(); 7 | 8 | const ids = await context.trips 9 | .include(p => p.agency) 10 | .groupBy(p => p.agencyId) 11 | .select(p => ({ 12 | aId: p.agencyId 13 | })) 14 | .distinct() 15 | .toList(); 16 | 17 | expect(ids).toBeDefined(); 18 | // these 2 use cases is to handle the fact that when running all the tests 19 | // 2 trips are being added to the db 20 | expect([...new Set(ids.map(id => id.aId))].length).toBeGreaterThanOrEqual( 21 | 116 22 | ); 23 | expect([...new Set(ids.map(id => id.aId))].length).toBeLessThanOrEqual(118); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /tests/repo-style/repos.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, GenericRepository } from '../../lib/repository'; 2 | import { Agency } from '../entities/agency'; 3 | import { Person } from '../entities/person'; 4 | import { Trip } from '../entities/trip'; 5 | import { UOW } from '../unit-of-work'; 6 | 7 | @EntityRepository(Agency) 8 | export class AgencyRepository extends GenericRepository { 9 | constructor() { 10 | super(UOW); 11 | } 12 | } 13 | 14 | // tslint:disable-next-line: max-classes-per-file 15 | @EntityRepository(Person) 16 | export class PersonRepository extends GenericRepository { 17 | constructor() { 18 | super(UOW); 19 | } 20 | } 21 | 22 | // tslint:disable-next-line: max-classes-per-file 23 | @EntityRepository(Trip) 24 | export class TripRepository extends GenericRepository { 25 | constructor() { 26 | super(UOW); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/uow/options-builder.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IDbContextOptionsBuilder, 3 | ILogger, 4 | ILoggerCategoryName, 5 | ILoggerFactory 6 | } from './interfaces'; 7 | 8 | export class DbContextOptionsBuilder implements IDbContextOptionsBuilder { 9 | private _loggerFactory: ILoggerFactory; 10 | private _loggerMap = new Map(); 11 | sensitiveDataLoggingEnabled: boolean; 12 | public useLoggerFactory(loggerFactory: ILoggerFactory): this { 13 | this._loggerFactory = loggerFactory; 14 | return this; 15 | } 16 | public enableSensitiveLogging(enabled = true): this { 17 | this.sensitiveDataLoggingEnabled = enabled; 18 | return this; 19 | } 20 | createLogger(catName: ILoggerCategoryName): ILogger { 21 | return ( 22 | this._loggerMap.get(catName) || 23 | (this._loggerMap.set(catName, this._loggerFactory?.createLogger(catName)), 24 | this._loggerMap.get(catName)) 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/uow/metadata-proxy.ts: -------------------------------------------------------------------------------- 1 | export class SelectStringProxy extends String { 2 | public $$propertyName: string; 3 | 4 | constructor(propName) { 5 | super(); 6 | this.$$propertyName = propName; 7 | } 8 | } 9 | 10 | // tslint:disable-next-line: max-classes-per-file 11 | export class SelectNumberProxy extends Number { 12 | public $$propertyName: string; 13 | 14 | constructor(propName) { 15 | super(); 16 | this.$$propertyName = propName; 17 | } 18 | } 19 | 20 | // tslint:disable-next-line: max-classes-per-file 21 | export class SelectBooleanProxy extends Boolean { 22 | public $$propertyName: string; 23 | 24 | constructor(propName) { 25 | super(); 26 | this.$$propertyName = propName; 27 | } 28 | } 29 | 30 | // tslint:disable-next-line: max-classes-per-file 31 | export class SelectArrayProxy extends Array { 32 | public $$propertyName: string; 33 | 34 | constructor(propName) { 35 | super(); 36 | this.$$propertyName = propName; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/uow/_internal.interface.ts: -------------------------------------------------------------------------------- 1 | import { IDbSet } from '../repository'; 2 | import { QueryType } from '../specification/specification.interface'; 3 | import { 4 | SelectArrayProxy, 5 | SelectBooleanProxy, 6 | SelectNumberProxy, 7 | SelectStringProxy 8 | } from './metadata-proxy'; 9 | 10 | export interface IInternalDbContext { 11 | execute( 12 | queryable: IDbSet, 13 | type: QueryType, 14 | ignoreFilters?: boolean 15 | ): Promise; 16 | getMetadata( 17 | type: new (...args) => T, 18 | includePaths: string[] 19 | ): Promise>; 20 | loadRelatedData(type: new (...args: []) => T, entity: T): Promise; 21 | } 22 | 23 | export type ProxyMetaDataInstance = { 24 | [x in keyof T]: ( 25 | | SelectBooleanProxy 26 | | SelectStringProxy 27 | | SelectNumberProxy 28 | | SelectArrayProxy 29 | ) & { 30 | $$propertyName: string; 31 | $$parentPropertyNames: string[]; 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /tests/db-set-style/update-person.test.ts: -------------------------------------------------------------------------------- 1 | import { With } from '../../src'; 2 | import { FakeDBContext } from '../db-context'; 3 | import { Person } from '../entities/person'; 4 | 5 | describe('Add person', () => { 6 | it('Should update person :: style 1', async () => { 7 | // Arrange 8 | const context = new FakeDBContext(); 9 | let dbPerson = await context.persons.first(); 10 | const person = { 11 | ...dbPerson, 12 | firstname: 'Bug', 13 | lastname: 'Master', 14 | tripId: '0e0079ce-48c2-3d6c-9cea-27b7b998a2f6', 15 | phone: '+8975211213', 16 | IDNumber: 7985631 17 | }; 18 | // Act 19 | context.persons.update(person); 20 | await context.saveChanges(); 21 | dbPerson = await context.persons.first((p, $) => p.id === $.id, { 22 | id: dbPerson.id 23 | }); 24 | context.dispose(); 25 | 26 | // Assert 27 | expect(person.id).toBe(dbPerson.id); 28 | expect(person.firstname).toBe(dbPerson.firstname); 29 | expect(person.lastname).toBe(dbPerson.lastname); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Yamsi B. Etienne 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/db-set-style/add-person.test.ts: -------------------------------------------------------------------------------- 1 | import { patch } from '../../src'; 2 | import { FakeDBContext } from '../db-context'; 3 | import { Person } from '../entities/person'; 4 | 5 | describe('Add person', () => { 6 | it('Should add person :: style 1', async () => { 7 | // Arrange 8 | const context = new FakeDBContext(); 9 | const person = new Person(); 10 | person.firstname = 'Buggy'; 11 | person.lastname = 'Maker'; 12 | person.IDNumber = 800; 13 | person.phone = '+237699977788'; 14 | 15 | // Act 16 | context.persons.add(person); 17 | await context.saveChanges(); 18 | context.dispose(); 19 | 20 | // Assert 21 | expect(person.id).toBeDefined(); 22 | }); 23 | it('Should add person :: style 2', async () => { 24 | // Arrange 25 | const context = new FakeDBContext(); 26 | // Act 27 | context.persons.add({ 28 | firstname: 'Another Buggy', 29 | lastname: 'Maker 2', 30 | IDNumber: 987654321, 31 | phone: '+237677788852' 32 | }); 33 | const { added } = await context.saveChanges(); 34 | context.dispose(); 35 | 36 | // Assert 37 | expect(added[0].id).toBeDefined(); 38 | expect(added[0].firstname).toBe('Another Buggy'); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/uow/model-builder.ts: -------------------------------------------------------------------------------- 1 | import { SlimExpressionFunction } from 'slim-exp'; 2 | import { IQueryable, QueryRefiner } from '../repository'; 3 | const GlobalMapKey = { 4 | name: Symbol('GlobalMapKey') 5 | }; 6 | 7 | export class DbContextModelBuilder { 8 | private _modelsFilterMap = new WeakMap(); 9 | private _currentType: any = void 0; 10 | entity(type: new () => T): DbContextModelBuilder { 11 | this._currentType = type; 12 | return this as any; 13 | } 14 | 15 | hasQueryFilter(query: QueryRefiner): DbContextModelBuilder { 16 | let expMap = []; 17 | if (this._modelsFilterMap.has(this._currentType)) { 18 | expMap = this._modelsFilterMap.get(this._currentType); 19 | } 20 | expMap.push(query); 21 | this._modelsFilterMap.delete(this._currentType); 22 | this._modelsFilterMap.set(this._currentType, expMap); 23 | this._currentType = void 0; 24 | return this; 25 | } 26 | 27 | hasGlobalQueryFilter(query: QueryRefiner) { 28 | const oldType = this._currentType; 29 | this._currentType = GlobalMapKey; 30 | this.hasQueryFilter(query as QueryRefiner); 31 | this._currentType = oldType; 32 | } 33 | resetAllFilters(): void { 34 | this._currentType = void 0; 35 | this._modelsFilterMap = new WeakMap(); 36 | } 37 | 38 | resetFilterFor(type: new () => T): void { 39 | this._modelsFilterMap.delete(this._currentType); 40 | this._currentType = void 0; 41 | } 42 | 43 | getFilters(type: any): SlimExpressionFunction[] { 44 | const filters = []; 45 | if (this._modelsFilterMap.has(type)) { 46 | filters.push(...this._modelsFilterMap.get(type)); 47 | } 48 | if (this._modelsFilterMap.has(GlobalMapKey)) { 49 | filters.push(...this._modelsFilterMap.get(GlobalMapKey)); 50 | } 51 | return filters; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/repository/repository.decorator.ts: -------------------------------------------------------------------------------- 1 | const repositoryMetadataKey = Symbol('entityRepository:key'); 2 | const entityMetadataKey = Symbol('entity:key'); 3 | const propMetadataKey = Symbol('prop:key'); 4 | const propListPropertyName = 'prop:list'; 5 | 6 | export function EntityRepository(entity: (new () => T) | (() => void)) { 7 | return (target: any) => { 8 | Reflect.defineMetadata(repositoryMetadataKey, entity, target); 9 | Reflect.defineMetadata(entityMetadataKey, entity, entity); 10 | }; 11 | } 12 | 13 | export function DbSetEntity(entity: (new () => T) | (() => void)) { 14 | return (target: any, propertykey: string) => { 15 | Reflect.defineMetadata( 16 | repositoryMetadataKey, 17 | entity, 18 | target.constructor, 19 | propertykey 20 | ); 21 | const props = 22 | Reflect.getOwnMetadata( 23 | propMetadataKey, 24 | target.constructor, 25 | propListPropertyName 26 | ) || []; 27 | props.push(propertykey); 28 | Reflect.defineMetadata( 29 | propMetadataKey, 30 | props, 31 | target.constructor, 32 | propListPropertyName 33 | ); 34 | 35 | EntityRepository(entity)(target); 36 | }; 37 | } 38 | 39 | export function getEntitySchema(target: any) { 40 | return ( 41 | Reflect.getOwnMetadata(repositoryMetadataKey, target.constructor) || 42 | Reflect.getOwnMetadata(entityMetadataKey, target.constructor) || 43 | Reflect.getOwnMetadata(repositoryMetadataKey, target) || 44 | Reflect.getOwnMetadata(entityMetadataKey, target) 45 | ); 46 | } 47 | 48 | export function getEntitySetKeys(target: any) { 49 | return Reflect.getOwnMetadata( 50 | propMetadataKey, 51 | target.constructor, 52 | propListPropertyName 53 | ); 54 | } 55 | 56 | export function getEntitySet(target: any, propertykey: string) { 57 | return ( 58 | Reflect.getOwnMetadata( 59 | entityMetadataKey, 60 | target.constructor, 61 | propertykey 62 | ) || 63 | Reflect.getOwnMetadata( 64 | repositoryMetadataKey, 65 | target.constructor, 66 | propertykey 67 | ) 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /src/specification/specification.interface.ts: -------------------------------------------------------------------------------- 1 | import { SlimExpressionFunction } from 'slim-exp'; 2 | import { SelectQueryBuilder } from 'typeorm'; 3 | 4 | export interface CriteriaExpression< 5 | T, 6 | S extends object = any, 7 | C extends object = any 8 | > { 9 | func: SlimExpressionFunction; 10 | context?: C; 11 | } 12 | 13 | export interface FieldsSelector { 14 | fieldsToSelect?: { 15 | field: string; 16 | // alias: string; 17 | }[]; 18 | builder?: SlimExpressionFunction; 19 | } 20 | 21 | export interface ISpecification { 22 | getIncludePaths(): string[]; 23 | getIncludes(): SlimExpressionFunction[]; 24 | getDistinct(): boolean; 25 | getCriterias(): CriteriaExpression[]; 26 | getChainedIncludes(): { 27 | initial: SlimExpressionFunction; 28 | chain: SlimExpressionFunction[]; 29 | }[]; 30 | 31 | getOrderBy(): SlimExpressionFunction; 32 | getGroupBy(): SlimExpressionFunction; 33 | getFunction(): { 34 | type: FunctionQueryType; 35 | func: SlimExpressionFunction; 36 | }; 37 | getOrderByDescending(): SlimExpressionFunction; 38 | getThenGroupBy(): SlimExpressionFunction[]; 39 | getThenOrderBy(): SlimExpressionFunction[]; 40 | getTake(): number; 41 | getSkip(): number; 42 | getIsPagingEnabled(): boolean; 43 | getSelector(): FieldsSelector; 44 | } 45 | export enum QueryType { 46 | ONE, 47 | ALL, 48 | RAW_ONE, 49 | RAW_ALL 50 | } 51 | export type FunctionQueryType = 'SUM' | 'COUNT' | 'MIN' | 'MAX' | 'AVG'; 52 | export type QuerySpecificationEvaluatorConstructor = new ( 53 | initialQuery: (alias: string) => SelectQueryBuilder, 54 | spec: ISpecification 55 | ) => IQuerySpecificationEvaluator; 56 | 57 | export declare class IQuerySpecificationEvaluator { 58 | constructor( 59 | initialQuery: (alias: string) => SelectQueryBuilder, 60 | spec: ISpecification 61 | ); 62 | executeQuery(type: QueryType): Promise; 63 | getQuery(): Promise | string; 64 | getParams(): Promise | any; 65 | } 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | lib 107 | database.sqlite 108 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slim-ef", 3 | "version": "0.9.0", 4 | "description": "An implementation of basic entity framework functionnalities in typescript", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "files": [ 8 | "lib" 9 | ], 10 | "scripts": { 11 | "start:dev": "nodemon", 12 | "build": "tsc", 13 | "format": "prettier --write \"src/**/*.(ts|js)\"", 14 | "lint": "tslint -p tsconfig.json", 15 | "prepare": "npm run build", 16 | "prepublishOnly": "npm run lint && npm run test", 17 | "preversion": "npm run lint", 18 | "version": "npm run format && git tag v%npm_package_version%", 19 | "postversion": "git push && git push --tags", 20 | "test": "jest" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/bugMaker-237/slim-ef.git" 25 | }, 26 | "keywords": [ 27 | "ef-core", 28 | "linq", 29 | "orm", 30 | "expression", 31 | "entity-framework", 32 | "mongo", 33 | "mongodb", 34 | "mysql", 35 | "mariadb", 36 | "postgresql", 37 | "sqlite", 38 | "sqlite3", 39 | "ts", 40 | "typescript", 41 | "js", 42 | "javascript", 43 | "entity", 44 | "ddd", 45 | "slim-ef", 46 | "unit-of-work", 47 | "data-mapper", 48 | "identity-map" 49 | ], 50 | "author": "bugmaker-237", 51 | "license": "ISC", 52 | "bugs": { 53 | "url": "https://github.com/bugMaker-237/slim-ef/issues" 54 | }, 55 | "homepage": "https://github.com/bugMaker-237/slim-ef#readme", 56 | "devDependencies": { 57 | "@types/jest": "^26.0.9", 58 | "@types/node": "^14.0.27", 59 | "jest": "^26.3.0", 60 | "mysql": "^2.18.1", 61 | "nodemon": "^2.0.4", 62 | "prettier": "^2.0.5", 63 | "rimraf": "^3.0.2", 64 | "sqlite3": "^5.0.0", 65 | "ts-jest": "^26.1.4", 66 | "ts-node": "^8.10.2", 67 | "tslint": "^6.1.3", 68 | "tslint-config-prettier": "^1.18.0", 69 | "typescript": "^3.9.7" 70 | }, 71 | "jest": { 72 | "transform": { 73 | "^.+\\.(t|j)sx?$": "ts-jest" 74 | }, 75 | "testRegex": "tests.*\\.test\\.(ts)$", 76 | "moduleFileExtensions": [ 77 | "ts", 78 | "tsx", 79 | "js", 80 | "jsx", 81 | "json", 82 | "node" 83 | ] 84 | }, 85 | "dependencies": { 86 | "slim-exp": "^0.3.2", 87 | "typeorm": "^0.2.41" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /tests/unit-of-work.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { Connection } from 'typeorm'; 3 | import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions'; 4 | import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'; 5 | import { SQLQuerySpecificationEvaluator } from '../src'; 6 | import { UnitOfWork } from '../src/uow'; 7 | import { IDbContextOptionsBuilder } from '../src/uow/interfaces'; 8 | import { DbContextModelBuilder } from '../src/uow/model-builder'; 9 | import { Agency } from './entities/agency'; 10 | import { Person } from './entities/person'; 11 | import { Trip } from './entities/trip'; 12 | 13 | class FakeUnitOfWork extends UnitOfWork { 14 | constructor() { 15 | /** 16 | * SQLite connection 17 | */ 18 | super( 19 | new Connection({ 20 | type: 'sqlite', 21 | database: resolve(__dirname, 'seeder', 'slim_ef_test.db'), 22 | entities: [Person, Agency, Trip], 23 | synchronize: false 24 | } as SqliteConnectionOptions), 25 | SQLQuerySpecificationEvaluator 26 | ); 27 | /** 28 | * MySql Connection 29 | */ 30 | // super( 31 | // new Connection({ 32 | // type: 'mysql', 33 | // host: 'localhost', 34 | // port: 3306, 35 | // username: 'admin', 36 | // password: 'admin0000', 37 | // database: 'slim_ef_test', 38 | // entities: [Person, Agency, Trip], 39 | // synchronize: true 40 | // } as MysqlConnectionOptions), 41 | // SQLQuerySpecificationEvaluator 42 | // ); 43 | } 44 | 45 | protected onModelCreation( 46 | builder: DbContextModelBuilder 47 | ): void { 48 | builder.entity(Person).hasQueryFilter(q => q.where(e => e.IDNumber > 50)); 49 | } 50 | 51 | protected onConfiguring(optionsBuilder: IDbContextOptionsBuilder): void { 52 | optionsBuilder.useLoggerFactory({ 53 | createLogger: (catName: string) => ({ 54 | log: (level, state) => console.log({ catName, state, level }) 55 | }) 56 | }); 57 | } 58 | } 59 | 60 | /** 61 | * In this programming pattern, since the unit of work is injected in 62 | * every repository, we are not going to create multiple connection instances 63 | * so a singleton pattern has to be applied here 64 | */ 65 | export const UOW = new FakeUnitOfWork(); 66 | -------------------------------------------------------------------------------- /tests/transactions/transactions.test.ts: -------------------------------------------------------------------------------- 1 | import { FakeDBContext } from '../db-context'; 2 | import { Person } from '../entities/person'; 3 | 4 | describe('Transactions test', () => { 5 | it('Should open transaction', async () => { 6 | // Arrange 7 | const context = new FakeDBContext(); 8 | 9 | // Act 10 | await context.openTransaction(); 11 | const isOpened = context.transactionIsOpen(); 12 | await context.rollbackTransaction(); 13 | 14 | // Assert 15 | expect(isOpened).toBe(true); 16 | }); 17 | 18 | it('Should open transaction then rollback', async () => { 19 | // Arrange 20 | const context = new FakeDBContext(); 21 | 22 | // Act 23 | await context.openTransaction(); 24 | const isOpened = context.transactionIsOpen(); 25 | await context.rollbackTransaction(); 26 | const isStillOpened = context.transactionIsOpen(); 27 | 28 | // Assert 29 | expect(isOpened).toBeTruthy(); 30 | expect(isStillOpened).toBeFalsy(); 31 | }); 32 | it('Should add person then rollback', async () => { 33 | // Arrange 34 | const context = new FakeDBContext(); 35 | 36 | // Act 37 | await context.openTransaction(); 38 | context.persons.add({ 39 | firstname: 'Another Buggy', 40 | lastname: 'Maker 2', 41 | IDNumber: 987654321, 42 | phone: '+237677788852' 43 | }); 44 | const { added } = await context.saveChanges(); 45 | const newPerson: Person = added[0]; 46 | 47 | await context.rollbackTransaction(); 48 | const exists = await context.persons.exists(newPerson.id); 49 | 50 | // Assert 51 | expect(newPerson.id).toBeDefined(); 52 | expect(exists).toBeFalsy(); 53 | }); 54 | it('Should add person then commit', async () => { 55 | // Arrange 56 | const context = new FakeDBContext(); 57 | 58 | // Act 59 | await context.openTransaction(); 60 | context.persons.add({ 61 | firstname: 'Another Buggy', 62 | lastname: 'Maker 2', 63 | IDNumber: 987654321, 64 | phone: '+237677788852' 65 | }); 66 | const { added } = await context.saveChanges(); 67 | const newPerson: Person = added[0]; 68 | 69 | await context.commitTransaction(); 70 | 71 | const existsNow = await context.persons.exists(newPerson.id); 72 | 73 | // Assert 74 | expect(newPerson.id).toBeDefined(); 75 | expect(existsNow).toBeTruthy(); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /tests/db-context.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { Connection } from 'typeorm'; 3 | import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions'; 4 | import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'; 5 | import { 6 | DbContext, 7 | DbSet, 8 | DbSetEntity, 9 | IDbSet, 10 | SQLQuerySpecificationEvaluator 11 | } from '../lib'; 12 | import { IDbContextOptionsBuilder } from '../lib/uow/interfaces'; 13 | import { DbContextModelBuilder } from '../lib/uow/model-builder'; 14 | import { Agency } from './entities/agency'; 15 | import { Person } from './entities/person'; 16 | import { Trip } from './entities/trip'; 17 | 18 | export class FakeDBContext extends DbContext { 19 | constructor() { 20 | /** 21 | * SQLite connection 22 | */ 23 | super( 24 | new Connection({ 25 | type: 'sqlite', 26 | database: resolve(__dirname, 'seeder', 'slim_ef_test.db'), 27 | entities: [Person, Agency, Trip], 28 | synchronize: false 29 | } as SqliteConnectionOptions), 30 | SQLQuerySpecificationEvaluator 31 | ); 32 | /** 33 | * MySql Connection 34 | */ 35 | // super( 36 | // new Connection({ 37 | // type: 'mysql', 38 | // host: 'localhost', 39 | // port: 3306, 40 | // username: 'admin', 41 | // password: 'admin0000', 42 | // database: 'slim_ef_test', 43 | // entities: [Person, Agency, Trip], 44 | // synchronize: true 45 | // } as MysqlConnectionOptions), 46 | // SQLQuerySpecificationEvaluator 47 | // ); 48 | return void 0; 49 | } 50 | 51 | protected onModelCreation( 52 | builder: DbContextModelBuilder 53 | ): void { 54 | builder.entity(Person).hasQueryFilter(q => q.where(e => e.IDNumber > 50)); 55 | } 56 | 57 | protected onConfiguring(optionsBuilder: IDbContextOptionsBuilder): void { 58 | optionsBuilder.useLoggerFactory({ 59 | createLogger: (catName: string) => ({ 60 | log: (level, state) => console.log({ catName, state, level }) 61 | }) 62 | }); 63 | } 64 | 65 | @DbSetEntity(Person) 66 | public readonly persons: IDbSet; 67 | 68 | @DbSetEntity(Agency) 69 | public readonly agencies: IDbSet; 70 | 71 | @DbSetEntity(Trip) 72 | public readonly trips: IDbSet; 73 | } 74 | -------------------------------------------------------------------------------- /tests/linq-query/sql-functions.test.ts: -------------------------------------------------------------------------------- 1 | import { FakeDBContext } from '../db-context'; 2 | 3 | /** 4 | * Make sure to run this tests before deleting any entity or running delete tests in the db 5 | * Because the computed values field values may not be identical again 6 | */ 7 | describe('SQL Functions call - LINQ', () => { 8 | it('Should have average greater than or equal to 500000', async () => { 9 | // Arrange 10 | const context = new FakeDBContext(); 11 | 12 | // Act 13 | const avg = await context.persons.average( 14 | p => p.IDNumber 15 | ); 16 | 17 | context.dispose(); 18 | 19 | // Assert 20 | expect(avg).toBeGreaterThanOrEqual(500000); 21 | }); 22 | 23 | it('Should have sum greater than or equal to 57512589', async () => { 24 | // Arrange 25 | const context = new FakeDBContext(); 26 | 27 | // Act 28 | const sum = await context.persons.sum( 29 | p => p.IDNumber 30 | ); 31 | 32 | context.dispose(); 33 | 34 | // Assert 35 | expect(sum).toBeGreaterThanOrEqual(57512589); 36 | }); 37 | 38 | it('Should have count greater than or equal to 100', async () => { 39 | // Arrange 40 | const context = new FakeDBContext(); 41 | 42 | // Act 43 | const count = await context.persons.count(); 44 | 45 | context.dispose(); 46 | 47 | // Assert 48 | expect(count).toBeGreaterThanOrEqual(100); 49 | }); 50 | 51 | it('Should have count greater than or equal to 61', async () => { 52 | // Arrange 53 | const context = new FakeDBContext(); 54 | 55 | // Act 56 | const count = await context.persons.count(p => p.IDNumber > 500000); 57 | 58 | context.dispose(); 59 | 60 | // Assert 61 | expect(count).toBeGreaterThanOrEqual(61); 62 | }); 63 | 64 | it('Should have MIN firstname equal to Abbey', async () => { 65 | // Arrange 66 | const context = new FakeDBContext(); 67 | 68 | // Act 69 | const min = await context.persons.min( 70 | p => p.firstname 71 | ); 72 | 73 | context.dispose(); 74 | 75 | // Assert 76 | expect(min).toBe('Abbey'); 77 | }); 78 | 79 | it('Should have max firstname equal to Zoey', async () => { 80 | // Arrange 81 | const context = new FakeDBContext(); 82 | 83 | // Act 84 | const max = await context.persons.max( 85 | p => p.firstname 86 | ); 87 | 88 | context.dispose(); 89 | 90 | // Assert 91 | expect(max).toBe('Zoey'); 92 | }); 93 | 94 | }); 95 | -------------------------------------------------------------------------------- /tests/db-set-style/add-agencies-trips.test.ts: -------------------------------------------------------------------------------- 1 | import { patch } from '../../src'; 2 | import { FakeDBContext } from '../db-context'; 3 | import { Agency } from '../entities/agency'; 4 | import { Person } from '../entities/person'; 5 | import { Trip } from '../entities/trip'; 6 | 7 | describe('Add agencies and trips', () => { 8 | it('Should add agency :: style 1', async () => { 9 | // Arrange 10 | const context = new FakeDBContext(); 11 | const agency = new Agency(); 12 | agency.name = 'Hello mundo'; 13 | agency.email = 'mundo@agency.super'; 14 | agency.phone = '+996985321'; 15 | 16 | const t1 = new Trip(); 17 | t1.departureDate = new Date(); 18 | t1.estimatedArrivalDate = new Date('09/10/2030'); 19 | t1.agency = agency; 20 | 21 | const t2 = new Trip(); 22 | t2.departureDate = new Date(); 23 | t2.estimatedArrivalDate = new Date('09/11/2030'); 24 | t2.agency = agency; 25 | 26 | // Act 27 | context.agencies.add(agency); 28 | await context.saveChanges(); 29 | 30 | t1.agencyId = agency.id; 31 | t2.agencyId = agency.id; 32 | context.trips.add(t1, t2); 33 | await context.saveChanges(); 34 | 35 | const dbAgency = await context.agencies.first((a, $) => a.id === $.id, { 36 | id: agency.id 37 | }); 38 | 39 | context.dispose(); 40 | 41 | // Assert 42 | expect(agency.id).toBeDefined(); 43 | expect(agency.id).toEqual(dbAgency.id); 44 | expect(agency.name).toEqual(dbAgency.name); 45 | expect(t1.id).toBeDefined(); 46 | expect(t2.id).toBeDefined(); 47 | }); 48 | 49 | it('Should add agency :: style 2', async () => { 50 | // Arrange 51 | const context = new FakeDBContext(); 52 | 53 | // Act 54 | context.agencies.add({ 55 | name: 'Hello secundo', 56 | email: 'secundo@agency.super', 57 | phone: '+996985321' 58 | }); 59 | let saved = await context.saveChanges(); 60 | const agency = saved.added[0]; 61 | 62 | context.trips.add( 63 | { 64 | departureDate: new Date(), 65 | estimatedArrivalDate: new Date('09/10/2030'), 66 | agencyId: agency.id 67 | }, 68 | { 69 | departureDate: new Date(), 70 | estimatedArrivalDate: new Date('09/11/2030'), 71 | agencyId: agency.id 72 | } 73 | ); 74 | 75 | saved = await context.saveChanges(); 76 | const [t1, t2] = saved.added; 77 | 78 | context.dispose(); 79 | 80 | // Assert 81 | expect(agency.id).toBeDefined(); 82 | expect(t1.id).toBeDefined(); 83 | expect(t2.id).toBeDefined(); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /tests/linq-query/logical.test.ts: -------------------------------------------------------------------------------- 1 | import { FakeDBContext } from '../db-context'; 2 | 3 | describe('Logical LINQ', () => { 4 | it('Should have correct firstname and lastname', async () => { 5 | // Arrange 6 | const context = new FakeDBContext(); 7 | 8 | // Act 9 | const person = await context.persons.first( 10 | p => p.firstname === 'Buggy' && p.lastname === 'Maker' 11 | ); 12 | 13 | context.dispose(); 14 | 15 | // Assert 16 | expect(person.firstname).toBe('Buggy'); 17 | expect(person.lastname).toBe('Maker'); 18 | }); 19 | 20 | it('Should have 32 trips in date interval', async () => { 21 | // Arrange 22 | const context = new FakeDBContext(); 23 | const data: { departureDate: Date; estimatedArrivalDate: Date } = { 24 | departureDate: new Date(2000, 1, 1), 25 | estimatedArrivalDate: new Date(2016, 1, 1) 26 | }; 27 | // Act 28 | const trips = await context.trips 29 | .where( 30 | (t, $) => 31 | t.departureDate > $.departureDate && 32 | t.estimatedArrivalDate < $.estimatedArrivalDate, 33 | data 34 | ) 35 | .toList(); 36 | 37 | context.dispose(); 38 | 39 | // Assert 40 | expect(trips.length).toEqual(32); 41 | expect(trips.some(t => t.departureDate < data.departureDate)).toBeFalsy(); 42 | expect( 43 | trips.some(t => t.estimatedArrivalDate > data.estimatedArrivalDate) 44 | ).toBeFalsy(); 45 | }); 46 | 47 | it('Should have email starting with um and name including um', async () => { 48 | // Arrange 49 | const context = new FakeDBContext(); 50 | const $ = { 51 | name: 'um' 52 | }; 53 | // Act 54 | const agencies = await context.agencies 55 | .where( 56 | (a, _) => 57 | a.email.endsWith(_.name) || (a.name.includes(_.name) && a.id > 45), 58 | $ 59 | ) 60 | .toList(); 61 | 62 | context.dispose(); 63 | 64 | // Assert 65 | expect(agencies.length).toEqual(8); 66 | expect( 67 | agencies.some(t => !t.email.endsWith($.name) && !t.name.includes($.name)) 68 | ).toBeFalsy(); 69 | }); 70 | 71 | it('Should have 35 trips with departureDate and arrivalDate as specified', async () => { 72 | // init 73 | const context = new FakeDBContext(); 74 | const data = { 75 | departureDate: new Date(2000, 1, 1), 76 | estimatedArrivalDate: new Date(2016, 1, 1) 77 | }; 78 | 79 | // getting the data 80 | const trips = await context.trips 81 | .include(t => t.passengers) 82 | .where( 83 | (t, $) => 84 | t.departureDate > $.departureDate && 85 | (t.estimatedArrivalDate < $.estimatedArrivalDate || 86 | t.passengers.some(p => p.willTravel === true)), 87 | data 88 | ) 89 | .toList(); 90 | 91 | context.dispose(); 92 | 93 | // Assert 94 | expect(trips.length).toEqual(35); 95 | }); 96 | }); 97 | -------------------------------------------------------------------------------- /src/specification/utils.ts: -------------------------------------------------------------------------------- 1 | import { SlimExpression } from 'slim-exp'; 2 | import { IQueryable, QueryRefiner } from '../repository/interfaces'; 3 | import { ISpecification } from './specification.interface'; 4 | 5 | const nameof = SlimExpression.nameOf; 6 | 7 | export function isSpecification( 8 | obj: ISpecification | any 9 | ): obj is ISpecification { 10 | return ( 11 | nameof>(s => s.getCriterias) in obj && 12 | nameof>(s => s.getIncludes) in obj && 13 | nameof>(s => s.getIsPagingEnabled) in obj && 14 | nameof>(s => s.getOrderBy) in obj && 15 | nameof>(s => s.getOrderByDescending) in obj && 16 | nameof>(s => s.getSelector) in obj && 17 | nameof>(s => s.getSkip) in obj && 18 | nameof>(s => s.getTake) in obj 19 | ); 20 | } 21 | 22 | export function isQueryable( 23 | obj: any 24 | ): obj is QueryRefiner { 25 | const props = [ 26 | nameof>(s => s.include), 27 | nameof>(s => s.orderBy), 28 | nameof>(s => s.orderByDescending), 29 | nameof>(s => s.select), 30 | nameof>(s => s.thenOrderBy), 31 | nameof>(s => s.where) 32 | ]; 33 | const content: string = obj.toString(); 34 | 35 | return props.some(p => content.includes(`.${p}(`)); 36 | } 37 | 38 | export const SQLConstants = { 39 | FALSE: 'FALSE', 40 | TRUE: 'TRUE' 41 | }; 42 | export const SQLStringFunctions = { 43 | startsWith: "LOWER({0}) LIKE LOWER('{1}%')", 44 | endsWith: "LOWER({0}) LIKE LOWER('%{1}')", 45 | includes: "LOWER({0}) LIKE LOWER('%{1}%')", 46 | toLowerCase: 'LOWER({0})', 47 | toUpperCase: 'UPPER({0})' 48 | }; 49 | 50 | export const SQLArrayFunctions = { 51 | includes: '{0} IN ({1})' 52 | }; 53 | 54 | export const SQLJoinFunctions = { 55 | some: 'some' 56 | }; 57 | export const ComparisonOperators = { 58 | ALL: ['==', '===', '>=', '<=', '!=', '!==', '<', '>'], 59 | EQUAL_TO: '==', 60 | STRICTLY_EQUAL_TO: '===', 61 | GREATER_THAN_OR_EQUAL: '>=', 62 | GREATER_THAN: '>', 63 | LESS_THAN_OR_EQUAL: '<=', 64 | LESS_THAN: '<', 65 | NOT_EQUAL_TO: '!=', 66 | STRICTLY_NOT_EQUAL_TO: '!==' 67 | }; 68 | 69 | export function convertToSqlComparisonOperator(op: string, val?: any) { 70 | let res = '' + op; 71 | 72 | if ( 73 | res === ComparisonOperators.EQUAL_TO || 74 | res === ComparisonOperators.STRICTLY_EQUAL_TO 75 | ) 76 | res = val === null ? 'is' : '='; 77 | else if (res === ComparisonOperators.STRICTLY_NOT_EQUAL_TO) 78 | res = val === null ? 'is not' : exports.ComparisonOperators.NOT_EQUAL_TO; 79 | 80 | return res; 81 | } 82 | 83 | export function format(toFormat: string, ...args: string[]) { 84 | return toFormat.replace(/{(\d+)}/g, (match, n) => 85 | typeof args[n] !== 'undefined' ? args[n] : match 86 | ); 87 | } 88 | -------------------------------------------------------------------------------- /tests/linq-query/simple.test.ts: -------------------------------------------------------------------------------- 1 | import { patch } from '../../src'; 2 | import { FakeDBContext } from '../db-context'; 3 | import { Person } from '../entities/person'; 4 | 5 | describe('Simple LINQ', () => { 6 | it('Should have at least 100 persons', async () => { 7 | // Arrange 8 | const context = new FakeDBContext(); 9 | 10 | // Act 11 | const persons = await context.persons.toList(); 12 | 13 | context.dispose(); 14 | 15 | // Assert 16 | expect(persons.length).toBeGreaterThanOrEqual(100); 17 | }); 18 | 19 | it('Should have correct firstname', async () => { 20 | // Arrange 21 | const context = new FakeDBContext(); 22 | 23 | // Act 24 | const person = await context.persons.first(p => p.firstname === 'Buggy'); 25 | 26 | context.dispose(); 27 | 28 | // Assert 29 | expect(person.firstname).toBe('Buggy'); 30 | expect(person.lastname).toBe('Maker'); 31 | }); 32 | 33 | it('Firstname Should start with bug', async () => { 34 | // Arrange 35 | const context = new FakeDBContext(); 36 | 37 | // Act 38 | const persons = await context.persons 39 | .where(p => p.firstname.startsWith('Bugg')) 40 | .toList(); 41 | 42 | context.dispose(); 43 | 44 | // Assert 45 | expect(persons.length).toBeGreaterThanOrEqual(1); 46 | expect(persons[0].firstname).toContain('Bugg'); 47 | }); 48 | 49 | it('Firstname Should end with gy', async () => { 50 | // Arrange 51 | const context = new FakeDBContext(); 52 | 53 | // Act 54 | const persons = await context.persons 55 | .where(p => p.firstname.endsWith('uggy')) 56 | .toList(); 57 | 58 | context.dispose(); 59 | 60 | // Assert 61 | expect(persons.length).toBeGreaterThanOrEqual(1); 62 | expect(persons[0].firstname).toContain('uggy'); 63 | }); 64 | 65 | it('Should have 41 persons traveling', async () => { 66 | // Arrange 67 | const context = new FakeDBContext(); 68 | 69 | // Act 70 | const persons = await context.persons 71 | .where(p => p.willTravel === true) 72 | .toList(); 73 | 74 | context.dispose(); 75 | 76 | // Assert 77 | expect(persons.length).toBeGreaterThanOrEqual(41); 78 | }); 79 | 80 | it('Should have 41 persons traveling (Double-Exclamation)', async () => { 81 | // Arrange 82 | const context = new FakeDBContext(); 83 | 84 | // Act 85 | const persons = await context.persons.where(p => !!p.willTravel).toList(); 86 | 87 | context.dispose(); 88 | 89 | // Assert 90 | expect(persons.length).toBeGreaterThanOrEqual(41); 91 | }); 92 | 93 | it('Should have 61 persons not traveling (Exclamation)', async () => { 94 | // Arrange 95 | const context = new FakeDBContext(); 96 | 97 | // Act 98 | const persons = await context.persons.where(p => !p.willTravel).toList(); 99 | 100 | context.dispose(); 101 | 102 | // Assert 103 | expect(persons.length).toBeGreaterThanOrEqual(61); 104 | }); 105 | 106 | it('Should have 61 persons not traveling', async () => { 107 | // Arrange 108 | const context = new FakeDBContext(); 109 | 110 | // Act 111 | const persons = await context.persons 112 | .where(p => p.willTravel === false) 113 | .toList(); 114 | 115 | context.dispose(); 116 | 117 | // Assert 118 | expect(persons.length).toBeGreaterThanOrEqual(61); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /tests/linq-query/select.test.ts: -------------------------------------------------------------------------------- 1 | import { FakeDBContext } from '../db-context'; 2 | import { Agency } from '../entities/agency'; 3 | 4 | class TripResponse { 5 | constructor( 6 | public agencyName: string, 7 | public agencyEmail: string, 8 | public departureDate: Date, 9 | public passengers: { name: string; phone: string; ID: number }[] 10 | ) {} 11 | } 12 | describe('Select test', () => { 13 | it('Should create instance of an anonymous object with values', async () => { 14 | const context = new FakeDBContext(); 15 | 16 | const person = await context.persons 17 | .include(p => p.trip) 18 | .thenInclude(t => t.agency) 19 | .select(p => ({ 20 | att1: p.trip.departureDate, 21 | att2: p.trip.agency.name 22 | })) 23 | .firstOrDefault(p => p.id === 'd1fa69b0-2092-3b6f-90d5-870e902c8315'); 24 | 25 | console.log({ person }); 26 | expect(person).toBeDefined(); 27 | expect(person.att1).toBeDefined(); // 1974-02-02 12:21:00 28 | expect(person.att2).toBeDefined(); // 'Recusandae aspernatur.' 29 | }); 30 | it('Should create instance of response with values', async () => { 31 | const context = new FakeDBContext(); 32 | 33 | const tripsQuery = context.trips 34 | .include(t => t.agency) 35 | .include(t => t.passengers) 36 | .select( 37 | t => 38 | new TripResponse( 39 | t.agency.name, 40 | t.agency.email, 41 | t.departureDate, 42 | t.passengers.map(p => ({ 43 | name: p.lastname, 44 | phone: p.phone, 45 | ID: p.IDNumber 46 | })) 47 | ) 48 | ); 49 | 50 | const res = await tripsQuery.toList(); 51 | 52 | expect(res.length).toBeGreaterThanOrEqual(100); 53 | expect(res[0].passengers).toBeDefined(); 54 | }); 55 | 56 | it('Should filter and create instance of response with values', async () => { 57 | const context = new FakeDBContext(); 58 | const ctx = { 59 | departureDate: new Date(2000, 1, 1), 60 | estimatedArrivalDate: new Date(2016, 1, 1) 61 | }; 62 | 63 | const tripsQuery = context.trips 64 | .include(t => t.agency) 65 | .include(t => t.passengers) 66 | .where( 67 | (t, $) => 68 | t.departureDate > $.departureDate && 69 | (t.estimatedArrivalDate < $.estimatedArrivalDate || 70 | t.passengers.some(p => p.willTravel === true)), 71 | ctx 72 | ) 73 | .select( 74 | t => 75 | new TripResponse( 76 | t.agency.name, 77 | t.agency.email, 78 | t.departureDate, 79 | t.passengers.map(p => ({ 80 | name: p.lastname, 81 | phone: p.phone, 82 | ID: p.IDNumber 83 | })) 84 | ) 85 | ); 86 | 87 | const res = await tripsQuery.toList(); 88 | 89 | expect(res.length).toEqual(35); 90 | expect(res[0].passengers).toBeDefined(); 91 | }); 92 | 93 | it('Should filter and create instance of response with values handling bracket in correct order', async () => { 94 | const context = new FakeDBContext(); 95 | const ctx = { 96 | departureDate: new Date(2000, 1, 1), 97 | estimatedArrivalDate: new Date(2016, 1, 1) 98 | }; 99 | 100 | const tripsQuery = context.trips 101 | .include(t => t.agency) 102 | .include(t => t.passengers) 103 | .where( 104 | (t, $) => 105 | (t.estimatedArrivalDate < $.estimatedArrivalDate || 106 | t.passengers.some(p => p.willTravel === true)) && 107 | t.departureDate > $.departureDate, 108 | ctx 109 | ) 110 | .select( 111 | t => 112 | new TripResponse( 113 | t.agency.name, 114 | t.agency.email, 115 | t.departureDate, 116 | t.passengers.map(p => ({ 117 | name: p.lastname, 118 | phone: p.phone, 119 | ID: p.IDNumber 120 | })) 121 | ) 122 | ); 123 | 124 | const res = await tripsQuery.toList(); 125 | 126 | expect(res.length).toEqual(35); 127 | expect(res[0].passengers).toBeDefined(); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /src/uow/metadata.ts: -------------------------------------------------------------------------------- 1 | import { Connection, EntityMetadata } from 'typeorm'; 2 | import { 3 | SelectArrayProxy, 4 | SelectBooleanProxy, 5 | SelectNumberProxy, 6 | SelectStringProxy 7 | } from './metadata-proxy'; 8 | import { ProxyMetaDataInstance } from './_internal.interface'; 9 | 10 | const map = new WeakMap(); 11 | 12 | export function getMetaData( 13 | con: Connection, 14 | type: new (...args) => T, 15 | includePaths: string[] 16 | ): ProxyMetaDataInstance { 17 | // if (map.has(type)) return map.get(type); 18 | // const meta = _getMetaData(con, type, [], '', includePaths); 19 | // map.set(type, meta); 20 | // TODO: Find a way to cache this with the include paths 21 | return _getMetaData(con, type, [], '', includePaths); 22 | } 23 | 24 | function _getMetaData( 25 | con: Connection, 26 | type: any, 27 | constructorChain = [], 28 | basePropName = '', 29 | includePaths: string[] = [] 30 | ): ProxyMetaDataInstance { 31 | const meta = con.getMetadata(type); 32 | // TODO: find a way to better cache the constructor chain. 33 | return _getTypeMetaData( 34 | meta, 35 | type, 36 | con, 37 | constructorChain, 38 | basePropName, 39 | includePaths 40 | ); 41 | } 42 | 43 | function _getTypeMetaData( 44 | meta: EntityMetadata, 45 | type: any, 46 | con: Connection, 47 | constructorChain: any[], 48 | basePropName = '', 49 | includePaths: string[] = [] 50 | ): ProxyMetaDataInstance { 51 | const index = constructorChain.find(o => o.constructor === type); 52 | if (index >= 0) { 53 | const proxy = constructorChain[index]; 54 | return proxy; 55 | } 56 | let instance = _getInstanceWithColumnsMetadata(meta, type, basePropName); 57 | 58 | instance = _getInstanceWithRelationsMetadata( 59 | con, 60 | meta, 61 | instance, 62 | type, 63 | constructorChain, 64 | basePropName, 65 | includePaths 66 | ); 67 | return instance; 68 | } 69 | 70 | function _getInstanceWithRelationsMetadata( 71 | con: Connection, 72 | metaData: EntityMetadata, 73 | previousInstance: any, 74 | type: any, 75 | constructorChain: any[], 76 | basePropName = '', 77 | includePaths: string[] = [] 78 | ): ProxyMetaDataInstance { 79 | const instance = new type(); 80 | 81 | Object.assign(instance, previousInstance); 82 | 83 | if (constructorChain.findIndex(o => o.constructor === type) < 0) 84 | constructorChain.push(instance); 85 | 86 | let toAssign; 87 | 88 | for (const c of metaData.relations) { 89 | toAssign = { 90 | $$propertyName: basePropName 91 | ? `${basePropName}.${c.propertyName}` 92 | : c.propertyName 93 | }; 94 | const subType = new (c as any).type(); 95 | let meta; 96 | const i = constructorChain.findIndex(o => o.constructor === c.type); 97 | if (includePaths.includes(toAssign.$$propertyName)) { 98 | if (i >= 0) { 99 | meta = constructorChain[i]; 100 | } else { 101 | meta = _getMetaData( 102 | con, 103 | c.type, 104 | constructorChain, 105 | toAssign.$$propertyName, 106 | includePaths 107 | ); 108 | } 109 | } 110 | if (meta) { 111 | if (c.isOneToMany) { 112 | // handling one to many as array since the type in target is Array 113 | const arrProxy = new SelectArrayProxy(toAssign.$$propertyName); 114 | instance[c.propertyName] = arrProxy; 115 | 116 | Object.assign(subType, toAssign, meta); 117 | instance[c.propertyName].push(subType); 118 | } else { 119 | Object.assign(subType, toAssign, meta); 120 | instance[c.propertyName] = subType; 121 | } 122 | } 123 | } 124 | return instance; 125 | } 126 | 127 | function _getInstanceWithColumnsMetadata( 128 | metaData: EntityMetadata, 129 | type: any, 130 | basePropName = '' 131 | ) { 132 | const instance = new type(); 133 | for (const c of metaData.columns) { 134 | const realProp = basePropName 135 | ? `${basePropName}.${c.propertyName}` 136 | : c.propertyName; 137 | if (c.type === Number) { 138 | instance[c.propertyName] = new SelectNumberProxy(realProp); 139 | } else if (c.type === Boolean) { 140 | instance[c.propertyName] = new SelectBooleanProxy(realProp); 141 | } else { 142 | instance[c.propertyName] = new SelectStringProxy(realProp); 143 | } 144 | } 145 | return instance; 146 | } 147 | -------------------------------------------------------------------------------- /src/uow/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { SelectQueryBuilder } from 'typeorm'; 2 | import { IDbSet } from '../repository'; 3 | 4 | export type IUnitOfWork = IDbContext; 5 | export interface IDbContext { 6 | /** 7 | * Begins tracking the given entity, and any other reachable entities that are not 8 | * already being tracked, in the Added state such that they will be inserted 9 | * into the database when `saveChanges()` is called. 10 | * @param entities 11 | */ 12 | add(...entities: T[]): Promise | void; 13 | 14 | /** 15 | * Begins tracking the given entity and entries reachable from the given entity using 16 | * the Modified state by default such that they will be updated 17 | * in the database when `saveChanges()` is called. 18 | * @param entities 19 | */ 20 | update(...entities: T[]): Promise | void; 21 | 22 | /** 23 | * Begins tracking the given entity in the Deleted state such that it will be removed 24 | * from the database when `saveChanges()` is called. 25 | * @param entities 26 | */ 27 | remove(...entities: T[]): Promise | void; 28 | 29 | /** 30 | * Removes entities from the list of currently tracked entities 31 | * @param entities 32 | */ 33 | unTrack(...entities: T[]): Promise | void; 34 | /** 35 | * Finds an entity with the given primary key values. If an entity with the given 36 | * primary key values is being tracked by the context, then it is returned 37 | * immediately without making a request to the database. Otherwise, a query is made 38 | * to the database for an entity with the given primary key values and this entity, 39 | * if found, is attached to the context and returned. If no entity is found, then 40 | * undefined is returned. 41 | * @param type The entity type 42 | * @param id The entity id 43 | */ 44 | find(type: new (...args: any) => T, id: any): Promise | T; 45 | /** 46 | * Creates a database connextion and executes the given query 47 | * @param query 48 | * @param parameters 49 | */ 50 | query(query: string, parameters: any[]): Promise; 51 | 52 | /** 53 | * Discard all they tracked modifications of all entity types or a specific type 54 | * @param entityType 55 | */ 56 | rollback(entityType: any | undefined): void; 57 | /** 58 | * Creates a DbSet that can be used to query and save instances of TEntity. 59 | * @param type 60 | */ 61 | set(type: new (...args: any) => T): IDbSet; 62 | 63 | /** 64 | * Saves all changes made in this context to the database. 65 | * This method will automatically call DetectChanges() to discover any changes to 66 | * entity instances before saving to the underlying database. 67 | */ 68 | saveChanges(withoutRefresh?: boolean): Promise; 69 | /** 70 | * Releases the allocated resources for this context. 71 | */ 72 | dispose(): void; 73 | 74 | /** 75 | * Starts transaction. 76 | */ 77 | openTransaction(): Promise; 78 | 79 | /** 80 | * Commits transaction. 81 | */ 82 | commitTransaction(): Promise; 83 | 84 | /** 85 | * Ends transaction. 86 | */ 87 | rollbackTransaction(): Promise; 88 | 89 | /** 90 | * Returns true if user-defined transaction is initialise 91 | */ 92 | transactionIsOpen(): boolean; 93 | 94 | /** 95 | * Creates a new entity from the given plain javascript object. If the entity already exist in the database, then it loads it (and everything related to it), replaces all values with the new ones from the given object, and returns the new entity. The new entity is actually loaded from the database entity with all properties replaced from the new object. 96 | * @param type The type of the enity to load 97 | * @param entity The partial entity values 98 | */ 99 | loadRelatedData(type: new (...args: []) => T, entity: T): Promise; 100 | } 101 | 102 | export type QueryInitializer = (alias: string) => SelectQueryBuilder; 103 | 104 | export interface ISavedTransaction { 105 | added: any[]; 106 | updated: any[]; 107 | deleted: any[]; 108 | } 109 | export interface ILogger { 110 | log( 111 | logLevel: 'error' | 'warning' | 'info', 112 | state: TState, 113 | exception?: Error 114 | ): void; 115 | } 116 | export type ILoggerCategoryName = 'config' | 'query'; 117 | export interface ILoggerFactory { 118 | createLogger: (categoryName: ILoggerCategoryName) => ILogger; 119 | } 120 | 121 | export interface IDbContextOptionsBuilder { 122 | useLoggerFactory(loggerFactory: ILoggerFactory): this; 123 | enableSensitiveLogging(enabled: boolean): this; 124 | } 125 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | "incremental": true /* Enable incremental compilation */, 7 | "target": "es2017", 8 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, 9 | // "lib": [ 10 | // "es6" 11 | // ] /* Specify library files to be included in the compilation. */, 12 | "allowJs": true /* Allow javascript files to be compiled. */, 13 | // "checkJs": true, /* Report errors in .js files. */ 14 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 15 | "declaration": true /* Generates corresponding '.d.ts' file. */, 16 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 17 | "sourceMap": true /* Generates corresponding '.map' file. */, 18 | // "outFile": "./", /* Concatenate and emit output to single file. */ 19 | "outDir": "lib" /* Redirect output structure to the directory. */, 20 | "rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, 21 | // "composite": true, /* Enable project compilation */ 22 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 23 | // "removeComments": true, /* Do not emit comments to output. */ 24 | // "noEmit": true, /* Do not emit outputs. */ 25 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 26 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 27 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 28 | 29 | /* Strict Type-Checking Options */ 30 | // "strict": true /* Enable all strict type-checking options. */, 31 | // "noImplicitAny": false /* Raise error on expressions and declarations with an implied 'any' type. */, 32 | // "strictNullChecks": true, /* Enable strict null checks. */ 33 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 34 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 35 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 36 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 37 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 38 | 39 | /* Additional Checks */ 40 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 41 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 42 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 43 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 44 | 45 | /* Module Resolution Options */ 46 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 47 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 48 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 49 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 50 | // "typeRoots": [], /* List of folders to include type definitions from. */ 51 | // "types": [], /* Type declaration files to be included in compilation. */ 52 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 53 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 54 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 55 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 56 | 57 | /* Source Map Options */ 58 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 61 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 62 | 63 | /* Experimental Options */ 64 | "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, 65 | "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */, 66 | 67 | /* Advanced Options */ 68 | "resolveJsonModule": true /* Include modules imported with '.json' extension */, 69 | "skipLibCheck": true /* Skip type checking of declaration files. */, 70 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 71 | }, 72 | "include": ["src"], 73 | "exclude": ["node_modules", "tests", "**/__tests__/*"] 74 | } 75 | -------------------------------------------------------------------------------- /src/specification/base.specification.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ExpressionResult, 3 | SlimExpression, 4 | SlimExpressionFunction 5 | } from 'slim-exp'; 6 | import { 7 | ISpecification, 8 | FieldsSelector, 9 | CriteriaExpression, 10 | FunctionQueryType 11 | } from './specification.interface'; 12 | 13 | export class BaseSpecification implements ISpecification { 14 | private _includes: SlimExpressionFunction[] = []; 15 | private _distinct: boolean = false; 16 | private _chainedIncludes: { 17 | initial: SlimExpressionFunction; 18 | chain: SlimExpressionFunction[]; 19 | }[] = []; 20 | private _criterias: CriteriaExpression[] = []; 21 | private _orderBy: SlimExpressionFunction; 22 | private _groupBy: SlimExpressionFunction; 23 | private _orderByDescending: SlimExpressionFunction; 24 | private _thenOrderBy: SlimExpressionFunction[] = []; 25 | private _thenGroupBy: SlimExpressionFunction[] = []; 26 | private _take = 0; 27 | private _skip = 0; 28 | private _isPagingEnabled: boolean; 29 | private _selector: FieldsSelector; 30 | private _func: { 31 | type: FunctionQueryType; 32 | func: SlimExpressionFunction; 33 | }; 34 | private _initializeThenInclude: boolean; 35 | 36 | getIncludePaths(): string[] { 37 | const paths = this._includes.map(i => SlimExpression.nameOf(i)); 38 | 39 | const chainedPaths = this._chainedIncludes.map(ci => { 40 | const nameOfInititial = SlimExpression.nameOf(ci.initial); 41 | return ci.chain.reduce( 42 | (p, c) => (p.push(`${p[p.length - 1]}.${SlimExpression.nameOf(c)}`), p), 43 | [nameOfInititial] 44 | ); 45 | }); 46 | paths.push(...chainedPaths.reduce((p, c) => (p.push(...c), p), [])); 47 | return paths; 48 | } 49 | getIncludes(): SlimExpressionFunction[] { 50 | return this._includes; 51 | } 52 | getDistinct(): boolean { 53 | return this._distinct; 54 | } 55 | getFunction(): { 56 | type: FunctionQueryType; 57 | func: SlimExpressionFunction; 58 | } { 59 | return this._func; 60 | } 61 | getChainedIncludes(): { 62 | initial: SlimExpressionFunction; 63 | chain: SlimExpressionFunction[]; 64 | }[] { 65 | return this._chainedIncludes; 66 | } 67 | getCriterias(): CriteriaExpression[] { 68 | return this._criterias; 69 | } 70 | getOrderBy(): SlimExpressionFunction { 71 | return this._orderBy; 72 | } 73 | getGroupBy(): SlimExpressionFunction { 74 | return this._groupBy; 75 | } 76 | getOrderByDescending(): SlimExpressionFunction { 77 | return this._orderByDescending; 78 | } 79 | getThenOrderBy(): SlimExpressionFunction[] { 80 | return this._thenOrderBy; 81 | } 82 | getThenGroupBy(): SlimExpressionFunction[] { 83 | return this._thenGroupBy; 84 | } 85 | getTake(): number { 86 | return this._take; 87 | } 88 | getSkip(): number { 89 | return this._skip; 90 | } 91 | getIsPagingEnabled(): boolean { 92 | return this._isPagingEnabled; 93 | } 94 | getSelector(): FieldsSelector { 95 | return this._selector; 96 | } 97 | 98 | applyPaging(skip: number, take: number) { 99 | this._skip = skip; 100 | this._take = take; 101 | this._isPagingEnabled = true; 102 | } 103 | addInclude(include: SlimExpressionFunction) { 104 | if (include) { 105 | this._includes.push(include); 106 | this._initializeThenInclude = true; 107 | } 108 | } 109 | applyDistinct(distinct = true) { 110 | this._distinct = distinct; 111 | } 112 | 113 | addChainedInclude( 114 | include: SlimExpressionFunction, 115 | include2: SlimExpressionFunction 116 | ); 117 | addChainedInclude( 118 | include: SlimExpressionFunction, 119 | include2: SlimExpressionFunction, 120 | include3: SlimExpressionFunction 121 | ); 122 | addChainedInclude( 123 | include: SlimExpressionFunction, 124 | include2: SlimExpressionFunction, 125 | include3: SlimExpressionFunction, 126 | include4: SlimExpressionFunction

127 | ); 128 | addChainedInclude< 129 | S extends object, 130 | R extends object, 131 | P extends object, 132 | O extends object 133 | >( 134 | include: SlimExpressionFunction, 135 | include2?: SlimExpressionFunction, 136 | include3?: SlimExpressionFunction, 137 | include4?: SlimExpressionFunction, 138 | include5?: SlimExpressionFunction 139 | ) { 140 | const chain = []; 141 | if (include2) chain.push(include2); 142 | if (include3) chain.push(include3); 143 | if (include4) chain.push(include4); 144 | if (include5) chain.push(include5); 145 | 146 | if (chain.length === 0) { 147 | this._includes.push(include); 148 | return; 149 | } 150 | 151 | this._includes = this._includes.filter( 152 | v => v.toString() !== include.toString() 153 | ); 154 | 155 | const lastIndex = this._chainedIncludes 156 | .map(val => val.initial.toString().trim()) 157 | .lastIndexOf(include.toString().trim()); 158 | const i = this._chainedIncludes[lastIndex]; 159 | 160 | if (i && !this._initializeThenInclude) { 161 | chain.forEach(c => { 162 | if (!i.chain.find(v => v.toString() === c.toString())) { 163 | i.chain.push(c); 164 | } 165 | }); 166 | } else { 167 | this._chainedIncludes.push({ 168 | initial: include, 169 | chain 170 | }); 171 | this._initializeThenInclude = false; 172 | } 173 | } 174 | 175 | addCriteria( 176 | func: SlimExpressionFunction, 177 | context?: C | null 178 | ) { 179 | if (func) this._criterias.push({ func, context }); 180 | } 181 | applySelector(selector: FieldsSelector) { 182 | if (selector) this._selector = selector; 183 | } 184 | applyOrderBy(orderBy: SlimExpressionFunction) { 185 | this._orderBy = orderBy; 186 | this._orderByDescending = null; 187 | } 188 | applyGroupBy(groupBy: SlimExpressionFunction) { 189 | this._groupBy = groupBy; 190 | } 191 | applyFunction(type: FunctionQueryType, func: SlimExpressionFunction) { 192 | if (type) 193 | this._func = { 194 | type, 195 | func 196 | }; 197 | } 198 | applyThenOrderBy(thenOrderBy: SlimExpressionFunction) { 199 | if (thenOrderBy) { 200 | this._thenOrderBy.push(thenOrderBy); 201 | } 202 | } 203 | applyThenGroupBy(thenBy: SlimExpressionFunction) { 204 | if (thenBy) { 205 | this._thenGroupBy.push(thenBy); 206 | } 207 | } 208 | applyOrderByDescending(orderBy: SlimExpressionFunction) { 209 | this._orderByDescending = orderBy; 210 | this._orderBy = null; 211 | } 212 | extend(spec: ISpecification) { 213 | this._includes.concat(spec.getIncludes()); 214 | this._distinct = spec.getDistinct(); 215 | this._chainedIncludes.concat(spec.getChainedIncludes()); 216 | this._criterias.concat(spec.getCriterias()); 217 | this._skip = spec.getSkip(); 218 | this._take = spec.getTake(); 219 | this._isPagingEnabled = spec.getIsPagingEnabled(); 220 | } 221 | 222 | clearSpecs() { 223 | this._includes = []; 224 | this._distinct = false; 225 | this._chainedIncludes = []; 226 | this._criterias = []; 227 | this._func = null; 228 | this._isPagingEnabled = false; 229 | this._orderBy = null; 230 | this._orderByDescending = null; 231 | this._selector = null; 232 | this._skip = 0; 233 | this._take = 0; 234 | this._thenOrderBy = []; 235 | this._thenGroupBy = []; 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /mysql2sqlite.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/awk -f 2 | 3 | # Authors: @esperlu, @artemyk, @gkuenning, @dumblob 4 | 5 | # FIXME detect empty input file and issue a warning 6 | 7 | function printerr( s ){ print s | "cat >&2" } 8 | 9 | BEGIN { 10 | if( ARGC != 2 ){ 11 | printerr( \ 12 | "USAGE:\n"\ 13 | " mysql2sqlite dump_mysql.sql > dump_sqlite3.sql\n" \ 14 | " OR\n" \ 15 | " mysql2sqlite dump_mysql.sql | sqlite3 sqlite.db\n" \ 16 | "\n" \ 17 | "NOTES:\n" \ 18 | " Dash in filename is not supported, because dash (-) means stdin." ) 19 | no_END = 1 20 | exit 1 21 | } 22 | 23 | # Find INT_MAX supported by both this AWK (usually an ISO C signed int) 24 | # and SQlite. 25 | # On non-8bit-based architectures, the additional bits are safely ignored. 26 | 27 | # 8bit (lower precision should not exist) 28 | s="127" 29 | # "63" + 0 avoids potential parser misbehavior 30 | if( (s + 0) "" == s ){ INT_MAX_HALF = "63" + 0 } 31 | # 16bit 32 | s="32767" 33 | if( (s + 0) "" == s ){ INT_MAX_HALF = "16383" + 0 } 34 | # 32bit 35 | s="2147483647" 36 | if( (s + 0) "" == s ){ INT_MAX_HALF = "1073741823" + 0 } 37 | # 64bit (as INTEGER in SQlite3) 38 | s="9223372036854775807" 39 | if( (s + 0) "" == s ){ INT_MAX_HALF = "4611686018427387904" + 0 } 40 | # # 128bit 41 | # s="170141183460469231731687303715884105728" 42 | # if( (s + 0) "" == s ){ INT_MAX_HALF = "85070591730234615865843651857942052864" + 0 } 43 | # # 256bit 44 | # s="57896044618658097711785492504343953926634992332820282019728792003956564819968" 45 | # if( (s + 0) "" == s ){ INT_MAX_HALF = "28948022309329048855892746252171976963317496166410141009864396001978282409984" + 0 } 46 | # # 512bit 47 | # s="6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042048" 48 | # if( (s + 0) "" == s ){ INT_MAX_HALF = "3351951982485649274893506249551461531869841455148098344430890360930441007518386744200468574541725856922507964546621512713438470702986642486608412251521024" + 0 } 49 | # # 1024bit 50 | # s="89884656743115795386465259539451236680898848947115328636715040578866337902750481566354238661203768010560056939935696678829394884407208311246423715319737062188883946712432742638151109800623047059726541476042502884419075341171231440736956555270413618581675255342293149119973622969239858152417678164812112068608" 51 | # if( (s + 0) "" == s ){ INT_MAX_HALF = "44942328371557897693232629769725618340449424473557664318357520289433168951375240783177119330601884005280028469967848339414697442203604155623211857659868531094441973356216371319075554900311523529863270738021251442209537670585615720368478277635206809290837627671146574559986811484619929076208839082406056034304" + 0 } 52 | # # higher precision probably not needed 53 | 54 | FS=",$" 55 | print "PRAGMA synchronous = OFF;" 56 | print "PRAGMA journal_mode = MEMORY;" 57 | print "BEGIN TRANSACTION;" 58 | } 59 | 60 | # historically 3 spaces separate non-argument local variables 61 | function bit_to_int( str_bit, powtwo, i, res, bit, overflow ){ 62 | powtwo = 1 63 | overflow = 0 64 | # 011101 = 1*2^0 + 0*2^1 + 1*2^2 ... 65 | for( i = length( str_bit ); i > 0; --i ){ 66 | bit = substr( str_bit, i, 1 ) 67 | if( overflow || ( bit == 1 && res > INT_MAX_HALF ) ){ 68 | printerr( \ 69 | NR ": WARN Bit field overflow, number truncated (LSBs saved, MSBs ignored)." ) 70 | break 71 | } 72 | res = res + bit * powtwo 73 | # no warning here as it might be the last iteration 74 | if( powtwo > INT_MAX_HALF ){ overflow = 1; continue } 75 | powtwo = powtwo * 2 76 | } 77 | return res 78 | } 79 | 80 | # CREATE TRIGGER statements have funny commenting. Remember we are in trigger. 81 | /^\/\*.*(CREATE.*TRIGGER|create.*trigger)/ { 82 | gsub( /^.*(TRIGGER|trigger)/, "CREATE TRIGGER" ) 83 | print 84 | inTrigger = 1 85 | next 86 | } 87 | # The end of CREATE TRIGGER has a stray comment terminator 88 | /(END|end) \*\/;;/ { gsub( /\*\//, "" ); print; inTrigger = 0; next } 89 | # The rest of triggers just get passed through 90 | inTrigger != 0 { print; next } 91 | 92 | # CREATE VIEW looks like a TABLE in comments 93 | /^\/\*.*(CREATE.*TABLE|create.*table)/ { 94 | inView = 1 95 | next 96 | } 97 | # end of CREATE VIEW 98 | /^(\).*(ENGINE|engine).*\*\/;)/ { 99 | inView = 0 100 | next 101 | } 102 | # content of CREATE VIEW 103 | inView != 0 { next } 104 | 105 | # skip comments 106 | /^\/\*/ { next } 107 | 108 | # skip PARTITION statements 109 | /^ *[(]?(PARTITION|partition) +[^ ]+/ { next } 110 | 111 | # print all INSERT lines 112 | ( /^ *\(/ && /\) *[,;] *$/ ) || /^(INSERT|insert|REPLACE|replace)/ { 113 | prev = "" 114 | 115 | # first replace \\ by \_ that mysqldump never generates to deal with 116 | # sequnces like \\n that should be translated into \n, not \. 117 | # After we convert all escapes we replace \_ by backslashes. 118 | gsub( /\\\\/, "\\_" ) 119 | 120 | # single quotes are escaped by another single quote 121 | gsub( /\\'/, "''" ) 122 | gsub( /\\n/, "\n" ) 123 | gsub( /\\r/, "\r" ) 124 | gsub( /\\"/, "\"" ) 125 | gsub( /\\\032/, "\032" ) # substitute char 126 | 127 | gsub( /\\_/, "\\" ) 128 | 129 | # sqlite3 is limited to 16 significant digits of precision 130 | while( match( $0, /0x[0-9a-fA-F]{17}/ ) ){ 131 | hexIssue = 1 132 | sub( /0x[0-9a-fA-F]+/, substr( $0, RSTART, RLENGTH-1 ), $0 ) 133 | } 134 | if( hexIssue ){ 135 | printerr( \ 136 | NR ": WARN Hex number trimmed (length longer than 16 chars)." ) 137 | hexIssue = 0 138 | } 139 | print 140 | next 141 | } 142 | 143 | # CREATE DATABASE is not supported 144 | /^(CREATE DATABASE|create database)/ { next } 145 | 146 | # print the CREATE line as is and capture the table name 147 | /^(CREATE|create)/ { 148 | if( $0 ~ /IF NOT EXISTS|if not exists/ || $0 ~ /TEMPORARY|temporary/ ){ 149 | caseIssue = 1 150 | printerr( \ 151 | NR ": WARN Potential case sensitivity issues with table/column naming\n" \ 152 | " (see INFO at the end)." ) 153 | } 154 | if( match( $0, /`[^`]+/ ) ){ 155 | tableName = substr( $0, RSTART+1, RLENGTH-1 ) 156 | } 157 | aInc = 0 158 | prev = "" 159 | firstInTable = 1 160 | print 161 | next 162 | } 163 | 164 | # Replace `FULLTEXT KEY` (probably other `XXXXX KEY`) 165 | /^ (FULLTEXT KEY|fulltext key)/ { gsub( /[A-Za-z ]+(KEY|key)/, " KEY" ) } 166 | 167 | # Get rid of field lengths in KEY lines 168 | / (PRIMARY |primary )?(KEY|key)/ { gsub( /\([0-9]+\)/, "" ) } 169 | 170 | aInc == 1 && /PRIMARY KEY|primary key/ { next } 171 | 172 | # Replace COLLATE xxx_xxxx_xx statements with COLLATE BINARY 173 | / (COLLATE|collate) [a-z0-9_]*/ { gsub( /(COLLATE|collate) [a-z0-9_]*/, "COLLATE BINARY" ) } 174 | 175 | # Print all fields definition lines except the `KEY` lines. 176 | /^ / && !/^( (KEY|key)|\);)/ { 177 | if( match( $0, /[^"`]AUTO_INCREMENT|auto_increment[^"`]/) ){ 178 | aInc = 1 179 | gsub( /AUTO_INCREMENT|auto_increment/, "PRIMARY KEY AUTOINCREMENT" ) 180 | } 181 | gsub( /(UNIQUE KEY|unique key) (`.*`|".*") /, "UNIQUE " ) 182 | gsub( /(CHARACTER SET|character set) [^ ]+[ ,]/, "" ) 183 | # FIXME 184 | # CREATE TRIGGER [UpdateLastTime] 185 | # AFTER UPDATE 186 | # ON Package 187 | # FOR EACH ROW 188 | # BEGIN 189 | # UPDATE Package SET LastUpdate = CURRENT_TIMESTAMP WHERE ActionId = old.ActionId; 190 | # END 191 | gsub( /(ON|on) (UPDATE|update) (CURRENT_TIMESTAMP|current_timestamp)(\(\))?/, "" ) 192 | gsub( /(DEFAULT|default) (CURRENT_TIMESTAMP|current_timestamp)(\(\))?/, "DEFAULT current_timestamp") 193 | gsub( /(COLLATE|collate) [^ ]+ /, "" ) 194 | gsub( /(ENUM|enum)[^)]+\)/, "text " ) 195 | gsub( /(SET|set)\([^)]+\)/, "text " ) 196 | gsub( /UNSIGNED|unsigned/, "" ) 197 | gsub( /_utf8mb3/, "" ) 198 | gsub( /` [^ ]*(INT|int|BIT|bit)[^ ]*/, "` integer" ) 199 | gsub( /" [^ ]*(INT|int|BIT|bit)[^ ]*/, "\" integer" ) 200 | ere_bit_field = "[bB]'[10]+'" 201 | if( match($0, ere_bit_field) ){ 202 | sub( ere_bit_field, bit_to_int( substr( $0, RSTART +2, RLENGTH -2 -1 ) ) ) 203 | } 204 | 205 | # remove USING BTREE and other suffixes for USING, for example: "UNIQUE KEY 206 | # `hostname_domain` (`hostname`,`domain`) USING BTREE," 207 | gsub( / USING [^, ]+/, "" ) 208 | 209 | # field comments are not supported 210 | gsub( / (COMMENT|comment).+$/, "" ) 211 | # Get commas off end of line 212 | gsub( /,.?$/, "" ) 213 | if( prev ){ 214 | if( firstInTable ){ 215 | print prev 216 | firstInTable = 0 217 | } 218 | else { 219 | print "," prev 220 | } 221 | } 222 | else { 223 | # FIXME check if this is correct in all cases 224 | if( match( $1, 225 | /(CONSTRAINT|constraint) ["].*["] (FOREIGN KEY|foreign key)/ ) ){ 226 | print "," 227 | } 228 | } 229 | prev = $1 230 | } 231 | 232 | / ENGINE| engine/ { 233 | if( prev ){ 234 | if( firstInTable ){ 235 | print prev 236 | firstInTable = 0 237 | } 238 | else { 239 | print "," prev 240 | } 241 | } 242 | prev="" 243 | print ");" 244 | next 245 | } 246 | # `KEY` lines are extracted from the `CREATE` block and stored in array for later print 247 | # in a separate `CREATE KEY` command. The index name is prefixed by the table name to 248 | # avoid a sqlite error for duplicate index name. 249 | /^( (KEY|key)|\);)/ { 250 | if( prev ){ 251 | if( firstInTable ){ 252 | print prev 253 | firstInTable = 0 254 | } 255 | else { 256 | print "," prev 257 | } 258 | } 259 | prev = "" 260 | if( $0 == ");" ){ 261 | print 262 | } 263 | else { 264 | if( match( $0, /`[^`]+/ ) ){ 265 | indexName = substr( $0, RSTART+1, RLENGTH-1 ) 266 | } 267 | if( match( $0, /\([^()]+/ ) ){ 268 | indexKey = substr( $0, RSTART+1, RLENGTH-1 ) 269 | } 270 | # idx_ prefix to avoid name clashes (they really happen!) 271 | key[tableName] = key[tableName] "CREATE INDEX \"idx_" \ 272 | tableName "_" indexName "\" ON \"" tableName "\" (" indexKey ");\n" 273 | } 274 | } 275 | 276 | END { 277 | if( no_END ){ exit 1} 278 | # print all KEY creation lines. 279 | for( table in key ){ printf key[table] } 280 | 281 | print "END TRANSACTION;" 282 | 283 | if( caseIssue ){ 284 | printerr( \ 285 | "INFO Pure sqlite identifiers are case insensitive (even if quoted\n" \ 286 | " or if ASCII) and doesnt cross-check TABLE and TEMPORARY TABLE\n" \ 287 | " identifiers. Thus expect errors like \"table T has no column named F\".") 288 | } 289 | } -------------------------------------------------------------------------------- /src/repository/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { SlimExpressionFunction } from 'slim-exp'; 2 | import { IDbContext, IUnitOfWork } from '../uow'; 3 | import { DeepPartial } from './_internal.interface'; 4 | 5 | export { DeepPartial }; 6 | export interface QueryRefiner { 7 | (obj: IQueryable); 8 | } 9 | 10 | export type EntityBase = {}; 11 | export type ExpressionResult = object | PrimitiveType; 12 | // T extends (infer U)[] ? U : T; 13 | export type Includable = T | T[]; 14 | export type PrimitiveType = string | number | boolean; 15 | 16 | export interface IQueryableSelectionResult< 17 | V extends EntityBase, 18 | T extends EntityBase = V 19 | > { 20 | /** 21 | * Asynchronously returns the first element of the sequence 22 | */ 23 | first(): Promise; 24 | /** 25 | * Asynchronously returns the first element of the sequence that satisfies a specified condition. 26 | * @param predicate A function to test each element for a condition. 27 | * @param context The predicate data source 28 | */ 29 | first( 30 | predicate: SlimExpressionFunction, 31 | context?: C 32 | ): Promise; 33 | /** 34 | * Asynchronously returns the first element of a sequence, or a default value if the sequence contains no elements. 35 | */ 36 | firstOrDefault(): Promise; 37 | /** 38 | * Asynchronously returns the first element of a sequence that satisfies a specified condition 39 | * or a default value if no such element is found. 40 | * @param predicate A function to test each element for a condition. 41 | * @param context The predicate data source 42 | */ 43 | firstOrDefault( 44 | predicate: SlimExpressionFunction, 45 | context?: C 46 | ): Promise; 47 | 48 | /** 49 | * Asynchronously creates aa `Array` of result from `IQueryable` by enumerating it asynchronously. 50 | */ 51 | toList(): Promise; 52 | /** 53 | * Applies distinct selection on projection 54 | */ 55 | distinct(): this; 56 | } 57 | 58 | export interface IQueryable 59 | extends IQueryableSelectionResult { 60 | /** 61 | * Specifies related entities to include in the query results. The navigation property 62 | * to be included is specified starting with the type of entity being queried (TEntity). 63 | * If you wish to include additional types based on the navigation properties of 64 | * the type being included, then chain a call to `thenInclude` after this call. 65 | * @param navigationPropertyPath The type of the related entity to be included. 66 | */ 67 | include( 68 | navigationPropertyPath: SlimExpressionFunction> 69 | ): IQueryable; 70 | 71 | /** 72 | * Specifies additional related data to be further included based on a related type 73 | * that was just included. 74 | * @param navigationPropertyPath The type of the related entity to be included. 75 | */ 76 | thenInclude( 77 | navigationPropertyPath: SlimExpressionFunction> 78 | ): IQueryable; 79 | 80 | /** 81 | * Filters the sequence based on a predicate. 82 | * @param predicate A function to test each element for a condition. 83 | * @param context The predicate data source 84 | */ 85 | where( 86 | predicate: SlimExpressionFunction, 87 | context?: C 88 | ): IQueryable; 89 | /** 90 | * Returns a specified number of contiguous elements from the start of the sequence. 91 | * @param count The number of elements to return. 92 | */ 93 | take(count: number): IQueryable; 94 | 95 | /** 96 | * Bypasses a specified number of elements in a sequence and then returns the remaining elements. 97 | * @param count The number of elements to skip before returning the remaining elements. 98 | */ 99 | skip(count: number): IQueryable; 100 | 101 | /** 102 | * Computes the sum of the sequence of number values that is obtained by 103 | * invoking a projection function on each element of the input sequence. 104 | * @param selector A projection function to apply to each element. 105 | */ 106 | sum(selector: SlimExpressionFunction): Promise; 107 | 108 | /** 109 | * Computes the average of a sequence of number values that is obtained by 110 | * invoking a projection function on each element of the input sequence. 111 | * @param selector A projection function to apply to each element. 112 | */ 113 | average(selector: SlimExpressionFunction): Promise; 114 | 115 | /** 116 | * Returns the number of elements in the specified sequence. If condition is provided, 117 | * the resulting elements will satisfy the condition. 118 | * @param predicate A function to test each element for a condition. 119 | */ 120 | count( 121 | predicate?: SlimExpressionFunction 122 | ): Promise; 123 | /** 124 | * Invokes a projection function on each element of the sequence 125 | * and returns the maximum resulting value. 126 | * @param selector A projection function to apply to each element. 127 | */ 128 | max( 129 | selector: SlimExpressionFunction 130 | ): Promise; 131 | /** 132 | * Invokes a projection function on each element of the sequence 133 | * and returns the minimum resulting value. 134 | * @param selector A projection function to apply to each element. 135 | */ 136 | min( 137 | selector: SlimExpressionFunction 138 | ): Promise; 139 | 140 | /** 141 | * Projects each element of a sequence into a new form. 142 | * @param selector A projection function to apply to each element. 143 | */ 144 | select( 145 | selector: SlimExpressionFunction 146 | ): IQueryableSelectionResult; 147 | 148 | /** 149 | * Specifies that the current query should not have any 150 | * model-levelentity query filters applied. 151 | */ 152 | ignoreQueryFilters(): IQueryable; 153 | 154 | /** 155 | * Sorts the elements of a sequence in descending order according to a key. 156 | * @param keySelector A function to extract a key from an element. 157 | */ 158 | orderBy(keySelector: SlimExpressionFunction): IQueryable; 159 | 160 | /** 161 | * Performs a subsequent ordering of the elements in a sequence in ascending order 162 | * @param keySelector 163 | */ 164 | thenOrderBy(keySelector: SlimExpressionFunction): IQueryable; 165 | /** 166 | * Groups the elements of a sequence according to a key. 167 | * @param keySelector A function to extract a key from an element. 168 | */ 169 | groupBy(keySelector: SlimExpressionFunction): IQueryable; 170 | 171 | /** 172 | * Performs a subsequent grouping of the elements in a sequence 173 | * @param keySelector 174 | */ 175 | thenGroupBy(keySelector: SlimExpressionFunction): IQueryable; 176 | 177 | /** 178 | * Sorts the elements of a sequence in ascending order according to a key. 179 | * @param keySelector A function to extract a key from an element. 180 | */ 181 | orderByDescending( 182 | keySelector: SlimExpressionFunction 183 | ): IQueryable; 184 | } 185 | 186 | export interface IDbSet< 187 | T extends EntityBase, 188 | R extends T | T[], 189 | P = T, 190 | DT = DeepPartial | T 191 | > extends IQueryable { 192 | /** 193 | * Begins tracking the given entity, and any other reachable entities that are not 194 | * already being tracked, in the Added state such that they will be inserted 195 | * into the database when `saveChanges()` is called. 196 | * @param entities 197 | */ 198 | add(...entities: DT[]): Promise | void; 199 | /** 200 | * Begins tracking the given entity and entries reachable from the given entity using 201 | * the Modified state by default such that they will be updated 202 | * in the database when `saveChanges()` is called. 203 | * @param entities 204 | */ 205 | update(...entities: DT[]): Promise | void; 206 | 207 | /** 208 | * Begins tracking the given entity in the Deleted state such that it will be removed 209 | * from the database when `saveChanges()` is called. 210 | * @param entities 211 | */ 212 | remove(...entities: DT[]): Promise | void; 213 | 214 | /** 215 | * Removes entities from the list of currently tracked entities 216 | * @param entities 217 | */ 218 | unTrack(...entities: DT[]): Promise | void; 219 | 220 | /** 221 | * Finds an entity with the given primary key values. If an entity with the given 222 | * primary key values is being tracked by the context, then it is returned 223 | * immediately without making a request to the database. Otherwise, a query is made 224 | * to the database for an entity with the given primary key values and this entity, 225 | * if found, is attached to the context and returned. If no entity is found, then 226 | * undefined is returned. 227 | * @param type The entity type 228 | * @param id The entity id 229 | */ 230 | find(id: any): Promise | T; 231 | 232 | /** 233 | * Checks if an entity with the given id exists in the data store 234 | * @param id 235 | */ 236 | exists(id: any): Promise; 237 | 238 | /** 239 | * Creates a new entity from the given plain javascript object. If the entity already exist in the database, then it loads it (and everything related to it), replaces all values with the new ones from the given object, and returns the new entity. The new entity is actually loaded from the database entity with all properties replaced from the new object. 240 | * @param type The type of the enity to load 241 | * @param entity The partial entity values 242 | */ 243 | loadRelatedData(entity: T): Promise; 244 | 245 | ignoreQueryFilters(): IQueryable; 246 | // join(queryable: this): this; ?? 247 | } 248 | declare global { 249 | interface String { 250 | /** 251 | * Returns true if one of the searchString appears in the result 252 | * @param searchString search string 253 | */ 254 | includes(searchStrings: string[]): boolean; 255 | } 256 | interface Object { 257 | /** 258 | * Returns true if one of the searchString appears in the result 259 | * @param searchString search string 260 | */ 261 | includes(searchStrings: string[]): boolean; 262 | } 263 | interface Number { 264 | /** 265 | * Returns true if one of the searchNumbers appears in the result 266 | * @param searchString search string 267 | */ 268 | includes(searchNumbers: number[]): boolean; 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/repository/db-set.ts: -------------------------------------------------------------------------------- 1 | import { BaseSpecification } from '../specification/base.specification'; 2 | import { 3 | FieldsSelector, 4 | ISpecification, 5 | QueryType 6 | } from '../specification/specification.interface'; 7 | import { ExpressionResult, SlimExpressionFunction } from 'slim-exp'; 8 | import { IDbContext, IUnitOfWork } from '../uow'; 9 | import { 10 | EntityBase, 11 | IDbSet, 12 | Includable, 13 | IQueryable, 14 | IQueryableSelectionResult 15 | } from './interfaces'; 16 | import { DeepPartial } from 'typeorm'; 17 | import { patchM } from './utilis'; 18 | import { getEntitySchema } from './repository.decorator'; 19 | import { EmptySetException } from './exception'; 20 | import { 21 | IInternalDbContext, 22 | ProxyMetaDataInstance 23 | } from '../uow/_internal.interface'; 24 | 25 | export const UnderlyingType = Symbol('__UnderlyingType'); 26 | 27 | export class DbSet< 28 | T extends EntityBase, 29 | R extends T | T[] = T, 30 | P = T, 31 | E = T | DeepPartial 32 | > implements IDbSet { 33 | private _queryTypeToExecute = QueryType.ALL; 34 | private _baseSpec = new BaseSpecification(); 35 | private _lastInclude: SlimExpressionFunction; 36 | private _currentSkip: number; 37 | private _currentTake: number; 38 | private _ignoreFilters: boolean; 39 | private _underlyingType: new (...args) => T; 40 | private _onGoingPromise: Promise; 41 | 42 | constructor(context: IDbContext | IUnitOfWork); 43 | constructor( 44 | public context: (IDbContext | IUnitOfWork) & IInternalDbContext 45 | ) {} 46 | 47 | private get [UnderlyingType]() { 48 | if (!this._underlyingType) { 49 | this._underlyingType = getEntitySchema(this); 50 | } 51 | return this._underlyingType; 52 | } 53 | 54 | private set [UnderlyingType](value) { 55 | if (value) { 56 | this._underlyingType = value; 57 | } 58 | } 59 | 60 | add(...entities: E[]): Promise | void { 61 | return this.context.add(...patchM(this[UnderlyingType])(...entities)); 62 | } 63 | 64 | update(...entities: E[]): Promise | void { 65 | return this.context.update(...patchM(this[UnderlyingType])(...entities)); 66 | } 67 | 68 | unTrack(...entities: E[]): Promise | void { 69 | return this.context.unTrack(...patchM(this[UnderlyingType])(...entities)); 70 | } 71 | 72 | remove(...entities: E[]): Promise | void { 73 | return this.context.remove(...patchM(this[UnderlyingType])(...entities)); 74 | } 75 | 76 | async find(id: any): Promise { 77 | return await this.context.find(this[UnderlyingType], id); 78 | } 79 | 80 | async exists(id: any): Promise { 81 | return !!(await this.find(id)); 82 | } 83 | 84 | loadRelatedData(entity: T): Promise { 85 | return this.context.loadRelatedData(this._underlyingType, entity); 86 | } 87 | async firstOrDefault(): Promise; 88 | async firstOrDefault( 89 | predicate: SlimExpressionFunction, 90 | // tslint:disable-next-line: unified-signatures 91 | context: C 92 | ): Promise; 93 | async firstOrDefault( 94 | predicate?: SlimExpressionFunction 95 | ): Promise; 96 | async firstOrDefault( 97 | predicate?: SlimExpressionFunction, 98 | context?: C 99 | ): Promise { 100 | this._baseSpec.applyPaging(0, 1); 101 | this.where(predicate, context); 102 | return await this.execute(QueryType.ONE); 103 | } 104 | 105 | async first(): Promise; 106 | async first( 107 | predicate: SlimExpressionFunction, 108 | // tslint:disable-next-line: unified-signatures 109 | context: C 110 | ): Promise; 111 | async first( 112 | func?: SlimExpressionFunction 113 | ): Promise; 114 | async first( 115 | func?: SlimExpressionFunction, 116 | context?: C 117 | ): Promise { 118 | const elt = await this.firstOrDefault(func, context); 119 | if (!elt) throw new EmptySetException(); 120 | 121 | return elt; 122 | } 123 | 124 | include( 125 | navigationPropertyPath: SlimExpressionFunction> 126 | ) { 127 | this._lastInclude = navigationPropertyPath; 128 | this._baseSpec.addInclude(navigationPropertyPath); 129 | return (this as unknown) as IQueryable; 130 | } 131 | thenInclude( 132 | navigationPropertyPath: SlimExpressionFunction> 133 | ) { 134 | this._baseSpec.addChainedInclude(this._lastInclude, navigationPropertyPath); 135 | return (this as unknown) as IQueryable; 136 | } 137 | 138 | where( 139 | predicate: SlimExpressionFunction, 140 | context?: C 141 | ): IQueryable { 142 | this._baseSpec.addCriteria(predicate, context); 143 | return (this as unknown) as IQueryable; 144 | } 145 | take(count: number): IQueryable { 146 | this._baseSpec.applyPaging(this._currentSkip, count); 147 | this._currentTake = count; 148 | return this as IQueryable; 149 | } 150 | skip(count: number): IQueryable { 151 | this._baseSpec.applyPaging(count, this._currentTake); 152 | this._currentSkip = count; 153 | return this as IQueryable; 154 | } 155 | 156 | select(selector: SlimExpressionFunction) { 157 | const thisType = this[UnderlyingType]; 158 | const includePaths = this._getIncludePaths(); 159 | this._onGoingPromise = this.context 160 | .getMetadata(thisType, includePaths) 161 | .then(proxyInstance => { 162 | const res = selector(proxyInstance as any) as ProxyMetaDataInstance; 163 | const fieldsToSelect = this._extractKeyFields(res); 164 | const s: FieldsSelector = { 165 | builder: selector, 166 | fieldsToSelect 167 | }; 168 | this._baseSpec.applySelector(s); 169 | return true; 170 | }) 171 | .catch(rej => { 172 | if ( 173 | rej.message && 174 | rej.message.includes('Cannot read property') && 175 | rej.message.includes('of undefined') 176 | ) { 177 | throw new Error( 178 | 'Select proxy cannot be build. You may have forget to include some properties using `.include` or `.thenInclude` or may be the selected property is not part of the entity schema. Internal Error: ' + 179 | rej.message 180 | ); 181 | } else { 182 | throw new Error(rej); 183 | } 184 | }); 185 | 186 | return (this as unknown) as IQueryableSelectionResult; 187 | } 188 | private _getIncludePaths(): string[] { 189 | const spec = this.asSpecification(); 190 | return spec.getIncludePaths(); 191 | } 192 | 193 | private _extractKeyFields( 194 | res: ProxyMetaDataInstance 195 | ): { 196 | field: string; 197 | }[] { 198 | const fieldsToSelect = []; 199 | for (const k in res) { 200 | if ( 201 | k !== '$$propertyName' && 202 | Object.prototype.hasOwnProperty.call(res, k) 203 | ) { 204 | const element = res[k]; 205 | if (!element) continue; 206 | if (!Array.isArray(element)) { 207 | const canGrind = 208 | !(element instanceof String) && 209 | !(element instanceof Number) && 210 | !(element instanceof Boolean) && 211 | element instanceof Object; 212 | if (canGrind) { 213 | fieldsToSelect.push( 214 | ...this._extractKeyFields(element as any).map(e => ({ 215 | field: `${e.field}` 216 | })) 217 | ); 218 | } else if (element.$$propertyName) { 219 | fieldsToSelect.push({ 220 | field: element.$$propertyName 221 | }); 222 | } 223 | } else { 224 | const arrElt = element[0]; 225 | fieldsToSelect.push( 226 | ...this._extractKeyFields(arrElt).map(e => ({ 227 | field: `${e.field}` 228 | })) 229 | ); 230 | } 231 | } 232 | } 233 | return fieldsToSelect; 234 | } 235 | 236 | async count( 237 | predicate?: SlimExpressionFunction 238 | ): Promise { 239 | this._baseSpec.addCriteria(predicate); 240 | this._baseSpec.applyFunction('COUNT', null); 241 | return Number.parseFloat( 242 | (await this.execute<{ COUNT: string }>(QueryType.RAW_ONE)).COUNT 243 | ); 244 | } 245 | 246 | async sum(selector: SlimExpressionFunction): Promise { 247 | this._baseSpec.applyFunction('SUM', selector); 248 | return Number.parseFloat( 249 | (await this.execute<{ SUM: string }>(QueryType.RAW_ONE)).SUM 250 | ); 251 | } 252 | 253 | async average(selector: SlimExpressionFunction): Promise { 254 | this._baseSpec.applyFunction('AVG', selector); 255 | return Number.parseFloat( 256 | (await this.execute<{ AVG: string }>(QueryType.RAW_ONE)).AVG 257 | ); 258 | } 259 | 260 | async max( 261 | selector: SlimExpressionFunction 262 | ): Promise { 263 | this._baseSpec.applyFunction('MAX', selector); 264 | return (await this.execute<{ MAX: RT }>(QueryType.RAW_ONE)).MAX; 265 | } 266 | async min( 267 | selector: SlimExpressionFunction 268 | ): Promise { 269 | this._baseSpec.applyFunction('MIN', selector); 270 | return (await this.execute<{ MIN: RT }>(QueryType.RAW_ONE)).MIN; 271 | } 272 | 273 | orderBy(keySelector: SlimExpressionFunction) { 274 | this._baseSpec.applyOrderBy(keySelector); 275 | return this as IQueryable; 276 | } 277 | 278 | thenOrderBy(keySelector: SlimExpressionFunction) { 279 | this._baseSpec.applyThenOrderBy(keySelector); 280 | return this as IQueryable; 281 | } 282 | 283 | groupBy(keySelector: SlimExpressionFunction) { 284 | this._baseSpec.applyGroupBy(keySelector); 285 | return this as IQueryable; 286 | } 287 | 288 | thenGroupBy(keySelector: SlimExpressionFunction) { 289 | this._baseSpec.applyThenGroupBy(keySelector); 290 | return this as IQueryable; 291 | } 292 | 293 | orderByDescending(keySelector: SlimExpressionFunction) { 294 | this._baseSpec.applyOrderByDescending(keySelector); 295 | return this as IQueryable; 296 | } 297 | 298 | asSpecification(): ISpecification { 299 | return this._baseSpec; 300 | } 301 | 302 | ignoreQueryFilters(): IQueryable { 303 | this._ignoreFilters = true; 304 | return this as IQueryable; 305 | } 306 | distinct() { 307 | this._baseSpec.applyDistinct(); 308 | return this; 309 | } 310 | 311 | fromSpecification(spec: ISpecification): IQueryable { 312 | this._baseSpec.extend(spec); 313 | this._baseSpec.applySelector(spec.getSelector()); 314 | this._baseSpec.applyOrderBy(spec.getOrderBy()); 315 | this._baseSpec.applyOrderByDescending(spec.getOrderByDescending()); 316 | return this as any; 317 | } 318 | 319 | toList(): Promise { 320 | return this.execute(this._queryTypeToExecute); 321 | } 322 | 323 | private async execute(type: QueryType): Promise { 324 | try { 325 | if (!this._onGoingPromise || (await this._onGoingPromise)) { 326 | const res = await this.context.execute( 327 | this as any, 328 | type, 329 | this._ignoreFilters 330 | ); 331 | this._baseSpec.clearSpecs(); 332 | return res; 333 | } 334 | } catch (err) { 335 | // wrapping this in finally, to clear specs on failure or success 336 | this._baseSpec.clearSpecs(); 337 | throw err; 338 | } 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /src/uow/db-context.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Connection, QueryRunner, Repository } from 'typeorm'; 2 | import { getEntitySchema, getEntitySet, getEntitySetKeys } from '../repository'; 3 | import { DbSet, UnderlyingType } from '../repository/db-set'; 4 | import { IDbSet, IQueryable } from '../repository/interfaces'; 5 | import { DeepPartial, IInternalDbSet } from '../repository/_internal.interface'; 6 | import { 7 | QuerySpecificationEvaluatorConstructor, 8 | QueryType 9 | } from '../specification/specification.interface'; 10 | import { 11 | ISavedTransaction, 12 | IDbContext, 13 | QueryInitializer, 14 | IDbContextOptionsBuilder 15 | } from './interfaces'; 16 | import { getMetaData } from './metadata'; 17 | import { DbContextModelBuilder } from './model-builder'; 18 | import { DbContextOptionsBuilder } from './options-builder'; 19 | import { 20 | IInternalDbContext, 21 | ProxyMetaDataInstance 22 | } from './_internal.interface'; 23 | interface IEntity { 24 | id?: any; 25 | } 26 | 27 | export abstract class DbContext implements IDbContext, IInternalDbContext { 28 | private _entitySets: WeakMap>; 29 | private _new: IEntity[] = []; 30 | private _dirty: IEntity[] = []; 31 | private _deleted: IEntity[] = []; 32 | private _queryRunner: QueryRunner | null; 33 | private _modelBuilder: DbContextModelBuilder; 34 | private _optionsBuilder: DbContextOptionsBuilder; 35 | private _isUserTransaction: boolean; 36 | 37 | constructor( 38 | protected _connection: Connection, 39 | protected evaluator: QuerySpecificationEvaluatorConstructor 40 | ) { 41 | this._entitySets = new WeakMap(); 42 | this._modelBuilder = new DbContextModelBuilder(); 43 | this._optionsBuilder = new DbContextOptionsBuilder(); 44 | this._initialise(); 45 | } 46 | 47 | private _initialise() { 48 | this._setUnderlyingEntityType(); 49 | this.onModelCreation(this._modelBuilder); 50 | this.onConfiguring(this._optionsBuilder); 51 | } 52 | 53 | protected abstract onModelCreation( 54 | builder: DbContextModelBuilder 55 | ): void; 56 | 57 | protected abstract onConfiguring( 58 | optionsBuilder: IDbContextOptionsBuilder 59 | ): void; 60 | 61 | public set(type: new (...args: any) => T): IDbSet { 62 | const dbSet = new DbSet(this); 63 | dbSet[UnderlyingType] = type; 64 | return dbSet as any; 65 | } 66 | 67 | public add(...entities: T[]) { 68 | this._new.push(...this._throwIfNullFound(entities, 'add')); 69 | } 70 | 71 | public unTrack(entity: T) { 72 | this._new = [...this._new.filter(n => n !== entity)]; 73 | this._dirty = [...this._dirty.filter(n => n !== entity)]; 74 | this._deleted = [...this._deleted.filter(n => n !== entity)]; 75 | } 76 | 77 | public update(...entities: T[]) { 78 | this._dirty.push(...this._throwIfNullFound(entities, 'attach')); 79 | } 80 | 81 | public remove(...entities: T[]) { 82 | this._deleted.push(...this._throwIfNullFound(entities, 'remove')); 83 | } 84 | 85 | private _throwIfNullFound( 86 | entities: T[], 87 | method: string 88 | ): T[] { 89 | if (entities.some(e => !e)) { 90 | throw new Error('Entities can not be null when call ' + method); 91 | } 92 | return entities; 93 | } 94 | 95 | public async find(type: any, id: any): Promise { 96 | await this._tryOpenConnection(); 97 | const repo = this._getRepository(type); 98 | if (repo) { 99 | const res = (await repo.findOne(id)) as T; 100 | return res; 101 | } 102 | return void 0; 103 | } 104 | 105 | public rollback(entityType: any = null): void { 106 | if (entityType !== null) { 107 | const type = typeof entityType; 108 | this._new = [...this._new.filter(n => typeof n !== type)]; 109 | this._dirty = [...this._dirty.filter(n => typeof n === type)]; 110 | this._deleted = [...this._deleted.filter(n => typeof n === type)]; 111 | } else { 112 | this._dispose(); 113 | } 114 | } 115 | 116 | public async saveChanges(): Promise; 117 | public async saveChanges(withoutRefresh = false): Promise { 118 | await this._tryOpenConnection(); 119 | const transIsOpened = this.transactionIsOpen(); 120 | 121 | if (!transIsOpened) { 122 | this._queryRunner = this._connection.createQueryRunner(); 123 | await this._queryRunner.startTransaction(); 124 | } 125 | try { 126 | const added = await this._commitNew(); 127 | const toUpdate = await this._commitDirty(); 128 | const deleted = await this._commitDeleted(); 129 | 130 | if (!transIsOpened) await this._queryRunner.commitTransaction(); 131 | 132 | const updated = withoutRefresh 133 | ? toUpdate 134 | : await this._getUpdates(toUpdate); 135 | return { added, updated, deleted }; 136 | } catch (e) { 137 | if (transIsOpened) await this._queryRunner.rollbackTransaction(); 138 | throw e; 139 | } finally { 140 | if (!this._queryRunner.isReleased && !transIsOpened) 141 | this._queryRunner.release(); 142 | this._dispose(); 143 | } 144 | } 145 | 146 | public transactionIsOpen(): boolean { 147 | return !!this._queryRunner?.isTransactionActive && this._isUserTransaction; 148 | } 149 | public async openTransaction(): Promise { 150 | if (this.transactionIsOpen()) { 151 | throw new Error( 152 | 'A transaction is already open, please release the later before opening a new one' 153 | ); 154 | } 155 | await this._tryOpenConnection(); 156 | this._isUserTransaction = true; 157 | this._queryRunner = this._connection.createQueryRunner(); 158 | await this._queryRunner.startTransaction(); 159 | } 160 | 161 | public async commitTransaction(): Promise { 162 | if (this.transactionIsOpen()) { 163 | await this._tryOpenConnection(); 164 | await this._queryRunner.commitTransaction(); 165 | await this._queryRunner.release(); 166 | this._queryRunner = null; 167 | this._isUserTransaction = false; 168 | } 169 | } 170 | 171 | public async rollbackTransaction(): Promise { 172 | if (this.transactionIsOpen()) { 173 | await this._queryRunner.rollbackTransaction(); 174 | await this._queryRunner.release(); 175 | this._queryRunner = null; 176 | this._isUserTransaction = false; 177 | } 178 | } 179 | 180 | public async query(query: string, parameters: any[]): Promise { 181 | await this._tryOpenConnection(); 182 | const result = await this._connection.query(query, parameters); 183 | // await this._tryCloseConenction(); 184 | return result; 185 | } 186 | 187 | public loadRelatedData( 188 | type: new (...args: []) => T, 189 | entity: T 190 | ): Promise { 191 | const manager = this._connection.manager; 192 | return manager.preload(type, entity); 193 | } 194 | //#region IInternalDbContext Implementation 195 | public async execute( 196 | queryable: IInternalDbSet, 197 | type: QueryType = QueryType.ALL, 198 | ignoreFilters = false 199 | ): Promise { 200 | await this._tryOpenConnection(); 201 | 202 | if (!ignoreFilters) { 203 | // applying filters 204 | const filters = this._modelBuilder.getFilters(queryable[UnderlyingType]); 205 | filters.forEach(f => { 206 | f(queryable); 207 | }); 208 | } 209 | 210 | const initializer = this._getSQLBuilder(queryable); 211 | const specEval = new this.evaluator( 212 | initializer, 213 | queryable.asSpecification() 214 | ); 215 | const logger = this._optionsBuilder.createLogger('query'); 216 | logger?.log('info', await specEval.getQuery()); 217 | logger?.log('info', await specEval.getParams()); 218 | const result = await specEval.executeQuery(type); 219 | 220 | // CLosing the connection here prevents typeorm lazyloading. 221 | // So commenting this, may be I will get e better solution for 222 | // managing these resources 223 | // await this._tryCloseConenction(); 224 | 225 | return result; 226 | } 227 | 228 | public async getMetadata( 229 | type: new (...args: any[]) => T, 230 | includePaths: string[] 231 | ): Promise> { 232 | await this._tryOpenConnection(); 233 | const md = getMetaData(this._connection, type, includePaths); 234 | // await this._tryCloseConenction(); 235 | return md; 236 | } 237 | 238 | //#endregion 239 | public dispose(): void { 240 | this._dispose(); 241 | this._tryCloseConenction(); 242 | } 243 | 244 | //#region Private methods 245 | private async _tryOpenConnection() { 246 | if (!this._connection.isConnected) await this._connection.connect(); 247 | } 248 | 249 | private async _tryCloseConenction() { 250 | if (this._connection.isConnected) await this._connection.close(); 251 | } 252 | 253 | private _dispose() { 254 | this._new = []; 255 | this._dirty = []; 256 | this._deleted = []; 257 | } 258 | 259 | private _setUnderlyingEntityType() { 260 | const underType: string[] = getEntitySetKeys(this); 261 | if (underType) { 262 | for (const t of underType) { 263 | const dbset = new DbSet(this); 264 | const entity = getEntitySet(this, t); 265 | 266 | if (dbset && entity) { 267 | Object.defineProperty(this, t, { 268 | configurable: false, 269 | enumerable: false, 270 | value: dbset, 271 | writable: false 272 | }); 273 | dbset[UnderlyingType] = entity; 274 | } 275 | } 276 | } 277 | } 278 | 279 | private async _getUpdates(toUpdate: IEntity[]): Promise { 280 | const updated = []; 281 | for (const n of toUpdate) { 282 | const repo = this._getRepository(n); 283 | updated.push(await repo.findOne(n.id)); 284 | } 285 | return updated; 286 | } 287 | 288 | private async _commitDeleted(): Promise { 289 | const deleted = []; 290 | for (const n of this._deleted) { 291 | const repo = this._getRepository(n); 292 | deleted.push(await repo.delete(n)); 293 | } 294 | return deleted; 295 | } 296 | 297 | private async _commitDirty(): Promise { 298 | return await this._commitDirtyAll(this._dirty); 299 | } 300 | 301 | private async _commitDirtyAll(dirty: IEntity[]): Promise { 302 | const updated = []; 303 | for (const n of dirty) { 304 | const repo = this._getRepository(n); 305 | updated.push(await repo.save(n)); 306 | } 307 | return updated; 308 | } 309 | 310 | private async _commitNew(): Promise { 311 | const added = []; 312 | for (const n of this._new) { 313 | const repo = this._getRepository(n); 314 | added.push(await repo.save(n)); 315 | } 316 | return added; 317 | } 318 | 319 | private _getSQLBuilder( 320 | type: IDbSet 321 | ): QueryInitializer { 322 | const repo = this._getRepository(type); 323 | const initializer = (alias: string) => repo.createQueryBuilder(alias); 324 | 325 | return initializer as QueryInitializer; 326 | } 327 | 328 | private _getRepository(type: any): Repository { 329 | if (type instanceof DbSet) { 330 | type = type[UnderlyingType]; 331 | } 332 | const schema = getEntitySchema(type); 333 | 334 | if (this._entitySets.has(type)) return this._entitySets.get(type); 335 | if (!schema) 336 | throw new Error( 337 | `Schema [${type?.constructor?.name}] not found. You may not have set EntityRepository decorator correctly` 338 | ); 339 | const repo = this._connection.getRepository(schema); 340 | this._entitySets.set(type, repo); 341 | 342 | return repo; 343 | } 344 | //#endregion 345 | } 346 | 347 | // tslint:disable-next-line: max-classes-per-file 348 | export abstract class UnitOfWork extends DbContext 349 | implements Omit, 'getMetadata'> {} 350 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Slim-Exp 2 | 3 | ![logo](/slim-ef.png) 4 | 5 | Slim-ef is an implementation of basic entity framework & LINQ functionnalities in typescript powered by [slim-exp expression parser](https://github.com/bugMaker-237/slim-exp) and [typeorm](https://github.com/typeorm/typeorm). Entity framework makes life easier for .NET developers with the help of the powerful fluent LINQ API. Many nodejs ORM exist out there. Unfortunately none of them offers a **completely string-literal-free fluent api**. Although this is normal for a .NET dev like me who will desire (and think that is a must) to work in such an environment. 6 | 7 | ### Prerequisite 8 | 9 | - A knowledge of [typeorm](https://github.com/typeorm/typeorm) 10 | Actually `slim-ef` is a wrapper around typeorm. Typeorm is a pretty complete orm. which offers a lot of tools form mapping rowdata with models. Most of these functinnalities have been abstracted by `slim-ef`, but you still need to write your models with typeorm's decorators (@Column, @OnetoMany, etc...). Future versions of `slim-ef` may abstract these decorators too. 11 | 12 | ### Why you should use slim-ef 13 | 14 | - **string-literal-free**: We know string literals are error prone. It's safer to call a function `.where((t,$)=> t.name.includes($.name)` than writing a string literal `"entity.name like 'buggy"`. 15 | - **Refactoring becomes a pleasure**: Sometimes refactoring your model attribute name can become a pain, because you may think about the sql queries that were written with the fields hardcoded as string literals. With this, the only think you will worry about is running the migration. 16 | - **Code readability**: Sql knowledge of the specifique database is no more useful, moreover the code is more readable (in fact, **the code read itself**) 17 | - **Transition from .NET**: Transition from .net world is easier if you have been using entity framework 18 | 19 | ### Works/tested on 20 | 21 | - MySQL 22 | - MSSQL 23 | - SQLLite 24 | - NodeJs v12.16.2 25 | 26 | ### Installing 27 | 28 | ``` 29 | npm i slim-ef 30 | ``` 31 | 32 | ### How to use 33 | 34 | #### Setup 35 | 36 | The design of this api was made in such a way that it ressemble as much as possible to **entity framework** 37 | First you need to create your various models using the typeorm decorators as specified [here](https://github.com/typeorm/typeorm/blob/master/docs/entities.md) 38 | 39 | Then you need to create a DbContext class that will inherit from slim-ef's DbContext. 40 | 41 | ```ts 42 | ... 43 | 44 | import { Connection } from 'typeorm'; 45 | import { 46 | DbContext, 47 | DbSet, 48 | DbSetEntity, 49 | IDbSet, 50 | SQLQuerySpecificationEvaluator 51 | } from 'slim-ef'; 52 | import { IDbContextOptionsBuilder, DbContextModelBuilder } from 'slim-ef/uow'; 53 | 54 | export class FakeDBContext extends DbContext { 55 | constructor() { 56 | /** 57 | * SQLite connection 58 | */ 59 | super( 60 | new Connection({ 61 | type: 'sqlite', 62 | database: resolve(__dirname, 'seeder', 'slim_ef_test.db'), 63 | entities: [Person, Agency, Trip], 64 | synchronize: false 65 | } as SqliteConnectionOptions), 66 | SQLQuerySpecificationEvaluator 67 | ); 68 | } 69 | 70 | protected onModelCreation( 71 | builder: DbContextModelBuilder 72 | ): void { 73 | builder.entity(Person).hasQueryFilter(q => q.where(e => e.IDNumber > 50)); 74 | } 75 | 76 | protected onConfiguring(optionsBuilder: IDbContextOptionsBuilder): void { 77 | optionsBuilder.useLoggerFactory({ 78 | createLogger: (catName: string) => ({ 79 | log: (level, state) => console.log({ catName, state, level }) 80 | }) 81 | }); 82 | } 83 | 84 | @DbSetEntity(Person) 85 | public readonly persons: IDbSet; 86 | 87 | @DbSetEntity(Agency) 88 | public readonly agencies: IDbSet; 89 | 90 | @DbSetEntity(Trip) 91 | public readonly trips: IDbSet; 92 | } 93 | ``` 94 | 95 | The `FakeDbContext` here represents our data strore. Each property marked with `@DbSetEntity` are data sets (collection of thier specifique type). 96 | 97 | Slim-ef's DbContext abstract class constructor takes as param an instance TypeOrm Connection and an a specification evaluator. There is already a default implementation of a query specification evaluator in slim-ef (SQLQuerySpecificationEvaluator). You can just import it and use it. 98 | 99 | The child DbContext needs to override onModelCreation & onConfiguring. In this version DbContextModelBuilder and IDbContextOptionsBuilder only permits you to add query filters and logger handlers respectively. 100 | 101 | #### Availaible APIs 102 | 103 | Data Store available APIs 104 | 105 | ```ts 106 | interface IDbContext { 107 | /** 108 | * Begins tracking the given entity, and any other reachable entities that are not 109 | * already being tracked, in the Added state such that they will be inserted 110 | * into the database when `saveChanges()` is called. 111 | * @param entities 112 | */ 113 | add(...entities: T[]): Promise | void; 114 | 115 | /** 116 | * Begins tracking the given entity and entries reachable from the given entity using 117 | * the Modified state by default such that they will be updated 118 | * in the database when `saveChanges()` is called. 119 | * @param entities 120 | */ 121 | update(...entities: T[]): Promise | void; 122 | 123 | /** 124 | * Begins tracking the given entity in the Deleted state such that it will be removed 125 | * from the database when `saveChanges()` is called. 126 | * @param entities 127 | */ 128 | remove(...entities: T[]): Promise | void; 129 | 130 | /** 131 | * Removes entities from the list of currently tracked entities 132 | * @param entities 133 | */ 134 | unTrack(...entities: T[]): Promise | void; 135 | /** 136 | * Finds an entity with the given primary key values. If an entity with the given 137 | * primary key values is being tracked by the context, then it is returned 138 | * immediately without making a request to the database. Otherwise, a query is made 139 | * to the database for an entity with the given primary key values and this entity, 140 | * if found, is attached to the context and returned. If no entity is found, then 141 | * undefined is returned. 142 | * @param type The entity type 143 | * @param id The entity id 144 | */ 145 | find(type: new (...args: any) => T, id: any): Promise | T; 146 | /** 147 | * Creates a database connextion and executes the given query 148 | * @param query 149 | * @param parameters 150 | */ 151 | query(query: string, parameters: any[]): Promise; 152 | 153 | /** 154 | * Discard all they tracked modifications of all entity types or a specific type 155 | * @param entityType 156 | */ 157 | rollback(entityType: any | undefined): void; 158 | /** 159 | * Creates a DbSet that can be used to query and save instances of TEntity. 160 | * @param type 161 | */ 162 | set(type: new (...args: any) => T): IDbSet; 163 | 164 | /** 165 | * Saves all changes made in this context to the database. 166 | * This method will automatically call DetectChanges() to discover any changes to 167 | * entity instances before saving to the underlying database. 168 | */ 169 | saveChanges(): void; 170 | 171 | /** 172 | * Releases the allocated resources for this context. 173 | */ 174 | dispose(): void; 175 | } 176 | ``` 177 | 178 | Collection available APIs 179 | 180 | ```ts 181 | interface IDbSet { 182 | constructor(context: IDbContext); 183 | 184 | /** 185 | * Begins tracking the given entity, and any other reachable entities that are not 186 | * already being tracked, in the Added state such that they will be inserted 187 | * into the database when `saveChanges()` is called. 188 | * @param entities 189 | */ 190 | add(...entities: DT[]): Promise | void; 191 | /** 192 | * Begins tracking the given entity and entries reachable from the given entity using 193 | * the Modified state by default such that they will be updated 194 | * in the database when `saveChanges()` is called. 195 | * @param entities 196 | */ 197 | update(...entities: DT[]): Promise | void; 198 | 199 | /** 200 | * Begins tracking the given entity in the Deleted state such that it will be removed 201 | * from the database when `saveChanges()` is called. 202 | * @param entities 203 | */ 204 | remove(...entities: DT[]): Promise | void; 205 | 206 | /** 207 | * Removes entities from the list of currently tracked entities 208 | * @param entities 209 | */ 210 | unTrack(...entities: DT[]): Promise | void; 211 | 212 | /** 213 | * Finds an entity with the given primary key values. If an entity with the given 214 | * primary key values is being tracked by the context, then it is returned 215 | * immediately without making a request to the database. Otherwise, a query is made 216 | * to the database for an entity with the given primary key values and this entity, 217 | * if found, is attached to the context and returned. If no entity is found, then 218 | * undefined is returned. 219 | * @param type The entity type 220 | * @param id The entity id 221 | */ 222 | find(id: any): Promise | T; 223 | 224 | /** 225 | * Checks if an entity with the given id exists in the data store 226 | * @param id 227 | */ 228 | exists(id: any): Promise; 229 | 230 | /** 231 | * Asynchronously returns the first element of the sequence 232 | */ 233 | first(): Promise; 234 | /** 235 | * Asynchronously returns the first element of the sequence that satisfies a specified condition. 236 | * @param predicate A function to test each element for a condition. 237 | * @param context The predicate data source 238 | */ 239 | first( 240 | predicate: SlimExpressionFunction, 241 | context?: C 242 | ): Promise; 243 | /** 244 | * Asynchronously returns the first element of a sequence, or a default value if the sequence contains no elements. 245 | */ 246 | firstOrDefault(): Promise; 247 | /** 248 | * Asynchronously returns the first element of a sequence that satisfies a specified condition 249 | * or a default value if no such element is found. 250 | * @param predicate A function to test each element for a condition. 251 | * @param context The predicate data source 252 | */ 253 | firstOrDefault( 254 | predicate: SlimExpressionFunction, 255 | context?: C 256 | ): Promise; 257 | 258 | /** 259 | * Asynchronously creates aa `Array` of result from `IQueryable` by enumerating it asynchronously. 260 | */ 261 | toList(): Promise; 262 | 263 | /** 264 | * Specifies related entities to include in the query results. The navigation property 265 | * to be included is specified starting with the type of entity being queried (TEntity). 266 | * If you wish to include additional types based on the navigation properties of 267 | * the type being included, then chain a call to `thenInclude` after this call. 268 | * @param navigationPropertyPath The type of the related entity to be included. 269 | */ 270 | include( 271 | navigationPropertyPath: SlimExpressionFunction 272 | ): IQueryable & IQueryable; 273 | 274 | /** 275 | * Specifies additional related data to be further included based on a related type 276 | * that was just included. 277 | * @param navigationPropertyPath The type of the related entity to be included. 278 | */ 279 | thenInclude( 280 | navigationPropertyPath: SlimExpressionFunction 281 | ): IQueryable & IQueryable; 282 | 283 | /** 284 | * Filters the sequence based on a predicate. 285 | * @param predicate A function to test each element for a condition. 286 | * @param context The predicate data source 287 | */ 288 | where( 289 | predicate: SlimExpressionFunction, 290 | context?: C 291 | ): IQueryable; 292 | /** 293 | * Returns a specified number of contiguous elements from the start of the sequence. 294 | * @param count The number of elements to return. 295 | */ 296 | take(count: number): IQueryable; 297 | 298 | /** 299 | * Bypasses a specified number of elements in a sequence and then returns the remaining elements. 300 | * @param count The number of elements to skip before returning the remaining elements. 301 | */ 302 | skip(count: number): IQueryable; 303 | 304 | /** 305 | * Computes the sum of the sequence of number values that is obtained by 306 | * invoking a projection function on each element of the input sequence. 307 | * @param selector A projection function to apply to each element. 308 | */ 309 | sum(selector: SlimExpressionFunction): Promise; 310 | 311 | /** 312 | * Computes the average of a sequence of number values that is obtained by 313 | * invoking a projection function on each element of the input sequence. 314 | * @param selector A projection function to apply to each element. 315 | */ 316 | average(selector: SlimExpressionFunction): Promise; 317 | 318 | /** 319 | * Returns the number of elements in the specified sequence. If condition is provided, 320 | * the resulting elements will satisfy the condition. 321 | * @param predicate A function to test each element for a condition. 322 | */ 323 | count( 324 | predicate?: SlimExpressionFunction 325 | ): Promise; 326 | /** 327 | * Invokes a projection function on each element of the sequence 328 | * and returns the maximum resulting value. 329 | * @param selector A projection function to apply to each element. 330 | */ 331 | max( 332 | selector: SlimExpressionFunction 333 | ): Promise; 334 | /** 335 | * Invokes a projection function on each element of the sequence 336 | * and returns the minimum resulting value. 337 | * @param selector A projection function to apply to each element. 338 | */ 339 | min( 340 | selector: SlimExpressionFunction 341 | ): Promise; 342 | 343 | /** 344 | * Projects each element of a sequence into a new form. 345 | * @param selector A projection function to apply to each element. 346 | */ 347 | select( 348 | selector: SlimExpressionFunction 349 | ): IQueryableSelectionResult; 350 | 351 | /** 352 | * Specifies that the current query should not have any 353 | * model-levelentity query filters applied. 354 | */ 355 | ignoreQueryFilters(): IQueryable; 356 | 357 | /** 358 | * Sorts the elements of a sequence in descending order according to a key. 359 | * @param keySelector A function to extract a key from an element. 360 | */ 361 | orderBy(keySelector: SlimExpressionFunction): IQueryable; 362 | 363 | /** 364 | * Performs a subsequent ordering of the elements in a sequence in ascending order 365 | * @param keySelector 366 | */ 367 | thenOrderBy(keySelector: SlimExpressionFunction): IQueryable; 368 | 369 | /** 370 | * Sorts the elements of a sequence in ascending order according to a key. 371 | * @param keySelector A function to extract a key from an element. 372 | */ 373 | orderByDescending(keySelector: SlimExpressionFunction): IQueryable; 374 | } 375 | ``` 376 | 377 | #### Examples 378 | 379 | You can play around and do stuffs like this :smile: 380 | 381 | ```ts 382 | ... 383 | const context = new DeltaTravelDBContext(); 384 | const pd = { 385 | departureDate: new Date(2000, 1, 1), 386 | estimatedArrivalDate: new Date(2016, 1, 1) 387 | }; 388 | 389 | const tripsQuery = context.trips 390 | .include(t => t.agency) 391 | .include(t => t.passengers) 392 | .where( 393 | (t, $) => 394 | t.departureDate > $.departureDate && 395 | (t.estimatedArrivalDate < $.estimatedArrivalDate || 396 | t.passengers.some(p => p.willTravel === true)), 397 | pd 398 | ) 399 | .select( 400 | t => 401 | new TripResponse( 402 | t.agency.name, 403 | t.agency.email, 404 | t.departureDate, 405 | t.passengers.map(p => ({ 406 | name: p.lastname , 407 | phone: p.phone, 408 | ID: p.IDNumber 409 | })) 410 | ) 411 | ); 412 | const res = await tripsQuery.toList(); 413 | 414 | ``` 415 | 416 | For detail examples see : [Slim-ef-examples](https://github.com/bugMaker-237/slim-ef-examples) 417 | 418 | ## Not YET Supported 419 | 420 | - The `select` api doesnot support operation evaluation, only direct assignation is supported i.e 421 | 422 | ```ts 423 | ... 424 | 425 | const tripsQuery = context.trips 426 | .include(t => t.agency) 427 | .include(t => t.passengers) 428 | .select( 429 | t => 430 | new TripResponse( 431 | t.agency.name, 432 | t.agency.email, 433 | t.departureDate, 434 | t.passengers.map(p => ({ 435 | name: p.lastname + ' ' + p.firstName, // <- Not supported 436 | phone: p.phone, 437 | ID: p.IDNumber, 438 | anotherOne: p.lastname.includes('something'), // <- Not supported 439 | andAlso: p.IDNumber > 8520 // <- Not supported 440 | })) 441 | ) 442 | ); 443 | ``` 444 | 445 | ## TO DO 446 | 447 | - Change tracking 448 | - Improve `select` api 449 | - More tests! 450 | 451 | ## Usage Note 452 | 453 | - Avoid useless paranthesis in Expression functions. 454 | 455 | ## Authors 456 | 457 | - **Etienne Yamsi Aka. Bugmaker** - _Initial work_ - [Bugmaker](https://github.com/bugmaker-237) 458 | 459 | ## License 460 | 461 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details 462 | -------------------------------------------------------------------------------- /src/specification/specification-evaluator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ISpecification, 3 | CriteriaExpression, 4 | IQuerySpecificationEvaluator, 5 | QueryType, 6 | FunctionQueryType, 7 | FieldsSelector 8 | } from './specification.interface'; 9 | import { 10 | SelectQueryBuilder, 11 | ObjectLiteral, 12 | Brackets, 13 | WhereExpressionBuilder 14 | } from 'typeorm'; 15 | import { 16 | SQLConstants, 17 | ComparisonOperators, 18 | convertToSqlComparisonOperator, 19 | SQLStringFunctions, 20 | format, 21 | SQLArrayFunctions, 22 | SQLJoinFunctions 23 | } from './utils'; 24 | 25 | import { 26 | SlimExpression, 27 | ExpressionLeftHandSide, 28 | ISlimExpression, 29 | SlimExpressionFunction, 30 | PrimitiveValueTypes as PrimitiveTypes, 31 | ExpressionRightHandSide 32 | } from 'slim-exp'; 33 | import { SQLQuerySpecificationException } from './exception'; 34 | const INITIAL_ALIAS = 'entity'; 35 | type WhereQuery = ( 36 | where: Brackets | string | ((qb: SelectQueryBuilder) => string) 37 | ) => SelectQueryBuilder; 38 | 39 | type Sequence = { 40 | toApply: WhereQuery; 41 | queryStr: string; 42 | queryParams?: Record; 43 | }; 44 | type QuerySequence = { 45 | firstSequence?: 'brackets' | 'topLevel'; 46 | topLevelSequence: Sequence[]; 47 | bracketSequence?: QuerySequence[]; 48 | initialToApply: WhereQuery; 49 | }; 50 | 51 | export class SQLQuerySpecificationEvaluator 52 | implements IQuerySpecificationEvaluator { 53 | private registerdAliases = [INITIAL_ALIAS]; 54 | private _query: SelectQueryBuilder; 55 | private _queryReady: boolean; 56 | private _discriminator: number = 0; 57 | private _selectBuilder: SlimExpressionFunction; 58 | 59 | constructor( 60 | private readonly initialQuery: (alias: string) => SelectQueryBuilder, 61 | private readonly spec: ISpecification 62 | ) { 63 | this._queryReady = false; 64 | } 65 | 66 | _applyLeftJoin( 67 | query: SelectQueryBuilder, 68 | intialAlias: string, 69 | exp: SlimExpressionFunction | string 70 | ) { 71 | return this._applyJoin(query.leftJoinAndSelect, query, intialAlias, exp); 72 | } 73 | 74 | private _applyJoin( 75 | toApply: (...args: any[]) => any, 76 | query: SelectQueryBuilder, 77 | intialAlias: string, 78 | exp: SlimExpressionFunction | string 79 | ) { 80 | const name = typeof exp === 'string' ? exp : SlimExpression.nameOf(exp); // expressionParser will parse property name as empty if trying to parse obj => obj 81 | if (!name.trim()) 82 | throw new SQLQuerySpecificationException( 83 | 'You are trying to include self entity' 84 | ); 85 | const { 86 | isAlreadyRegistered, 87 | propertyName, 88 | entityAlias 89 | } = this._getFieldNameAndAlias(intialAlias, name); 90 | if (!isAlreadyRegistered) toApply.call(query, propertyName, entityAlias); 91 | return { propertyName, entityAlias }; 92 | } 93 | 94 | private _getFieldNameAndAlias( 95 | alias: string, 96 | name: string, 97 | handlingFunction = false 98 | ) { 99 | const splitted = name.split('.'); 100 | if (splitted.length >= 2 && !handlingFunction) 101 | throw new SQLQuerySpecificationException( 102 | 'Include or Where syntax error. Use thenInclude to include composite entity' 103 | ); 104 | name = splitted[0]; 105 | const entityAlias = `${alias}_${name}`; 106 | 107 | const isAlreadyRegistered = this.registerdAliases.includes(entityAlias); 108 | if (!isAlreadyRegistered && !handlingFunction) { 109 | if (!handlingFunction) this.registerdAliases.push(entityAlias); 110 | else 111 | throw new SQLQuerySpecificationException( 112 | 'Include or Where syntax error. Use thenInclude to include composite entity' 113 | ); 114 | } 115 | 116 | return { 117 | propertyName: `${alias}.${name}`, 118 | entityAlias: handlingFunction 119 | ? `${alias}_${splitted.join('_')}` 120 | : entityAlias, 121 | isAlreadyRegistered 122 | }; 123 | } 124 | 125 | private _getPropertyAlias(f: SlimExpressionFunction | string) { 126 | const name = typeof f === 'string' ? f : SlimExpression.nameOf(f); 127 | if (!name.trim()) 128 | throw new SQLQuerySpecificationException( 129 | 'You are trying to apply boolean condition on self entity' 130 | ); 131 | const propertyAlias = this._getFieldFromRegisteredAlias( 132 | INITIAL_ALIAS, 133 | name 134 | ); 135 | return propertyAlias; 136 | } 137 | 138 | private _getFieldFromRegisteredAlias(initialAlias: string, name: string) { 139 | const path = name.split('.'); 140 | let finalAlias = initialAlias; 141 | const finalName = path.pop(); 142 | if (path.length !== 0) { 143 | finalAlias += `_${path.join('_')}`; 144 | } 145 | if (!this.registerdAliases.includes(finalAlias)) 146 | throw new SQLQuerySpecificationException( 147 | "Condition added to where clause with a property that was not included in the query expression.Please use 'include' with/or 'thenInclude' to include desire entity: " + 148 | finalAlias 149 | ); 150 | return `${finalAlias}.${finalName}`; 151 | } 152 | 153 | private _isOrBinding(val: string) { 154 | return val === '||' || val === '|'; 155 | } 156 | 157 | private _generateQuery( 158 | alias: string, 159 | sqlQuery: SelectQueryBuilder, 160 | selector: CriteriaExpression, 161 | isFirst = false 162 | ): SelectQueryBuilder { 163 | try { 164 | const exp = new SlimExpression(); 165 | exp.fromAction(selector.func, selector.context, false); 166 | exp.compile(); 167 | const toApply = isFirst ? sqlQuery.where : sqlQuery.andWhere; 168 | // applying brackets around each where clause to improve consistency 169 | // and fiability of the request 170 | const brackets = new Brackets(wh => { 171 | this._chunkDownToQuery(exp, wh, alias, toApply); 172 | }); 173 | toApply.call(sqlQuery, brackets); 174 | return sqlQuery; 175 | } catch (error) { 176 | throw new SQLQuerySpecificationException( 177 | error?.message + 178 | '; func:' + 179 | selector.func.toString() + 180 | '; context: ' + 181 | selector.context 182 | ? JSON.stringify(selector.context) 183 | : '' 184 | ); 185 | } 186 | } 187 | 188 | private _chunkDownToQuery( 189 | exp: ISlimExpression, 190 | sqlQuery: WhereExpressionBuilder, 191 | alias: ((implicitName: string) => string) | string, 192 | initialToApply: WhereQuery, 193 | closingExp?: ISlimExpression, 194 | setupBrackets: 'inactive' | 'active' = 'inactive' 195 | ): WhereExpressionBuilder { 196 | try { 197 | const querySequence = this._getQuerySequence( 198 | exp, 199 | alias, 200 | initialToApply, 201 | closingExp, 202 | setupBrackets 203 | ); 204 | this._applyRecursively(sqlQuery, querySequence); 205 | 206 | return sqlQuery; 207 | } catch (error) { 208 | throw new SQLQuerySpecificationException(error.message); 209 | } 210 | } 211 | private _applyRecursively( 212 | sqlQuery: WhereExpressionBuilder, 213 | querySequence: QuerySequence 214 | ) { 215 | const first = querySequence; 216 | 217 | const callBrackets = () => { 218 | for (const b of first.bracketSequence) { 219 | const brackets = new Brackets(wh => { 220 | this._applyRecursively(wh, b); 221 | }); 222 | b.initialToApply.call(sqlQuery, brackets); 223 | } 224 | }; 225 | 226 | const callTopLevel = () => { 227 | for (const s of first.topLevelSequence) { 228 | s.toApply.call(sqlQuery, s.queryStr, s.queryParams); 229 | } 230 | }; 231 | 232 | if (first.firstSequence === 'brackets') { 233 | callBrackets(); 234 | callTopLevel(); 235 | } else { 236 | callTopLevel(); 237 | callBrackets(); 238 | } 239 | } 240 | 241 | private _getQuerySequence( 242 | exp: ISlimExpression, 243 | alias: ((implicitName: string) => string) | string, 244 | initialToApply: WhereQuery, 245 | closingExp?: ISlimExpression, 246 | setupBrackets: 'inactive' | 'active' = 'inactive' 247 | ): QuerySequence { 248 | let e = exp; 249 | let toApply = initialToApply; 250 | const querySequence: QuerySequence = { 251 | topLevelSequence: [], 252 | bracketSequence: [], 253 | initialToApply 254 | }; 255 | do { 256 | // When trying to understand this stuff, remember that logical operators are 257 | // associative, no matter the other be it exp1 && exp2 or exp2 && exp1 the 258 | // result is the same. So the real focus is to be able to handle the paranthesis 259 | // in a clean and oderable manner 260 | if (e.brackets?.openingExp) { 261 | querySequence.bracketSequence.push( 262 | this._getQuerySequence( 263 | e.brackets.openingExp, 264 | alias, 265 | toApply, 266 | e.brackets.closingExp, 267 | 'active' 268 | ) 269 | ); 270 | if (!querySequence.firstSequence) { 271 | querySequence.firstSequence = 'brackets'; 272 | } 273 | } 274 | 275 | if (e.computeHash() === closingExp?.computeHash()) { 276 | setupBrackets = 'inactive'; 277 | } 278 | 279 | const sequence: QuerySequence = this._buildQueryFromExpression( 280 | e.leftHandSide, 281 | e.rightHandSide, 282 | e.operator, 283 | e.expObjectName, 284 | alias, 285 | toApply, 286 | setupBrackets !== 'inactive' 287 | ); 288 | 289 | if (!querySequence.firstSequence) { 290 | querySequence.firstSequence = 'topLevel'; 291 | } 292 | querySequence.topLevelSequence.push( 293 | ...(sequence.topLevelSequence.filter(s => !!s.queryStr) || []) 294 | ); 295 | querySequence.bracketSequence.push(...(sequence.bracketSequence || [])); 296 | 297 | if (e.next) { 298 | toApply = this._isOrBinding(e.next.bindedBy) 299 | ? SelectQueryBuilder.prototype.orWhere 300 | : SelectQueryBuilder.prototype.andWhere; 301 | } 302 | e = e?.next?.followedBy; 303 | } while (e); 304 | 305 | return querySequence; 306 | } 307 | 308 | private _buildQueryFromExpression( 309 | lhs: ExpressionLeftHandSide, 310 | rhs: ExpressionRightHandSide, 311 | operator: string, 312 | expObjectName: string, 313 | alias: string | ((implicitName: string) => string), 314 | toApply?: WhereQuery, 315 | isInBracketGroup: boolean = false 316 | ): QuerySequence { 317 | let queryStr = ''; 318 | let queryParams: ObjectLiteral; 319 | if (!lhs && !rhs) { 320 | return { 321 | topLevelSequence: [{ toApply, queryStr, queryParams }], 322 | initialToApply: toApply 323 | }; 324 | } 325 | 326 | const lhsProp = lhs.isMethod 327 | ? lhs.propertyTree.slice(0, lhs.propertyTree.length - 1).join('.') 328 | : lhs.propertyName; 329 | const rhsProp = rhs?.propertyName; 330 | 331 | let lhsAlias: string; 332 | if (typeof alias !== 'string') { 333 | lhsAlias = alias(expObjectName); 334 | } else { 335 | lhsAlias = alias; 336 | } 337 | let rhsAlias: string; 338 | if (rhs) { 339 | if (typeof alias !== 'string') { 340 | rhsAlias = alias(rhs.implicitContextName); 341 | } else { 342 | rhsAlias = alias; 343 | } 344 | } 345 | const lhsName = this._getFieldFromRegisteredAlias(lhsAlias, lhsProp); 346 | const rhsName = 347 | rhsProp && rhsAlias 348 | ? this._getFieldFromRegisteredAlias(rhsAlias, rhsProp) 349 | : ''; 350 | 351 | if (lhs.suffixOperator && !lhs.isMethod && !operator && !rhs) { 352 | const suffixOp = lhs.suffixOperator; 353 | queryStr += ` ${lhsName} ${convertToSqlComparisonOperator( 354 | ComparisonOperators.EQUAL_TO 355 | )} ${suffixOp === '!' ? SQLConstants.FALSE : SQLConstants.TRUE}`; 356 | } 357 | 358 | if (lhs.isMethod) { 359 | return this._handleFunctionInvokation( 360 | lhsName, 361 | lhsAlias, 362 | expObjectName, 363 | lhs, 364 | toApply, 365 | isInBracketGroup 366 | ); 367 | } 368 | 369 | if (!lhs.suffixOperator && operator && rhs) { 370 | if (rhs.propertyType !== PrimitiveTypes.undefined) { 371 | const paramName = this._getUniqueParamName(rhs.propertyName); 372 | 373 | queryStr += ` ${ 374 | lhs.isMethod ? '' : lhsName 375 | } ${convertToSqlComparisonOperator( 376 | operator, 377 | rhs.propertyValue 378 | )} :${paramName}`; 379 | queryParams = {} as any; 380 | 381 | // we need to format the date because typeorm has issue handling it with 382 | // sqlite 383 | queryParams[paramName] = 384 | rhs.propertyType === PrimitiveTypes.date 385 | ? this._polyfillDate(rhs.propertyValue) 386 | : rhs.propertyType === PrimitiveTypes.number 387 | ? rhs.propertyValue.toString() 388 | : rhs.propertyValue; 389 | } else { 390 | queryStr += ` ${ 391 | lhs.isMethod ? '' : lhsName 392 | } ${convertToSqlComparisonOperator( 393 | operator, 394 | rhs.propertyValue 395 | )} ${rhsName}`; 396 | } 397 | } 398 | return { 399 | topLevelSequence: [{ toApply, queryStr, queryParams }], 400 | initialToApply: toApply 401 | }; 402 | } 403 | private _polyfillDate(val: Date): any { 404 | const pad = (num: number, p = 2, bf = true) => 405 | bf ? num.toString().padStart(p, '0') : num.toString().padEnd(p, '0'); 406 | 407 | return `${val.getFullYear()}-${pad(val.getMonth() + 1)}-${pad( 408 | val.getDate() 409 | )} ${pad(val.getHours())}-${pad(val.getMinutes())}-${pad( 410 | val.getSeconds() 411 | )}.${pad(val.getMilliseconds(), 3, false)}`; 412 | } 413 | 414 | private _handleFunctionInvokation( 415 | name: string, 416 | initialAlias: string, 417 | initialExpObjectName: string, 418 | leftHandSide: ExpressionLeftHandSide, 419 | toApply?: WhereQuery, 420 | isInBracketGroup: boolean = false 421 | ): QuerySequence { 422 | if (!leftHandSide.content) 423 | throw new Error('LeftHandSide Content not defined'); 424 | 425 | let func: string; 426 | let sqlPart: string; 427 | const propName = name; 428 | const content = leftHandSide.content; 429 | if ( 430 | content.type in PrimitiveTypes && 431 | content.methodName in SQLStringFunctions 432 | ) { 433 | func = SQLStringFunctions[content.methodName]; 434 | sqlPart = format(func, propName, content.primitiveValue.toString()); 435 | return { 436 | topLevelSequence: [{ toApply, queryStr: sqlPart }], 437 | initialToApply: toApply 438 | }; 439 | } else if ( 440 | !(content.type in PrimitiveTypes) && 441 | content.methodName in SQLArrayFunctions && 442 | Array.isArray(content.primitiveValue) 443 | ) { 444 | func = SQLArrayFunctions[content.methodName]; 445 | const tb = content.primitiveValue.map(v => `'${v}'`); 446 | sqlPart = format(func, propName, tb.toString()); 447 | return { 448 | topLevelSequence: [{ toApply, queryStr: sqlPart }], 449 | initialToApply: toApply 450 | }; 451 | } else if ( 452 | !(content.type in PrimitiveTypes) && 453 | content.methodName in SQLJoinFunctions && 454 | content.isExpression 455 | ) { 456 | let LHS = leftHandSide; 457 | let alias = initialAlias; 458 | const contextNamesAndalias = new Map(); 459 | contextNamesAndalias.set(initialExpObjectName, initialAlias); 460 | do { 461 | // trying to get the property name on which the function is called 462 | // i.e t => t.tickets.some(...), we have to get 'tickets' 463 | // but since .propertyName attribute gives 'some' we 464 | // have to go up the propertytree 465 | // LHS.propertyTree.slice(0, LHS.propertyTree.length - 1).join('.') 466 | const { entityAlias } = this._getFieldNameAndAlias( 467 | alias, 468 | LHS.propertyTree.slice(0, LHS.propertyTree.length - 1).join('.'), 469 | true 470 | ); 471 | alias = entityAlias; 472 | const exp = LHS?.content?.expression; 473 | if (exp) { 474 | contextNamesAndalias.set(exp.expObjectName, alias); 475 | if (exp.rightHandSide) { 476 | return this._getQuerySequence( 477 | exp, 478 | o => contextNamesAndalias.get(o), 479 | toApply, 480 | null, 481 | isInBracketGroup ? 'active' : 'inactive' 482 | ); 483 | } else if ( 484 | exp.leftHandSide && 485 | exp.leftHandSide.isMethod && 486 | !exp.leftHandSide.content.isExpression 487 | ) { 488 | const propTree = exp.leftHandSide.propertyName.split('.'); 489 | propTree.pop(); 490 | const field = this._getFieldFromRegisteredAlias( 491 | alias, 492 | propTree.join('.') 493 | ); 494 | return this._handleFunctionInvokation( 495 | field, 496 | entityAlias, 497 | exp.expObjectName, 498 | exp.leftHandSide, 499 | toApply, 500 | false 501 | ); 502 | } 503 | } 504 | LHS = exp?.leftHandSide; 505 | } while (LHS && LHS.isMethod && LHS.content && LHS.content.isExpression); 506 | 507 | return { 508 | topLevelSequence: [{ toApply, queryStr: sqlPart }], 509 | initialToApply: toApply 510 | }; 511 | } 512 | throw new Error('Unsupported Function Invokation: ' + content.methodName); 513 | } 514 | 515 | private _getUniqueParamName(paramName: string): string { 516 | paramName = paramName.replace(/\[|\]|\(|\)|\*|\+|\-|\'|\"/g, ''); 517 | 518 | return paramName + '_' + ++this._discriminator; 519 | } 520 | 521 | public getParams(): any { 522 | return this._query.getParameters(); 523 | } 524 | public getQuery(): Promise { 525 | return new Promise((res, rej) => { 526 | try { 527 | this._query = this.initialQuery(INITIAL_ALIAS).select(); 528 | // tslint:disable-next-line: one-variable-per-declaration 529 | const includes = this.spec.getIncludes(), 530 | chainedIncludes = this.spec.getChainedIncludes(), 531 | criterias = this.spec.getCriterias(), 532 | orderBy = this.spec.getOrderBy(), 533 | groupBy = this.spec.getGroupBy(), 534 | thenGroupBy = this.spec.getThenGroupBy(), 535 | orderByDescending = this.spec.getOrderByDescending(), 536 | selector = this.spec.getSelector(), 537 | take = this.spec.getTake(), 538 | skip = this.spec.getSkip(), 539 | thenOrderBy = this.spec.getThenOrderBy(), 540 | isPagingEnabled = this.spec.getIsPagingEnabled(), 541 | func = this.spec.getFunction(), 542 | isDistinct = this.spec.getDistinct(); 543 | 544 | if (chainedIncludes && chainedIncludes.length > 0) { 545 | for (const i of chainedIncludes) { 546 | let { entityAlias: currentAlias } = this._applyLeftJoin( 547 | this._query, 548 | INITIAL_ALIAS, 549 | i.initial 550 | ); 551 | i.chain.forEach(c => { 552 | const { entityAlias } = this._applyLeftJoin( 553 | this._query, 554 | currentAlias, 555 | c 556 | ); 557 | currentAlias = entityAlias; 558 | }); 559 | } 560 | } 561 | 562 | if (includes && includes.length > 0) { 563 | for (const i of includes) { 564 | this._applyLeftJoin(this._query, INITIAL_ALIAS, i); 565 | } 566 | } 567 | 568 | if (criterias && criterias.length > 0) { 569 | const [first, ...rest] = criterias; 570 | 571 | this._query = this._generateQuery( 572 | INITIAL_ALIAS, 573 | this._query, 574 | first, 575 | true 576 | ); 577 | if (rest && rest.length > 0) { 578 | for (const q of rest) { 579 | this._query = this._generateQuery(INITIAL_ALIAS, this._query, q); 580 | } 581 | } 582 | } 583 | 584 | if (func) { 585 | this._query = this._applyFunction(this._query, func); 586 | } else { 587 | let propertyAlias; 588 | let isAsc = false; 589 | if (orderBy) { 590 | propertyAlias = this._getPropertyAlias(orderBy); 591 | isAsc = true; 592 | this._query = this._query.orderBy(propertyAlias, 'ASC'); 593 | } else if (orderByDescending) { 594 | propertyAlias = this._getPropertyAlias(orderByDescending); 595 | this._query = this._query.orderBy(propertyAlias, 'DESC'); 596 | } 597 | 598 | if ((orderBy || orderByDescending) && thenOrderBy?.length) { 599 | thenOrderBy.forEach(tb => { 600 | propertyAlias = this._getPropertyAlias(tb); 601 | this._query = this._query.addOrderBy( 602 | propertyAlias, 603 | isAsc ? 'ASC' : 'DESC' 604 | ); 605 | }); 606 | } 607 | 608 | if (groupBy) { 609 | propertyAlias = this._getPropertyAlias(groupBy); 610 | this._query = this._query.groupBy(propertyAlias); 611 | } 612 | 613 | if (groupBy && thenGroupBy?.length) { 614 | thenGroupBy.forEach(tb => { 615 | propertyAlias = this._getPropertyAlias(tb); 616 | this._query = this._query.addGroupBy(propertyAlias); 617 | }); 618 | } 619 | if (isPagingEnabled) { 620 | if (take) { 621 | this._query = this._query.take(take); 622 | } 623 | if (skip) { 624 | this._query = this._query.skip(skip); 625 | } 626 | } 627 | 628 | if (isDistinct) { 629 | this._query = this._query.distinct(true); 630 | } 631 | 632 | if ( 633 | selector && 634 | selector.fieldsToSelect && 635 | selector.fieldsToSelect.length > 0 636 | ) { 637 | const toSelect = this._buildSelect(this._query, selector); 638 | this._query.select(toSelect); 639 | this._selectBuilder = selector.builder; 640 | } 641 | } 642 | this._queryReady = true; 643 | return res(this._query.getQuery()); 644 | } catch (error) { 645 | throw new SQLQuerySpecificationException(error); 646 | } 647 | }); 648 | } 649 | private _buildSelect( 650 | _query: SelectQueryBuilder, 651 | selector: FieldsSelector 652 | ): string[] { 653 | const toSelect = [ 654 | ...selector.fieldsToSelect.map(f => this._getPropertyAlias(f.field)) 655 | ]; 656 | 657 | // If id is not present typeorm throws an exception. 658 | // So we go get the id field 659 | 660 | for (const c of this._query.expressionMap.mainAlias.metadata.columns) { 661 | if (c.isPrimary) { 662 | toSelect.push( 663 | this._getFieldFromRegisteredAlias(INITIAL_ALIAS, c.propertyName) 664 | ); 665 | break; 666 | } 667 | } 668 | 669 | // I didn't find any other way to add the realtionsId to 670 | // the select query. 671 | // If the relations' id are not present in the select query 672 | // the entities will not be loaded by typeorm 673 | for (const j of this._query.expressionMap.joinAttributes) { 674 | const propName = j.alias.name.split('_'); 675 | propName.pop(); 676 | const finalName = propName.join('_'); 677 | for (const f of j.relation.foreignKeys) { 678 | for (const c of f.columnNames) { 679 | toSelect.push(this._getFieldFromRegisteredAlias(finalName, c)); 680 | } 681 | } 682 | } 683 | return toSelect; 684 | } 685 | private _applyFunction( 686 | query: SelectQueryBuilder, 687 | value: { 688 | type: FunctionQueryType; 689 | func: SlimExpressionFunction; 690 | } 691 | ): SelectQueryBuilder { 692 | const field = value.func ? this._getPropertyAlias(value.func) : '*'; 693 | return query.select(`${value.type}(${field}) as ${value.type}`); 694 | } 695 | 696 | public async executeQuery( 697 | type: QueryType 698 | ): Promise { 699 | if (!this._queryReady) await this.getQuery(); 700 | let toApply; 701 | let defaultVal; 702 | switch (type) { 703 | case QueryType.ALL: 704 | toApply = this._query.getMany; 705 | defaultVal = []; 706 | break; 707 | case QueryType.ONE: 708 | toApply = this._query.getOne; 709 | defaultVal = void 0; 710 | break; 711 | case QueryType.RAW_ONE: 712 | toApply = this._query.getRawOne; 713 | defaultVal = void 0; 714 | break; 715 | case QueryType.RAW_ALL: 716 | toApply = this._query.getRawMany; 717 | defaultVal = []; 718 | break; 719 | 720 | default: 721 | break; 722 | } 723 | const result = (await toApply.call(this._query)) || defaultVal; 724 | let finalRes: any; 725 | if (this._selectBuilder) { 726 | finalRes = Array.isArray(result) 727 | ? result.map(r => this._selectBuilder(r)) 728 | : this._selectBuilder(result); 729 | } else { 730 | finalRes = result; 731 | } 732 | return finalRes; 733 | } 734 | } 735 | -------------------------------------------------------------------------------- /tests/seeder/sqlite-seed.sql: -------------------------------------------------------------------------------- 1 | PRAGMA synchronous = OFF; 2 | PRAGMA journal_mode = MEMORY; 3 | BEGIN TRANSACTION; 4 | CREATE TABLE `agency` ( 5 | `name` varchar(255) NOT NULL 6 | , `phone` varchar(255) NOT NULL 7 | , `email` varchar(255) DEFAULT NULL 8 | , `id` integer NOT NULL PRIMARY KEY AUTOINCREMENT 9 | ); 10 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Molestias voluptatibus.', '582-214-5404', 'hgusikowski@example.com', 1); 11 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Deleniti impedit.', '1-161-025-3477x922', 'janet95@example.org', 2); 12 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Vitae maiores dolor.', '108.353.7124x3544', 'dave.torphy@example.net', 3); 13 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Suscipit quo aspernatur.', '1-343-819-0189x89374', 'laron75@example.net', 4); 14 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Et saepe.', '729-007-5012', 'ecollins@example.org', 5); 15 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Error dolore.', '517.363.4474x382', 'kelvin.quitzon@example.net', 6); 16 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('In occaecati.', '299.804.6993', 'hschultz@example.com', 7); 17 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Sed iste dolorem.', '+67(6)3812293753', 'qsimonis@example.net', 8); 18 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Vitae sed consequuntur.', '(505)479-3563x1524', 'eva.hoeger@example.net', 9); 19 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Provident facere in.', '1-049-840-0951x7209', 'abbie.bauch@example.org', 10); 20 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Officiis hic.', '499-765-4515x3832', 'will30@example.com', 11); 21 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Aspernatur et.', '1-168-228-9642x2819', 'toy.geovanni@example.org', 12); 22 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Rerum iste.', '1-509-697-5837x7030', 'lzemlak@example.org', 13); 23 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Nostrum autem eligendi.', '555.593.5264x9974', 'nils.kemmer@example.org', 14); 24 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('A perferendis.', '335-325-5893', 'dovie.ernser@example.net', 15); 25 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Eius voluptatem ullam.', '016-045-8694x9351', 'arturo46@example.net', 16); 26 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Eum et sed.', '1-611-598-9642', 'peyton04@example.org', 17); 27 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Eaque adipisci illo.', '932.913.5106x6531', 'moen.emery@example.net', 18); 28 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Et iusto harum.', '(143)298-1963x717', 'femard@example.net', 19); 29 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Tenetur provident.', '1-689-134-0730', 'johan59@example.com', 20); 30 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Voluptas quia.', '1-292-861-5780', 'barrows.bettie@example.org', 21); 31 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Consectetur est architecto.', '(343)878-4702', 'glenda60@example.net', 22); 32 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Natus cum asperiores.', '(617)550-7774x26616', 'emelia.will@example.org', 23); 33 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Corrupti sit sit.', '(699)122-2231', 'leannon.reynold@example.net', 24); 34 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Architecto facilis.', '1-922-106-5367', 'foster.hintz@example.net', 25); 35 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Suscipit quia.', '+17(4)3168621967', 'flavie50@example.com', 26); 36 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Dolor occaecati soluta.', '(948)013-2193', 'maryse.morissette@example.net', 27); 37 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Error a.', '1-623-079-6708', 'pweissnat@example.org', 28); 38 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Inventore consequatur.', '1-102-288-3399x95975', 'alana.grant@example.com', 29); 39 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Autem nemo.', '+57(2)7522633308', 'nora.bernier@example.com', 30); 40 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Molestiae dolor.', '660-815-0078', 'audra.cormier@example.net', 31); 41 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Odio culpa nulla.', '(881)338-1334x712', 'hilpert.yasmine@example.org', 32); 42 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Aut tempore quasi.', '+84(3)6956151653', 'issac77@example.com', 33); 43 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Porro eum dignissimos.', '04311385067', 'garrick.leannon@example.net', 34); 44 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Doloremque iste quia.', '(119)348-3180', 'nmacejkovic@example.com', 35); 45 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Ut dolores.', '(920)589-7783x9327', 'wmoen@example.org', 36); 46 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Recusandae aspernatur.', '512-480-8096x776', 'wanda.kemmer@example.org', 37); 47 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Aut quisquam aliquam.', '1-531-484-3231', 'elmer.smitham@example.net', 38); 48 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Omnis qui occaecati.', '(148)382-5153x21525', 'izaiah.klocko@example.org', 39); 49 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Amet nemo voluptatem.', '(351)601-1430', 'jamey.emmerich@example.com', 40); 50 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Aut praesentium.', '309.726.6147x246', 'qcorkery@example.com', 41); 51 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Architecto ad.', '(225)832-1430', 'uriah46@example.net', 42); 52 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Reiciendis unde quibusdam.', '1-050-942-3457', 'whitney81@example.net', 43); 53 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Numquam itaque sint.', '870.403.4895', 'ned28@example.net', 44); 54 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Sequi dolor et.', '213.806.7172', 'rjacobs@example.com', 45); 55 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Voluptatibus explicabo.', '07973104654', 'borer.jamaal@example.com', 46); 56 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Voluptatibus natus.', '(256)171-9556', 'emanuel.gusikowski@example.net', 47); 57 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Voluptatum non.', '04166502476', 'rturner@example.com', 48); 58 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Architecto qui.', '652-633-1032', 'reichert.alexandra@example.org', 49); 59 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Sed minima possimus.', '1-228-944-4162x7826', 'xwelch@example.com', 50); 60 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('A error.', '702.046.3020x189', 'mason50@example.org', 51); 61 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Ut aut.', '(678)892-5775x697', 'mia.tremblay@example.net', 52); 62 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Rerum est cumque.', '(814)102-7001x7235', 'mclaughlin.reanna@example.com', 53); 63 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Commodi dolore recusandae.', '645-441-3153', 'jeffery70@example.org', 54); 64 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Tempora quia.', '1-391-113-0639x63672', 'adan44@example.com', 55); 65 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Accusamus nihil.', '896-074-2132', 'otilia60@example.org', 56); 66 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Quam sit.', '1-549-302-9249x232', 'boyd70@example.com', 57); 67 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Deleniti molestiae quos.', '(619)173-9047x820', 'jerad.prosacco@example.org', 58); 68 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('A ipsa.', '+70(7)8722545870', 'stroman.wilhelm@example.net', 59); 69 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Quo dolorem.', '1-645-903-6334x668', 'norma81@example.org', 60); 70 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Eos in.', '740-713-1364x8189', 'reynolds.rachael@example.org', 61); 71 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Asperiores sunt.', '09164660748', 'esperanza22@example.net', 62); 72 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Praesentium inventore occaecati.', '09793626344', 'considine.amaya@example.net', 63); 73 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Molestiae est.', '1-964-903-3025x191', 'alabadie@example.com', 64); 74 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Aut aliquam.', '540-014-9423x1399', 'rwisozk@example.net', 65); 75 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Ut ex reiciendis.', '882.914.4770', 'gordon.rutherford@example.com', 66); 76 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Possimus explicabo.', '991-223-5900x3717', 'dameon67@example.com', 67); 77 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Ipsum dolore.', '(208)617-0727x768', 'aniya88@example.org', 68); 78 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Autem voluptas.', '1-416-472-0886x65888', 'jamal.beahan@example.net', 69); 79 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Est nulla eos.', '(411)325-0128x87882', 'rusty20@example.net', 70); 80 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Vero maxime culpa.', '403-040-3346x192', 'jaqueline55@example.net', 71); 81 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Aliquam officiis.', '00188157655', 'estracke@example.net', 72); 82 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Ut dolorum doloremque.', '862.909.2232x02360', 'stewart.schumm@example.com', 73); 83 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Iusto voluptatem quos.', '418.486.0704x8489', 'twalsh@example.com', 74); 84 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Neque inventore voluptas.', '725-865-4956', 'skye60@example.com', 75); 85 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Voluptatibus reiciendis.', '159.205.0985x141', 'assunta33@example.com', 76); 86 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Distinctio fugit aut.', '+78(1)5807433431', 'willow.feeney@example.com', 77); 87 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Placeat blanditiis.', '887.569.2350', 'jhand@example.org', 78); 88 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Suscipit nostrum.', '(774)806-7884x535', 'ondricka.rhea@example.net', 79); 89 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Quo recusandae ducimus.', '(737)376-1636x4847', 'vfisher@example.org', 80); 90 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Non quasi rerum.', '08008267499', 'viola.hahn@example.org', 81); 91 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Rerum est aut.', '519-298-5181x704', 'schaefer.ludie@example.org', 82); 92 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Non ipsa consequuntur.', '+86(0)0132346650', 'sarina60@example.net', 83); 93 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Error aliquid.', '323-679-1390', 'iturner@example.org', 84); 94 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Voluptates voluptatem ab.', '+87(9)7735835169', 'halvorson.tyrique@example.net', 85); 95 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Sunt dignissimos vel.', '379.996.3658', 'britney.runolfsdottir@example.net', 86); 96 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Saepe magni.', '1-076-383-5742', 'pollich.ebony@example.org', 87); 97 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Vel iure pariatur.', '05423738397', 'shanahan.lisa@example.net', 88); 98 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Modi doloremque enim.', '(059)398-8992x9196', 'sallie46@example.net', 89); 99 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Autem illo.', '1-237-005-0621x9497', 'velda27@example.com', 90); 100 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Possimus minus.', '624-785-6969', 'lgoodwin@example.com', 91); 101 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Quaerat eos.', '1-226-164-2387x646', 'mozell.gerhold@example.com', 92); 102 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Et debitis a.', '942.302.3471x15612', 'dgulgowski@example.org', 93); 103 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Quia ducimus.', '+17(6)2374905093', 'utowne@example.org', 94); 104 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Repellendus quibusdam nihil.', '478.273.0100', 'mbreitenberg@example.com', 95); 105 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Laboriosam dolor.', '928-105-3738x7856', 'vince76@example.net', 96); 106 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Vitae rem adipisci.', '1-582-203-3290x87859', 'caleb16@example.net', 97); 107 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Est perspiciatis.', '651-723-6105', 'kautzer.libbie@example.net', 98); 108 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Ratione sed.', '1-639-019-7638x318', 'paige.greenholt@example.com', 99); 109 | INSERT INTO `agency` (`name`, `phone`, `email`, `id`) VALUES ('Accusamus omnis.', '(395)567-9346x44955', 'maverick81@example.org', 100); 110 | CREATE TABLE `person` ( 111 | `id` varchar(255) NOT NULL 112 | , `tripId` varchar(255) DEFAULT NULL 113 | , `firstname` varchar(255) NOT NULL 114 | , `lastname` varchar(255) NOT NULL 115 | , `phone` varchar(255) NOT NULL 116 | , `IDNumber` integer NOT NULL 117 | , `email` varchar(255) DEFAULT NULL 118 | , `willTravel` integer NOT NULL DEFAULT 0 119 | , PRIMARY KEY (`id`) 120 | , CONSTRAINT `FK_9b53a69c297420a377dae12e437` FOREIGN KEY (`tripId`) REFERENCES `trip` (`id`) 121 | ); 122 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('015c5549-3bbb-31ef-b2d5-402c6a9f746e', '30057c8c-7772-3ae0-b423-661e7b10d26b', 'Gudrun', 'Hodkiewicz', '+27(7)4217166816', 764280, 'gibson.rodrigo@example.org', 1); 123 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('04f6f603-4eac-395c-a310-98b7cf7c19ce', 'fab2bdb7-de25-3ef2-9711-6c995ba230e8', 'Nick', 'Hudson', '480-472-8973x736', 537845, 'carolanne.rowe@example.net', 1); 124 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('0ac3fd9c-7f37-320c-9c91-d69829b3f2cd', 'd2d016c1-7fc9-3120-aeb0-82c6d96b9917', 'Randall', 'Rau', '005.772.5432x606', 614833, 'hagenes.jakayla@example.net', 1); 125 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('0e4cebc8-77c1-3ca2-8b95-ef4d08f4cc33', 'e8b923c3-6eef-3fbb-a8cf-80043752a7a0', 'Mike', 'Kuhic', '07765475288', 966601, 'batz.opal@example.org', 0); 126 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('0e537561-68bc-3d00-acbe-354a9904a955', 'c8b88eee-e33d-3501-9aa9-6f2460e386fa', 'Sterling', 'Blick', '259.402.0592', 723224, 'brandyn.sawayn@example.com', 0); 127 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('0e625e74-165a-38fb-bb32-2ed8bbb66727', '34c1b5ef-ff97-3242-b341-59696c38e15b', 'Elfrieda', 'Klein', '(012)385-8227', 662410, 'klocko.alfreda@example.org', 0); 128 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('1d8deef8-ac88-3030-8b70-280d8a808767', 'c0b5674e-14eb-35b0-8e35-d9a60eeef140', 'Helen', 'Kessler', '(902)265-8680x9352', 971370, 'witting.erica@example.net', 1); 129 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('258f4d19-fc23-3f1b-8fdd-4ee784173230', '4f8062b2-9dfb-300b-9a2e-18c645ceb33d', 'Juwan', 'Bartell', '048.241.9871', 671986, 'friedrich.runolfsson@example.com', 1); 130 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('2a32b07f-774e-3e41-818a-b6318de1b2e8', '2e396218-ffc3-36c7-989a-602ba0699f4b', 'Leanne', 'Wolf', '748-427-1318', 837434, 'kub.jared@example.com', 0); 131 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('2e101a23-4c68-3d03-a0a4-714733d102e6', 'f587d552-e609-3911-ab65-1a83afb9bed4', 'Dee', 'Yundt', '1-143-021-2425x5545', 722319, 'brooks.emard@example.com', 1); 132 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('30fa24a8-e480-3fe6-a3ef-4fc89333b271', '8eca0c51-75f0-3c66-9ce3-ce42612ed2de', 'Jerod', 'Murazik', '08210439983', 268915, 'lgreen@example.net', 1); 133 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('31b03ee2-6277-3c84-b232-2090d0ee7c7b', '668da9cd-3340-34a2-b84b-f437e91c1161', 'Dejah', 'Ratke', '844-806-3142', 409068, 'caden86@example.org', 0); 134 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('32c84c9f-a7d5-321c-b18d-30d88d8c037d', '39f473a2-6476-367b-a7fc-b071abf28725', 'Leonor', 'Kohler', '696.483.9829', 508477, 'daryl.bartell@example.net', 1); 135 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('33adedb1-2d65-3f61-9cec-de25c7f438f2', 'e67f30bf-ef94-31f3-9a38-df8a94b08165', 'Lynn', 'Bogisich', '(774)099-7892', 609107, 'giovanny42@example.com', 0); 136 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('33cb173b-451c-39cd-b2d8-bd6c6d8b01e2', '505b1df5-5599-3493-9be4-084aea063128', 'Ben', 'Kessler', '747.913.3062', 856415, 'maggio.edythe@example.com', 0); 137 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('38db0412-85e0-3bef-9084-74bde3aef4db', '2220bb77-5a3c-3742-a1b9-35cdaeb60c6f', 'Jason', 'Berge', '(796)602-1018', 234972, 'hodkiewicz.sharon@example.org', 0); 138 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('3e5bc80c-dab8-3d43-a69f-bf0f874e0203', 'd3153931-5be2-3454-bc24-69fb7ead0c67', 'Hermina', 'Ziemann', '1-760-381-7576x908', 884848, 'tyree87@example.com', 0); 139 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('3eb27377-0545-3319-b64d-d761178ec6fe', 'c7874b73-c506-3405-8b8b-32da6ce19340', 'Verna', 'Kautzer', '02151755487', 940013, 'johann17@example.net', 1); 140 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('40ca65b3-c6b6-38ec-aaa7-571d8e59e508', 'cb99f406-2fa0-3496-8f52-bf93f53754c3', 'Silas', 'Corwin', '843.846.9827x7893', 297970, 'everardo19@example.org', 0); 141 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('42a0cc25-16ea-359c-a46f-399f443a4b10', '2528a3fd-f504-3170-b0f3-cadc989e42c4', 'Viviane', 'Conroy', '655-367-7928', 599168, 'macie.bogan@example.org', 1); 142 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('42d7ca55-4bf3-33cc-98bc-9d704c1d2a99', '4abc6694-a037-3506-8e30-356b32110d07', 'Dallas', 'Smitham', '191-512-0407x012', 189457, 'o''conner.candida@example.net', 0); 143 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('433747c0-e6fc-3ef7-a787-2aab5ee31366', '567f8560-3f44-3f8d-8d52-d9c6ffd5d2ac', 'Ernestine', 'Sanford', '997-611-6934x45603', 255154, 'qpredovic@example.net', 1); 144 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('435fb097-b0d6-3138-a346-dd935d176409', 'cde4f888-c94c-370d-8017-abbf1130db1b', 'Ceasar', 'Hartmann', '(727)023-3369', 763191, 'gdaugherty@example.org', 0); 145 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('47e1631a-daa4-3678-9637-f842a2aa0a0f', 'fdb06532-0d03-31ce-9fb5-84675715f204', 'Lavina', 'Johns', '+65(9)7677954239', 598760, 'jeromy31@example.org', 1); 146 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('54e2a73d-c654-32b2-89a1-fa2e2749384c', 'da433e81-7718-39d5-a69f-7da0ebe11360', 'Curtis', 'Stark', '975.123.8131x659', 570901, 'piper60@example.net', 1); 147 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('562564d8-e6b5-3305-a701-372c87d8603c', '3be9a1dd-21a0-36f0-844b-ef331fd4fbfa', 'Paolo', 'Breitenberg', '(504)038-1574x2830', 495831, 'cconnelly@example.com', 0); 148 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('581cbc32-a87e-3751-809b-ec3ad2549195', '35370776-6438-3770-9804-efaa0e01d6a2', 'Abbey', 'Abshire', '1-057-520-1834x442', 697097, 'kristopher.maggio@example.net', 1); 149 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('5984d16b-049c-38df-a1f8-de29d0457fff', '18a04c4a-b3ab-3ae8-bfb2-1126d0d17d04', 'Della', 'Erdman', '08323032246', 907930, 'mgorczany@example.com', 0); 150 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('5e273f4a-d578-361a-9759-6632ba7c6ca3', '4ba36302-da43-3d9d-857f-5e6ab074396b', 'Ova', 'Padberg', '777.704.9290', 189023, 'caden39@example.com', 0); 151 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('619f4739-359c-3c9f-b7da-a95b9ed7d118', '8389ac6b-172e-3121-b059-383cb02b1306', 'Zoey', 'Gerhold', '686.049.7225', 347358, 'ottis80@example.org', 1); 152 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('62aad3c7-1c13-3a9f-a8f0-e33ca32abb4f', '71bbf8a0-7a66-30f4-b9b5-15da9c7ffa63', 'Berneice', 'Quitzon', '771.465.9264x2269', 889268, 'boehm.abagail@example.org', 0); 153 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('62d26e0d-1805-36e2-bfea-ba927d1b2476', '35d4aa95-3153-3d1b-b144-0f7b2eda0192', 'Antonia', 'Gibson', '495.209.2779', 553891, 'harmony04@example.com', 0); 154 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('67212653-8f59-3d14-a5bd-343346c4100c', 'cd2b73e3-2bd7-330d-a925-f5bfaf3d3335', 'Bennett', 'Shanahan', '(399)331-9726x0233', 578726, 'hschulist@example.org', 1); 155 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('67d2d442-05d1-388b-a5a0-acad63f81e19', '8d3482dd-73ad-3135-9ebc-3195960bc722', 'Cedrick', 'Zieme', '1-860-023-5963x11024', 552346, 'o''keefe.nina@example.com', 1); 156 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('6e17f3fd-e1fe-3633-862e-128f3711f9be', 'fb2042b3-39b5-39b2-9a80-20c957ee9d82', 'Rosina', 'Hamill', '1-657-157-2242x3105', 743978, 'antoinette.senger@example.com', 0); 157 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('6e9bcd19-0830-3f15-8353-540e4b30bc00', '6f186e6e-44a9-3a11-93df-67e0758bdb31', 'Willis', 'Mayer', '472-726-5512', 232577, 'kali03@example.com', 0); 158 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('7c41de36-5776-34c0-82d9-08d58fa6182e', 'fd7d6b83-985b-3e9f-91d3-01293ee09291', 'Adalberto', 'Hahn', '458-757-2552', 755040, 'leopoldo.blick@example.com', 0); 159 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('7e428895-4f5f-3937-aa17-5cb230c780e6', '60d0aeab-149c-397f-a0af-dea959f90c29', 'Carlotta', 'Lowe', '09660278574', 397734, 'angelita61@example.com', 1); 160 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('830fdf92-5544-3d8c-a21d-6d25bc98e9a8', 'b7eb0da2-a44c-3570-b16f-282280f16105', 'Lyla', 'Paucek', '(047)672-8080', 742411, 'price68@example.org', 1); 161 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('85271769-6ed5-337b-9c01-c5e916fd1f52', 'a5c431bd-6976-3676-b191-0f624d85d0ad', 'Caleb', 'Carter', '946.986.9035x450', 456805, 'corkery.bethel@example.com', 0); 162 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('866506ac-1fba-39b3-a2d4-b9d92230a406', '1a4356ed-dcda-3e24-94a7-5193491a4003', 'Lina', 'Ziemann', '1-822-752-4276', 449829, 'dovie.barrows@example.com', 1); 163 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('8728b36e-80a6-3238-8997-acf11363112a', 'bc034317-3a4e-3640-afef-4780a32f4d80', 'Niko', 'Terry', '03590489742', 850451, 'lucius.bosco@example.org', 0); 164 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('8ab20161-9795-3a37-9ba5-26551e676fa8', '4e497b94-6d61-34e8-8329-65d577fb2da2', 'Tyrique', 'Stiedemann', '(737)743-7960', 168213, 'aleen51@example.com', 1); 165 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('8bb2c5fd-4ab7-345d-80f6-baac583d5223', 'de2fea93-2200-3a40-9591-30637df46cdc', 'Destany', 'Durgan', '847-385-2520x982', 871074, 'zena35@example.org', 1); 166 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('8be0a065-32a1-341a-a4ff-b436818f0bd3', '19538eec-9ec3-355b-9823-91be968868d4', 'Kathryn', 'Nicolas', '1-629-323-8778x1421', 512939, 'padberg.myrna@example.net', 1); 167 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('8c6ae4c5-6cbd-3fc1-b869-5f81f43ab10d', '56315c81-6b74-3d42-9aa1-d2eb66cd853f', 'Susan', 'Cummings', '05394216554', 515100, 'zlockman@example.com', 0); 168 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('9098c312-8891-37d3-a33e-f2b76ca244e9', 'fce5b260-7d0a-3de4-97a1-ba456578377c', 'Lorenza', 'Connelly', '+20(6)5189639911', 698821, 'maia43@example.net', 0); 169 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('92e70490-82a2-37d7-a253-de31bdcd9614', 'c3628917-6623-30ee-95f4-c4d92ad599fe', 'Cornelius', 'Upton', '616.722.0177', 204124, 'ebony.buckridge@example.org', 1); 170 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('9a8b9b46-72ac-3f71-8368-ad88e853ac35', 'd8a08d31-55af-305b-8df0-51eb19636fe1', 'Michaela', 'Yundt', '+91(2)8973826965', 840617, 'schumm.genoveva@example.org', 1); 171 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('9ad83778-778b-377c-ba54-9a3297826ca4', '21e1b956-b852-366b-8a7c-ffb60ce9fa7c', 'Thora', 'Homenick', '(734)379-8148x24059', 791409, 'jamir83@example.com', 0); 172 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('9b34ed22-f817-3f38-8057-4b17e6bcfc37', '14151e0d-a025-383f-81bd-b01ce8dd50ab', 'Eldred', 'Hartmann', '1-396-481-3563', 829078, 'hhyatt@example.org', 1); 173 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('9d35353e-938e-3a26-b7a5-3db132885494', '24364b77-9204-3966-831e-b8a21e5d6fb6', 'Sigurd', 'Morissette', '+65(7)9028532776', 971819, 'kasandra.kreiger@example.org', 0); 174 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('9e6e742b-99b5-3b8e-b024-eab4f5a87ceb', '55bbfa4c-9a3f-3cc6-9173-5489f90fcc5a', 'Ryan', 'Armstrong', '+74(0)3295527261', 974603, 'kuvalis.darrick@example.org', 0); 175 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('a1137642-b9b5-3d34-8c64-f8d4ed7aab04', 'a47b2557-21cc-344d-9582-2b7caa1451c3', 'Destiny', 'Veum', '(164)454-8342x34231', 427894, 'cristian.dooley@example.org', 1); 176 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('a3472c95-a088-3ad9-9599-376608f12603', '189433cb-f489-309d-a065-8c7e83d990b5', 'Andrew', 'Mohr', '1-131-178-4201', 950908, 'narmstrong@example.net', 0); 177 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('a55f06c6-ba8b-3b89-adec-299bf2f663e9', '77e0518f-d95b-37f0-95dd-5e2bbbc5cde2', 'Kamryn', 'Hauck', '+38(6)9916857214', 351773, 'alycia89@example.net', 0); 178 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('a80d098d-4637-33b6-b323-be2125edcf83', '46933c4c-1a24-39a0-a5a1-28f279b9b60d', 'Kelsie', 'Hayes', '1-752-038-9849x6987', 909740, 'schmitt.darien@example.com', 0); 179 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('aa6d838b-89ab-39d1-9b9e-aed1aceda3a9', '67c5072d-e7cf-3b6f-81ba-f79c018b7b6c', 'Doyle', 'Greenholt', '604.569.9887x49637', 575738, 'zfarrell@example.org', 1); 180 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('ac627557-4472-3b6d-bb10-b3358291ad0f', '59acb2c2-fb15-382d-bbaa-6b87b19204d9', 'Skye', 'Harber', '1-641-694-0819x88224', 420548, 'louie.keeling@example.com', 0); 181 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('ae34bc1a-ebd6-37eb-a576-a78d5d561cda', 'e02e86d9-435f-3b64-b8f3-d6f6ed55bc1c', 'Layne', 'Sawayn', '429-537-3161x23667', 774758, 'cassin.thea@example.com', 0); 182 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('af94f8f1-4eb3-33c9-9c73-acca3fc90bb9', '52858ea5-2d89-3601-88a9-92e8efc000d9', 'Aiden', 'Mann', '1-628-832-4672x11244', 947315, 'hoppe.urban@example.org', 1); 183 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('b4110ea4-1db8-3d6b-bd32-55606398df53', '2d4e6ed2-4b0c-3bb2-b1d9-d1bc899665be', 'Mallie', 'Donnelly', '854.754.4496', 464644, 'mcclure.larue@example.net', 0); 184 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('ba072750-e824-34e1-8ae8-b8556066b252', '9e0977a6-f966-386e-acaf-e40c57ea5ba6', 'Dortha', 'Zboncak', '311-037-9766', 469667, 'zulauf.rosendo@example.com', 0); 185 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('ba8353ff-7754-388e-b105-7eae522af2f1', 'd60978a0-3ce2-31f8-8e55-620a57271dc5', 'Adriel', 'Swaniawski', '473.728.4976x5414', 451899, 'elva.herman@example.org', 0); 186 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('bce1e023-3391-37f2-b0b1-8588ed2c073d', '7f054c60-d313-3af7-82c1-00a7705939cf', 'Forrest', 'Armstrong', '(864)303-8050', 318346, 'rnikolaus@example.com', 1); 187 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('c3bc64e8-79ac-368a-9536-d273080fa776', '4a1b1132-378b-3f06-9c35-cf0550c2f2b0', 'Edwin', 'Kautzer', '1-263-910-6081', 548275, 'keaton41@example.org', 0); 188 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('c5fd7022-a065-30bf-976f-8cbaeaca1269', '841b145e-747a-3b34-9bbc-e4ba7de25c12', 'Marian', 'Kohler', '286-987-7845x457', 619710, 'lhayes@example.org', 0); 189 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('c7f9660e-0dec-38f6-ab79-91f5e9fb422b', '8468ec07-95bc-34e4-b2ee-1adf3dd0c72b', 'Malinda', 'Prosacco', '01386374216', 494569, 'rcollier@example.net', 0); 190 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('ca6741ad-0441-3476-bcc6-f27e70e602c7', 'a677051d-9655-3d74-b7c8-40c43ddb71b5', 'Noelia', 'Schoen', '1-938-477-9383', 189818, 'virginia.towne@example.com', 0); 191 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('ca7797d7-8715-36cf-b112-34090f3b877b', '94a48af1-e87c-33c5-bf39-168a9e782c66', 'Lela', 'Frami', '521-647-3107x3044', 708699, 'dsipes@example.org', 1); 192 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('cb4b3181-b868-3e9a-a4e2-44f0dea2afe3', 'fcb271b4-f8b5-3792-b90b-bc26ed9c2198', 'Meda', 'Spencer', '780.318.1592', 849607, 'dwilderman@example.org', 1); 193 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('cbdaf2c4-21ce-3285-b6bf-296e1b8ff992', 'b82fef20-56c9-347a-9dae-448e1056b468', 'Javon', 'Simonis', '1-953-438-7093', 967221, 'zoey82@example.com', 0); 194 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('cbf70c14-b481-3eff-8fa9-5c450f4e884d', 'e028aed8-e94c-374f-b466-d4e2f7bf1e0f', 'Sister', 'O''Reilly', '200-962-3004x46526', 268158, 'michelle61@example.org', 1); 195 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('ce2463f3-d38d-3968-8a01-0251346baa20', 'dfc1869b-a466-3223-b26c-888988f297ed', 'Rene', 'Harris', '812-632-9051x455', 302748, 'awilliamson@example.org', 0); 196 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('ce9f02a6-ad4b-3f35-9f17-389e921c2694', '25a54615-fce7-3076-9efe-fa8b6f8c6f13', 'Luis', 'Huels', '+01(2)1854527603', 313813, 'orosenbaum@example.org', 1); 197 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('d0784c9d-1deb-323b-9cfe-8491dac1b1d9', '43f61360-e3fd-31d2-95fe-a270c4a27c89', 'Ludwig', 'Bogisich', '425-522-0169x8205', 747781, 'bernardo87@example.net', 0); 198 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('d0be2cc4-12c7-36c9-9973-27c4b0454ca5', '5418d075-8bd1-366d-a711-3c4197569ad6', 'Esther', 'Block', '(437)065-8139x803', 235097, 'erdman.dell@example.com', 1); 199 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('d1731bb8-399d-340c-aa65-6c4f5299f101', '9f8f8b88-ce36-3bde-8a85-dc4799034b00', 'Willow', 'Funk', '1-258-784-3885x2349', 776313, 'lavern.goodwin@example.net', 1); 200 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('d1ef1611-4d0a-3e7f-ab5e-defbb5c46c30', 'bc1f4c7c-4508-394e-ad96-eedd3631fcab', 'Breana', 'Davis', '891-669-5410x312', 753448, 'dejon20@example.org', 0); 201 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('d1fa69b0-2092-3b6f-90d5-870e902c8315', 'baaa005d-ff39-3633-9351-bbffa2394951', 'Abdul', 'Schumm', '1-556-168-3702x906', 325387, 'stoltenberg.isabel@example.net', 0); 202 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('d407406d-8ffc-3fba-a8a2-5d9925cfb3c3', 'cbb1d2af-5ec0-3116-baaf-fcd187632da1', 'Garland', 'Reinger', '1-022-403-3624x05377', 810706, 'xstehr@example.com', 0); 203 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('d9b2bb24-28ea-3eb2-9050-e0af9df4aa88', '934d8a7b-edb8-32b7-b93e-c8d6490f2eb1', 'Lee', 'Breitenberg', '862.992.9627x628', 764954, 'vpadberg@example.net', 0); 204 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('da0fd69f-6c19-3466-8e24-ba610c573802', '58661423-0142-3902-a445-1262a2c70594', 'Reba', 'Kling', '1-829-004-4619x76485', 202616, 'kay.crist@example.net', 0); 205 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('de000355-5a70-3396-9e5f-aa13a95efca8', 'fea12f6b-3e43-3bb7-b3cf-594c1d790953', 'Cary', 'Cassin', '(002)012-1364', 619925, 'fahey.dock@example.org', 0); 206 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('de0c9567-f020-3df6-a2d8-dbb11eb5486c', '9b0ebf4a-4a63-3976-97ed-1d880d5954f2', 'Marian', 'Schaden', '05829641289', 662119, 'pedro91@example.com', 1); 207 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('df0638ca-5d35-39f6-b585-1372f28299d3', '7aafe6a7-f953-3211-931e-1c10adf12885', 'Kenya', 'Roob', '1-618-930-1067x01725', 458373, 'auer.jada@example.com', 1); 208 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('e0ea0bdb-6c01-38f9-8713-581144bfc2dd', '18163a72-f921-36a5-8369-21b0617dbfdc', 'Deangelo', 'Rempel', '546.458.8357x089', 428542, 'miles71@example.net', 0); 209 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('e28a2529-d570-33fe-8ef8-cb775671d918', '0e0079ce-48c2-3d6c-9cea-27b7b998a2f6', 'Vaughn', 'Klocko', '1-392-419-3102x67282', 561494, 'mbahringer@example.net', 0); 210 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('e527b1dc-d66b-3c76-abd7-a405e68bccb6', '5b822bc4-c021-370c-bafc-3356af3d61a2', 'Kiana', 'Dach', '1-164-843-5904x8423', 231667, 'berge.luisa@example.net', 0); 211 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('e6e5cf94-fe38-3573-916a-b47b6f67b11c', '533b7c43-9b0c-334a-afdd-ef3609c8f016', 'Rhett', 'Kihn', '091-981-1661x7358', 237748, 'broderick81@example.com', 0); 212 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('e96cd6ed-8007-3cc3-9b5d-e4059f24c50e', 'c238aa59-52b8-35cc-8a03-ea39c0515baf', 'Caroline', 'Erdman', '305-470-9394', 255342, 'harrison06@example.com', 1); 213 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('e9802cc3-e12c-3f59-b293-13230d90708a', '742b0e4b-1afc-342d-8f3b-bc2f19860383', 'Shannon', 'Gorczany', '640-820-3138x263', 334589, 'caesar.adams@example.net', 0); 214 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('ec543db3-f9e3-31a6-bab5-078319a66626', '237f1a94-a897-3906-9ddf-35e104c2e5cf', 'Henry', 'Bernhard', '1-016-277-0076x964', 725275, 'xcarter@example.net', 0); 215 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('ecafb671-efe8-30d5-ba97-d4efda8191f5', '4f589d4d-6003-3348-bc23-49c38222bcd0', 'Retta', 'Kunze', '042.538.9456', 833682, 'brekke.tessie@example.net', 0); 216 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('eef178e1-0250-38f0-b974-8012ad1fddd1', '01af2c30-af69-3118-9ee1-9eae0a4df259', 'Ricardo', 'Hodkiewicz', '03712902985', 558124, 'sjohns@example.net', 0); 217 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('f775a51b-9ee0-329c-9ede-174c069ebd99', '6120cba3-4cbf-31ca-9449-3372036805e6', 'Wilford', 'Bechtelar', '477.870.0437x327', 551947, 'walker.bria@example.net', 0); 218 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('f82475ef-1620-3fd8-8086-c6039c2ec2ba', '693ff438-7b9c-3579-b603-b77171b3b999', 'Leann', 'Bailey', '879-172-3757x2306', 293167, 'lysanne.kemmer@example.org', 0); 219 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('ff09f073-6e97-35ed-884f-38da2408f01b', '73027a8a-003b-3867-9c26-790cd0e072b6', 'Salvatore', 'Cassin', '066-485-4103x69956', 656376, 'nschuster@example.com', 1); 220 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('ff1c0e40-91ed-3e06-8fa6-ce0da1acf3d2', '05c04d6b-ddf3-371b-b71d-c84827530f35', 'Camron', 'Lowe', '(760)216-0037x02659', 530774, 'celestino.walker@example.net', 1); 221 | INSERT INTO `person` (`id`, `tripId`, `firstname`, `lastname`, `phone`, `IDNumber`, `email`, `willTravel`) VALUES ('ff857324-4c2f-3333-95e5-b2144df82791', '9f7be9f7-ed73-378f-8204-2747fb6d8f8b', 'Lloyd', 'Huel', '1-442-241-7679', 492330, 'hagenes.mable@example.net', 0); 222 | CREATE TABLE `trip` ( 223 | `id` varchar(255) NOT NULL 224 | , `departureDate` datetime NOT NULL 225 | , `estimatedArrivalDate` datetime DEFAULT NULL 226 | , `agencyId` integer NOT NULL 227 | , PRIMARY KEY (`id`) 228 | , CONSTRAINT `FK_a747b50b9a8fd33dfa70aefc775` FOREIGN KEY (`agencyId`) REFERENCES `agency` (`id`) 229 | ); 230 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('01af2c30-af69-3118-9ee1-9eae0a4df259', '2012-09-05 09:47:07', '1987-05-05 11:05:35', 72); 231 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('05c04d6b-ddf3-371b-b71d-c84827530f35', '2001-10-13 13:29:40', '2009-07-29 06:50:13', 78); 232 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('0e0079ce-48c2-3d6c-9cea-27b7b998a2f6', '2005-03-31 21:29:34', '2004-07-12 13:11:22', 38); 233 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('14151e0d-a025-383f-81bd-b01ce8dd50ab', '1970-09-14 15:32:01', '1996-01-16 04:03:52', 20); 234 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('18163a72-f921-36a5-8369-21b0617dbfdc', '1983-09-22 17:32:39', '1972-12-27 11:53:38', 56); 235 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('189433cb-f489-309d-a065-8c7e83d990b5', '1976-04-30 16:49:44', '1986-01-02 09:28:37', 76); 236 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('18a04c4a-b3ab-3ae8-bfb2-1126d0d17d04', '1994-08-14 19:12:39', '1980-09-15 14:22:50', 62); 237 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('19538eec-9ec3-355b-9823-91be968868d4', '1983-10-27 20:05:13', '1972-03-30 14:41:00', 25); 238 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('1a4356ed-dcda-3e24-94a7-5193491a4003', '1975-11-07 05:51:57', '1983-06-12 21:45:49', 69); 239 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('21e1b956-b852-366b-8a7c-ffb60ce9fa7c', '1986-08-22 10:36:15', '1994-02-14 09:46:48', 2); 240 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('2220bb77-5a3c-3742-a1b9-35cdaeb60c6f', '1995-03-14 10:20:21', '1983-12-08 09:21:54', 74); 241 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('237f1a94-a897-3906-9ddf-35e104c2e5cf', '2018-05-31 23:29:39', '1976-12-01 00:13:47', 45); 242 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('24364b77-9204-3966-831e-b8a21e5d6fb6', '2005-12-26 19:06:02', '1970-07-21 11:20:13', 10); 243 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('2528a3fd-f504-3170-b0f3-cadc989e42c4', '2019-06-16 14:01:03', '1978-07-30 23:55:13', 93); 244 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('25a54615-fce7-3076-9efe-fa8b6f8c6f13', '1977-08-04 22:40:20', '2002-07-07 12:17:24', 71); 245 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('2d4e6ed2-4b0c-3bb2-b1d9-d1bc899665be', '1987-09-13 20:36:22', '1988-05-02 17:24:32', 39); 246 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('2e396218-ffc3-36c7-989a-602ba0699f4b', '2001-07-08 11:59:11', '1989-11-25 12:00:15', 12); 247 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('30057c8c-7772-3ae0-b423-661e7b10d26b', '1999-08-10 01:24:45', '1987-02-06 23:15:18', 21); 248 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('34c1b5ef-ff97-3242-b341-59696c38e15b', '1996-01-18 16:28:59', '1976-09-19 00:55:28', 83); 249 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('35370776-6438-3770-9804-efaa0e01d6a2', '2004-07-06 23:42:48', '2016-07-23 16:40:48', 31); 250 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('35d4aa95-3153-3d1b-b144-0f7b2eda0192', '1979-05-08 09:07:49', '2019-07-25 02:27:45', 53); 251 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('39f473a2-6476-367b-a7fc-b071abf28725', '1970-05-23 01:05:20', '2016-07-03 23:30:00', 90); 252 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('3be9a1dd-21a0-36f0-844b-ef331fd4fbfa', '1985-04-01 04:10:10', '2009-08-04 01:23:07', 14); 253 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('43f61360-e3fd-31d2-95fe-a270c4a27c89', '1992-09-23 09:02:25', '1985-03-27 00:10:24', 73); 254 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('46933c4c-1a24-39a0-a5a1-28f279b9b60d', '1998-04-29 06:05:43', '2002-09-27 15:20:27', 81); 255 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('4a1b1132-378b-3f06-9c35-cf0550c2f2b0', '1990-09-05 12:09:04', '1986-02-13 15:17:45', 43); 256 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('4abc6694-a037-3506-8e30-356b32110d07', '1980-10-27 09:07:52', '1984-08-04 08:45:16', 96); 257 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('4ba36302-da43-3d9d-857f-5e6ab074396b', '2019-08-06 19:16:38', '2001-02-04 10:54:36', 11); 258 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('4e497b94-6d61-34e8-8329-65d577fb2da2', '1990-11-22 21:46:42', '1973-01-26 16:19:09', 67); 259 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('4f589d4d-6003-3348-bc23-49c38222bcd0', '1977-06-02 20:00:31', '1988-10-06 02:28:34', 34); 260 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('4f8062b2-9dfb-300b-9a2e-18c645ceb33d', '1991-09-26 15:14:40', '1988-03-28 02:58:29', 89); 261 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('505b1df5-5599-3493-9be4-084aea063128', '1998-08-12 17:44:58', '2006-01-21 07:47:23', 97); 262 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('52858ea5-2d89-3601-88a9-92e8efc000d9', '1976-08-11 20:33:07', '2018-02-02 17:02:48', 41); 263 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('533b7c43-9b0c-334a-afdd-ef3609c8f016', '1990-10-08 10:27:49', '1983-06-16 11:41:28', 86); 264 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('5418d075-8bd1-366d-a711-3c4197569ad6', '2001-10-20 05:46:09', '2017-03-14 21:25:20', 29); 265 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('55bbfa4c-9a3f-3cc6-9173-5489f90fcc5a', '2000-09-10 20:45:16', '1988-02-18 12:04:04', 18); 266 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('56315c81-6b74-3d42-9aa1-d2eb66cd853f', '1988-08-10 07:13:22', '1995-07-27 05:51:57', 28); 267 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('567f8560-3f44-3f8d-8d52-d9c6ffd5d2ac', '2002-01-10 04:23:08', '2010-01-09 00:13:47', 68); 268 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('58661423-0142-3902-a445-1262a2c70594', '2018-04-04 03:19:49', '1985-06-03 21:16:35', 77); 269 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('59acb2c2-fb15-382d-bbaa-6b87b19204d9', '1987-08-11 04:59:16', '2004-05-09 11:58:57', 44); 270 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('5b822bc4-c021-370c-bafc-3356af3d61a2', '2001-03-01 18:47:32', '1988-07-19 04:42:54', 92); 271 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('60d0aeab-149c-397f-a0af-dea959f90c29', '1985-01-16 13:42:40', '1981-07-15 17:27:06', 52); 272 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('6120cba3-4cbf-31ca-9449-3372036805e6', '1972-04-30 02:14:58', '2004-10-20 18:57:52', 91); 273 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('668da9cd-3340-34a2-b84b-f437e91c1161', '1996-10-07 06:20:43', '1995-01-13 12:12:56', 32); 274 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('67c5072d-e7cf-3b6f-81ba-f79c018b7b6c', '2010-03-31 15:05:43', '1979-03-11 02:52:04', 22); 275 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('693ff438-7b9c-3579-b603-b77171b3b999', '1977-11-02 05:13:05', '1980-09-06 11:45:28', 99); 276 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('6f186e6e-44a9-3a11-93df-67e0758bdb31', '1974-06-26 06:30:02', '2015-02-23 19:49:28', 63); 277 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('71bbf8a0-7a66-30f4-b9b5-15da9c7ffa63', '2008-04-24 07:07:02', '1973-10-28 20:20:09', 23); 278 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('73027a8a-003b-3867-9c26-790cd0e072b6', '1983-07-18 01:13:32', '2014-09-23 22:43:05', 13); 279 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('742b0e4b-1afc-342d-8f3b-bc2f19860383', '1980-01-28 05:01:22', '2008-09-06 17:49:35', 16); 280 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('77e0518f-d95b-37f0-95dd-5e2bbbc5cde2', '2007-08-05 19:49:00', '2016-08-24 02:10:19', 6); 281 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('7aafe6a7-f953-3211-931e-1c10adf12885', '1973-06-26 00:29:49', '2013-04-14 21:48:31', 88); 282 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('7f054c60-d313-3af7-82c1-00a7705939cf', '1986-03-14 14:14:42', '2002-08-13 08:09:49', 51); 283 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('8389ac6b-172e-3121-b059-383cb02b1306', '1986-02-21 07:10:27', '2017-04-08 18:56:39', 42); 284 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('841b145e-747a-3b34-9bbc-e4ba7de25c12', '1998-12-04 09:37:19', '1985-09-10 02:00:02', 65); 285 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('8468ec07-95bc-34e4-b2ee-1adf3dd0c72b', '2016-08-13 02:12:36', '2007-08-27 20:37:55', 57); 286 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('8d3482dd-73ad-3135-9ebc-3195960bc722', '2018-09-19 17:36:00', '1991-02-08 13:18:30', 27); 287 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('8eca0c51-75f0-3c66-9ce3-ce42612ed2de', '1972-06-18 05:27:06', '1993-06-13 12:34:10', 7); 288 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('934d8a7b-edb8-32b7-b93e-c8d6490f2eb1', '1994-10-16 19:57:37', '1984-03-30 03:06:28', 82); 289 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('94a48af1-e87c-33c5-bf39-168a9e782c66', '1976-03-09 07:26:00', '1998-06-12 07:15:51', 35); 290 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('9b0ebf4a-4a63-3976-97ed-1d880d5954f2', '2011-10-30 01:44:26', '2002-06-28 06:12:54', 1); 291 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('9e0977a6-f966-386e-acaf-e40c57ea5ba6', '2008-03-09 05:46:02', '1991-12-28 05:33:40', 84); 292 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('9f7be9f7-ed73-378f-8204-2747fb6d8f8b', '1981-10-05 12:59:23', '1994-10-19 15:58:05', 8); 293 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('9f8f8b88-ce36-3bde-8a85-dc4799034b00', '1988-04-17 22:01:22', '1989-08-19 13:18:34', 36); 294 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('a47b2557-21cc-344d-9582-2b7caa1451c3', '1991-12-08 13:54:12', '2003-12-28 10:16:16', 17); 295 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('a5c431bd-6976-3676-b191-0f624d85d0ad', '1982-07-10 07:36:27', '1975-11-15 07:47:16', 59); 296 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('a677051d-9655-3d74-b7c8-40c43ddb71b5', '2020-01-01 03:05:13', '1997-12-02 16:27:34', 55); 297 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('b7eb0da2-a44c-3570-b16f-282280f16105', '1972-10-31 03:07:45', '2019-07-24 05:25:41', 9); 298 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('b82fef20-56c9-347a-9dae-448e1056b468', '1995-12-13 23:48:40', '1989-06-25 13:55:02', 15); 299 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('baaa005d-ff39-3633-9351-bbffa2394951', '1974-02-02 12:21:00', '2014-10-24 05:48:05', 37); 300 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('bc034317-3a4e-3640-afef-4780a32f4d80', '1985-07-07 21:50:52', '2009-08-29 07:19:47', 46); 301 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('bc1f4c7c-4508-394e-ad96-eedd3631fcab', '2010-09-24 04:49:42', '2019-04-18 02:49:41', 47); 302 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('c0b5674e-14eb-35b0-8e35-d9a60eeef140', '2007-06-08 20:08:39', '1977-09-01 21:03:45', 33); 303 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('c238aa59-52b8-35cc-8a03-ea39c0515baf', '2005-07-31 22:50:11', '1979-09-29 22:57:05', 54); 304 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('c3628917-6623-30ee-95f4-c4d92ad599fe', '2001-08-12 09:11:44', '2016-08-22 02:02:24', 94); 305 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('c7874b73-c506-3405-8b8b-32da6ce19340', '1999-09-04 14:44:34', '2007-09-30 07:18:20', 49); 306 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('c8b88eee-e33d-3501-9aa9-6f2460e386fa', '1984-04-08 15:39:28', '1970-12-04 04:14:47', 87); 307 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('cb99f406-2fa0-3496-8f52-bf93f53754c3', '1985-07-31 17:18:13', '1993-06-02 23:38:27', 24); 308 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('cbb1d2af-5ec0-3116-baaf-fcd187632da1', '1999-10-27 01:54:05', '1984-06-10 15:10:41', 40); 309 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('cd2b73e3-2bd7-330d-a925-f5bfaf3d3335', '2011-09-01 13:07:42', '1987-12-10 15:16:31', 61); 310 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('cde4f888-c94c-370d-8017-abbf1130db1b', '2013-05-14 19:07:08', '1987-04-17 16:20:51', 3); 311 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('d2d016c1-7fc9-3120-aeb0-82c6d96b9917', '1976-10-31 00:41:31', '1970-03-19 20:42:31', 75); 312 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('d3153931-5be2-3454-bc24-69fb7ead0c67', '2006-05-09 14:27:56', '2020-04-04 08:20:27', 19); 313 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('d60978a0-3ce2-31f8-8e55-620a57271dc5', '2009-04-15 07:48:26', '2006-05-29 07:14:45', 80); 314 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('d8a08d31-55af-305b-8df0-51eb19636fe1', '1986-06-05 04:52:48', '1992-01-14 09:16:04', 85); 315 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('da433e81-7718-39d5-a69f-7da0ebe11360', '2000-05-02 17:57:31', '1981-11-28 20:31:18', 48); 316 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('de2fea93-2200-3a40-9591-30637df46cdc', '1994-10-24 07:51:41', '2018-11-25 06:21:47', 98); 317 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('dfc1869b-a466-3223-b26c-888988f297ed', '2008-05-02 21:37:18', '2012-08-07 22:58:31', 4); 318 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('e028aed8-e94c-374f-b466-d4e2f7bf1e0f', '2011-04-28 23:12:37', '1982-02-21 19:04:39', 30); 319 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('e02e86d9-435f-3b64-b8f3-d6f6ed55bc1c', '1984-03-05 04:23:42', '1992-10-26 06:14:47', 58); 320 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('e67f30bf-ef94-31f3-9a38-df8a94b08165', '1971-09-12 22:46:46', '2011-06-12 13:58:01', 60); 321 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('e8b923c3-6eef-3fbb-a8cf-80043752a7a0', '2011-03-14 11:06:35', '2005-12-26 20:59:24', 50); 322 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('f587d552-e609-3911-ab65-1a83afb9bed4', '2018-05-10 02:07:19', '1990-11-06 22:38:39', 95); 323 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('fab2bdb7-de25-3ef2-9711-6c995ba230e8', '2017-10-16 17:05:20', '1981-01-04 07:41:19', 70); 324 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('fb2042b3-39b5-39b2-9a80-20c957ee9d82', '1988-06-28 17:19:14', '1985-04-09 21:58:41', 66); 325 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('fcb271b4-f8b5-3792-b90b-bc26ed9c2198', '1993-02-09 03:00:32', '2005-07-07 19:33:31', 64); 326 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('fce5b260-7d0a-3de4-97a1-ba456578377c', '2010-03-26 18:01:54', '1989-07-01 21:58:56', 79); 327 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('fd7d6b83-985b-3e9f-91d3-01293ee09291', '1994-09-18 16:16:17', '2001-05-15 07:36:49', 26); 328 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('fdb06532-0d03-31ce-9fb5-84675715f204', '2008-12-30 16:42:00', '1993-12-26 08:23:58', 5); 329 | INSERT INTO `trip` (`id`, `departureDate`, `estimatedArrivalDate`, `agencyId`) VALUES ('fea12f6b-3e43-3bb7-b3cf-594c1d790953', '1984-03-27 09:34:36', '1993-09-12 23:40:45', 100); 330 | CREATE INDEX "idx_person_FK_9b53a69c297420a377dae12e437" ON "person" (`tripId`); 331 | CREATE INDEX "idx_trip_FK_a747b50b9a8fd33dfa70aefc775" ON "trip" (`agencyId`); 332 | END TRANSACTION; 333 | --------------------------------------------------------------------------------