├── .gitignore ├── .env ├── .prettierrc ├── tsconfig.json ├── src ├── mikro │ ├── entities │ │ ├── details.ts │ │ ├── suppliers.ts │ │ ├── products.ts │ │ ├── customers.ts │ │ ├── employees.ts │ │ └── orders.ts │ └── benchmark.ts ├── typeorm │ ├── entities │ │ ├── details.ts │ │ ├── suppliers.ts │ │ ├── customers.ts │ │ ├── products.ts │ │ ├── employees.ts │ │ └── orders.ts │ └── benchmark.ts ├── kysely │ ├── db.ts │ └── benchmark.ts ├── common │ ├── meta.ts │ └── benchmark.ts ├── utils.ts ├── prisma │ ├── benchmark.ts │ └── schema.prisma ├── drizzle │ ├── schema.ts │ ├── benchmark-pgsl.ts │ ├── benchmark-pg.ts │ ├── benchmark-pgnative.ts │ ├── benchmark-pgp.ts │ └── benchmark-pgrqbp.ts ├── knex │ └── benchmark.ts ├── pg │ └── benchmark.ts └── pg-node_vs_drizzle.ts ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | temp 2 | node_modules 3 | .DS_STORE -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | DB_PRISMA_URL= "postgres://postgres:postgres@localhost:55008/postgres" -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "singleQuote": false 5 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ESNext", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "strictPropertyInitialization": false, 12 | "allowSyntheticDefaultImports": true, 13 | "moduleResolution": "node", 14 | "paths": { 15 | "@/*": ["./src/*"], 16 | }, 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/mikro/entities/details.ts: -------------------------------------------------------------------------------- 1 | import { Entity, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'; 2 | import { Order } from './orders'; 3 | import { Product } from './products'; 4 | 5 | @Entity({ tableName: 'order_details' }) 6 | export class Detail { 7 | @Property({ fieldName: 'unit_price', columnType: 'decimal', precision: 10, scale: 2, default: 0 }) 8 | unitPrice: number; 9 | 10 | @Property({ fieldName: 'quantity' }) 11 | quantity: number; 12 | 13 | @Property({ fieldName: 'discount', columnType: 'decimal', precision: 10, scale: 2, default: 0 }) 14 | discount: number; 15 | 16 | @PrimaryKey({ fieldName: 'order_id' }) 17 | orderId: string; 18 | @ManyToOne(() => Order) 19 | order: Order; 20 | 21 | @PrimaryKey({ fieldName: 'product_id' }) 22 | productId: string; 23 | @ManyToOne(() => Product) 24 | product: Product; 25 | } 26 | -------------------------------------------------------------------------------- /src/typeorm/entities/details.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Column, Entity, JoinColumn, ManyToOne, PrimaryColumn, 3 | } from 'typeorm'; 4 | import { Order } from './orders'; 5 | import { Product } from './products'; 6 | 7 | @Entity({ name: 'order_details' }) 8 | export class Detail { 9 | @Column({ name: 'unit_price', type: 'decimal', precision: 10, scale: 2, default: 0 }) 10 | unitPrice: number; 11 | 12 | @Column({ name: 'quantity', type: 'integer' }) 13 | quantity: number; 14 | 15 | @Column({ name: 'discount', type: 'decimal', precision: 10, scale: 2, default: 0 }) 16 | discount: number; 17 | 18 | @PrimaryColumn({ name: 'order_id', type: 'varchar' }) 19 | orderId: string; 20 | @ManyToOne(() => Order, (order) => order.details, { onDelete: 'CASCADE' }) 21 | @JoinColumn({ name: 'order_id' }) 22 | order: Order; 23 | 24 | @PrimaryColumn({ name: 'product_id', type: 'varchar' }) 25 | productId: string; 26 | @ManyToOne(() => Product, (product) => product.details, { onDelete: 'CASCADE' }) 27 | @JoinColumn({ name: 'product_id' }) 28 | product: Product; 29 | } 30 | -------------------------------------------------------------------------------- /src/mikro/entities/suppliers.ts: -------------------------------------------------------------------------------- 1 | import { Cascade, Collection, Entity, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; 2 | import { Product } from './products'; 3 | 4 | @Entity({ tableName: 'suppliers' }) 5 | export class Supplier { 6 | @PrimaryKey() 7 | id: string; 8 | 9 | @Property({ fieldName: 'company_name' }) 10 | companyName: string; 11 | 12 | @Property({ fieldName: 'contact_name' }) 13 | contactName: string; 14 | 15 | @Property({ fieldName: 'contact_title' }) 16 | contactTitle: string; 17 | 18 | @Property({ fieldName: 'address' }) 19 | address: string; 20 | 21 | @Property({ fieldName: 'city' }) 22 | city: string; 23 | 24 | @Property({ fieldName: 'region', columnType: 'varchar', nullable: true }) 25 | region?: string; 26 | 27 | @Property({ fieldName: 'postal_code' }) 28 | postalCode: string; 29 | 30 | @Property({ fieldName: 'country' }) 31 | country: string; 32 | 33 | @Property({ fieldName: 'phone' }) 34 | phone: string; 35 | 36 | @OneToMany(() => Product, (product) => product.supplier, { cascade: [Cascade.ALL] }) 37 | products = new Collection(this); 38 | } 39 | -------------------------------------------------------------------------------- /src/typeorm/entities/suppliers.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, Column, OneToMany, PrimaryColumn, 3 | } from 'typeorm'; 4 | import { Product } from './products'; 5 | 6 | @Entity({ name: 'suppliers' }) 7 | export class Supplier { 8 | @PrimaryColumn({ type: 'varchar' }) 9 | id: string; 10 | 11 | @Column({ name: 'company_name', type: 'varchar' }) 12 | companyName: string; 13 | 14 | @Column({ name: 'contact_name', type: 'varchar' }) 15 | contactName: string; 16 | 17 | @Column({ name: 'contact_title', type: 'varchar' }) 18 | contactTitle: string; 19 | 20 | @Column({ name: 'address', type: 'varchar' }) 21 | address: string; 22 | 23 | @Column({ name: 'city', type: 'varchar' }) 24 | city: string; 25 | 26 | @Column({ name: 'region', type: 'varchar', nullable: true }) 27 | region: string | null; 28 | 29 | @Column({ name: 'postal_code', type: 'varchar' }) 30 | postalCode: string; 31 | 32 | @Column({ name: 'country', type: 'varchar' }) 33 | country: string; 34 | 35 | @Column({ name: 'phone', type: 'varchar' }) 36 | phone: string; 37 | 38 | @OneToMany(() => Product, (product) => product.supplier) 39 | products: Product[]; 40 | } 41 | -------------------------------------------------------------------------------- /src/mikro/entities/products.ts: -------------------------------------------------------------------------------- 1 | import { Cascade, Collection, Entity, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; 2 | import { Supplier } from './suppliers'; 3 | import { Detail } from './details'; 4 | 5 | @Entity({ tableName: 'products' }) 6 | export class Product { 7 | @PrimaryKey() 8 | id: string; 9 | 10 | @Property({ fieldName: 'name' }) 11 | name: string; 12 | 13 | @Property({ fieldName: 'qt_per_unit' }) 14 | qtPerUnit: string; 15 | 16 | @Property({ fieldName: 'unit_price', columnType: 'decimal', precision: 10, scale: 2, default: 0 }) 17 | unitPrice: number; 18 | 19 | @Property({ fieldName: 'units_in_stock' }) 20 | unitsInStock: number; 21 | 22 | @Property({ fieldName: 'units_on_order' }) 23 | unitsOnOrder: number; 24 | 25 | @Property({ fieldName: 'reorder_level' }) 26 | reorderLevel: number; 27 | 28 | @Property({ fieldName: 'discontinued' }) 29 | discontinued: number; 30 | 31 | @Property({ fieldName: 'supplier_id' }) 32 | supplierId: string; 33 | @ManyToOne(() => Supplier) 34 | supplier: Supplier; 35 | 36 | @OneToMany(() => Detail, (detail) => detail.product, { cascade: [Cascade.ALL] }) 37 | details = new Collection(this); 38 | } 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "drizzle_pg_benchmarks", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "prepare:prisma": "npx prisma generate --schema src/prisma/schema.prisma", 10 | "start": "pnpm run prepare:prisma && tsx src/common/benchmark" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "@mikro-orm/core": "^5.7.14", 17 | "@mikro-orm/postgresql": "^5.7.14", 18 | "@mikro-orm/reflection": "^5.7.14", 19 | "@prisma/client": "^4.6.1", 20 | "dotenv": "^16.0.3", 21 | "drizzle-orm": "0.27.3-0ea6f8c", 22 | "get-port": "^6.1.2", 23 | "knex": "^2.5.1", 24 | "kysely": "^0.26.1", 25 | "mitata": "^0.1.6", 26 | "pg": "^8.11.2", 27 | "pg-native": "^3.0.1", 28 | "postgres": "^3.3.5", 29 | "prisma": "^5.1.1", 30 | "typeorm": "^0.3.17", 31 | "uuid": "^9.0.0" 32 | }, 33 | "devDependencies": { 34 | "@balena/dockerignore": "^1.0.2", 35 | "@types/dockerode": "^3.3.14", 36 | "@types/pg": "^8.10.2", 37 | "@types/uuid": "^9.0.0", 38 | "dockerode": "^3.3.4", 39 | "tsx": "^3.12.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/mikro/entities/customers.ts: -------------------------------------------------------------------------------- 1 | import { Cascade, Collection, Entity, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; 2 | import { Order } from './orders'; 3 | 4 | @Entity({ tableName: 'customers' }) 5 | export class Customer { 6 | @PrimaryKey() 7 | id: string; 8 | 9 | @Property({ fieldName: 'company_name' }) 10 | companyName: string; 11 | 12 | @Property({ fieldName: 'contact_name' }) 13 | contactName: string; 14 | 15 | @Property({ fieldName: 'contact_title' }) 16 | contactTitle: string; 17 | 18 | @Property({ fieldName: 'address' }) 19 | address: string; 20 | 21 | @Property({ fieldName: 'city' }) 22 | city: string; 23 | 24 | @Property({ fieldName: 'postal_code', columnType: 'varchar', nullable: true }) 25 | postalCode?: string; 26 | 27 | @Property({ fieldName: 'region', columnType: 'varchar', nullable: true }) 28 | region?: string; 29 | 30 | @Property({ fieldName: 'country' }) 31 | country: string; 32 | 33 | @Property({ fieldName: 'phone' }) 34 | phone: string; 35 | 36 | @Property({ fieldName: 'fax', columnType: 'varchar', nullable: true }) 37 | fax?: string; 38 | 39 | @OneToMany(() => Order, (order) => order.customer, { cascade: [Cascade.ALL] }) 40 | orders = new Collection(this); 41 | } 42 | -------------------------------------------------------------------------------- /src/typeorm/entities/customers.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, Column, OneToMany, PrimaryColumn, 3 | } from 'typeorm'; 4 | import { Order } from './orders'; 5 | 6 | @Entity({ name: 'customers' }) 7 | export class Customer { 8 | @PrimaryColumn({ type: 'varchar' }) 9 | id: string; 10 | 11 | @Column({ name: 'company_name', type: 'varchar' }) 12 | companyName: string; 13 | 14 | @Column({ name: 'contact_name', type: 'varchar' }) 15 | contactName: string; 16 | 17 | @Column({ name: 'contact_title', type: 'varchar' }) 18 | contactTitle: string; 19 | 20 | @Column({ name: 'address', type: 'varchar' }) 21 | address: string; 22 | 23 | @Column({ name: 'city', type: 'varchar' }) 24 | city: string; 25 | 26 | @Column({ name: 'postal_code', type: 'varchar', nullable: true }) 27 | postalCode: string | null; 28 | 29 | @Column({ name: 'region', type: 'varchar', nullable: true }) 30 | region: string | null; 31 | 32 | @Column({ name: 'country', type: 'varchar' }) 33 | country: string; 34 | 35 | @Column({ name: 'phone', type: 'varchar' }) 36 | phone: string; 37 | 38 | @Column({ name: 'fax', type: 'varchar', nullable: true }) 39 | fax: string | null; 40 | 41 | @OneToMany(() => Order, (order) => order.customer) 42 | orders: Order[]; 43 | } 44 | -------------------------------------------------------------------------------- /src/typeorm/entities/products.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, Column, ManyToOne, JoinColumn, OneToMany, PrimaryColumn, 3 | } from 'typeorm'; 4 | import { Supplier } from './suppliers'; 5 | import { Detail } from './details'; 6 | 7 | @Entity({ name: 'products' }) 8 | export class Product { 9 | @PrimaryColumn({ type: 'varchar' }) 10 | id: string; 11 | 12 | @Column({ name: 'name', type: 'varchar' }) 13 | name: string; 14 | 15 | @Column({ name: 'qt_per_unit', type: 'varchar' }) 16 | qtPerUnit: string; 17 | 18 | @Column({ name: 'unit_price', type: 'decimal', precision: 10, scale: 2, default: 0 }) 19 | unitPrice: number; 20 | 21 | @Column({ name: 'units_in_stock', type: 'integer' }) 22 | unitsInStock: number; 23 | 24 | @Column({ name: 'units_on_order', type: 'integer' }) 25 | unitsOnOrder: number; 26 | 27 | @Column({ name: 'reorder_level', type: 'integer' }) 28 | reorderLevel: number; 29 | 30 | @Column({ name: 'discontinued', type: 'integer' }) 31 | discontinued: number; 32 | 33 | @Column({ name: 'supplier_id', type: 'varchar' }) 34 | supplierId: string; 35 | @ManyToOne(() => Supplier, (supplier) => supplier.products, { onDelete: 'CASCADE' }) 36 | @JoinColumn({ name: 'supplier_id' }) 37 | supplier: Supplier; 38 | 39 | @OneToMany(() => Detail, (detail) => detail.product) 40 | details: Detail[]; 41 | } 42 | -------------------------------------------------------------------------------- /src/mikro/entities/employees.ts: -------------------------------------------------------------------------------- 1 | import { Cascade, Collection, Entity, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; 2 | import { Order } from './orders'; 3 | 4 | @Entity({ tableName: 'employees' }) 5 | export class Employee { 6 | @PrimaryKey() 7 | id: string; 8 | 9 | @Property({ fieldName: 'last_name' }) 10 | lastName: string; 11 | 12 | @Property({ fieldName: 'first_name', columnType: 'varchar', nullable: true }) 13 | firstName?: string; 14 | 15 | @Property({ fieldName: 'title' }) 16 | title: string; 17 | 18 | @Property({ fieldName: 'title_of_courtesy' }) 19 | titleOfCourtesy: string; 20 | 21 | @Property({ fieldName: 'birth_date' }) 22 | birthDate: Date; 23 | 24 | @Property({ fieldName: 'hire_date' }) 25 | hireDate: Date; 26 | 27 | @Property({ fieldName: 'address' }) 28 | address: string; 29 | 30 | @Property({ fieldName: 'city' }) 31 | city: string; 32 | 33 | @Property({ name: 'postal_code' }) 34 | postalCode: string; 35 | 36 | @Property({ fieldName: 'country' }) 37 | country: string; 38 | 39 | @Property({ fieldName: 'home_phone' }) 40 | homePhone: string; 41 | 42 | @Property({ fieldName: 'extension' }) 43 | extension: number; 44 | 45 | @Property({ fieldName: 'notes', columnType: 'text' }) 46 | notes: string; 47 | 48 | @Property({ fieldName: 'recipient_id', columnType: 'varchar', nullable: true }) 49 | recipientId?: string; 50 | @ManyToOne(() => Employee) 51 | recipient?: Employee; 52 | 53 | @OneToMany(() => Order, (order) => order.employee, { cascade: [Cascade.ALL] }) 54 | orders = new Collection(this); 55 | } 56 | -------------------------------------------------------------------------------- /src/typeorm/entities/employees.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, Column, OneToMany, PrimaryColumn, ManyToOne, JoinColumn, 3 | } from 'typeorm'; 4 | import { Order } from './orders'; 5 | 6 | @Entity({ name: 'employees' }) 7 | export class Employee { 8 | @PrimaryColumn({ type: 'varchar' }) 9 | id: string; 10 | 11 | @Column({ name: 'last_name', type: 'varchar' }) 12 | lastName: string; 13 | 14 | @Column({ name: 'first_name', type: 'varchar', nullable: true }) 15 | firstName: string | null; 16 | 17 | @Column({ name: 'title', type: 'varchar' }) 18 | title: string; 19 | 20 | @Column({ name: 'title_of_courtesy', type: 'varchar' }) 21 | titleOfCourtesy: string; 22 | 23 | @Column({ name: 'birth_date', type: 'date' }) 24 | birthDate: Date; 25 | 26 | @Column({ name: 'hire_date', type: 'date' }) 27 | hireDate: Date; 28 | 29 | @Column({ name: 'address', type: 'varchar' }) 30 | address: string; 31 | 32 | @Column({ name: 'city', type: 'varchar' }) 33 | city: string; 34 | 35 | @Column({ name: 'postal_code', type: 'varchar' }) 36 | postalCode: string; 37 | 38 | @Column({ name: 'country', type: 'varchar' }) 39 | country: string; 40 | 41 | @Column({ name: 'home_phone', type: 'varchar' }) 42 | homePhone: string; 43 | 44 | @Column({ name: 'extension', type: 'varchar' }) 45 | extension: number; 46 | 47 | @Column({ name: 'notes', type: 'text' }) 48 | notes: string; 49 | 50 | @Column({ name: 'recipient_id', type: 'varchar', nullable: true }) 51 | recipientId: string | null; 52 | @ManyToOne(() => Employee, (employee) => employee.recipient) 53 | @JoinColumn({ name: 'recipient_id' }) 54 | recipient: Employee; 55 | 56 | @OneToMany(() => Order, (order) => order.employee) 57 | orders: Order[]; 58 | } 59 | -------------------------------------------------------------------------------- /src/mikro/entities/orders.ts: -------------------------------------------------------------------------------- 1 | import { Cascade, Collection, Entity, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; 2 | import { Customer } from './customers'; 3 | import { Detail } from './details'; 4 | import { Employee } from './employees'; 5 | 6 | @Entity({ tableName: 'orders' }) 7 | export class Order { 8 | @PrimaryKey() 9 | id: string; 10 | 11 | @Property({ fieldName: 'order_date', columnType: 'date' }) 12 | orderDate: Date; 13 | 14 | @Property({ fieldName: 'required_date' }) 15 | requiredDate: Date; 16 | 17 | @Property({ fieldName: 'shipped_date', columnType: 'date', nullable: true }) 18 | shippedDate: Date | null; 19 | 20 | @Property({ fieldName: 'ship_via' }) 21 | shipVia: number; 22 | 23 | @Property({ fieldName: 'freight', columnType: 'decimal', precision: 10, scale: 2, default: 0 }) 24 | freight: number; 25 | 26 | @Property({ fieldName: 'ship_name' }) 27 | shipName: string; 28 | 29 | @Property({ fieldName: 'ship_city' }) 30 | shipCity: string; 31 | 32 | @Property({ fieldName: 'ship_region', columnType: 'varchar', nullable: true }) 33 | shipRegion: string | null; 34 | 35 | @Property({ fieldName: 'ship_postal_code', columnType: 'varchar', nullable: true }) 36 | shipPostalCode: string | null; 37 | 38 | @Property({ fieldName: 'ship_country' }) 39 | shipCountry: string; 40 | 41 | @Property({ fieldName: 'customer_id' }) 42 | customerId: string; 43 | @ManyToOne(() => Customer) 44 | customer: Customer; 45 | 46 | @Property({ fieldName: 'employee_id' }) 47 | employeeId: string; 48 | @ManyToOne(() => Employee) 49 | employee: Employee; 50 | 51 | @OneToMany(() => Detail, (detail) => detail.order, { cascade: [Cascade.ALL] }) 52 | details = new Collection(this); 53 | } 54 | -------------------------------------------------------------------------------- /src/typeorm/entities/orders.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, Column, ManyToOne, JoinColumn, OneToMany, PrimaryColumn, 3 | } from 'typeorm'; 4 | import { Customer } from './customers'; 5 | import { Detail } from './details'; 6 | import { Employee } from './employees'; 7 | 8 | @Entity({ name: 'orders' }) 9 | export class Order { 10 | @PrimaryColumn({ type: 'varchar' }) 11 | id: string; 12 | 13 | @Column({ name: 'order_date', type: 'date' }) 14 | orderDate: Date; 15 | 16 | @Column({ name: 'required_date', type: 'date' }) 17 | requiredDate: Date; 18 | 19 | @Column({ name: 'shipped_date', type: 'date', nullable: true }) 20 | shippedDate: Date | null; 21 | 22 | @Column({ name: 'ship_via', type: 'integer' }) 23 | shipVia: number; 24 | 25 | @Column({ name: 'freight', type: 'decimal', precision: 10, scale: 2, default: 0 }) 26 | freight: number; 27 | 28 | @Column({ name: 'ship_name', type: 'varchar' }) 29 | shipName: string; 30 | 31 | @Column({ name: 'ship_city', type: 'varchar' }) 32 | shipCity: string; 33 | 34 | @Column({ name: 'ship_region', type: 'varchar', nullable: true }) 35 | shipRegion: string | null; 36 | 37 | @Column({ name: 'ship_postal_code', type: 'varchar', nullable: true }) 38 | shipPostalCode: string | null; 39 | 40 | @Column({ name: 'ship_country', type: 'varchar' }) 41 | shipCountry: string; 42 | 43 | @Column({ name: 'customer_id', type: 'varchar' }) 44 | customerId: string; 45 | @ManyToOne(() => Customer, (customer) => customer.orders, { onDelete: 'CASCADE' }) 46 | @JoinColumn({ name: 'customer_id' }) 47 | customer: Customer; 48 | 49 | @Column({ name: 'employee_id', type: 'varchar' }) 50 | employeeId: string; 51 | @ManyToOne(() => Employee, (employee) => employee.orders, { onDelete: 'CASCADE' }) 52 | @JoinColumn({ name: 'employee_id' }) 53 | employee: Employee; 54 | 55 | @OneToMany(() => Detail, (detail) => detail.order) 56 | details: Detail[]; 57 | } 58 | -------------------------------------------------------------------------------- /src/kysely/db.ts: -------------------------------------------------------------------------------- 1 | interface Customers { 2 | id: string; 3 | company_name: string; 4 | contact_name: string; 5 | contact_title: string; 6 | address: string; 7 | city: string; 8 | postal_code: string | null; 9 | region: string | null; 10 | country: string; 11 | phone: string; 12 | fax: string | null; 13 | } 14 | 15 | interface Employees { 16 | id: string; 17 | last_name: string; 18 | first_name: string | null; 19 | title: string; 20 | title_of_courtesy: string; 21 | birth_date: Date; 22 | hire_date: Date; 23 | address: string; 24 | city: string; 25 | postal_code: string; 26 | country: string; 27 | home_phone: string; 28 | extension: number; 29 | notes: string; 30 | recipient_id: number | null; 31 | } 32 | 33 | interface Orders { 34 | id: string; 35 | order_date: Date; 36 | required_date: Date; 37 | shipped_date: Date | null; 38 | ship_via: number; 39 | freight: number; 40 | ship_name: string; 41 | ship_city: string; 42 | ship_region: string | null; 43 | ship_postal_code: string | null; 44 | ship_country: string; 45 | customer_id: string; 46 | employee_id: number; 47 | } 48 | 49 | interface Suppliers { 50 | id: string; 51 | company_name: string; 52 | contact_name: string; 53 | contact_title: string; 54 | address: string; 55 | city: string; 56 | region: string | null; 57 | postal_code: string; 58 | country: string; 59 | phone: string; 60 | } 61 | 62 | interface Products { 63 | id: string; 64 | name: string; 65 | qt_per_unit: string; 66 | unit_price: number; 67 | units_in_stock: number; 68 | units_on_order: number; 69 | reorder_level: number; 70 | discontinued: number; 71 | supplier_id: number; 72 | } 73 | 74 | interface Details { 75 | id: string, 76 | unit_price: number; 77 | quantity: number; 78 | discount: number; 79 | order_id: number; 80 | product_id: number; 81 | } 82 | 83 | export interface Database { 84 | customers: Customers; 85 | employees: Employees; 86 | orders: Orders; 87 | products: Products; 88 | suppliers: Suppliers; 89 | order_details: Details; 90 | } 91 | -------------------------------------------------------------------------------- /src/common/meta.ts: -------------------------------------------------------------------------------- 1 |  // prettier-ignore 2 | export const customerIds = [ 3 | 'LAZYK', 'FOLIG', 'QUEDE', 'MORGK', 'SAVEA', 'AROUT', 4 | 'VICTE', 'ISLAT', 'EASTC', 'BOLID', 'SIMOB', 'LEHMS', 5 | 'LETSS', 'FRANS', 'FAMIA', 'LACOR', 'GROSR', 'MEREP', 6 | 'BERGS', 'RICAR', 'CENTC', 'FISSA', 'WANDK', 'BLONP', 7 | 'OCEAN', 'PERIC', 'MAISD', 'LAMAI', 'LINOD', 'BOTTM', 8 | 'NORTS', 'QUEEN', 'LILAS', 'SPLIR', 'WILMK', 'HILAA', 9 | 'LONEP', 'TRAIH', 'SANTG', 'FRANK', 'TRADH', 'WARTH', 10 | 'REGGC', 'RICSU', 'THECR', 'VAFFE', 'ANATR', 'BSBEV', 11 | 'TORTU', 'WOLZA', 'WHITC', 'SUPRD', 'TOMSP', 'HANAR', 12 | 'DRACD', 'RANCH', 'SEVES', 'GODOS', 'CHOPS', 'BONAP', 13 | 'KOENE', 'COMMI', 'CACTU', 'GREAL', 'ALFKI', 'BLAUS', 14 | 'OTTIK', 'WELLI', 'ERNSH', 'OLDWO', 'FRANR', 'PRINI', 15 | 'VINET', 'MAGAA', 'GOURL', 'LAUGB', 'PARIS', 'GALED', 16 | 'DUMON', 'HUNGC', 'QUICK', 'SPECD', 'HUNGO', 'RATTC', 17 | 'PICCO', 'FURIB', 'THEBI', 'ROMEY', 'CONSH', 'FOLKO', 18 | 'ANTON' 19 | ] 20 | 21 | // prettier-ignore 22 | export const customerSearches = [ 23 | "ve", "ey", "or", "bb", "te", 24 | "ab", "ca", "ki", "ap", "be", 25 | "ct", "hi", "er", "pr", "pi", 26 | "en", "au", "ra", "ti", "ke", 27 | "ou", "ur", "me", "ea", "op", 28 | "at", "ne", "na", "os", "ri", 29 | "on", "ha", "il", "to", "as", 30 | "io", "di", "zy", "az", "la", 31 | "ko", "st", "gh", "ug", "ac", 32 | "cc", "ch", "hu", "re", "an", 33 | ]; 34 | 35 | // prettier-ignore 36 | export const productSearches = [ 37 | "ha", "ey", "or", "po", "te", 38 | "ab", "er", "ke", "ap", "be", 39 | "en", "au", "ra", "ti", "su", 40 | "sa", "hi", "nu", "ge", "pi", 41 | "ou", "ur", "me", "ea", "tu", 42 | "at", "ne", "na", "os", "ri", 43 | "on", "ka", "il", "to", "as", 44 | "io", "di", "za", "fa", "la", 45 | "ko", "st", "gh", "ug", "ac", 46 | "cc", "ch", "pa", "re", "an", 47 | ]; 48 | 49 | const employeeIdStart = 1; 50 | const employeeIdEnd = 10; 51 | export const employeeIds = Array.from({ length: employeeIdEnd - employeeIdStart }, (_, i) => String(i + employeeIdStart)); 52 | 53 | const supplierIdStart = 1; 54 | const supplierIdEnd = 30; 55 | export const supplierIds = Array.from({ length: supplierIdEnd - supplierIdStart }, (_, i) => String(i + supplierIdStart)); 56 | 57 | const productIdStart = 1; 58 | const productIdEnd = 78; 59 | export const productIds = Array.from({ length: productIdEnd }, (_, i) => String(i + 1)); 60 | 61 | const getRandomOrderIds = () => { 62 | const firstId = 10248; 63 | const lastId = 11077; 64 | const orderIds = new Set(); 65 | while (orderIds.size <= 200) orderIds.add(String(Math.round(firstId + Math.random() * (lastId - firstId)))); 66 | return Array.from(orderIds); 67 | }; 68 | 69 | export const orderIds = getRandomOrderIds(); 70 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import Docker from "dockerode"; 2 | import { v4 as uuid } from "uuid"; 3 | import getPort from "get-port"; 4 | import pkg from "pg"; 5 | import path from "node:path"; 6 | import fs from "fs"; 7 | 8 | export interface DockerDBs { 9 | pgContainer: Docker.Container; 10 | port: number; 11 | } 12 | 13 | export const ports = { 14 | pg: 55000, 15 | pgPrepared: 55001, 16 | drizzle: 55002, 17 | drizzlePrepared: 55003, 18 | knex: 55004, 19 | kysely: 55005, 20 | typeOrm: 55006, 21 | mikroOrm: 55007, 22 | prismaOrm: 55008, 23 | postgresjs: 55009, 24 | }; 25 | 26 | export async function createDockerDBs(ports: { 27 | [key: string]: number; 28 | }): Promise { 29 | const docker = new Docker(); 30 | const image = "postgres"; 31 | const pullStream = await docker.pull(image); 32 | await new Promise((resolve, reject) => 33 | docker.modem.followProgress(pullStream, (err) => (err ? reject(err) : resolve(err))) 34 | ); 35 | const dockerDBs: DockerDBs[] = []; 36 | await Promise.all( 37 | Object.values(ports).map(async (port) => { 38 | const pgContainer = await docker.createContainer({ 39 | Image: image, 40 | Env: [ 41 | "POSTGRES_PASSWORD=postgres", 42 | "POSTGRES_USER=postgres", 43 | "POSTGRES_DB=postgres", 44 | ], 45 | name: `benchmarks-tests-${uuid()}`, 46 | HostConfig: { 47 | AutoRemove: true, 48 | PortBindings: { 49 | "5432/tcp": [{ HostPort: `${port}` }], 50 | }, 51 | }, 52 | }); 53 | await pgContainer.start(); 54 | dockerDBs.push({ pgContainer, port }); 55 | }) 56 | ); 57 | 58 | await addDataToDB(dockerDBs); 59 | return dockerDBs; 60 | } 61 | 62 | export const addDataToDB = async (dockerDBs: DockerDBs[]) => { 63 | const sql_script = fs.readFileSync(path.resolve("data/init-db.sql"), "utf-8"); 64 | await Promise.all( 65 | dockerDBs.map(async (dockerDb) => { 66 | const connectionString = `postgres://postgres:postgres@localhost:${dockerDb.port}/postgres`; 67 | let sleep = 250; 68 | let timeLeft = 5000; 69 | let connected = false; 70 | let lastError: unknown | undefined; 71 | const pool = new pkg.Pool({ connectionString }); 72 | do { 73 | try { 74 | await pool.connect(); 75 | connected = true; 76 | break; 77 | } catch (e) { 78 | lastError = e; 79 | await new Promise((resolve) => setTimeout(resolve, sleep)); 80 | timeLeft -= sleep; 81 | } 82 | } while (timeLeft > 0); 83 | if (!connected) { 84 | console.error("Cannot connect to Postgres"); 85 | throw lastError; 86 | } 87 | await pool.query(sql_script); 88 | }) 89 | ); 90 | }; 91 | 92 | export const deleteDockerDBs = async (dockerDBs: DockerDBs[]) => { 93 | await Promise.all( 94 | dockerDBs.map(async (dockerDB) => { 95 | await dockerDB.pgContainer.stop().catch(console.error); 96 | }) 97 | ); 98 | }; 99 | -------------------------------------------------------------------------------- /src/prisma/benchmark.ts: -------------------------------------------------------------------------------- 1 | import { run, bench } from "mitata"; 2 | import * as Prisma from "@prisma/client"; 3 | 4 | import { 5 | customerIds, 6 | employeeIds, 7 | orderIds, 8 | productIds, 9 | productSearches, 10 | customerSearches, 11 | supplierIds, 12 | } from "../common/meta"; 13 | import { products } from "../drizzle/schema"; 14 | 15 | const prisma = new Prisma.PrismaClient(); 16 | 17 | bench("Prisma ORM Customers: getAll", async () => { 18 | await prisma.customer.findMany(); 19 | }); 20 | 21 | bench("Prisma ORM Customers: getInfo", async () => { 22 | for (const id of customerIds) { 23 | await prisma.customer.findUnique({ 24 | where: { 25 | id, 26 | }, 27 | }); 28 | } 29 | }); 30 | 31 | bench("Prisma ORM Customers: search", async () => { 32 | for (const it of customerSearches) { 33 | await prisma.customer.findMany({ 34 | where: { 35 | companyName: { 36 | contains: it, 37 | mode: "insensitive", 38 | }, 39 | }, 40 | }); 41 | } 42 | }); 43 | 44 | bench("Prisma ORM Employees: getAll", async () => { 45 | await prisma.employee.findMany(); 46 | }); 47 | 48 | bench("Prisma ORM Employees: getInfo", async () => { 49 | for (const id of employeeIds) { 50 | await prisma.employee.findUnique({ 51 | where: { 52 | id, 53 | }, 54 | include: { 55 | recipient: true, 56 | }, 57 | }); 58 | } 59 | }); 60 | bench("Prisma ORM Suppliers: getAll", async () => { 61 | await prisma.supplier.findMany(); 62 | }); 63 | 64 | bench("Prisma ORM Suppliers: getInfo", async () => { 65 | for (const id of supplierIds) { 66 | await prisma.supplier.findUnique({ 67 | where: { 68 | id, 69 | }, 70 | }); 71 | } 72 | }); 73 | 74 | bench("Prisma ORM Products: getAll", async () => { 75 | await prisma.product.findMany(); 76 | }); 77 | 78 | bench("Prisma ORM Products: getInfo", async () => { 79 | for (const id of productIds) { 80 | await prisma.product.findUnique({ 81 | where: { 82 | id, 83 | }, 84 | include: { 85 | supplier: true, 86 | }, 87 | }); 88 | } 89 | }); 90 | 91 | bench("Prisma ORM Products: search", async () => { 92 | for (const it of productSearches) { 93 | await prisma.product.findMany({ 94 | where: { 95 | name: { 96 | contains: it, 97 | mode: "insensitive", 98 | }, 99 | }, 100 | }); 101 | } 102 | }); 103 | 104 | bench("Prisma ORM Orders: getAll", async () => { 105 | const result = await prisma.order.findMany({ 106 | include: { 107 | details: true, 108 | }, 109 | }); 110 | const orders = result.map((item) => { 111 | return { 112 | id: item.id, 113 | shippedDate: item.shippedDate, 114 | shipName: item.shipName, 115 | shipCity: item.shipCity, 116 | shipCountry: item.shipCountry, 117 | productsCount: item.details.length, 118 | quantitySum: item.details.reduce( 119 | (sum, deteil) => (sum += +deteil.quantity), 120 | 0 121 | ), 122 | totalPrice: item.details.reduce( 123 | (sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 124 | 0 125 | ), 126 | }; 127 | }); 128 | }); 129 | 130 | bench("Prisma ORM Orders: getById", async () => { 131 | for (const id of orderIds) { 132 | const result = await prisma.order.findFirst({ 133 | include: { 134 | details: true, 135 | }, 136 | where: { 137 | id, 138 | }, 139 | }); 140 | const order = { 141 | id: result!.id, 142 | shippedDate: result!.shippedDate, 143 | shipName: result!.shipName, 144 | shipCity: result!.shipCity, 145 | shipCountry: result!.shipCountry, 146 | productsCount: result!.details.length, 147 | quantitySum: result!.details.reduce( 148 | (sum, deteil) => (sum += +deteil.quantity), 149 | 0 150 | ), 151 | totalPrice: result!.details.reduce( 152 | (sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 153 | 0 154 | ), 155 | }; 156 | } 157 | }); 158 | 159 | bench("Prisma ORM Orders: getInfo", async () => { 160 | for (const id of orderIds) { 161 | await prisma.order.findMany({ 162 | where: { 163 | id, 164 | }, 165 | include: { 166 | details: { 167 | include: { 168 | product: true, 169 | }, 170 | }, 171 | }, 172 | }); 173 | } 174 | }); 175 | 176 | const main = async () => { 177 | await run(); 178 | }; 179 | 180 | main(); 181 | -------------------------------------------------------------------------------- /src/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | previewFeatures = ["referentialIntegrity"] 4 | binaryTargets = ["native", "rhel-openssl-1.0.x"] 5 | } 6 | 7 | datasource db { 8 | provider = "postgresql" 9 | url = env("DB_PRISMA_URL") 10 | } 11 | 12 | model Customer { 13 | id String @id() @db.VarChar(5) 14 | companyName String @map("company_name") @db.VarChar() 15 | contactName String @map("contact_name") @db.VarChar() 16 | contactTitle String @map("contact_title") @db.VarChar() 17 | address String @db.VarChar() 18 | city String @db.VarChar() 19 | postalCode String? @map("postal_code") @db.VarChar() 20 | region String? @db.VarChar() 21 | country String @db.VarChar() 22 | phone String @db.VarChar() 23 | fax String? @db.VarChar() 24 | orders Order[] 25 | 26 | @@map("customers") 27 | } 28 | 29 | model Employee { 30 | id String @id() @db.VarChar() 31 | lastName String @map("last_name") @db.VarChar() 32 | firstName String? @map("first_name") @db.VarChar() 33 | title String @db.VarChar() 34 | titleOfCourtesy String @map("title_of_courtesy") @db.VarChar() 35 | birthDate DateTime @map("birth_date") 36 | hireDate DateTime @map("hire_date") 37 | address String @db.VarChar() 38 | city String @db.VarChar() 39 | postalCode String @map("postal_code") @db.VarChar() 40 | country String @db.VarChar() 41 | homePhone String @map("home_phone") @db.VarChar() 42 | extension Int 43 | notes String @db.VarChar() 44 | recipientId String? @map("recipient_id") @db.VarChar() 45 | recipient Employee? @relation("reports", fields: [recipientId], references: [id], onDelete: Cascade) 46 | reporters Employee[] @relation("reports") 47 | orders Order[] 48 | 49 | @@map("employees") 50 | } 51 | 52 | model Order { 53 | id String @id() @db.VarChar() 54 | orderDate DateTime @map("order_date") 55 | requiredDate DateTime @map("required_date") 56 | shippedDate DateTime? @map("shipped_date") 57 | shipVia Int @map("ship_via") 58 | freight Decimal @db.Decimal(10, 2) 59 | shipName String @map("ship_name") @db.VarChar() 60 | shipCity String @map("ship_city") @db.VarChar() 61 | shipRegion String? @map("ship_region") @db.VarChar() 62 | shipPostalCode String? @map("ship_postal_code") @db.VarChar() 63 | shipCountry String @map("ship_country") @db.VarChar() 64 | customerId String @map("customer_id") @db.VarChar() 65 | employeeId String @map("employee_id") @db.VarChar() 66 | customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade) 67 | employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade) 68 | details Detail[] 69 | 70 | @@map("orders") 71 | } 72 | 73 | model Detail { 74 | unitPrice Decimal @map("unit_price") @db.Decimal(10, 2) 75 | quantity Int 76 | discount Decimal @db.Decimal(10, 2) 77 | orderId String @map("order_id") @db.VarChar() 78 | productId String @map("product_id") @db.VarChar() 79 | order Order @relation(fields: [orderId], references: [id], onDelete: Cascade) 80 | product Product @relation(fields: [productId], references: [id], onDelete: Cascade) 81 | 82 | 83 | @@id([orderId, productId]) 84 | @@map("order_details") 85 | } 86 | 87 | model Product { 88 | id String @id() @db.VarChar() 89 | name String @db.VarChar() 90 | quantityPerUnit String @map("qt_per_unit") @db.VarChar() 91 | unitPrice Decimal @map("unit_price") @db.Decimal(10, 2) 92 | unitsInStock Int @map("units_in_stock") 93 | unitsOnOrder Int @map("units_on_order") 94 | reorderLevel Int @map("reorder_level") 95 | discontinued Int 96 | supplierId String @map("supplier_id") @db.VarChar() 97 | details Detail[] 98 | supplier Supplier @relation(fields: [supplierId], references: [id], onDelete: Cascade) 99 | 100 | @@map("products") 101 | } 102 | 103 | model Supplier { 104 | id String @id() @db.VarChar() 105 | companyName String @map("company_name") @db.VarChar() 106 | contactName String @map("contact_name") @db.VarChar() 107 | contactTitle String @map("contact_title") @db.VarChar() 108 | address String @db.VarChar() 109 | city String @db.VarChar() 110 | region String? @db.VarChar() 111 | postalCode String @map("postal_code") @db.VarChar() 112 | country String @db.VarChar() 113 | phone String @db.VarChar() 114 | products Product[] 115 | 116 | @@map("suppliers") 117 | } 118 | -------------------------------------------------------------------------------- /src/typeorm/benchmark.ts: -------------------------------------------------------------------------------- 1 | import { run, bench } from "mitata"; 2 | import { DataSource, ILike } from "typeorm"; 3 | import dotenv from 'dotenv'; 4 | import { Customer } from "./entities/customers"; 5 | import { Employee } from "./entities/employees"; 6 | import { Supplier } from "./entities/suppliers"; 7 | import { Product } from "./entities/products"; 8 | import { Order } from "./entities/orders"; 9 | import { Detail } from "./entities/details"; 10 | import { customerIds, employeeIds, orderIds, productIds, productSearches, customerSearches, supplierIds } from "../common/meta"; 11 | 12 | dotenv.config() 13 | const { DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD } = process.env; 14 | 15 | const typeorm = new DataSource({ 16 | type: 'postgres', 17 | host: DB_HOST, 18 | port: Number(DB_PORT), 19 | username: DB_USER, 20 | password: DB_PASSWORD, 21 | database: DB_NAME, 22 | entities: [Customer, Employee, Order, Supplier, Product, Detail], 23 | synchronize: false, 24 | logging: false, 25 | extra: { 26 | decimalNumbers: true, 27 | }, 28 | }); 29 | 30 | 31 | bench("TypeORM Customers: getAll", async () => { 32 | await typeorm.getRepository(Customer).find(); 33 | }); 34 | 35 | bench("TypeORM Customers: getInfo", async () => { 36 | for (const id of customerIds) { await typeorm.getRepository(Customer).findOneBy({ id }); } 37 | }); 38 | 39 | bench("TypeORM Customers: search", async () => { 40 | for (const it of customerSearches) { 41 | await typeorm 42 | .getRepository(Customer) 43 | .find({ 44 | where: { 45 | companyName: ILike(`%${it}%`), 46 | }, 47 | }); 48 | } 49 | }); 50 | 51 | bench("TypeORM Employees: getAll", async () => { 52 | await typeorm.getRepository(Employee).find(); 53 | }); 54 | 55 | bench("TypeORM Employees: getInfo", async () => { 56 | for (const id of employeeIds) { 57 | await typeorm.getRepository(Employee).findOne({ 58 | where: { 59 | id, 60 | }, 61 | relations: { 62 | recipient: true, 63 | }, 64 | }); 65 | } 66 | }); 67 | 68 | bench("TypeORM Suppliers: getAll", async () => { 69 | await typeorm.getRepository(Supplier).find(); 70 | }); 71 | 72 | bench("TypeORM Suppliers: getInfo", async () => { 73 | for (const id of supplierIds) { 74 | await typeorm.getRepository(Supplier).findOneBy({ id }); 75 | } 76 | }); 77 | 78 | bench("TypeORM Products: getAll", async () => { 79 | await typeorm.getRepository(Product).find(); 80 | }); 81 | 82 | bench("TypeORM Products: getInfo", async () => { 83 | for (const id of productIds) { 84 | await typeorm.getRepository(Product).findOne({ 85 | where: { 86 | id, 87 | }, 88 | relations: ["supplier"], 89 | }); 90 | } 91 | }); 92 | 93 | bench("TypeORM Products: search", async () => { 94 | for (const it of productSearches) { 95 | await typeorm.getRepository(Product).find({ 96 | where: { 97 | name: ILike(`%${it}%`), 98 | }, 99 | }); 100 | } 101 | }); 102 | 103 | bench("TypeORM Orders: getAll", async () => { 104 | const result = await typeorm.getRepository(Order).find({ 105 | relations: { 106 | details: true, 107 | }, 108 | }); 109 | const orders = result.map((item) => { 110 | return { 111 | id: item.id, 112 | shippedDate: item.shippedDate, 113 | shipName: item.shipName, 114 | shipCity: item.shipCity, 115 | shipCountry: item.shipCountry, 116 | productsCount: item.details.length, 117 | quantitySum: item.details.reduce( 118 | (sum, deteil) => (sum += +deteil.quantity), 119 | 0 120 | ), 121 | totalPrice: item.details.reduce( 122 | (sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 123 | 0 124 | ), 125 | }; 126 | }); 127 | }); 128 | 129 | bench("TypeORM Orders: getById", async () => { 130 | for (const id of orderIds) { 131 | const result = await typeorm.getRepository(Order).findOne({ 132 | relations: { 133 | details: true, 134 | }, 135 | where: { 136 | id 137 | } 138 | }); 139 | const order = { 140 | id: result!.id, 141 | shippedDate: result!.shippedDate, 142 | shipName: result!.shipName, 143 | shipCity: result!.shipCity, 144 | shipCountry: result!.shipCountry, 145 | productsCount: result!.details.length, 146 | quantitySum: result!.details.reduce( 147 | (sum, deteil) => (sum += +deteil.quantity), 148 | 0 149 | ), 150 | totalPrice: result!.details.reduce( 151 | (sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 152 | 0 153 | ), 154 | }; 155 | } 156 | }); 157 | 158 | bench("TypeORM Orders: getInfo", async () => { 159 | for (const id of orderIds) { 160 | await typeorm.getRepository(Order).find({ 161 | relations: ["details", "details.product"], 162 | where: { 163 | id, 164 | }, 165 | }); 166 | } 167 | }); 168 | 169 | const main = async () => { 170 | await typeorm.initialize(); 171 | await run(); 172 | } 173 | 174 | main(); 175 | -------------------------------------------------------------------------------- /src/drizzle/schema.ts: -------------------------------------------------------------------------------- 1 | import { relations } from "drizzle-orm"; 2 | import { 3 | pgTable, 4 | varchar, 5 | date, 6 | text, 7 | foreignKey, 8 | integer, 9 | doublePrecision, 10 | } from "drizzle-orm/pg-core"; 11 | 12 | export const customers = pgTable("customers", { 13 | id: varchar("id", { length: 5 }).primaryKey().notNull(), 14 | companyName: varchar("company_name").notNull(), 15 | contactName: varchar("contact_name").notNull(), 16 | contactTitle: varchar("contact_title").notNull(), 17 | address: varchar("address").notNull(), 18 | city: varchar("city").notNull(), 19 | postalCode: varchar("postal_code"), 20 | region: varchar("region"), 21 | country: varchar("country").notNull(), 22 | phone: varchar("phone").notNull(), 23 | fax: varchar("fax"), 24 | }); 25 | 26 | export const employees = pgTable( 27 | "employees", 28 | { 29 | id: varchar("id").primaryKey().notNull(), 30 | lastName: varchar("last_name").notNull(), 31 | firstName: varchar("first_name"), 32 | title: varchar("title").notNull(), 33 | titleOfCourtesy: varchar("title_of_courtesy").notNull(), 34 | birthDate: date("birth_date", { mode: "date" }).notNull(), 35 | hireDate: date("hire_date", { mode: "date" }).notNull(), 36 | address: varchar("address").notNull(), 37 | city: varchar("city").notNull(), 38 | postalCode: varchar("postal_code").notNull(), 39 | country: varchar("country").notNull(), 40 | homePhone: varchar("home_phone").notNull(), 41 | extension: integer("extension").notNull(), 42 | notes: text("notes").notNull(), 43 | recipientId: varchar("recipient_id"), 44 | }, 45 | (table) => ({ 46 | recipientFk: foreignKey({ 47 | columns: [table.recipientId], 48 | foreignColumns: [table.id], 49 | }), 50 | }) 51 | ); 52 | 53 | export const orders = pgTable("orders", { 54 | id: varchar("id").primaryKey().notNull(), 55 | orderDate: date("order_date", { mode: "date" }).notNull(), 56 | requiredDate: date("required_date", { mode: "date" }).notNull(), 57 | shippedDate: date("shipped_date", { mode: "date" }), 58 | shipVia: integer("ship_via").notNull(), 59 | freight: doublePrecision("freight").notNull(), 60 | shipName: varchar("ship_name").notNull(), 61 | shipCity: varchar("ship_city").notNull(), 62 | shipRegion: varchar("ship_region"), 63 | shipPostalCode: varchar("ship_postal_code"), 64 | shipCountry: varchar("ship_country").notNull(), 65 | 66 | customerId: varchar("customer_id") 67 | .notNull() 68 | .references(() => customers.id, { onDelete: "cascade" }), 69 | 70 | employeeId: varchar("employee_id") 71 | .notNull() 72 | .references(() => employees.id, { onDelete: "cascade" }), 73 | }); 74 | 75 | export const suppliers = pgTable("suppliers", { 76 | id: varchar("id").primaryKey().notNull(), 77 | companyName: varchar("company_name").notNull(), 78 | contactName: varchar("contact_name").notNull(), 79 | contactTitle: varchar("contact_title").notNull(), 80 | address: varchar("address").notNull(), 81 | city: varchar("city").notNull(), 82 | region: varchar("region"), 83 | postalCode: varchar("postal_code").notNull(), 84 | country: varchar("country").notNull(), 85 | phone: varchar("phone").notNull(), 86 | }); 87 | 88 | export const products = pgTable("products", { 89 | id: varchar("id").primaryKey().notNull(), 90 | name: varchar("name").notNull(), 91 | quantityPerUnit: varchar("qt_per_unit").notNull(), 92 | unitPrice: doublePrecision("unit_price").notNull(), 93 | unitsInStock: integer("units_in_stock").notNull(), 94 | unitsOnOrder: integer("units_on_order").notNull(), 95 | reorderLevel: integer("reorder_level").notNull(), 96 | discontinued: integer("discontinued").notNull(), 97 | 98 | supplierId: varchar("supplier_id") 99 | .notNull() 100 | .references(() => suppliers.id, { onDelete: "cascade" }), 101 | }); 102 | 103 | export const details = pgTable("order_details", { 104 | unitPrice: doublePrecision("unit_price").notNull(), 105 | quantity: integer("quantity").notNull(), 106 | discount: doublePrecision("discount").notNull(), 107 | 108 | orderId: varchar("order_id") 109 | .notNull() 110 | .references(() => orders.id, { onDelete: "cascade" }), 111 | 112 | productId: varchar("product_id") 113 | .notNull() 114 | .references(() => products.id, { onDelete: "cascade" }), 115 | }); 116 | 117 | export const ordersRelations = relations(orders, (r) => { 118 | return { 119 | details: r.many(details), 120 | products: r.many(products) 121 | }; 122 | }); 123 | 124 | 125 | export const detailsRelations = relations(details, (r) => { 126 | return { 127 | order: r.one(orders, { 128 | fields: [details.orderId], 129 | references: [orders.id], 130 | }), 131 | product: r.one(products, { 132 | fields: [details.productId], 133 | references: [products.id] 134 | }) 135 | }; 136 | }); 137 | 138 | export const employeesRelations = relations(employees, (r) => { 139 | return { 140 | recipient: r.one(employees, { 141 | fields: [employees.recipientId], 142 | references: [employees.id], 143 | }), 144 | }; 145 | }); 146 | 147 | export const productsRelations = relations(products, (r)=>{ 148 | return { 149 | supplier: r.one(suppliers, { 150 | fields: [products.supplierId], 151 | references: [suppliers.id] 152 | }), 153 | order: r.one(orders, ) 154 | } 155 | }) 156 | 157 | -------------------------------------------------------------------------------- /src/knex/benchmark.ts: -------------------------------------------------------------------------------- 1 | import knex from "knex"; 2 | import { bench, run } from "mitata"; 3 | import { 4 | customerIds, 5 | employeeIds, 6 | orderIds, 7 | productIds, 8 | productSearches, 9 | customerSearches, 10 | supplierIds, 11 | } from "../common/meta"; 12 | import dotenv from "dotenv"; 13 | 14 | dotenv.config(); 15 | 16 | const { DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD } = process.env; 17 | const db = knex({ 18 | client: "pg", 19 | connection: { 20 | host: DB_HOST, 21 | port: +DB_PORT!, 22 | user: DB_USER, 23 | password: DB_PASSWORD, 24 | database: DB_NAME, 25 | }, 26 | useNullAsDefault: true, 27 | }); 28 | 29 | bench("Knex ORM customer: getAll", async () => { 30 | await db("customers"); 31 | }); 32 | bench("Knex ORM customer: getInfo", async () => { 33 | for (const id of customerIds) { 34 | await db("customers").where({ id }); 35 | } 36 | }); 37 | bench("Knex ORM customer: search", async () => { 38 | for (const it of customerSearches) { 39 | await db("customers").whereILike("company_name", `%${it}%`); 40 | } 41 | }); 42 | 43 | bench("Knex ORM Employees: getAll", async () => { 44 | await db("employees"); 45 | }); 46 | bench("Knex ORM Employees: getInfo", async () => { 47 | for (const id of employeeIds) { 48 | await db("employees as e1") 49 | .select([ 50 | "e1.*", 51 | "e2.id as e2_id", 52 | "e2.last_name as e2_last_name", 53 | "e2.first_name as e2_first_name", 54 | "e2.title as e2_title", 55 | "e2.title_of_courtesy as e2_title_of_courtesy", 56 | "e2.birth_date as e2_birth_date", 57 | "e2.hire_date as e2_hire_date", 58 | "e2.address as e2_address", 59 | "e2.city as e2_city", 60 | "e2.postal_code as e2_postal_code", 61 | "e2.country as e2_country", 62 | "e2.home_phone as e2_home_phone", 63 | "e2.extension as e2_extension", 64 | "e2.notes as e2_notes", 65 | "e2.recipient_id as e2_recipient_id", 66 | ]) 67 | .where("e1.id", "=", id) 68 | .leftJoin("employees as e2", "e1.recipient_id", "e2.id"); 69 | } 70 | }); 71 | 72 | bench("Knex ORM Suppliers: getAll", async () => { 73 | await db("suppliers"); 74 | }); 75 | bench("Knex ORM Suppliers: getInfo", async () => { 76 | for (const id of supplierIds) { 77 | await db("suppliers").where({ id }).first(); 78 | } 79 | }); 80 | 81 | bench("Knex ORM Products: getAll", async () => { 82 | await db("products"); 83 | }); 84 | 85 | bench("Knex ORM Products: getInfo", async () => { 86 | for (const id of productIds) { 87 | await db("products") 88 | .select([ 89 | "products.*", 90 | "suppliers.id as s_id", 91 | "company_name", 92 | "contact_name", 93 | "contact_title", 94 | "address", 95 | "city", 96 | "region", 97 | "postal_code", 98 | "country", 99 | "phone", 100 | ]) 101 | .where("products.id", "=", id) 102 | .leftJoin("suppliers", "suppliers.id", "products.supplier_id"); 103 | } 104 | }); 105 | 106 | bench("Knex ORM Products: search", async () => { 107 | for (const it of productSearches) { 108 | await db("products").whereILike("name", `%${it}%`); 109 | } 110 | }); 111 | 112 | bench("Knex ORM Orders: getAll", async () => { 113 | await db("orders") 114 | .select([ 115 | "orders.id", 116 | "orders.shipped_date", 117 | "orders.ship_name", 118 | "orders.ship_city", 119 | "orders.ship_country", 120 | ]) 121 | .leftJoin("order_details", "order_details.order_id", "orders.id") 122 | .count("product_id as products_count") 123 | .sum("quantity as quantity_sum") 124 | .sum({ total_price: db.raw("?? * ??", ["quantity", "unit_price"]) }) 125 | .groupBy("orders.id") 126 | .orderBy("orders.id", "asc"); 127 | }); 128 | 129 | bench("Knex ORM Orders: getById", async () => { 130 | for (const id of orderIds) { 131 | await db("orders") 132 | .select([ 133 | "orders.id", 134 | "orders.shipped_date", 135 | "orders.ship_name", 136 | "orders.ship_city", 137 | "orders.ship_country", 138 | ]) 139 | .where("orders.id", "=", id) 140 | .leftJoin("order_details", "order_details.order_id", "orders.id") 141 | .count("product_id as products_count") 142 | .sum("quantity as quantity_sum") 143 | .sum({ total_price: db.raw("?? * ??", ["quantity", "unit_price"]) }) 144 | .groupBy("orders.id") 145 | .orderBy("orders.id", "asc"); 146 | } 147 | }); 148 | 149 | bench("Knex ORM Orders: getInfo", async () => { 150 | for (const id of orderIds) { 151 | await db("orders") 152 | .select([ 153 | "order_details.*", 154 | "orders.id as o_id", 155 | "order_date", 156 | "required_date", 157 | "shipped_date", 158 | "ship_via", 159 | "freight", 160 | "ship_name", 161 | "ship_city", 162 | "ship_region", 163 | "ship_postal_code", 164 | "ship_country", 165 | "customer_id", 166 | "employee_id", 167 | "products.id as p_id", 168 | "name", 169 | "qt_per_unit", 170 | "products.unit_price as p_unit_price", 171 | "units_in_stock", 172 | "units_on_order", 173 | "reorder_level", 174 | "discontinued", 175 | "supplier_id", 176 | ]) 177 | .where("orders.id", "=", id) 178 | .leftJoin("order_details", "order_details.order_id", "orders.id") 179 | .leftJoin("products", "products.id", "order_details.product_id"); 180 | } 181 | }); 182 | 183 | const main = async () => { 184 | await run(); 185 | }; 186 | main(); 187 | -------------------------------------------------------------------------------- /src/mikro/benchmark.ts: -------------------------------------------------------------------------------- 1 | import { MikroORM, QueryOrder } from '@mikro-orm/core'; 2 | import { TsMorphMetadataProvider } from '@mikro-orm/reflection'; 3 | import { PostgreSqlDriver, SqlEntityManager } from '@mikro-orm/postgresql'; 4 | import { run, bench } from "mitata"; 5 | import dotenv from 'dotenv' 6 | import { 7 | customerIds, 8 | employeeIds, 9 | orderIds, 10 | productIds, 11 | productSearches, 12 | customerSearches, 13 | supplierIds, 14 | } from "../common/meta"; 15 | import { Customer } from "./entities/customers"; 16 | import { Detail } from "./entities/details"; 17 | import { Employee } from "./entities/employees"; 18 | import { Order } from "./entities/orders"; 19 | import { Product } from "./entities/products"; 20 | import { Supplier } from "./entities/suppliers"; 21 | 22 | dotenv.config() 23 | const { DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD } = process.env; 24 | 25 | let mikro:SqlEntityManager; 26 | 27 | bench("MikroORM Customers: getAll", async () => { 28 | await mikro.find(Customer, {}); 29 | mikro.clear(); 30 | }); 31 | bench("MikroORM Customers: getInfo", async () => { 32 | for (const id of customerIds) { 33 | await mikro.findOne(Customer, { id }); 34 | } 35 | mikro.clear(); 36 | }); 37 | bench("MikroORM Customers: search", async () => { 38 | for (const it of customerSearches) { 39 | await mikro.find(Customer, { 40 | companyName: { $like: `%${it}%` }, 41 | }); 42 | } 43 | mikro.clear(); 44 | }); 45 | bench("MikroORM Employees: getAll", async () => { 46 | await mikro.find(Employee, {}); 47 | mikro.clear(); 48 | }); 49 | bench("MikroORM Employees: getInfo", async () => { 50 | for (const id of employeeIds) { 51 | await mikro.findOne(Employee, { id }, { populate: ["recipient"] }); 52 | } 53 | mikro.clear(); 54 | }); 55 | bench("MikroORM Suppliers: getAll", async () => { 56 | await mikro.find(Supplier, {}); 57 | mikro.clear(); 58 | }); 59 | bench("MikroORM Suppliers: getInfo", async () => { 60 | for (const id of supplierIds) { 61 | await mikro.findOne(Supplier, { id }); 62 | } 63 | mikro.clear(); 64 | }); 65 | bench("MikroORM Products: getAll", async () => { 66 | await mikro.find(Product, {}); 67 | mikro.clear(); 68 | }); 69 | bench("MikroORM Products: getInfo", async () => { 70 | for (const id of productIds) { 71 | await mikro.findOne(Product, { id }, { populate: ["supplier"] }); 72 | } 73 | mikro.clear(); 74 | }); 75 | bench("MikroORM Products: search", async () => { 76 | for (const it of productSearches) { 77 | await mikro.find(Product, { 78 | name: { $ilike: `%${it}%` }, 79 | }); 80 | } 81 | mikro.clear(); 82 | }); 83 | 84 | bench("MikroORM Orders: getAll", async () => { 85 | const result = await mikro.find( 86 | Order, 87 | {}, 88 | { populate: ["details"] } 89 | ) 90 | const orders = result.map((item) => { 91 | const details = item.details.toArray() 92 | return { 93 | id: item.id, 94 | shippedDate: item.shippedDate, 95 | shipName: item.shipName, 96 | shipCity: item.shipCity, 97 | shipCountry: item.shipCountry, 98 | productsCount: item.details.length, 99 | quantitySum: details.reduce( 100 | (sum, deteil) => (sum += +deteil.quantity), 101 | 0 102 | ), 103 | totalPrice: details.reduce( 104 | (sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 105 | 0 106 | ), 107 | }; 108 | }); 109 | }); 110 | 111 | bench("MikroORM Orders: getById", async () => { 112 | for (const id of orderIds) { 113 | const result = await mikro.findOne( 114 | Order, 115 | { id }, 116 | { populate: ["details"] } 117 | ) 118 | const deteils = result!.details.getItems() 119 | const order = { 120 | id: result!.id, 121 | shippedDate: result!.shippedDate, 122 | shipName: result!.shipName, 123 | shipCity: result!.shipCity, 124 | shipCountry: result!.shipCountry, 125 | productsCount: result!.details.length, 126 | quantitySum: deteils.reduce( 127 | (sum, deteil) => (sum += +deteil.quantity), 128 | 0 129 | ), 130 | totalPrice: deteils.reduce( 131 | (sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 132 | 0 133 | ), 134 | }; 135 | } 136 | }); 137 | 138 | bench("MikroORM Orders: getInfo", async () => { 139 | for (const id of orderIds) { 140 | await mikro.find( 141 | Order, 142 | { id }, 143 | { populate: ["details", "details.product"] } 144 | ); 145 | } 146 | mikro.clear(); 147 | }); 148 | 149 | const main = async () => { 150 | const orm = await MikroORM.init({ 151 | type: 'postgresql', 152 | host: DB_HOST, 153 | port: +DB_PORT!, 154 | user: DB_USER, 155 | password: DB_PASSWORD, 156 | dbName: DB_NAME, 157 | entities: [Customer, Employee, Order, Supplier, Product, Detail], 158 | metadataProvider: TsMorphMetadataProvider, 159 | }); 160 | mikro = orm.em.fork(); 161 | // await run(); 162 | 163 | const result = await mikro.findOne( 164 | Order, 165 | { id: "10248" }, 166 | { populate: ["details"] } 167 | ) 168 | console.log(result); 169 | 170 | const deteils = result!.details.getItems() 171 | const order = { 172 | id: result!.id, 173 | shippedDate: result!.shippedDate, 174 | shipName: result!.shipName, 175 | shipCity: result!.shipCity, 176 | shipCountry: result!.shipCountry, 177 | productsCount: result!.details.length, 178 | quantitySum: deteils.reduce( 179 | (sum, deteil) => (sum += +deteil.quantity), 180 | 0 181 | ), 182 | totalPrice: deteils.reduce( 183 | (sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 184 | 0 185 | ), 186 | }; 187 | }; 188 | main(); 189 | -------------------------------------------------------------------------------- /src/pg/benchmark.ts: -------------------------------------------------------------------------------- 1 | import { run, bench } from "mitata"; 2 | import pkg from "pg"; 3 | import dotenv from "dotenv"; 4 | import Docker from 'dockerode'; 5 | import { v4 as uuid } from 'uuid'; 6 | import getPort from 'get-port'; 7 | 8 | import path from "node:path"; 9 | import fs from "fs"; 10 | 11 | const { Pool } = pkg; 12 | dotenv.config(); 13 | 14 | import { 15 | customerIds, 16 | employeeIds, 17 | orderIds, 18 | productIds, 19 | customerSearches, 20 | productSearches, 21 | supplierIds, 22 | } from "../common/meta"; 23 | 24 | // const { DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD } = process.env; 25 | 26 | // const pg = new Pool({ 27 | // host: DB_HOST, 28 | // port: +DB_PORT!, 29 | // user: DB_USER, 30 | // password: DB_PASSWORD, 31 | // database: DB_NAME, 32 | // }); 33 | 34 | let pg: pkg.Pool; 35 | let pgContainer: Docker.Container; 36 | 37 | 38 | async function createDockerDB(): Promise { 39 | const docker = new Docker() 40 | const port = await getPort({ port: 5432 }); 41 | const image = 'postgres'; 42 | 43 | await docker.pull(image); 44 | 45 | pgContainer = await docker.createContainer({ 46 | Image: image, 47 | Env: ['POSTGRES_PASSWORD=postgres', 'POSTGRES_USER=postgres', 'POSTGRES_DB=postgres'], 48 | name: `benchmarks-tests-${uuid()}`, 49 | HostConfig: { 50 | AutoRemove: true, 51 | PortBindings: { 52 | '5432/tcp': [{ HostPort: `${port}` }], 53 | }, 54 | }, 55 | }) 56 | 57 | await pgContainer.start(); 58 | 59 | return `postgres://postgres:postgres@localhost:${port}/postgres`; 60 | } 61 | 62 | const getConection = async () => { 63 | const connectionString = process.env['PG_CONNECTION_STRING'] ?? await createDockerDB(); 64 | 65 | let sleep = 250; 66 | let timeLeft = 5000; 67 | let connected = false; 68 | let lastError: unknown | undefined; 69 | const pool = new Pool({connectionString}); 70 | do { 71 | try { 72 | await pool.connect(); 73 | connected = true; 74 | break; 75 | } catch (e) { 76 | lastError = e; 77 | await new Promise((resolve) => setTimeout(resolve, sleep)); 78 | timeLeft -= sleep; 79 | } 80 | } while (timeLeft > 0); 81 | if (!connected) { 82 | console.error('Cannot connect to Postgres'); 83 | throw lastError; 84 | } 85 | pg = pool 86 | } 87 | 88 | 89 | bench("Customers: getAll", async () => { 90 | await pg.query('select * from "customers"'); 91 | }); 92 | bench("Customers: getInfo", async () => { 93 | for await (const id of customerIds) { 94 | await pg.query('select * from "customers" where "customers"."id" = $1', [ 95 | id, 96 | ]); 97 | } 98 | }); 99 | bench("Customers: search", async () => { 100 | for await (const it of customerSearches) { 101 | await pg.query( 102 | 'select * from "customers" where "customers"."company_name" ilike $1', 103 | [`%${it}%`] 104 | ); 105 | } 106 | }); 107 | 108 | bench("Employees: getAll", async () => { 109 | await pg.query('select * from "employees"'); 110 | }); 111 | 112 | bench("Employees: getInfo", async () => { 113 | for await (const id of employeeIds) { 114 | await pg.query( 115 | `select "e1".*, "e2"."last_name" as "reports_lname", "e2"."first_name" as "reports_fname" 116 | from "employees" as "e1" left join "employees" as "e2" on "e2"."id" = "e1"."recipient_id" where "e1"."id" = $1`, 117 | [id] 118 | ); 119 | } 120 | }); 121 | 122 | bench("Suppliers: getAll", async () => { 123 | await pg.query('select * from "suppliers"'); 124 | }); 125 | 126 | bench("Suppliers: getInfo", async () => { 127 | for await (const id of supplierIds) { 128 | await pg.query('select * from "suppliers" where "suppliers"."id" = $1', [id]); 129 | } 130 | }); 131 | 132 | bench("Products: getAll", async () => { 133 | await pg.query('select * from "products"'); 134 | }); 135 | 136 | bench("Products: getInfo", async () => { 137 | for await (const id of productIds) { 138 | await pg.query( 139 | `select "products".*, "suppliers".* 140 | from "products" left join "suppliers" on "products"."supplier_id" = "suppliers"."id" where "products"."id" = $1`, 141 | [id] 142 | ); 143 | } 144 | }); 145 | bench("Products: search", async () => { 146 | for await (const it of productSearches) { 147 | await pg.query( 148 | 'select * from "products" where "products"."name" ilike $1', 149 | [`%${it}%`] 150 | ); 151 | } 152 | }); 153 | 154 | bench("Orders: getAll", async () => { 155 | await pg.query(`select "id", "shipped_date", "ship_name", "ship_city", "ship_country", count("product_id") as "products", 156 | sum("quantity") as "quantity", sum("quantity" * "unit_price") as "total_price" 157 | from "orders" as "o" left join "order_details" as "od" on "od"."order_id" = "o"."id" group by "o"."id" order by "o"."id" asc`); 158 | }); 159 | 160 | bench("Orders: getById", async () => { 161 | for (const id of orderIds) { 162 | await pg.query(`select "id", "shipped_date", "ship_name", "ship_city", "ship_country", count("product_id") as "products", 163 | sum("quantity") as "quantity", sum("quantity" * "unit_price") as "total_price" 164 | from "orders" as "o" left join "order_details" as "od" on "od"."order_id" = "o"."id" where "o"."id" = $1 group by "o"."id" order by "o"."id" asc`, 165 | [id] 166 | ); 167 | } 168 | }); 169 | 170 | bench("Orders: getInfo", async () => { 171 | for await (const id of orderIds) { 172 | await pg.query( 173 | `SELECT * FROM "orders" AS o 174 | LEFT JOIN "order_details" AS od ON o.id = od.order_id 175 | LEFT JOIN "products" AS p ON od.product_id = p.id 176 | WHERE o.id = $1`, 177 | [id] 178 | ); 179 | } 180 | }); 181 | 182 | const main = async () => { 183 | await getConection() 184 | const sql_script = fs.readFileSync(path.resolve("data/init-db.sql"), 'utf-8') 185 | await pg.query(sql_script); 186 | await run(); 187 | process.exit(1) 188 | }; 189 | 190 | main(); 191 | -------------------------------------------------------------------------------- /src/drizzle/benchmark-pgsl.ts: -------------------------------------------------------------------------------- 1 | import { run, bench } from "mitata"; 2 | import Docker from "dockerode"; 3 | import { v4 as uuid } from "uuid"; 4 | import getPort from "get-port"; 5 | import { asc, eq, ilike } from "drizzle-orm"; 6 | import dotenv from "dotenv"; 7 | import { sql } from "drizzle-orm"; 8 | import * as fs from "fs"; 9 | import * as path from "path"; 10 | import { 11 | employees, 12 | customers, 13 | suppliers, 14 | products, 15 | orders, 16 | details, 17 | } from "./schema"; 18 | import { 19 | customerIds, 20 | employeeIds, 21 | orderIds, 22 | productIds, 23 | customerSearches, 24 | productSearches, 25 | supplierIds, 26 | } from "../common/meta"; 27 | import { alias } from "drizzle-orm/pg-core"; 28 | import postgres from "postgres"; 29 | import { PostgresJsDatabase, drizzle as drzl } from "drizzle-orm/postgres-js"; 30 | 31 | dotenv.config(); 32 | // const pool = new Pool({ connectionString: process.env.DATABASE_URL }); 33 | // const connector = new PgConnector(pool); 34 | 35 | let drizzle: PostgresJsDatabase; 36 | 37 | async function createDockerDB(): Promise { 38 | const docker = new Docker(); 39 | const port = await getPort({ port: 5432 }); 40 | const image = "postgres"; 41 | 42 | await docker.pull(image); 43 | 44 | const pgContainer = await docker.createContainer({ 45 | Image: image, 46 | Env: [ 47 | "POSTGRES_PASSWORD=postgres", 48 | "POSTGRES_USER=postgres", 49 | "POSTGRES_DB=postgres", 50 | ], 51 | name: `benchmarks-tests-${uuid()}`, 52 | HostConfig: { 53 | AutoRemove: true, 54 | PortBindings: { 55 | "5432/tcp": [{ HostPort: `${port}` }], 56 | }, 57 | }, 58 | }); 59 | 60 | await pgContainer.start(); 61 | 62 | return `postgres://postgres:postgres@localhost:${port}/postgres`; 63 | } 64 | 65 | bench("Customers: getAll", async () => { 66 | await drizzle.select().from(customers); 67 | }); 68 | 69 | bench("Customers: getInfo", async () => { 70 | for (const id of customerIds) { 71 | await drizzle.select().from(customers).where(eq(customers.id, id)); 72 | } 73 | }); 74 | 75 | bench("Customers: search", async () => { 76 | for (const it of customerSearches) { 77 | await drizzle 78 | .select() 79 | .from(customers) 80 | .where(ilike(customers.companyName, `%${it}%`)); 81 | } 82 | }); 83 | 84 | bench("Employees: getAll", async () => { 85 | await drizzle.select().from(employees); 86 | }); 87 | 88 | bench("Employees: getInfo", async () => { 89 | const e2 = alias(employees, "recipient"); 90 | 91 | for (const id of employeeIds) { 92 | await drizzle 93 | .select() 94 | .from(employees) 95 | .leftJoin(e2, eq(e2.id, employees.recipientId)) 96 | .where(eq(employees.id, id)); 97 | } 98 | }); 99 | 100 | bench("Suppliers: getAll", async () => { 101 | await drizzle.select().from(suppliers); 102 | }); 103 | 104 | bench("Suppliers: getInfo", async () => { 105 | for (const id of supplierIds) { 106 | await drizzle.select().from(suppliers).where(eq(suppliers.id, id)); 107 | } 108 | }); 109 | 110 | bench("Products: getAll", async () => { 111 | await drizzle.select().from(products); 112 | }); 113 | 114 | bench("Products: getInfo", async () => { 115 | for (const id of productIds) { 116 | await drizzle 117 | .select() 118 | .from(products) 119 | .leftJoin(suppliers, eq(products.supplierId, suppliers.id)) 120 | .where(eq(products.id, id)); 121 | } 122 | }); 123 | 124 | bench("Products: search", async () => { 125 | for (const it of productSearches) { 126 | await drizzle 127 | .select() 128 | .from(products) 129 | .where(ilike(products.name, `%${it}%`)); 130 | } 131 | }); 132 | 133 | bench("Orders: getAll", async () => { 134 | await drizzle 135 | .select({ 136 | id: orders.id, 137 | shippedDate: orders.shippedDate, 138 | shipName: orders.shipName, 139 | shipCity: orders.shipCity, 140 | shipCountry: orders.shipCountry, 141 | productsCount: sql`count(${details.productId})`.as(), 142 | quantitySum: sql`sum(${details.quantity})`.as(), 143 | totalPrice: 144 | sql`sum(${details.quantity} * ${details.unitPrice})`.as(), 145 | }) 146 | .from(orders) 147 | .leftJoin(details, eq(orders.id, details.orderId)) 148 | .groupBy(orders.id) 149 | .orderBy(asc(orders.id)); 150 | }); 151 | 152 | bench("Orders: getById", async () => { 153 | for (const id of orderIds) { 154 | await drizzle 155 | .select({ 156 | id: orders.id, 157 | shippedDate: orders.shippedDate, 158 | shipName: orders.shipName, 159 | shipCity: orders.shipCity, 160 | shipCountry: orders.shipCountry, 161 | productsCount: sql`count(${details.productId})`.as(), 162 | quantitySum: sql`sum(${details.quantity})`.as(), 163 | totalPrice: 164 | sql`sum(${details.quantity} * ${details.unitPrice})`.as(), 165 | }) 166 | .from(orders) 167 | .leftJoin(details, eq(orders.id, details.orderId)) 168 | .where(eq(orders.id, id)) 169 | .groupBy(orders.id) 170 | .orderBy(asc(orders.id)); 171 | } 172 | }); 173 | 174 | bench("Orders: getInfo", async () => { 175 | for (const id of orderIds) { 176 | await drizzle 177 | .select() 178 | .from(orders) 179 | .leftJoin(details, eq(orders.id, details.orderId)) 180 | .leftJoin(products, eq(details.productId, products.id)) 181 | .where(eq(orders.id, id)); 182 | } 183 | }); 184 | 185 | const main = async () => { 186 | const connectionString = 187 | process.env["PG_CONNECTION_STRING"] ?? (await createDockerDB()); 188 | 189 | let sleep = 250; 190 | let timeLeft = 5000; 191 | let connected = false; 192 | let lastError: unknown | undefined; 193 | const pgjs = postgres(connectionString); 194 | const sql_script = fs.readFileSync(path.resolve("data/init-db.sql"), "utf-8"); 195 | drizzle = drzl(pgjs); 196 | do { 197 | try { 198 | await drizzle.execute(sql.raw(sql_script)); 199 | connected = true; 200 | break; 201 | } catch (e) { 202 | lastError = e; 203 | await new Promise((resolve) => setTimeout(resolve, sleep)); 204 | timeLeft -= sleep; 205 | } 206 | } while (timeLeft > 0); 207 | if (!connected) { 208 | console.error("Cannot connect to Postgres"); 209 | throw lastError; 210 | } 211 | 212 | // drizzle connect 213 | 214 | await run(); 215 | process.exit(0); 216 | }; 217 | 218 | main(); 219 | -------------------------------------------------------------------------------- /src/drizzle/benchmark-pg.ts: -------------------------------------------------------------------------------- 1 | import { run, bench } from "mitata"; 2 | import Docker from "dockerode"; 3 | import { v4 as uuid } from "uuid"; 4 | import getPort from "get-port"; 5 | import { asc, eq, ilike } from "drizzle-orm"; 6 | import dotenv from "dotenv"; 7 | import { sql } from "drizzle-orm"; 8 | import * as fs from "fs"; 9 | import * as path from "path"; 10 | import { 11 | employees, 12 | customers, 13 | suppliers, 14 | products, 15 | orders, 16 | details, 17 | } from "./schema"; 18 | import { 19 | customerIds, 20 | employeeIds, 21 | orderIds, 22 | productIds, 23 | customerSearches, 24 | productSearches, 25 | supplierIds, 26 | } from "../common/meta"; 27 | import { alias } from "drizzle-orm/pg-core"; 28 | import { NodePgDatabase, drizzle as drzl } from "drizzle-orm/node-postgres"; 29 | import * as pg from "pg"; 30 | const { Pool } = pg.default; 31 | 32 | dotenv.config(); 33 | // const pool = new Pool({ connectionString: process.env.DATABASE_URL }); 34 | // const connector = new PgConnector(pool); 35 | 36 | let drizzle: NodePgDatabase; 37 | 38 | async function createDockerDB(): Promise { 39 | const docker = new Docker(); 40 | const port = await getPort({ port: 5432 }); 41 | const image = "postgres"; 42 | 43 | await docker.pull(image); 44 | 45 | const pgContainer = await docker.createContainer({ 46 | Image: image, 47 | Env: [ 48 | "POSTGRES_PASSWORD=postgres", 49 | "POSTGRES_USER=postgres", 50 | "POSTGRES_DB=postgres", 51 | ], 52 | name: `benchmarks-tests-${uuid()}`, 53 | HostConfig: { 54 | AutoRemove: true, 55 | PortBindings: { 56 | "5432/tcp": [{ HostPort: `${port}` }], 57 | }, 58 | }, 59 | }); 60 | 61 | await pgContainer.start(); 62 | 63 | return `postgres://postgres:postgres@localhost:${port}/postgres`; 64 | } 65 | 66 | bench("Customers: getAll", async () => { 67 | await drizzle.select().from(customers); 68 | }); 69 | 70 | bench("Customers: getInfo", async () => { 71 | for (const id of customerIds) { 72 | await drizzle.select().from(customers).where(eq(customers.id, id)); 73 | } 74 | }); 75 | 76 | bench("Customers: search", async () => { 77 | for (const it of customerSearches) { 78 | await drizzle 79 | .select() 80 | .from(customers) 81 | .where(ilike(customers.companyName, `%${it}%`)); 82 | } 83 | }); 84 | 85 | bench("Employees: getAll", async () => { 86 | await drizzle.select().from(employees); 87 | }); 88 | 89 | bench("Employees: getInfo", async () => { 90 | const e2 = alias(employees, "recipient"); 91 | 92 | for (const id of employeeIds) { 93 | await drizzle 94 | .select() 95 | .from(employees) 96 | .leftJoin(e2, eq(e2.id, employees.recipientId)) 97 | .where(eq(employees.id, id)); 98 | } 99 | }); 100 | 101 | bench("Suppliers: getAll", async () => { 102 | await drizzle.select().from(suppliers); 103 | }); 104 | 105 | bench("Suppliers: getInfo", async () => { 106 | for (const id of supplierIds) { 107 | await drizzle.select().from(suppliers).where(eq(suppliers.id, id)); 108 | } 109 | }); 110 | 111 | bench("Products: getAll", async () => { 112 | await drizzle.select().from(products); 113 | }); 114 | 115 | bench("Products: getInfo", async () => { 116 | for (const id of productIds) { 117 | await drizzle 118 | .select() 119 | .from(products) 120 | .leftJoin(suppliers, eq(products.supplierId, suppliers.id)) 121 | .where(eq(products.id, id)); 122 | } 123 | }); 124 | 125 | bench("Products: search", async () => { 126 | for (const it of productSearches) { 127 | await drizzle 128 | .select() 129 | .from(products) 130 | .where(ilike(products.name, `%${it}%`)); 131 | } 132 | }); 133 | 134 | bench("Orders: getAll", async () => { 135 | await drizzle 136 | .select({ 137 | id: orders.id, 138 | shippedDate: orders.shippedDate, 139 | shipName: orders.shipName, 140 | shipCity: orders.shipCity, 141 | shipCountry: orders.shipCountry, 142 | productsCount: sql`count(${details.productId})`.as(), 143 | quantitySum: sql`sum(${details.quantity})`.as(), 144 | totalPrice: 145 | sql`sum(${details.quantity} * ${details.unitPrice})`.as(), 146 | }) 147 | .from(orders) 148 | .leftJoin(details, eq(orders.id, details.orderId)) 149 | .groupBy(orders.id) 150 | .orderBy(asc(orders.id)); 151 | }); 152 | 153 | bench("Orders: getById", async () => { 154 | for (const id of orderIds) { 155 | await drizzle 156 | .select({ 157 | id: orders.id, 158 | shippedDate: orders.shippedDate, 159 | shipName: orders.shipName, 160 | shipCity: orders.shipCity, 161 | shipCountry: orders.shipCountry, 162 | productsCount: sql`count(${details.productId})`.as(), 163 | quantitySum: sql`sum(${details.quantity})`.as(), 164 | totalPrice: 165 | sql`sum(${details.quantity} * ${details.unitPrice})`.as(), 166 | }) 167 | .from(orders) 168 | .leftJoin(details, eq(orders.id, details.orderId)) 169 | .where(eq(orders.id, id)) 170 | .groupBy(orders.id) 171 | .orderBy(asc(orders.id)); 172 | } 173 | }); 174 | 175 | bench("Orders: getInfo", async () => { 176 | for (const id of orderIds) { 177 | await drizzle 178 | .select() 179 | .from(orders) 180 | .leftJoin(details, eq(orders.id, details.orderId)) 181 | .leftJoin(products, eq(details.productId, products.id)) 182 | .where(eq(orders.id, id)); 183 | } 184 | }); 185 | 186 | const main = async () => { 187 | const connectionString = 188 | process.env["PG_CONNECTION_STRING"] ?? (await createDockerDB()); 189 | 190 | let sleep = 250; 191 | let timeLeft = 5000; 192 | let connected = false; 193 | let lastError: unknown | undefined; 194 | const pool = new Pool({ connectionString }); 195 | do { 196 | try { 197 | await pool.connect(); 198 | connected = true; 199 | break; 200 | } catch (e) { 201 | lastError = e; 202 | await new Promise((resolve) => setTimeout(resolve, sleep)); 203 | timeLeft -= sleep; 204 | } 205 | } while (timeLeft > 0); 206 | if (!connected) { 207 | console.error("Cannot connect to Postgres"); 208 | throw lastError; 209 | } 210 | const sql_script = fs.readFileSync(path.resolve("data/init-db.sql"), "utf-8"); 211 | await pool.query(sql_script); 212 | 213 | drizzle = drzl(pool); 214 | 215 | await run(); 216 | process.exit(0) 217 | }; 218 | 219 | main(); 220 | -------------------------------------------------------------------------------- /src/drizzle/benchmark-pgnative.ts: -------------------------------------------------------------------------------- 1 | import { run, bench } from "mitata"; 2 | import Docker from "dockerode"; 3 | import { v4 as uuid } from "uuid"; 4 | import getPort from "get-port"; 5 | import { asc, eq, ilike } from "drizzle-orm"; 6 | import dotenv from "dotenv"; 7 | import { sql } from "drizzle-orm"; 8 | import * as fs from "fs"; 9 | import * as path from "path"; 10 | import { 11 | employees, 12 | customers, 13 | suppliers, 14 | products, 15 | orders, 16 | details, 17 | } from "./schema"; 18 | import { 19 | customerIds, 20 | employeeIds, 21 | orderIds, 22 | productIds, 23 | customerSearches, 24 | productSearches, 25 | supplierIds, 26 | } from "../common/meta"; 27 | import { alias } from "drizzle-orm/pg-core"; 28 | import { NodePgDatabase, drizzle as drzl } from "drizzle-orm/node-postgres"; 29 | import * as pg from "pg"; 30 | const { Pool } = pg.default.native; 31 | 32 | dotenv.config(); 33 | // const pool = new Pool({ connectionString: process.env.DATABASE_URL }); 34 | // const connector = new PgConnector(pool); 35 | 36 | let drizzle: NodePgDatabase; 37 | 38 | async function createDockerDB(): Promise { 39 | const docker = new Docker(); 40 | const port = await getPort({ port: 5432 }); 41 | const image = "postgres"; 42 | 43 | await docker.pull(image); 44 | 45 | const pgContainer = await docker.createContainer({ 46 | Image: image, 47 | Env: [ 48 | "POSTGRES_PASSWORD=postgres", 49 | "POSTGRES_USER=postgres", 50 | "POSTGRES_DB=postgres", 51 | ], 52 | name: `benchmarks-tests-${uuid()}`, 53 | HostConfig: { 54 | AutoRemove: true, 55 | PortBindings: { 56 | "5432/tcp": [{ HostPort: `${port}` }], 57 | }, 58 | }, 59 | }); 60 | 61 | await pgContainer.start(); 62 | 63 | return `postgres://postgres:postgres@localhost:${port}/postgres`; 64 | } 65 | 66 | bench("Customers: getAll", async () => { 67 | await drizzle.select().from(customers); 68 | }); 69 | 70 | bench("Customers: getInfo", async () => { 71 | for (const id of customerIds) { 72 | await drizzle.select().from(customers).where(eq(customers.id, id)); 73 | } 74 | }); 75 | 76 | bench("Customers: search", async () => { 77 | for (const it of customerSearches) { 78 | await drizzle 79 | .select() 80 | .from(customers) 81 | .where(ilike(customers.companyName, `%${it}%`)); 82 | } 83 | }); 84 | 85 | bench("Employees: getAll", async () => { 86 | await drizzle.select().from(employees); 87 | }); 88 | 89 | bench("Employees: getInfo", async () => { 90 | const e2 = alias(employees, "recipient"); 91 | 92 | for (const id of employeeIds) { 93 | await drizzle 94 | .select() 95 | .from(employees) 96 | .leftJoin(e2, eq(e2.id, employees.recipientId)) 97 | .where(eq(employees.id, id)); 98 | } 99 | }); 100 | 101 | bench("Suppliers: getAll", async () => { 102 | await drizzle.select().from(suppliers); 103 | }); 104 | 105 | bench("Suppliers: getInfo", async () => { 106 | for (const id of supplierIds) { 107 | await drizzle.select().from(suppliers).where(eq(suppliers.id, id)); 108 | } 109 | }); 110 | 111 | bench("Products: getAll", async () => { 112 | await drizzle.select().from(products); 113 | }); 114 | 115 | bench("Products: getInfo", async () => { 116 | for (const id of productIds) { 117 | await drizzle 118 | .select() 119 | .from(products) 120 | .leftJoin(suppliers, eq(products.supplierId, suppliers.id)) 121 | .where(eq(products.id, id)); 122 | } 123 | }); 124 | 125 | bench("Products: search", async () => { 126 | for (const it of productSearches) { 127 | await drizzle 128 | .select() 129 | .from(products) 130 | .where(ilike(products.name, `%${it}%`)); 131 | } 132 | }); 133 | 134 | bench("Orders: getAll", async () => { 135 | await drizzle 136 | .select({ 137 | id: orders.id, 138 | shippedDate: orders.shippedDate, 139 | shipName: orders.shipName, 140 | shipCity: orders.shipCity, 141 | shipCountry: orders.shipCountry, 142 | productsCount: sql`count(${details.productId})`.as(), 143 | quantitySum: sql`sum(${details.quantity})`.as(), 144 | totalPrice: 145 | sql`sum(${details.quantity} * ${details.unitPrice})`.as(), 146 | }) 147 | .from(orders) 148 | .leftJoin(details, eq(orders.id, details.orderId)) 149 | .groupBy(orders.id) 150 | .orderBy(asc(orders.id)); 151 | }); 152 | 153 | bench("Orders: getById", async () => { 154 | for (const id of orderIds) { 155 | await drizzle 156 | .select({ 157 | id: orders.id, 158 | shippedDate: orders.shippedDate, 159 | shipName: orders.shipName, 160 | shipCity: orders.shipCity, 161 | shipCountry: orders.shipCountry, 162 | productsCount: sql`count(${details.productId})`.as(), 163 | quantitySum: sql`sum(${details.quantity})`.as(), 164 | totalPrice: 165 | sql`sum(${details.quantity} * ${details.unitPrice})`.as(), 166 | }) 167 | .from(orders) 168 | .leftJoin(details, eq(orders.id, details.orderId)) 169 | .where(eq(orders.id, id)) 170 | .groupBy(orders.id) 171 | .orderBy(asc(orders.id)); 172 | } 173 | }); 174 | 175 | bench("Orders: getInfo", async () => { 176 | for (const id of orderIds) { 177 | await drizzle 178 | .select() 179 | .from(orders) 180 | .leftJoin(details, eq(orders.id, details.orderId)) 181 | .leftJoin(products, eq(details.productId, products.id)) 182 | .where(eq(orders.id, id)); 183 | } 184 | }); 185 | 186 | const main = async () => { 187 | const connectionString = 188 | process.env["PG_CONNECTION_STRING"] ?? (await createDockerDB()); 189 | 190 | let sleep = 250; 191 | let timeLeft = 5000; 192 | let connected = false; 193 | let lastError: unknown | undefined; 194 | const pool = new Pool({ connectionString }); 195 | do { 196 | try { 197 | await pool.connect(); 198 | connected = true; 199 | break; 200 | } catch (e) { 201 | lastError = e; 202 | await new Promise((resolve) => setTimeout(resolve, sleep)); 203 | timeLeft -= sleep; 204 | } 205 | } while (timeLeft > 0); 206 | if (!connected) { 207 | console.error("Cannot connect to Postgres"); 208 | throw lastError; 209 | } 210 | const sql_script = fs.readFileSync(path.resolve("data/init-db.sql"), "utf-8"); 211 | await pool.query(sql_script); 212 | 213 | drizzle = drzl(pool); 214 | 215 | await run(); 216 | process.exit(0) 217 | }; 218 | 219 | main(); 220 | -------------------------------------------------------------------------------- /src/kysely/benchmark.ts: -------------------------------------------------------------------------------- 1 | import { run, bench } from "mitata"; 2 | import { Kysely, sql, PostgresDialect } from "kysely"; 3 | import { Database } from "./db"; 4 | import { 5 | customerIds, 6 | employeeIds, 7 | orderIds, 8 | productIds, 9 | productSearches, 10 | customerSearches, 11 | supplierIds, 12 | } from "../common/meta"; 13 | 14 | import pkg from "pg"; 15 | import dotenv from "dotenv"; 16 | 17 | const { Pool } = pkg; 18 | dotenv.config(); 19 | 20 | const { DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD } = process.env; 21 | const db = new Kysely({ 22 | dialect: new PostgresDialect({ 23 | pool: new Pool({ 24 | host: DB_HOST, 25 | port: +DB_PORT!, 26 | user: DB_USER, 27 | password: DB_PASSWORD, 28 | database: DB_NAME, 29 | }), 30 | }), 31 | }); 32 | 33 | bench("Kysely ORM Customers: getAll", async () => { 34 | await db.selectFrom("customers").selectAll().execute(); 35 | }); 36 | bench("Kysely ORM Customers: getInfo", async () => { 37 | for (const id of customerIds) { 38 | await db 39 | .selectFrom("customers") 40 | .selectAll() 41 | .where("customers.id", "=", id) 42 | .execute(); 43 | } 44 | }); 45 | bench("Kysely ORM Customers: search", async () => { 46 | for (const it of customerSearches) { 47 | await db 48 | .selectFrom("customers") 49 | .selectAll() 50 | .where(sql`lower(company_name)`, "like", `%${it}%`) 51 | .execute(); 52 | } 53 | }); 54 | 55 | bench("Kysely ORM Employees: getAll", async () => { 56 | await db.selectFrom("employees").selectAll().execute(); 57 | }); 58 | bench("Kysely ORM Employees: getInfo", async () => { 59 | for (const id of employeeIds) { 60 | await db 61 | .selectFrom("employees as e1") 62 | .selectAll() 63 | .where("e1.id", "=", id) 64 | .leftJoin( 65 | db 66 | .selectFrom("employees as e2") 67 | .select([ 68 | "id as e2_id", 69 | "last_name as e2_last_name", 70 | "first_name as e2_first_name", 71 | "title as e2_title", 72 | "title_of_courtesy as e2_title_of_courtesy", 73 | "birth_date as e2_birth_date", 74 | "hire_date as e2_hire_date", 75 | "address as e2_address", 76 | "city as e2_city", 77 | "postal_code as e2_postal_code", 78 | "country as e2_country", 79 | "home_phone as e2_home_phone", 80 | "extension as e2_extension", 81 | "notes as e2_notes", 82 | "recipient_id as e2_recipient_id", 83 | ]) 84 | .as("e2"), 85 | "e2.e2_id", 86 | "e1.recipient_id" 87 | ) 88 | .execute(); 89 | } 90 | }); 91 | 92 | bench("Kysely ORM Suppliers: getAll", async () => { 93 | await db.selectFrom("suppliers").selectAll().execute(); 94 | }); 95 | bench("Kysely ORM Suppliers: getInfo", async () => { 96 | for (const id of supplierIds) { 97 | await db 98 | .selectFrom("suppliers") 99 | .selectAll() 100 | .where("suppliers.id", "=", id) 101 | .execute(); 102 | } 103 | }); 104 | 105 | bench("Kysely ORM Products: getAll", async () => { 106 | await db.selectFrom("products").selectAll().execute(); 107 | }); 108 | bench("Kysely ORM Products: getInfo", async () => { 109 | for (const id of productIds) { 110 | await db 111 | .selectFrom("products") 112 | .selectAll() 113 | .where("products.id", "=", id) 114 | .leftJoin( 115 | db 116 | .selectFrom("suppliers") 117 | .select([ 118 | "id as s_id", 119 | "company_name", 120 | "contact_name", 121 | "contact_title", 122 | "address", 123 | "city", 124 | "region", 125 | "postal_code", 126 | "country", 127 | "phone", 128 | ]) 129 | .as("s1"), 130 | "s1.s_id", 131 | "products.supplier_id" 132 | ) 133 | .execute(); 134 | } 135 | }); 136 | bench("Kysely ORM Products: search", async () => { 137 | for (const it of productSearches) { 138 | await db 139 | .selectFrom("products") 140 | .selectAll() 141 | .where(sql`lower(name)`, "like", `%${it}%`) 142 | .execute(); 143 | } 144 | }); 145 | 146 | bench("Kysely ORM Orders: getAll", async () => { 147 | await db 148 | .selectFrom("orders") 149 | .select([ 150 | "orders.id", 151 | "orders.shipped_date", 152 | "orders.ship_name", 153 | "orders.ship_city", 154 | "orders.ship_country", 155 | db.fn.count("product_id").as("products_count"), 156 | db.fn.sum("quantity").as("quantity_sum"), 157 | sql`SUM(quantity * unit_price)`.as("total_price"), 158 | ]) 159 | .leftJoin("order_details", "order_details.order_id", "orders.id") 160 | .groupBy("orders.id") 161 | .orderBy("orders.id", "asc") 162 | .execute(); 163 | }); 164 | 165 | bench("Kysely ORM Orders: getById", async () => { 166 | for (const id of orderIds) { 167 | await db 168 | .selectFrom("orders") 169 | .select([ 170 | "orders.id", 171 | "orders.shipped_date", 172 | "orders.ship_name", 173 | "orders.ship_city", 174 | "orders.ship_country", 175 | db.fn.count("product_id").as("products_count"), 176 | db.fn.sum("quantity").as("quantity_sum"), 177 | sql`SUM(quantity * unit_price)`.as("total_price"), 178 | ]) 179 | .where("orders.id", "=", id) 180 | .leftJoin("order_details", "order_details.order_id", "orders.id") 181 | .groupBy("orders.id") 182 | .orderBy("orders.id", "asc") 183 | .execute(); 184 | } 185 | }); 186 | 187 | bench("Kysely ORM Orders: getInfo", async () => { 188 | for (const id of orderIds) { 189 | await db 190 | .selectFrom("orders") 191 | .selectAll() 192 | .where("id", "=", id) 193 | .leftJoin( 194 | db 195 | .selectFrom("order_details") 196 | .select([ 197 | "discount", 198 | "order_id", 199 | "product_id", 200 | "unit_price", 201 | "quantity", 202 | ]) 203 | .as("od"), 204 | "od.order_id", 205 | "orders.id" 206 | ) 207 | .leftJoin( 208 | db 209 | .selectFrom("products") 210 | .select([ 211 | "products.id as p_id", 212 | "name", 213 | "qt_per_unit", 214 | "products.unit_price as p_unit_price", 215 | "units_in_stock", 216 | "units_on_order", 217 | "reorder_level", 218 | "discontinued", 219 | "supplier_id", 220 | ]) 221 | .as("p"), 222 | "p.p_id", 223 | "od.product_id" 224 | ) 225 | .execute(); 226 | } 227 | }); 228 | 229 | const main = async () => { 230 | await run(); 231 | }; 232 | main(); 233 | -------------------------------------------------------------------------------- /src/drizzle/benchmark-pgp.ts: -------------------------------------------------------------------------------- 1 | import { run, bench } from "mitata"; 2 | import Docker from "dockerode"; 3 | import { v4 as uuid } from "uuid"; 4 | import getPort from "get-port"; 5 | import { asc, eq, ilike, placeholder } from "drizzle-orm"; 6 | import dotenv from "dotenv"; 7 | import { sql } from "drizzle-orm"; 8 | import * as fs from "fs"; 9 | import * as path from "path"; 10 | import { 11 | employees, 12 | customers, 13 | suppliers, 14 | products, 15 | orders, 16 | details, 17 | } from "./schema"; 18 | import { 19 | customerIds, 20 | employeeIds, 21 | orderIds, 22 | productIds, 23 | customerSearches, 24 | productSearches, 25 | supplierIds, 26 | } from "../common/meta"; 27 | import { alias } from "drizzle-orm/pg-core"; 28 | import { NodePgDatabase, drizzle as drzl } from "drizzle-orm/node-postgres"; 29 | import * as pg from "pg"; 30 | const { Pool } = pg.default; 31 | 32 | dotenv.config(); 33 | // const pool = new Pool({ connectionString: process.env.DATABASE_URL }); 34 | // const connector = new PgConnector(pool); 35 | 36 | const port = 54321; 37 | const dburl = `postgres://postgres:postgres@localhost:${port}/postgres`; 38 | const pool = new Pool({ connectionString: dburl }); 39 | const drizzle = drzl(pool); 40 | 41 | async function createDockerDB(desiredPort: number) { 42 | const docker = new Docker(); 43 | const port = await getPort({ port: desiredPort }); 44 | if (desiredPort !== port) { 45 | throw new Error(`${desiredPort} port is taken`); 46 | } 47 | const image = "postgres"; 48 | 49 | await docker.pull(image); 50 | 51 | const pgContainer = await docker.createContainer({ 52 | Image: image, 53 | Env: [ 54 | "POSTGRES_PASSWORD=postgres", 55 | "POSTGRES_USER=postgres", 56 | "POSTGRES_DB=postgres", 57 | ], 58 | name: `benchmarks-tests-${uuid()}`, 59 | HostConfig: { 60 | AutoRemove: true, 61 | PortBindings: { 62 | "5432/tcp": [{ HostPort: `${port}` }], 63 | }, 64 | }, 65 | }); 66 | 67 | await pgContainer.start(); 68 | } 69 | const p1 = drizzle.select().from(customers).prepare("p1"); 70 | bench("Customers: getAll", async () => { 71 | await p1.execute(); 72 | }); 73 | 74 | const p2 = drizzle 75 | .select() 76 | .from(customers) 77 | .where(eq(customers.id, placeholder("id"))) 78 | .prepare("p2"); 79 | 80 | bench("Customers: get by id", async () => { 81 | for (const id of customerIds) { 82 | await p2.execute({ id: id }); 83 | } 84 | }); 85 | 86 | const p3 = drizzle 87 | .select() 88 | .from(customers) 89 | .where(ilike(customers.companyName, placeholder("term"))) 90 | .prepare("p3"); 91 | bench("Customers: search", async () => { 92 | for (const it of customerSearches) { 93 | await p3.execute({ term: `%${it}%` }); 94 | } 95 | }); 96 | 97 | const p4 = drizzle.select().from(employees).prepare("p4"); 98 | bench("Employees: getAll", async () => { 99 | await p4.execute(); 100 | }); 101 | 102 | const e2 = alias(employees, "recipient"); 103 | const p5 = drizzle 104 | .select() 105 | .from(employees) 106 | .leftJoin(e2, eq(e2.id, employees.recipientId)) 107 | .where(eq(employees.id, placeholder("id"))) 108 | .prepare("p5"); 109 | 110 | bench("Employees: get by id", async () => { 111 | for (const id of employeeIds) { 112 | await p5.execute({ 113 | id, 114 | }); 115 | } 116 | }); 117 | 118 | const p6 = drizzle.select().from(suppliers).prepare("p6"); 119 | bench("Suppliers: getAll", async () => { 120 | await p6.execute(); 121 | }); 122 | 123 | const p7 = drizzle 124 | .select() 125 | .from(suppliers) 126 | .where(eq(suppliers.id, placeholder("id"))) 127 | .prepare("p7"); 128 | bench("Suppliers: get by id", async () => { 129 | for (const id of supplierIds) { 130 | await p6.execute({ id }); 131 | } 132 | }); 133 | 134 | const p8 = drizzle.select().from(products).prepare("p8"); 135 | bench("Products: getAll", async () => { 136 | await p8.execute(); 137 | }); 138 | 139 | const p9 = drizzle 140 | .select() 141 | .from(products) 142 | .leftJoin(suppliers, eq(products.supplierId, suppliers.id)) 143 | .where(eq(products.id, placeholder("id"))) 144 | .prepare("p9"); 145 | bench("Products: get by id", async () => { 146 | for (const id of productIds) { 147 | await p9.execute({ id }); 148 | } 149 | }); 150 | 151 | const p10 = drizzle 152 | .select() 153 | .from(products) 154 | .where(ilike(products.name, placeholder("term"))) 155 | .prepare("p10"); 156 | bench("Products: search", async () => { 157 | for (const it of productSearches) { 158 | await p10.execute({ term: `%${it}%` }); 159 | } 160 | }); 161 | 162 | const p11 = drizzle 163 | .select({ 164 | id: orders.id, 165 | shippedDate: orders.shippedDate, 166 | shipName: orders.shipName, 167 | shipCity: orders.shipCity, 168 | shipCountry: orders.shipCountry, 169 | productsCount: sql`count(${details.productId})`.as(), 170 | quantitySum: sql`sum(${details.quantity})`.as(), 171 | totalPrice: 172 | sql`sum(${details.quantity} * ${details.unitPrice})`.as(), 173 | }) 174 | .from(orders) 175 | .leftJoin(details, eq(orders.id, details.orderId)) 176 | .groupBy(orders.id) 177 | .orderBy(asc(orders.id)) 178 | .prepare("p11"); 179 | 180 | bench("Orders: get all with details", async () => { 181 | await p11.execute({}); 182 | }); 183 | 184 | const p12 = drizzle 185 | .select({ 186 | id: orders.id, 187 | shippedDate: orders.shippedDate, 188 | shipName: orders.shipName, 189 | shipCity: orders.shipCity, 190 | shipCountry: orders.shipCountry, 191 | productsCount: sql`count(${details.productId})`.as(), 192 | quantitySum: sql`sum(${details.quantity})`.as(), 193 | totalPrice: 194 | sql`sum(${details.quantity} * ${details.unitPrice})`.as(), 195 | }) 196 | .from(orders) 197 | .leftJoin(details, eq(orders.id, details.orderId)) 198 | .where(eq(orders.id, placeholder("id"))) 199 | .groupBy(orders.id) 200 | .orderBy(asc(orders.id)) 201 | .prepare("p12"); 202 | 203 | bench("Orders: get by id with details", async () => { 204 | for (const id of orderIds) { 205 | await p12.execute({ id }); 206 | } 207 | }); 208 | 209 | const p13 = drizzle 210 | .select() 211 | .from(orders) 212 | .leftJoin(details, eq(orders.id, details.orderId)) 213 | .leftJoin(products, eq(details.productId, products.id)) 214 | .where(eq(orders.id, placeholder("id"))) 215 | .prepare("p13"); 216 | 217 | bench("Orders: get by id", async () => { 218 | for (const id of orderIds) { 219 | await p13.execute({ id }); 220 | } 221 | }); 222 | 223 | const main = async () => { 224 | await createDockerDB(port); 225 | 226 | let sleep = 250; 227 | let timeLeft = 5000; 228 | let connected = false; 229 | let lastError: unknown | undefined; 230 | do { 231 | try { 232 | await pool.connect(); 233 | connected = true; 234 | break; 235 | } catch (e) { 236 | lastError = e; 237 | await new Promise((resolve) => setTimeout(resolve, sleep)); 238 | timeLeft -= sleep; 239 | } 240 | } while (timeLeft > 0); 241 | if (!connected) { 242 | console.error("Cannot connect to Postgres"); 243 | throw lastError; 244 | } 245 | const sql_script = fs.readFileSync(path.resolve("data/init-db.sql"), "utf-8"); 246 | await pool.query(sql_script); 247 | 248 | await run(); 249 | process.exit(0); 250 | }; 251 | 252 | main(); 253 | -------------------------------------------------------------------------------- /src/drizzle/benchmark-pgrqbp.ts: -------------------------------------------------------------------------------- 1 | import { run, bench } from "mitata"; 2 | import Docker from "dockerode"; 3 | import { v4 as uuid } from "uuid"; 4 | import getPort from "get-port"; 5 | import { asc, eq, ilike, placeholder } from "drizzle-orm"; 6 | import dotenv from "dotenv"; 7 | import { sql } from "drizzle-orm"; 8 | import * as fs from "fs"; 9 | import * as path from "path"; 10 | import { 11 | employees, 12 | customers, 13 | suppliers, 14 | products, 15 | orders, 16 | details, 17 | } from "./schema"; 18 | import * as schema from "./schema"; 19 | import { 20 | customerIds, 21 | employeeIds, 22 | orderIds, 23 | productIds, 24 | customerSearches, 25 | productSearches, 26 | supplierIds, 27 | } from "../common/meta"; 28 | import { alias } from "drizzle-orm/pg-core"; 29 | import { NodePgDatabase, drizzle as drzl } from "drizzle-orm/node-postgres"; 30 | import * as pg from "pg"; 31 | const { Pool } = pg.default; 32 | 33 | dotenv.config(); 34 | // const pool = new Pool({ connectionString: process.env.DATABASE_URL }); 35 | // const connector = new PgConnector(pool); 36 | 37 | const port = 54321; 38 | const dburl = `postgres://postgres:postgres@localhost:${port}/postgres`; 39 | const pool = new Pool({ connectionString: dburl }); 40 | const drizzle = drzl(pool, { schema }); 41 | 42 | async function createDockerDB(desiredPort: number) { 43 | const docker = new Docker(); 44 | const port = await getPort({ port: desiredPort }); 45 | if (desiredPort !== port) { 46 | throw new Error(`${desiredPort} port is taken`); 47 | } 48 | const image = "postgres"; 49 | 50 | await docker.pull(image); 51 | 52 | const pgContainer = await docker.createContainer({ 53 | Image: image, 54 | Env: [ 55 | "POSTGRES_PASSWORD=postgres", 56 | "POSTGRES_USER=postgres", 57 | "POSTGRES_DB=postgres", 58 | ], 59 | name: `benchmarks-tests-${uuid()}`, 60 | HostConfig: { 61 | AutoRemove: true, 62 | PortBindings: { 63 | "5432/tcp": [{ HostPort: `${port}` }], 64 | }, 65 | }, 66 | }); 67 | 68 | await pgContainer.start(); 69 | } 70 | 71 | const p1 = drizzle.query.customers.findMany().prepare("p1"); 72 | bench("Customers: getAll", async () => { 73 | await p1.execute(); 74 | }); 75 | 76 | const p2 = drizzle.query.customers 77 | .findFirst({ 78 | where: eq(customers.id, placeholder("id")), 79 | }) 80 | .prepare("p2"); 81 | bench("Customers: get by id", async () => { 82 | for (const id of customerIds) { 83 | await p2.execute({ id: id }); 84 | } 85 | }); 86 | 87 | const p3 = drizzle.query.customers 88 | .findMany({ 89 | where: ilike(customers.companyName, placeholder("term")), 90 | }) 91 | .prepare("p3"); 92 | bench("Customers: search", async () => { 93 | for (const it of customerSearches) { 94 | await p3.execute({ term: `%${it}%` }); 95 | } 96 | }); 97 | 98 | const p4 = drizzle.query.employees.findMany().prepare("p4"); 99 | bench("Employees: getAll", async () => { 100 | await p4.execute(); 101 | }); 102 | 103 | const e2 = alias(employees, "recipient"); 104 | const p5 = drizzle.query.employees 105 | .findMany({ 106 | with: { 107 | recipient: true, 108 | }, 109 | where: eq(employees.id, placeholder("id")), 110 | }) 111 | .prepare("p5"); 112 | 113 | bench("Employees: get by id", async () => { 114 | for (const id of employeeIds) { 115 | await p5.execute({ 116 | id, 117 | }); 118 | } 119 | }); 120 | 121 | const p6 = drizzle.query.suppliers.findMany().prepare("p6"); 122 | bench("Suppliers: getAll", async () => { 123 | await p6.execute(); 124 | }); 125 | 126 | const p7 = drizzle.query.suppliers 127 | .findFirst({ 128 | where: eq(suppliers.id, placeholder("id")), 129 | }) 130 | .prepare("p7"); 131 | bench("Suppliers: get by id", async () => { 132 | for (const id of supplierIds) { 133 | await p6.execute({ id }); 134 | } 135 | }); 136 | 137 | const p8 = drizzle.query.products.findMany().prepare("p8"); 138 | bench("Products: getAll", async () => { 139 | await p8.execute(); 140 | }); 141 | 142 | const p9 = drizzle.query.products 143 | .findMany({ 144 | where: eq(products.id, placeholder("id")), 145 | with: { 146 | supplier: true, 147 | }, 148 | }) 149 | .prepare("p9"); 150 | bench("Products: get by id", async () => { 151 | for (const id of productIds) { 152 | await p9.execute({ id }); 153 | } 154 | }); 155 | 156 | const p10 = drizzle.query.products 157 | .findMany({ 158 | where: ilike(products.name, placeholder("term")), 159 | }) 160 | .prepare("p10"); 161 | bench("Products: search", async () => { 162 | for (const it of productSearches) { 163 | await p10.execute({ term: `%${it}%` }); 164 | } 165 | }); 166 | 167 | const p11 = drizzle 168 | .select({ 169 | id: orders.id, 170 | shippedDate: orders.shippedDate, 171 | shipName: orders.shipName, 172 | shipCity: orders.shipCity, 173 | shipCountry: orders.shipCountry, 174 | productsCount: sql`count(${details.productId})`.as(), 175 | quantitySum: sql`sum(${details.quantity})`.as(), 176 | totalPrice: 177 | sql`sum(${details.quantity} * ${details.unitPrice})`.as(), 178 | }) 179 | .from(orders) 180 | .leftJoin(details, eq(orders.id, details.orderId)) 181 | .groupBy(orders.id) 182 | .orderBy(asc(orders.id)) 183 | .prepare("p11"); 184 | 185 | bench("Orders: get all with details", async () => { 186 | await p11.execute({}); 187 | }); 188 | 189 | const p12 = drizzle 190 | .select({ 191 | id: orders.id, 192 | shippedDate: orders.shippedDate, 193 | shipName: orders.shipName, 194 | shipCity: orders.shipCity, 195 | shipCountry: orders.shipCountry, 196 | productsCount: sql`count(${details.productId})`.as(), 197 | quantitySum: sql`sum(${details.quantity})`.as(), 198 | totalPrice: 199 | sql`sum(${details.quantity} * ${details.unitPrice})`.as(), 200 | }) 201 | .from(orders) 202 | .leftJoin(details, eq(orders.id, details.orderId)) 203 | .where(eq(orders.id, placeholder("id"))) 204 | .groupBy(orders.id) 205 | .orderBy(asc(orders.id)) 206 | .prepare("p12"); 207 | 208 | bench("Orders: get by id with details", async () => { 209 | for (const id of orderIds) { 210 | await p12.execute({ id }); 211 | } 212 | }); 213 | 214 | const p13 = drizzle.query.orders 215 | .findMany({ 216 | with: { 217 | details: { 218 | with: { 219 | product: true, 220 | }, 221 | }, 222 | }, 223 | where: eq(orders.id, placeholder("id")), 224 | }) 225 | .prepare("p13"); 226 | 227 | bench("Orders: get by id", async () => { 228 | for (const id of orderIds) { 229 | await p13.execute({ id }); 230 | } 231 | }); 232 | 233 | const main = async () => { 234 | await createDockerDB(port); 235 | 236 | let sleep = 250; 237 | let timeLeft = 5000; 238 | let connected = false; 239 | let lastError: unknown | undefined; 240 | do { 241 | try { 242 | await pool.connect(); 243 | connected = true; 244 | break; 245 | } catch (e) { 246 | lastError = e; 247 | await new Promise((resolve) => setTimeout(resolve, sleep)); 248 | timeLeft -= sleep; 249 | } 250 | } while (timeLeft > 0); 251 | if (!connected) { 252 | console.error("Cannot connect to Postgres"); 253 | throw lastError; 254 | } 255 | const sql_script = fs.readFileSync(path.resolve("data/init-db.sql"), "utf-8"); 256 | await pool.query(sql_script); 257 | 258 | await run(); 259 | process.exit(0); 260 | }; 261 | 262 | main(); 263 | -------------------------------------------------------------------------------- /src/pg-node_vs_drizzle.ts: -------------------------------------------------------------------------------- 1 | import { run, bench, group } from "mitata"; 2 | import { asc, eq, ilike } from "drizzle-orm/expressions"; 3 | 4 | dotenv.config(); 5 | import dotenv from "dotenv"; 6 | import { sql } from "drizzle-orm"; 7 | import { 8 | employees, 9 | customers, 10 | suppliers, 11 | products, 12 | orders, 13 | details, 14 | } from "../src/drizzle/schema"; 15 | import { 16 | customerIds, 17 | employeeIds, 18 | orderIds, 19 | productIds, 20 | customerSearches, 21 | productSearches, 22 | supplierIds, 23 | } from "../src/common/meta"; 24 | import { PgConnector, PgDatabase, alias } from "drizzle-orm-pg"; 25 | import { Pool } from "pg"; 26 | 27 | dotenv.config(); 28 | const pool = new Pool({ connectionString: process.env.DATABASE_URL }); 29 | const connector = new PgConnector(pool); 30 | let drizzle: PgDatabase; 31 | 32 | const { DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD } = process.env; 33 | 34 | const db = new Pool({ 35 | host: DB_HOST, 36 | port: +DB_PORT!, 37 | user: DB_USER, 38 | password: DB_PASSWORD, 39 | database: DB_NAME, 40 | }); 41 | 42 | group({ name: "Drizzle", summary: false }, () => { 43 | bench("Drizzle-ORM Customers: getAll", async () => { 44 | await drizzle.select(customers); 45 | }); 46 | 47 | bench("Drizzle-ORM Customers: getInfo", async () => { 48 | for (const id of customerIds) { 49 | await drizzle.select(customers).where(eq(customers.id, id)); 50 | } 51 | }); 52 | 53 | bench("Drizzle-ORM Customers: search", async () => { 54 | for (const it of customerSearches) { 55 | await drizzle 56 | .select(customers) 57 | .where(ilike(customers.companyName, `%${it}%`)); 58 | } 59 | }); 60 | 61 | bench("Drizzle-ORM Employees: getAll", async () => { 62 | await drizzle.select(employees); 63 | }); 64 | 65 | bench("Drizzle-ORM Employees: getInfo", async () => { 66 | const e2 = alias(employees, "recipient"); 67 | 68 | for (const id of employeeIds) { 69 | await drizzle 70 | .select(employees) 71 | .leftJoin(e2, eq(e2.id, employees.recipientId)) 72 | .where(eq(employees.id, id)); 73 | } 74 | }); 75 | 76 | bench("Drizzle-ORM Suppliers: getAll", async () => { 77 | await drizzle.select(suppliers); 78 | }); 79 | 80 | bench("Drizzle-ORM Suppliers: getInfo", async () => { 81 | for (const id of supplierIds) { 82 | await drizzle.select(suppliers).where(eq(suppliers.id, id)); 83 | } 84 | }); 85 | 86 | bench("Drizzle-ORM Products: getAll", async () => { 87 | await drizzle.select(products); 88 | }); 89 | 90 | bench("Drizzle-ORM Products: getInfo", async () => { 91 | for (const id of productIds) { 92 | await drizzle 93 | .select(products) 94 | .leftJoin(suppliers, eq(products.supplierId, suppliers.id)) 95 | .where(eq(products.id, id)); 96 | } 97 | }); 98 | 99 | bench("Drizzle-ORM Products: search", async () => { 100 | for (const it of productSearches) { 101 | await drizzle.select(products).where(ilike(products.name, `%${it}%`)); 102 | } 103 | }); 104 | 105 | // bench("Drizzle-ORM Orders: getAll", async () => { 106 | // await drizzle.select(orders) 107 | // .fields({ 108 | // id: orders.id, 109 | // shippedDate: orders.shippedDate, 110 | // shipName: orders.shipName, 111 | // shipCity: orders.shipCity, 112 | // shipCountry: orders.shipCountry, 113 | // productsCount: sql`count(${details.productId})`.as(), 114 | // quantitySum: sql`sum(${details.quantity})`.as(), 115 | // totalPrice: sql`sum(${details.quantity} * ${details.unitPrice})`.as(), 116 | // }) 117 | // .leftJoin(details, eq(orders.id, details.orderId)) 118 | // .groupBy(orders.id) 119 | // .orderBy(asc(orders.id)) 120 | // }); 121 | 122 | bench("Drizzle-ORM Orders: getInfo", async () => { 123 | for (const id of orderIds) { 124 | await drizzle 125 | .select(orders) 126 | .leftJoin(details, eq(orders.id, details.orderId)) 127 | .leftJoin(products, eq(details.productId, products.id)) 128 | .where(eq(orders.id, id)); 129 | } 130 | }); 131 | }); 132 | 133 | group({ name: "Pg Driver Prepared", summary: false }, () => { 134 | const query = { 135 | name: "Customers-getAll", 136 | text: 'select * from "customers"', 137 | }; 138 | bench("Pg Driver Customers: getAll", async () => { 139 | await db.query(query); 140 | }); 141 | 142 | const query2 = { 143 | name: "Customers-getInfo", 144 | text: 'select * from "customers" where "customers"."id" = $1', 145 | }; 146 | bench("Pg Driver Customers: getInfo", async () => { 147 | for await (const id of customerIds) { 148 | await db.query(query2, [id]); 149 | } 150 | }); 151 | 152 | const query3 = { 153 | name: "Customers-search", 154 | text: 'select * from "customers" where "customers"."company_name" ilike $1', 155 | }; 156 | bench("Pg Driver Customers: search", async () => { 157 | for await (const it of customerSearches) { 158 | await db.query(query3, [`%${it}%`]); 159 | } 160 | }); 161 | 162 | const query4 = { 163 | name: "Employees-getAll", 164 | text: 'select * from "employees"', 165 | }; 166 | bench("Pg Driver Employees: getAll", async () => { 167 | await db.query(query4); 168 | }); 169 | 170 | const query5 = { 171 | name: "Employees-getInfo", 172 | text: `select "e1".*, "e2"."last_name" as "reports_lname", "e2"."first_name" as "reports_fname" 173 | from "employees" as "e1" left join "employees" as "e2" on "e2"."id" = "e1"."recipient_id" where "e1"."id" = $1`, 174 | }; 175 | bench("Pg Driver Employees: getInfo", async () => { 176 | for await (const id of employeeIds) { 177 | await db.query(query5, [id]); 178 | } 179 | }); 180 | 181 | const query6 = { 182 | name: "Suppliers-getAll", 183 | text: 'select * from "suppliers"', 184 | }; 185 | 186 | bench("Pg Driver Suppliers: getAll", async () => { 187 | await db.query(query6); 188 | }); 189 | 190 | const query7 = { 191 | name: "Suppliers-getInfo", 192 | text: 'select * from "suppliers" where "suppliers"."id" = $1', 193 | }; 194 | 195 | bench("Pg Driver Suppliers: getInfo", async () => { 196 | for await (const id of supplierIds) { 197 | await db.query(query7, [id]); 198 | } 199 | }); 200 | 201 | const query8 = { 202 | name: "Products-getAll", 203 | text: 'select * from "products"', 204 | }; 205 | 206 | bench("Pg Driver Products: getAll", async () => { 207 | await db.query(query8); 208 | }); 209 | 210 | const query9 = { 211 | name: "Products-getInfo", 212 | text: `select "products".*, "suppliers".* 213 | from "products" left join "suppliers" on "products"."supplier_id" = "suppliers"."id" where "products"."id" = $1`, 214 | }; 215 | 216 | bench("Pg Driver Products: getInfo", async () => { 217 | for await (const id of productIds) { 218 | await db.query(query9, [id]); 219 | } 220 | }); 221 | 222 | const query10 = { 223 | name: "Products-search", 224 | text: 'select * from "products" where "products"."name" ilike $1', 225 | }; 226 | 227 | bench("Pg Driver Products: search", async () => { 228 | for await (const it of productSearches) { 229 | await db.query(query10, [`%${it}%`]); 230 | } 231 | }); 232 | 233 | const query11 = { 234 | name: "Orders-getAll", 235 | text: `select "id", "shipped_date", "ship_name", "ship_city", "ship_country", count("product_id") as "products", 236 | sum("quantity") as "quantity", sum("quantity" * "unit_price") as "total_price" 237 | from "orders" as "o" left join "order_details" as "od" on "od"."order_id" = "o"."id" group by "o"."id" order by "o"."id" asc`, 238 | }; 239 | 240 | bench("Pg Driver Orders: getAll", async () => { 241 | await db.query(query11); 242 | }); 243 | 244 | const query12 = { 245 | name: "Orders-getInfo", 246 | text: `SELECT * FROM "orders" AS o 247 | LEFT JOIN "order_details" AS od ON o.id = od.order_id 248 | LEFT JOIN "products" AS p ON od.product_id = p.id 249 | WHERE o.id = $1`, 250 | }; 251 | 252 | bench("Pg Driver Orders: getInfo", async () => { 253 | for await (const id of orderIds) { 254 | await db.query(query12, [id]); 255 | } 256 | }); 257 | }); 258 | 259 | group({ name: "Pg Driver", summary: false }, () => { 260 | bench("Pg Driver Customers: getAll", async () => { 261 | await db.query('select * from "customers"'); 262 | }); 263 | bench("Pg Driver Customers: getInfo", async () => { 264 | for await (const id of customerIds) { 265 | await db.query('select * from "customers" where "customers"."id" = $1', [ 266 | id, 267 | ]); 268 | } 269 | }); 270 | bench("Pg Driver Customers: search", async () => { 271 | for await (const it of customerSearches) { 272 | await db.query( 273 | 'select * from "customers" where "customers"."company_name" ilike $1', 274 | [`%${it}%`] 275 | ); 276 | } 277 | }); 278 | 279 | bench("Pg Driver Employees: getAll", async () => { 280 | await db.query('select * from "employees"'); 281 | }); 282 | 283 | bench("Pg Driver Employees: getInfo", async () => { 284 | for await (const id of employeeIds) { 285 | await db.query( 286 | `select "e1".*, "e2"."last_name" as "reports_lname", "e2"."first_name" as "reports_fname" 287 | from "employees" as "e1" left join "employees" as "e2" on "e2"."id" = "e1"."recipient_id" where "e1"."id" = $1`, 288 | [id] 289 | ); 290 | } 291 | }); 292 | 293 | bench("Pg Driver Suppliers: getAll", async () => { 294 | await db.query('select * from "suppliers"'); 295 | }); 296 | 297 | bench("Pg Driver Suppliers: getInfo", async () => { 298 | for await (const id of supplierIds) { 299 | await db.query('select * from "suppliers" where "suppliers"."id" = $1', [ 300 | id, 301 | ]); 302 | } 303 | }); 304 | 305 | bench("Pg Driver Products: getAll", async () => { 306 | await db.query('select * from "products"'); 307 | }); 308 | 309 | bench("Pg Driver Products: getInfo", async () => { 310 | for await (const id of productIds) { 311 | await db.query( 312 | `select "products".*, "suppliers".* 313 | from "products" left join "suppliers" on "products"."supplier_id" = "suppliers"."id" where "products"."id" = $1`, 314 | [id] 315 | ); 316 | } 317 | }); 318 | bench("Pg Driver Products: search", async () => { 319 | for await (const it of productSearches) { 320 | await db.query( 321 | 'select * from "products" where "products"."name" ilike $1', 322 | [`%${it}%`] 323 | ); 324 | } 325 | }); 326 | 327 | bench("Pg Driver Orders: getAll", async () => { 328 | await db.query(`select "id", "shipped_date", "ship_name", "ship_city", "ship_country", count("product_id") as "products", 329 | sum("quantity") as "quantity", sum("quantity" * "unit_price") as "total_price" 330 | from "orders" as "o" left join "order_details" as "od" on "od"."order_id" = "o"."id" group by "o"."id" order by "o"."id" asc`); 331 | }); 332 | 333 | bench("Pg Driver Orders: getInfo", async () => { 334 | for await (const id of orderIds) { 335 | await db.query( 336 | `SELECT * FROM "orders" AS o 337 | LEFT JOIN "order_details" AS od ON o.id = od.order_id 338 | LEFT JOIN "products" AS p ON od.product_id = p.id 339 | WHERE o.id = $1`, 340 | [id] 341 | ); 342 | } 343 | }); 344 | }); 345 | 346 | const main = async () => { 347 | drizzle = await connector.connect(); 348 | await run(); 349 | 350 | // console.log( await drizzle.select(orders) 351 | // .fields({ 352 | // id: orders.id, 353 | // shippedDate: orders.shippedDate, 354 | // shipName: orders.shipName, 355 | // shipCity: orders.shipCity, 356 | // shipCountry: orders.shipCountry, 357 | // productsCount: sql`count(${details.productId})`.as(), 358 | // quantitySum: sql`sum(${details.quantity})`.as(), 359 | // totalPrice: sql`sum(${details.quantity} * ${details.unitPrice})`.as(), 360 | // }) 361 | // .leftJoin(details, eq(orders.id, details.orderId)) 362 | // .groupBy(orders.id) 363 | // .orderBy(asc(orders.id))); 364 | 365 | // const query = drizzle 366 | // .select(orders) 367 | // .fields({ 368 | // id: orders.id, 369 | // shippedDate: orders.shippedDate, 370 | // shipName: orders.shipName, 371 | // shipCity: orders.shipCity, 372 | // shipCountry: orders.shipCountry, 373 | // productsCount: sql`count(${details.productId})`.as(), 374 | // quantitySum: sql`sum(${details.quantity})`.as(), 375 | // totalPrice: 376 | // sql`sum(${details.quantity} * ${details.unitPrice})`.as(), 377 | // }) 378 | // .leftJoin(details, eq(orders.id, details.orderId)) 379 | // .groupBy(orders.id) 380 | // .orderBy(asc(orders.id)); 381 | 382 | // console.log(drizzle.buildQuery(query)); 383 | // // drizzle.buildQuery(query) 384 | }; 385 | 386 | main(); 387 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # General setup 2 | 3 | ### Installing node 4 | --- 5 | ```bash 6 | # https://github.com/nvm-sh/nvm#install--update-script 7 | curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.2/install.sh | bash 8 | # or any minor version for node18+ 9 | nvm install 18.13.0 10 | nvm use 18.13.0 11 | ``` 12 | ### Install pnpm 13 | --- 14 | ```bash 15 | # https://pnpm.io/installation 16 | npm install -g pnpm 17 | ``` 18 | ### Install docker 19 | --- 20 | ```bash 21 | # https://docs.docker.com/get-docker/ 22 | Use docker guide to install docker on your OS 23 | ``` 24 | 25 | # How to run 26 | To run benchmarks locally just use current command. 27 | 28 | > Note: make sure you will have docker running as long as benchmark script will create several docker containers with pg instance inside and run each orm library in new one. To prevent pg caching between different orm's running query 29 | 30 | ```bash 31 | pnpm run start 32 | ``` 33 | 34 | # Sample runs 35 | ```text 36 | cpu: Apple M1 37 | runtime: node v18.13.0 (x64-darwin) 38 | 39 | benchmark time (avg) (min … max) p75 p99 p995 40 | ------------------------------------------------- ----------------------------- 41 | • select * from customer 42 | ------------------------------------------------- ----------------------------- 43 | pg 2.66 ms/iter (1.04 ms … 42.77 ms) 2.7 ms 33.58 ms 42.77 ms 44 | pg:p 3.18 ms/iter (1.1 ms … 30.46 ms) 3.54 ms 16.74 ms 30.46 ms 45 | drizzle 8.38 ms/iter (1.52 ms … 119.66 ms) 7.07 ms 119.66 ms 119.66 ms 46 | drizzle:p 1.76 ms/iter (978.83 µs … 17.49 ms) 1.84 ms 5.74 ms 5.84 ms 47 | knex 1.69 ms/iter (899.67 µs … 11.69 ms) 1.83 ms 4.2 ms 7.59 ms 48 | kysely 2.95 ms/iter (1.05 ms … 29.61 ms) 3.11 ms 26.55 ms 29.61 ms 49 | mikro 6.63 ms/iter (2.16 ms … 56.53 ms) 7.85 ms 56.53 ms 56.53 ms 50 | typeorm 2.98 ms/iter (1.52 ms … 12.95 ms) 3.08 ms 11.76 ms 12.95 ms 51 | prisma 3.43 ms/iter (2.37 ms … 9.21 ms) 3.49 ms 8.72 ms 9.21 ms 52 | 53 | summary for select * from customer 54 | knex 55 | 1.04x faster than drizzle:p 56 | 1.57x faster than pg 57 | 1.75x faster than kysely 58 | 1.77x faster than typeorm 59 | 1.88x faster than pg:p 60 | 2.03x faster than prisma 61 | 3.93x faster than mikro 62 | 4.96x faster than drizzle 63 | 64 | • select * from customer where id = ? 65 | ------------------------------------------------- ----------------------------- 66 | pg 111.26 ms/iter (82.23 ms … 143.88 ms) 127.24 ms 143.88 ms 143.88 ms 67 | pg:p 106.75 ms/iter (83.67 ms … 191.55 ms) 111.33 ms 191.55 ms 191.55 ms 68 | drizzle 130.49 ms/iter (108.13 ms … 201.81 ms) 127.73 ms 201.81 ms 201.81 ms 69 | drizzle:p 92.47 ms/iter (80.57 ms … 108.48 ms) 99.18 ms 108.48 ms 108.48 ms 70 | knex 117.4 ms/iter (82.29 ms … 223.48 ms) 125.89 ms 223.48 ms 223.48 ms 71 | kysely 111.69 ms/iter (78.33 ms … 177.55 ms) 122.29 ms 177.55 ms 177.55 ms 72 | mikro 122.55 ms/iter (99.11 ms … 199.75 ms) 125.99 ms 199.75 ms 199.75 ms 73 | typeorm 117.89 ms/iter (88.3 ms … 155.82 ms) 146.56 ms 155.82 ms 155.82 ms 74 | prisma 184.46 ms/iter (123.07 ms … 338.22 ms) 211.13 ms 338.22 ms 338.22 ms 75 | 76 | summary for select * from customer where id = ? 77 | drizzle:p 78 | 1.15x faster than pg:p 79 | 1.2x faster than pg 80 | 1.21x faster than kysely 81 | 1.27x faster than knex 82 | 1.27x faster than typeorm 83 | 1.33x faster than mikro 84 | 1.41x faster than drizzle 85 | 1.99x faster than prisma 86 | 87 | • select * from customer where company_name ilike ? 88 | ------------------------------------------------- ----------------------------- 89 | pg 69.87 ms/iter (56.02 ms … 147.9 ms) 69.04 ms 147.9 ms 147.9 ms 90 | pg:p 90.12 ms/iter (50.23 ms … 259.53 ms) 73.97 ms 259.53 ms 259.53 ms 91 | drizzle 91.09 ms/iter (47.11 ms … 285.87 ms) 123.06 ms 285.87 ms 285.87 ms 92 | drizzle:p 58.25 ms/iter (43.55 ms … 113.52 ms) 59.08 ms 113.52 ms 113.52 ms 93 | knex 80.13 ms/iter (50.95 ms … 125.36 ms) 87.74 ms 125.36 ms 125.36 ms 94 | kysely 70.89 ms/iter (55.68 ms … 101.36 ms) 73.47 ms 101.36 ms 101.36 ms 95 | mikro 130.42 ms/iter (71.16 ms … 211.76 ms) 141.58 ms 211.76 ms 211.76 ms 96 | typeorm 100.83 ms/iter (70.18 ms … 228.38 ms) 102.09 ms 228.38 ms 228.38 ms 97 | prisma 108.91 ms/iter (70.47 ms … 202.51 ms) 141.28 ms 202.51 ms 202.51 ms 98 | 99 | summary for select * from customer where company_name ilike ? 100 | drizzle:p 101 | 1.2x faster than pg 102 | 1.22x faster than kysely 103 | 1.38x faster than knex 104 | 1.55x faster than pg:p 105 | 1.56x faster than drizzle 106 | 1.73x faster than typeorm 107 | 1.87x faster than prisma 108 | 2.24x faster than mikro 109 | 110 | • "SELECT * FROM employee" 111 | ------------------------------------------------- ----------------------------- 112 | pg 1.21 ms/iter (722.63 µs … 11.89 ms) 1.17 ms 5.09 ms 7.23 ms 113 | pg:p 1.48 ms/iter (736.54 µs … 22.24 ms) 1.37 ms 10.59 ms 14.28 ms 114 | drizzle 1.42 ms/iter (730.71 µs … 22.13 ms) 1.44 ms 6.08 ms 14.41 ms 115 | drizzle:p 1.09 ms/iter (682.71 µs … 4.81 ms) 1.15 ms 3.1 ms 3.56 ms 116 | knex 1.31 ms/iter (713.33 µs … 4.27 ms) 1.51 ms 2.82 ms 2.99 ms 117 | kysely 1.36 ms/iter (653.75 µs … 7.99 ms) 1.51 ms 4.64 ms 7.08 ms 118 | mikro 2.97 ms/iter (1.74 ms … 8.02 ms) 3.24 ms 7.87 ms 8.02 ms 119 | typeorm 2.21 ms/iter (935.5 µs … 40.27 ms) 2.12 ms 17.86 ms 19.91 ms 120 | prisma 1.89 ms/iter (1.12 ms … 4.93 ms) 2.26 ms 4.22 ms 4.26 ms 121 | 122 | summary for "SELECT * FROM employee" 123 | drizzle:p 124 | 1.11x faster than pg 125 | 1.2x faster than knex 126 | 1.25x faster than kysely 127 | 1.31x faster than drizzle 128 | 1.36x faster than pg:p 129 | 1.73x faster than prisma 130 | 2.03x faster than typeorm 131 | 2.72x faster than mikro 132 | 133 | • select * from employee where id = ? left join reportee 134 | ------------------------------------------------- ----------------------------- 135 | pg 9.94 ms/iter (6.88 ms … 22.33 ms) 10.89 ms 22.33 ms 22.33 ms 136 | pg:p 12.07 ms/iter (6.52 ms … 67.09 ms) 13.78 ms 67.09 ms 67.09 ms 137 | drizzle 15.21 ms/iter (9.25 ms … 31.98 ms) 20.06 ms 31.98 ms 31.98 ms 138 | drizzle:p 9.27 ms/iter (6.68 ms … 23.71 ms) 10.42 ms 23.71 ms 23.71 ms 139 | knex 12.06 ms/iter (8.34 ms … 27.93 ms) 12.27 ms 27.93 ms 27.93 ms 140 | kysely 13.79 ms/iter (8.16 ms … 57.99 ms) 13.6 ms 57.99 ms 57.99 ms 141 | mikro 15.35 ms/iter (10.47 ms … 27.56 ms) 17.6 ms 27.56 ms 27.56 ms 142 | typeorm 46.03 ms/iter (25.99 ms … 123.24 ms) 54.07 ms 123.24 ms 123.24 ms 143 | prisma 23.44 ms/iter (17.18 ms … 33.96 ms) 25.88 ms 33.96 ms 33.96 ms 144 | 145 | summary for select * from employee where id = ? left join reportee 146 | drizzle:p 147 | 1.07x faster than pg 148 | 1.3x faster than knex 149 | 1.3x faster than pg:p 150 | 1.49x faster than kysely 151 | 1.64x faster than drizzle 152 | 1.66x faster than mikro 153 | 2.53x faster than prisma 154 | 4.96x faster than typeorm 155 | 156 | • SELECT * FROM supplier 157 | ------------------------------------------------- ----------------------------- 158 | pg 1.19 ms/iter (771.58 µs … 3.18 ms) 1.27 ms 2 ms 2.31 ms 159 | pg:p 1.35 ms/iter (813.5 µs … 11.92 ms) 1.22 ms 6.45 ms 11.54 ms 160 | drizzle 1.31 ms/iter (838.08 µs … 4.47 ms) 1.4 ms 2.71 ms 4.18 ms 161 | drizzle:p 1.13 ms/iter (786.5 µs … 3.34 ms) 1.19 ms 2.14 ms 2.24 ms 162 | knex 1.26 ms/iter (902.42 µs … 4.03 ms) 1.36 ms 2.2 ms 2.39 ms 163 | kysely 1.85 ms/iter (867.71 µs … 12.75 ms) 1.77 ms 11.95 ms 12.36 ms 164 | mikro 3.03 ms/iter (1.31 ms … 57.96 ms) 2.83 ms 19.42 ms 57.96 ms 165 | typeorm 1.52 ms/iter (1.08 ms … 5.13 ms) 1.53 ms 4.38 ms 4.49 ms 166 | prisma 1.8 ms/iter (1.41 ms … 3.34 ms) 1.89 ms 2.7 ms 3 ms 167 | 168 | summary for SELECT * FROM supplier 169 | drizzle:p 170 | 1.05x faster than pg 171 | 1.12x faster than knex 172 | 1.15x faster than drizzle 173 | 1.19x faster than pg:p 174 | 1.34x faster than typeorm 175 | 1.59x faster than prisma 176 | 1.63x faster than kysely 177 | 2.68x faster than mikro 178 | 179 | • select * from supplier where id = ? 180 | ------------------------------------------------- ----------------------------- 181 | pg 39.5 ms/iter (22.15 ms … 84.52 ms) 37.48 ms 84.52 ms 84.52 ms 182 | pg:p 33.25 ms/iter (21.44 ms … 133.69 ms) 30.1 ms 133.69 ms 133.69 ms 183 | drizzle 37.45 ms/iter (28.81 ms … 87.89 ms) 36.12 ms 87.89 ms 87.89 ms 184 | drizzle:p 26.87 ms/iter (21 ms … 46.7 ms) 27.67 ms 46.7 ms 46.7 ms 185 | knex 37.5 ms/iter (24.37 ms … 115.04 ms) 36.63 ms 115.04 ms 115.04 ms 186 | kysely 32.98 ms/iter (24.66 ms … 67.13 ms) 35.03 ms 67.13 ms 67.13 ms 187 | mikro 73.34 ms/iter (29.19 ms … 380.66 ms) 67.14 ms 380.66 ms 380.66 ms 188 | typeorm 51.22 ms/iter (30.38 ms … 267.4 ms) 45.08 ms 267.4 ms 267.4 ms 189 | prisma 41.92 ms/iter (30.78 ms … 56.61 ms) 50.9 ms 56.61 ms 56.61 ms 190 | 191 | summary for select * from supplier where id = ? 192 | drizzle:p 193 | 1.23x faster than kysely 194 | 1.24x faster than pg:p 195 | 1.39x faster than drizzle 196 | 1.4x faster than knex 197 | 1.47x faster than pg 198 | 1.56x faster than prisma 199 | 1.91x faster than typeorm 200 | 2.73x faster than mikro 201 | 202 | • SELECT * FROM product 203 | ------------------------------------------------- ----------------------------- 204 | pg 2.98 ms/iter (1.18 ms … 11.6 ms) 3.59 ms 7.63 ms 11.6 ms 205 | pg:p 1.72 ms/iter (957.29 µs … 7.49 ms) 1.96 ms 4.59 ms 7 ms 206 | drizzle 1.65 ms/iter (1.06 ms … 6.13 ms) 1.91 ms 3.27 ms 4.26 ms 207 | drizzle:p 1.81 ms/iter (993.17 µs … 66.87 ms) 1.47 ms 7.72 ms 19.1 ms 208 | knex 3.24 ms/iter (1.6 ms … 7.32 ms) 3.64 ms 6.95 ms 7.32 ms 209 | kysely 3.64 ms/iter (1.8 ms … 17.42 ms) 4.18 ms 11.65 ms 17.42 ms 210 | mikro 6.69 ms/iter (3.06 ms … 15.81 ms) 7.71 ms 15.81 ms 15.81 ms 211 | typeorm 6.51 ms/iter (2.8 ms … 56.85 ms) 5.91 ms 56.85 ms 56.85 ms 212 | prisma 6.51 ms/iter (4.13 ms … 11.88 ms) 7.12 ms 11.88 ms 11.88 ms 213 | 214 | summary for SELECT * FROM product 215 | drizzle 216 | 1.05x faster than pg:p 217 | 1.1x faster than drizzle:p 218 | 1.81x faster than pg 219 | 1.96x faster than knex 220 | 2.21x faster than kysely 221 | 3.95x faster than prisma 222 | 3.95x faster than typeorm 223 | 4.06x faster than mikro 224 | 225 | • SELECT * FROM product LEFT JOIN supplier WHERE product.id = ? 226 | ------------------------------------------------- ----------------------------- 227 | pg 101.48 ms/iter (68.1 ms … 180.85 ms) 115.62 ms 180.85 ms 180.85 ms 228 | pg:p 95.17 ms/iter (60.29 ms … 216.46 ms) 98.02 ms 216.46 ms 216.46 ms 229 | drizzle 108.1 ms/iter (91.67 ms … 200.25 ms) 104.39 ms 200.25 ms 200.25 ms 230 | drizzle:p 80.01 ms/iter (59.36 ms … 148.66 ms) 78.74 ms 148.66 ms 148.66 ms 231 | knex 105.12 ms/iter (78.6 ms … 164.04 ms) 107.93 ms 164.04 ms 164.04 ms 232 | kysely 93.93 ms/iter (88.51 ms … 97.61 ms) 96.43 ms 97.61 ms 97.61 ms 233 | mikro 163.53 ms/iter (112.09 ms … 232.31 ms) 189.16 ms 232.31 ms 232.31 ms 234 | typeorm 277.87 ms/iter (225.82 ms … 317.05 ms) 306.89 ms 317.05 ms 317.05 ms 235 | prisma 267.18 ms/iter (200.32 ms … 333.29 ms) 292.96 ms 333.29 ms 333.29 ms 236 | 237 | summary for SELECT * FROM product LEFT JOIN supplier WHERE product.id = ? 238 | drizzle:p 239 | 1.17x faster than kysely 240 | 1.19x faster than pg:p 241 | 1.27x faster than pg 242 | 1.31x faster than knex 243 | 1.35x faster than drizzle 244 | 2.04x faster than mikro 245 | 3.34x faster than prisma 246 | 3.47x faster than typeorm 247 | 248 | • SELECT * FROM product WHERE product.name ILIKE ? 249 | ------------------------------------------------- ----------------------------- 250 | pg 52.34 ms/iter (43.27 ms … 61.92 ms) 57.39 ms 61.92 ms 61.92 ms 251 | pg:p 50.24 ms/iter (44.58 ms … 57.74 ms) 51.37 ms 57.74 ms 57.74 ms 252 | drizzle 66.68 ms/iter (44.34 ms … 133.29 ms) 76.31 ms 133.29 ms 133.29 ms 253 | drizzle:p 56.94 ms/iter (48.92 ms … 117.31 ms) 55.18 ms 117.31 ms 117.31 ms 254 | knex 71.52 ms/iter (51.33 ms … 184.71 ms) 74.42 ms 184.71 ms 184.71 ms 255 | kysely 81.99 ms/iter (53.12 ms … 187.72 ms) 89.28 ms 187.72 ms 187.72 ms 256 | mikro 141.46 ms/iter (85.68 ms … 251.83 ms) 170.57 ms 251.83 ms 251.83 ms 257 | typeorm 91.46 ms/iter (75.51 ms … 155.69 ms) 96.68 ms 155.69 ms 155.69 ms 258 | prisma 180.99 ms/iter (125.24 ms … 316.46 ms) 175.25 ms 316.46 ms 316.46 ms 259 | 260 | summary for SELECT * FROM product WHERE product.name ILIKE ? 261 | pg:p 262 | 1.04x faster than pg 263 | 1.13x faster than drizzle:p 264 | 1.33x faster than drizzle 265 | 1.42x faster than knex 266 | 1.63x faster than kysely 267 | 1.82x faster than typeorm 268 | 2.82x faster than mikro 269 | 3.6x faster than prisma 270 | 271 | • select all order with sum and count 272 | ------------------------------------------------- ----------------------------- 273 | pg 6.6 ms/iter (3.71 ms … 49.63 ms) 7.08 ms 49.63 ms 49.63 ms 274 | pg:p 5.3 ms/iter (3.29 ms … 15.01 ms) 5.96 ms 13.59 ms 15.01 ms 275 | drizzle 5.75 ms/iter (4.18 ms … 9.99 ms) 6.23 ms 9.99 ms 9.99 ms 276 | drizzle:p 5.4 ms/iter (3.83 ms … 10.04 ms) 5.8 ms 9.55 ms 10.04 ms 277 | knex 8.12 ms/iter (4.11 ms … 34.04 ms) 8.12 ms 34.04 ms 34.04 ms 278 | kysely 5.54 ms/iter (3.97 ms … 10.38 ms) 6.59 ms 8.83 ms 10.38 ms 279 | mikro 197.91 ms/iter (167.61 ms … 331.46 ms) 191.09 ms 331.46 ms 331.46 ms 280 | typeorm 27.58 ms/iter (23.7 ms … 34.43 ms) 28.41 ms 34.43 ms 34.43 ms 281 | prisma 60.32 ms/iter (52.02 ms … 107.18 ms) 61.24 ms 107.18 ms 107.18 ms 282 | 283 | summary for select all order with sum and count 284 | pg:p 285 | 1.02x faster than drizzle:p 286 | 1.05x faster than kysely 287 | 1.09x faster than drizzle 288 | 1.25x faster than pg 289 | 1.53x faster than knex 290 | 5.21x faster than typeorm 291 | 11.39x faster than prisma 292 | 37.37x faster than mikro 293 | 294 | • select order with sum and count using limit with offset 295 | ------------------------------------------------- ----------------------------- 296 | pg 57.29 ms/iter (49.2 ms … 93.95 ms) 58.7 ms 93.95 ms 93.95 ms 297 | pg:p 56.44 ms/iter (50.58 ms … 87.78 ms) 55.49 ms 87.78 ms 87.78 ms 298 | drizzle 59.39 ms/iter (44.63 ms … 114.36 ms) 61.18 ms 114.36 ms 114.36 ms 299 | drizle:p 52.06 ms/iter (46.11 ms … 66.06 ms) 54.18 ms 66.06 ms 66.06 ms 300 | knex 56.62 ms/iter (47.35 ms … 97 ms) 59.13 ms 97 ms 97 ms 301 | kysely 56.48 ms/iter (49.37 ms … 100.97 ms) 57.98 ms 100.97 ms 100.97 ms 302 | mikro 160.83 ms/iter (124.71 ms … 238.05 ms) 180.83 ms 238.05 ms 238.05 ms 303 | typeorm 107.87 ms/iter (91.32 ms … 174.22 ms) 110.64 ms 174.22 ms 174.22 ms 304 | prisma 114.35 ms/iter (98.28 ms … 161.81 ms) 122.01 ms 161.81 ms 161.81 ms 305 | 306 | summary for select order with sum and count using limit with offset 307 | drizle:p 308 | 1.08x faster than pg:p 309 | 1.09x faster than kysely 310 | 1.09x faster than knex 311 | 1.1x faster than pg 312 | 1.14x faster than drizzle 313 | 2.07x faster than typeorm 314 | 2.2x faster than prisma 315 | 3.09x faster than mikro 316 | 317 | • select order where order.id = ? with sum and count 318 | ------------------------------------------------- ----------------------------- 319 | pg 63.35 ms/iter (48.77 ms … 104.83 ms) 64.15 ms 104.83 ms 104.83 ms 320 | pg:p 54.37 ms/iter (40.97 ms … 99.41 ms) 54.38 ms 99.41 ms 99.41 ms 321 | drizzle 58.76 ms/iter (51.52 ms … 76.24 ms) 61.21 ms 76.24 ms 76.24 ms 322 | drizzle:p 46.61 ms/iter (38.96 ms … 54.62 ms) 51.04 ms 54.62 ms 54.62 ms 323 | knex 57.72 ms/iter (53.77 ms … 62.54 ms) 59.83 ms 62.54 ms 62.54 ms 324 | kysely 56.93 ms/iter (49.57 ms … 70.77 ms) 59.26 ms 70.77 ms 70.77 ms 325 | mikro 138.81 ms/iter (113.25 ms … 263.33 ms) 137.17 ms 263.33 ms 263.33 ms 326 | prisma 89.73 ms/iter (80.33 ms … 103.21 ms) 91.08 ms 103.21 ms 103.21 ms 327 | typeorm 140.7 ms/iter (123.75 ms … 221.22 ms) 141.45 ms 221.22 ms 221.22 ms 328 | 329 | summary for select order where order.id = ? with sum and count 330 | drizzle:p 331 | 1.17x faster than pg:p 332 | 1.22x faster than kysely 333 | 1.24x faster than knex 334 | 1.26x faster than drizzle 335 | 1.36x faster than pg 336 | 1.93x faster than prisma 337 | 2.98x faster than mikro 338 | 3.02x faster than typeorm 339 | 340 | • SELECT * FROM order_detail WHERE order_id = ? 341 | ------------------------------------------------- ----------------------------- 342 | pg 235.75 ms/iter (200.16 ms … 308.49 ms) 237.78 ms 308.49 ms 308.49 ms 343 | pg:p 224.74 ms/iter (186 ms … 335.61 ms) 219.28 ms 335.61 ms 335.61 ms 344 | drizzle 279.71 ms/iter (258.3 ms … 338.51 ms) 268.15 ms 338.51 ms 338.51 ms 345 | drizzle:p 213.52 ms/iter (199.68 ms … 261.62 ms) 211.5 ms 261.62 ms 261.62 ms 346 | knex 270.84 ms/iter (256.77 ms … 340.4 ms) 268.92 ms 340.4 ms 340.4 ms 347 | kysely 270.46 ms/iter (235.6 ms … 321.93 ms) 288.65 ms 321.93 ms 321.93 ms 348 | mikro 862.84 ms/iter (570.71 ms … 1.43 s) 1.06 s 1.43 s 1.43 s 349 | typeorm 478.49 ms/iter (310.02 ms … 686.99 ms) 506.64 ms 686.99 ms 686.99 ms 350 | prisma 1.08 s/iter (913.1 ms … 1.29 s) 1.15 s 1.29 s 1.29 s 351 | 352 | summary for SELECT * FROM order_detail WHERE order_id = ? 353 | drizzle:p 354 | 1.05x faster than pg:p 355 | 1.1x faster than pg 356 | 1.27x faster than kysely 357 | 1.27x faster than knex 358 | 1.31x faster than drizzle 359 | 2.24x faster than typeorm 360 | 4.04x faster than mikro 361 | 5.08x faster than prisma 362 | ``` -------------------------------------------------------------------------------- /src/common/benchmark.ts: -------------------------------------------------------------------------------- 1 | import { run, bench, group } from "mitata"; 2 | import { eq, ilike, placeholder } from "drizzle-orm"; 3 | import { sql } from "drizzle-orm"; 4 | // import { drizzle as drzl } from "drizzle-orm/node-postgres"; 5 | import { drizzle as drzl } from "drizzle-orm/postgres-js"; 6 | import { alias } from "drizzle-orm/pg-core"; 7 | import pkg from "pg"; 8 | import postgres from "postgres"; 9 | import knex from "knex"; 10 | import dotenv from "dotenv"; 11 | import { Kysely, sql as k_sql, PostgresDialect } from "kysely"; 12 | import { DataSource, ILike } from "typeorm"; 13 | import { MikroORM } from "@mikro-orm/core"; 14 | import { TsMorphMetadataProvider } from "@mikro-orm/reflection"; 15 | import { PostgreSqlDriver } from "@mikro-orm/postgresql"; 16 | import * as Prisma from "@prisma/client"; 17 | 18 | import { Database } from "@/kysely/db"; 19 | import { Customer } from "@/typeorm/entities/customers"; 20 | import { Employee } from "@/typeorm/entities/employees"; 21 | import { Supplier } from "@/typeorm/entities/suppliers"; 22 | import { Order } from "@/typeorm/entities/orders"; 23 | import { Product } from "@/typeorm/entities/products"; 24 | import { Detail } from "@/typeorm/entities/details"; 25 | import { 26 | employees, 27 | customers, 28 | suppliers, 29 | products, 30 | orders, 31 | details, 32 | } from "@/drizzle/schema"; 33 | import { Customer as m_Customer } from "@/mikro/entities/customers"; 34 | import { Detail as m_Detail } from "@/mikro/entities/details"; 35 | import { Employee as m_Employee } from "@/mikro/entities/employees"; 36 | import { Order as m_Order } from "@/mikro/entities/orders"; 37 | import { Product as m_Product } from "@/mikro/entities/products"; 38 | import { Supplier as m_Supplier } from "@/mikro/entities/suppliers"; 39 | import { 40 | customerIds, 41 | employeeIds, 42 | orderIds, 43 | productIds, 44 | customerSearches, 45 | productSearches, 46 | supplierIds, 47 | } from "./meta"; 48 | import { createDockerDBs, ports, deleteDockerDBs, DockerDBs } from "@/utils"; 49 | 50 | dotenv.config(); 51 | 52 | const DB_HOST = process.env.DB_HOST ?? "localhost"; 53 | const DB_NAME = process.env.DB_NAME ?? "postgres"; 54 | const DB_USER = process.env.DB_USER ?? "postgres"; 55 | const DB_PASSWORD = process.env.DB_PASSWORD ?? "postgres"; 56 | const DB_PORT = process.env.DB_PORT; 57 | 58 | console.log(DB_HOST, DB_NAME, DB_USER, DB_PASSWORD, DB_PORT); 59 | const port = Number(DB_PORT || ports.drizzle); 60 | console.log(port); 61 | 62 | const dockersDbs = await createDockerDBs(ports); 63 | 64 | // const { Pool } = pkg; 65 | // pg connect 66 | const pg = new pkg.Pool({ 67 | host: DB_HOST, 68 | port: Number(DB_PORT || ports.pg), 69 | user: DB_USER, 70 | password: DB_PASSWORD, 71 | database: DB_NAME, 72 | }); 73 | 74 | // pgPrepared connect 75 | const pgPrepared = new pkg.Pool({ 76 | host: DB_HOST, 77 | port: Number(DB_PORT || ports.pgPrepared), 78 | user: DB_USER, 79 | password: DB_PASSWORD, 80 | database: DB_NAME, 81 | }); 82 | 83 | const pgjs = postgres({ 84 | host: DB_HOST, 85 | port: Number(DB_PORT || ports.postgresjs), 86 | user: DB_USER, 87 | password: DB_PASSWORD, 88 | database: DB_NAME, 89 | }); 90 | 91 | // drizzle connect 92 | const drizzlePool = postgres( 93 | process.env.DATABASE_URL ?? 94 | `postgres://postgres:postgres@localhost:${ports.drizzle}/postgres` 95 | ); 96 | const drizzle = drzl(drizzlePool); 97 | 98 | // drizzlePrepared connect 99 | const drizzlePreparedPool = postgres( 100 | process.env.DATABASE_URL ?? 101 | `postgres://postgres:postgres@localhost:${ports.drizzlePrepared}/postgres` 102 | ); 103 | // await drizzlePreparedPool.connect(); 104 | const drizzlePrepared = drzl(drizzlePreparedPool); 105 | 106 | // mikro connect 107 | const mikroOrm = await MikroORM.init({ 108 | type: "postgresql", 109 | host: DB_HOST, 110 | port: Number(DB_PORT || ports.mikroOrm), 111 | user: DB_USER, 112 | password: DB_PASSWORD, 113 | dbName: DB_NAME, 114 | entities: [m_Customer, m_Employee, m_Order, m_Supplier, m_Product, m_Detail], 115 | metadataProvider: TsMorphMetadataProvider, 116 | }); 117 | const mikro = mikroOrm.em.fork(); 118 | 119 | // knex connect 120 | const knexDb = knex({ 121 | client: "pg", 122 | connection: { 123 | host: DB_HOST, 124 | port: Number(DB_PORT || ports.knex), 125 | user: DB_USER, 126 | password: DB_PASSWORD, 127 | database: DB_NAME, 128 | }, 129 | useNullAsDefault: true, 130 | }); 131 | 132 | // kysely connect 133 | const kysely = new Kysely({ 134 | dialect: new PostgresDialect({ 135 | pool: new pkg.Pool({ 136 | host: DB_HOST, 137 | port: Number(DB_PORT || ports.kysely), 138 | user: DB_USER, 139 | password: DB_PASSWORD, 140 | database: DB_NAME, 141 | }), 142 | }), 143 | }); 144 | 145 | // prisma connect 146 | const prisma = new Prisma.PrismaClient(); 147 | 148 | // typeorm connect 149 | const typeorm = new DataSource({ 150 | type: "postgres", 151 | host: DB_HOST, 152 | port: Number(DB_PORT || ports.typeOrm), 153 | username: DB_USER, 154 | password: DB_PASSWORD, 155 | database: DB_NAME, 156 | entities: [Customer, Employee, Order, Supplier, Product, Detail], 157 | synchronize: false, 158 | logging: false, 159 | extra: { 160 | decimalNumbers: true, 161 | }, 162 | }); 163 | await typeorm.initialize(); 164 | 165 | group("select * from customer", () => { 166 | bench("pg", async () => { 167 | await pg.query('select * from "customers"'); 168 | }); 169 | 170 | const query = { 171 | name: "Customers-getAll", 172 | text: 'select * from "customers"', 173 | }; 174 | bench("pg:p", async () => { 175 | await pgPrepared.query(query); 176 | }); 177 | 178 | bench("postgresjs", async () => { 179 | await pgjs`select * from "customers"`; 180 | }); 181 | 182 | bench("drizzle", async () => { 183 | await drizzle.select().from(customers); 184 | }); 185 | 186 | const prepared = drizzlePrepared 187 | .select() 188 | .from(customers) 189 | .prepare("Customers-getAll-D"); 190 | 191 | bench("drizzle:p", async () => { 192 | await prepared.execute(); 193 | }); 194 | 195 | bench("knex", async () => { 196 | await knexDb("customers"); 197 | }); 198 | 199 | bench("kysely", async () => { 200 | await kysely.selectFrom("customers").selectAll().execute(); 201 | }); 202 | 203 | bench("mikro", async () => { 204 | await mikro.find(Customer, {}); 205 | mikro.clear(); 206 | }); 207 | 208 | bench("typeorm", async () => { 209 | await typeorm.getRepository(Customer).find(); 210 | }); 211 | 212 | bench("prisma", async () => { 213 | await prisma.customer.findMany(); 214 | }); 215 | }); 216 | 217 | // 218 | group("select * from customer where id = ?", () => { 219 | bench("pg", async () => { 220 | for (const id of customerIds) { 221 | await pg.query('select * from "customers" where "customers"."id" = $1', [ 222 | id, 223 | ]); 224 | } 225 | }); 226 | const query = { 227 | name: "Customers-getInfo", 228 | text: 'select * from "customers" where "customers"."id" = $1', 229 | }; 230 | 231 | bench("pg:p", async () => { 232 | for (const id of customerIds) { 233 | await pgPrepared.query(query, [id]); 234 | } 235 | }); 236 | 237 | bench("postgresjs", async () => { 238 | for (const id of customerIds) { 239 | await pgjs`select * from "customers" where "customers"."id" = ${id}`; 240 | } 241 | }); 242 | 243 | bench("drizzle", async () => { 244 | for (const id of customerIds) { 245 | await drizzle.select().from(customers).where(eq(customers.id, id)); 246 | } 247 | }); 248 | const prepared = drizzlePrepared 249 | .select() 250 | .from(customers) 251 | .where(eq(customers.id, placeholder("userId"))) 252 | .prepare("Customers-getInfo-D"); 253 | 254 | bench("drizzle:p", async () => { 255 | for (const id of customerIds) { 256 | await prepared.execute({ userId: id }); 257 | } 258 | }); 259 | 260 | bench("knex", async () => { 261 | for (const id of customerIds) { 262 | await knexDb("customers").where({ id }); 263 | } 264 | }); 265 | 266 | bench("kysely", async () => { 267 | for (const id of customerIds) { 268 | await kysely 269 | .selectFrom("customers") 270 | .selectAll() 271 | .where("customers.id", "=", id) 272 | .execute(); 273 | } 274 | }); 275 | 276 | bench("mikro", async () => { 277 | for (const id of customerIds) { 278 | await mikro.findOne(m_Customer, { id }); 279 | } 280 | mikro.clear(); 281 | }); 282 | 283 | const repo = typeorm.getRepository(Customer); 284 | bench("typeorm", async () => { 285 | for (const id of customerIds) { 286 | await repo.findOne({ 287 | where: { 288 | id, 289 | }, 290 | }); 291 | } 292 | }); 293 | 294 | bench("prisma", async () => { 295 | for (const id of customerIds) { 296 | await prisma.customer.findUnique({ 297 | where: { 298 | id, 299 | }, 300 | }); 301 | } 302 | }); 303 | }); 304 | 305 | // 306 | group("select * from customer where company_name ilike ?", () => { 307 | bench("pg", async () => { 308 | for (const it of customerSearches) { 309 | await pg.query( 310 | 'select * from "customers" where "customers"."company_name" ilike $1', 311 | [`%${it}%`] 312 | ); 313 | } 314 | }); 315 | 316 | const query = { 317 | name: "Customers-search", 318 | text: 'select * from "customers" where "customers"."company_name" ilike $1', 319 | }; 320 | bench("pg:p", async () => { 321 | for (const it of customerSearches) { 322 | await pgPrepared.query(query, [`%${it}%`]); 323 | } 324 | }); 325 | 326 | bench("postgresjs", async () => { 327 | for (const id of customerIds) { 328 | await pgjs`select * from "customers" where "customers"."company_name" ilike ${id}`; 329 | } 330 | }); 331 | 332 | bench("drizzle", async () => { 333 | for (const it of customerSearches) { 334 | await drizzle 335 | .select() 336 | .from(customers) 337 | .where(ilike(customers.companyName, `%${it}%`)); 338 | } 339 | }); 340 | 341 | const prepared = drizzlePrepared 342 | .select() 343 | .from(customers) 344 | .where(sql`${customers.companyName} ilike ${placeholder("name")}`) 345 | .prepare("Customers-search-D"); 346 | 347 | bench("drizzle:p", async () => { 348 | for (const it of customerSearches) { 349 | await prepared.execute({ name: `%${it}%` }); 350 | } 351 | }); 352 | 353 | bench("knex", async () => { 354 | for (const it of customerSearches) { 355 | await knexDb("customers").whereILike("company_name", `%${it}%`); 356 | } 357 | }); 358 | 359 | bench("kysely", async () => { 360 | for (const it of customerSearches) { 361 | await kysely 362 | .selectFrom("customers") 363 | .selectAll() 364 | .where(k_sql`company_name`, "ilike", `%${it}%`) 365 | .execute(); 366 | } 367 | }); 368 | 369 | bench("mikro", async () => { 370 | for (const it of customerSearches) { 371 | await mikro.find(m_Customer, { 372 | companyName: { $like: `%${it}%` }, 373 | }); 374 | } 375 | mikro.clear(); 376 | }); 377 | 378 | const repo = typeorm.getRepository(Customer); 379 | bench("typeorm", async () => { 380 | for (const it of customerSearches) { 381 | await typeorm.getRepository(Customer).find({ 382 | where: { 383 | companyName: ILike(`%${it}%`), 384 | }, 385 | }); 386 | } 387 | }); 388 | 389 | bench("prisma", async () => { 390 | for (const it of customerSearches) { 391 | await prisma.customer.findMany({ 392 | where: { 393 | companyName: { 394 | contains: it, 395 | mode: "insensitive", 396 | }, 397 | }, 398 | }); 399 | } 400 | }); 401 | }); 402 | 403 | group('"SELECT * FROM employee"', () => { 404 | bench("pg", async () => { 405 | await pg.query('select * from "employees"'); 406 | }); 407 | 408 | const query = { 409 | name: "Employees-getAll", 410 | text: 'select * from "employees"', 411 | }; 412 | 413 | bench("pg:p", async () => { 414 | await pgPrepared.query(query); 415 | }); 416 | 417 | bench("drizzle", async () => { 418 | await drizzle.select().from(employees); 419 | }); 420 | 421 | const prepared = drizzlePrepared 422 | .select() 423 | .from(employees) 424 | .prepare("Employees-getAll-D"); 425 | 426 | bench("drizzle:p", async () => { 427 | await prepared.execute(); 428 | }); 429 | 430 | bench("knex", async () => { 431 | await knexDb("employees"); 432 | }); 433 | 434 | bench("kysely", async () => { 435 | await kysely.selectFrom("employees").selectAll().execute(); 436 | }); 437 | 438 | bench("mikro", async () => { 439 | await mikro.find(m_Employee, {}); 440 | mikro.clear(); 441 | }); 442 | 443 | bench("typeorm", async () => { 444 | await typeorm.getRepository(Employee).find(); 445 | }); 446 | 447 | bench("prisma", async () => { 448 | await prisma.employee.findMany(); 449 | }); 450 | }); 451 | 452 | // 453 | group("select * from employee where id = ? left join reportee", () => { 454 | bench("pg", async () => { 455 | for (const id of employeeIds) { 456 | await pg.query( 457 | `select "e1".*, "e2"."last_name" as "reports_lname", "e2"."first_name" as "reports_fname" 458 | from "employees" as "e1" left join "employees" as "e2" on "e2"."id" = "e1"."recipient_id" where "e1"."id" = $1`, 459 | [id] 460 | ); 461 | } 462 | }); 463 | const query = { 464 | name: "Employees-getInfo", 465 | text: `select "e1".*, "e2"."last_name" as "reports_lname", "e2"."first_name" as "reports_fname" 466 | from "employees" as "e1" left join "employees" as "e2" on "e2"."id" = "e1"."recipient_id" where "e1"."id" = $1`, 467 | }; 468 | 469 | bench("pg:p", async () => { 470 | for await (const id of employeeIds) { 471 | await pgPrepared.query(query, [id]); 472 | } 473 | }); 474 | 475 | bench("drizzle", async () => { 476 | const e2 = alias(employees, "recipient"); 477 | 478 | for (const id of employeeIds) { 479 | await drizzle 480 | .select() 481 | .from(employees) 482 | .leftJoin(e2, eq(e2.id, employees.recipientId)) 483 | .where(eq(employees.id, id)); 484 | } 485 | }); 486 | 487 | const e2 = alias(employees, "recipient"); 488 | const prepared = drizzlePrepared 489 | .select() 490 | .from(employees) 491 | .leftJoin(e2, eq(e2.id, employees.recipientId)) 492 | .where(eq(employees.id, placeholder("employeeId"))) 493 | .prepare("Employees-getInfo-D"); 494 | 495 | bench("drizzle:p", async () => { 496 | for (const id of employeeIds) { 497 | await prepared.execute({ employeeId: id }); 498 | } 499 | }); 500 | 501 | bench("knex", async () => { 502 | for (const id of employeeIds) { 503 | await knexDb("employees as e1") 504 | .select([ 505 | "e1.*", 506 | "e2.id as e2_id", 507 | "e2.last_name as e2_last_name", 508 | "e2.first_name as e2_first_name", 509 | "e2.title as e2_title", 510 | "e2.title_of_courtesy as e2_title_of_courtesy", 511 | "e2.birth_date as e2_birth_date", 512 | "e2.hire_date as e2_hire_date", 513 | "e2.address as e2_address", 514 | "e2.city as e2_city", 515 | "e2.postal_code as e2_postal_code", 516 | "e2.country as e2_country", 517 | "e2.home_phone as e2_home_phone", 518 | "e2.extension as e2_extension", 519 | "e2.notes as e2_notes", 520 | "e2.recipient_id as e2_recipient_id", 521 | ]) 522 | .where("e1.id", "=", id) 523 | .leftJoin("employees as e2", "e1.recipient_id", "e2.id"); 524 | } 525 | }); 526 | 527 | bench("kysely", async () => { 528 | for (const id of employeeIds) { 529 | await kysely 530 | .selectFrom("employees as e1") 531 | .selectAll() 532 | .where("e1.id", "=", id) 533 | .leftJoin( 534 | kysely 535 | .selectFrom("employees as e2") 536 | .select([ 537 | "id as e2_id", 538 | "last_name as e2_last_name", 539 | "first_name as e2_first_name", 540 | "title as e2_title", 541 | "title_of_courtesy as e2_title_of_courtesy", 542 | "birth_date as e2_birth_date", 543 | "hire_date as e2_hire_date", 544 | "address as e2_address", 545 | "city as e2_city", 546 | "postal_code as e2_postal_code", 547 | "country as e2_country", 548 | "home_phone as e2_home_phone", 549 | "extension as e2_extension", 550 | "notes as e2_notes", 551 | "recipient_id as e2_recipient_id", 552 | ]) 553 | .as("e2"), 554 | "e2.e2_id", 555 | "e1.recipient_id" 556 | ) 557 | .execute(); 558 | } 559 | }); 560 | 561 | bench("mikro", async () => { 562 | for (const id of employeeIds) { 563 | await mikro.findOne(Employee, { id }, { populate: ["recipient"] }); 564 | } 565 | mikro.clear(); 566 | }); 567 | 568 | bench("typeorm", async () => { 569 | for (const id of employeeIds) { 570 | await typeorm.getRepository(Employee).findOne({ 571 | where: { 572 | id, 573 | }, 574 | relations: { 575 | recipient: true, 576 | }, 577 | }); 578 | } 579 | }); 580 | 581 | bench("prisma", async () => { 582 | for (const id of employeeIds) { 583 | await prisma.employee.findUnique({ 584 | where: { 585 | id, 586 | }, 587 | include: { 588 | recipient: true, 589 | }, 590 | }); 591 | } 592 | }); 593 | }); 594 | 595 | // 596 | group("SELECT * FROM supplier", () => { 597 | bench("pg", async () => { 598 | await pg.query('select * from "suppliers"'); 599 | }); 600 | 601 | const query = { 602 | name: "Suppliers-getAll", 603 | text: 'select * from "suppliers"', 604 | }; 605 | bench("pg:p", async () => { 606 | await pgPrepared.query(query); 607 | }); 608 | 609 | bench("drizzle", async () => { 610 | await drizzle.select().from(suppliers); 611 | }); 612 | 613 | const prepared = drizzlePrepared 614 | .select() 615 | .from(suppliers) 616 | .prepare("Suppliers-getAll-D"); 617 | 618 | bench("drizzle:p", async () => { 619 | await prepared.execute(); 620 | }); 621 | 622 | bench("knex", async () => { 623 | await knexDb("suppliers"); 624 | }); 625 | 626 | bench("kysely", async () => { 627 | await kysely.selectFrom("suppliers").selectAll().execute(); 628 | }); 629 | 630 | bench("mikro", async () => { 631 | await mikro.find(m_Supplier, {}); 632 | mikro.clear(); 633 | }); 634 | 635 | bench("typeorm", async () => { 636 | await typeorm.getRepository(Supplier).find(); 637 | }); 638 | 639 | bench("prisma", async () => { 640 | await prisma.supplier.findMany(); 641 | }); 642 | }); 643 | 644 | // 645 | group("select * from supplier where id = ?", () => { 646 | bench("pg", async () => { 647 | for (const id of supplierIds) { 648 | await pg.query('select * from "suppliers" where "suppliers"."id" = $1', [ 649 | id, 650 | ]); 651 | } 652 | }); 653 | 654 | const query = { 655 | name: "Suppliers-getInfo", 656 | text: 'select * from "suppliers" where "suppliers"."id" = $1', 657 | }; 658 | bench("pg:p", async () => { 659 | for (const id of supplierIds) { 660 | await pgPrepared.query(query, [id]); 661 | } 662 | }); 663 | 664 | bench("drizzle", async () => { 665 | for (const id of supplierIds) { 666 | await drizzle.select().from(suppliers).where(eq(suppliers.id, id)); 667 | } 668 | }); 669 | 670 | const prepared = drizzlePrepared 671 | .select() 672 | .from(suppliers) 673 | .where(eq(suppliers.id, placeholder("supplierId"))) 674 | .prepare("Suppliers-getInfo-D"); 675 | 676 | bench("drizzle:p", async () => { 677 | for (const id of supplierIds) { 678 | await prepared.execute({ supplierId: id }); 679 | } 680 | }); 681 | 682 | bench("knex", async () => { 683 | for (const id of supplierIds) { 684 | await knexDb("suppliers").where({ id }).first(); 685 | } 686 | }); 687 | 688 | bench("kysely", async () => { 689 | for (const id of supplierIds) { 690 | await kysely 691 | .selectFrom("suppliers") 692 | .selectAll() 693 | .where("suppliers.id", "=", id) 694 | .execute(); 695 | } 696 | }); 697 | 698 | bench("mikro", async () => { 699 | for (const id of supplierIds) { 700 | await mikro.findOne(m_Supplier, { id }); 701 | } 702 | mikro.clear(); 703 | }); 704 | 705 | bench("typeorm", async () => { 706 | for (const id of supplierIds) { 707 | await typeorm.getRepository(Supplier).findOneBy({ id }); 708 | } 709 | }); 710 | 711 | bench("prisma", async () => { 712 | for (const id of supplierIds) { 713 | await prisma.supplier.findUnique({ 714 | where: { 715 | id, 716 | }, 717 | }); 718 | } 719 | }); 720 | }); 721 | 722 | // 723 | group("SELECT * FROM product", () => { 724 | bench("pg", async () => { 725 | await pg.query('select * from "products"'); 726 | }); 727 | 728 | const query = { 729 | name: "Products-getAll", 730 | text: 'select * from "products"', 731 | }; 732 | bench("pg:p", async () => { 733 | await pgPrepared.query(query); 734 | }); 735 | 736 | bench("drizzle", async () => { 737 | await drizzle.select().from(products); 738 | }); 739 | 740 | const prepared = drizzlePrepared 741 | .select() 742 | .from(products) 743 | .prepare("Products-getAll-D"); 744 | 745 | bench("drizzle:p", async () => { 746 | await prepared.execute(); 747 | }); 748 | 749 | bench("knex", async () => { 750 | await knexDb("products"); 751 | }); 752 | 753 | bench("kysely", async () => { 754 | await kysely.selectFrom("products").selectAll().execute(); 755 | }); 756 | 757 | bench("mikro", async () => { 758 | await mikro.find(m_Product, {}); 759 | mikro.clear(); 760 | }); 761 | 762 | bench("typeorm", async () => { 763 | await typeorm.getRepository(Product).find(); 764 | }); 765 | 766 | bench("prisma", async () => { 767 | await prisma.product.findMany(); 768 | }); 769 | }); 770 | 771 | // 772 | group("SELECT * FROM product LEFT JOIN supplier WHERE product.id = ?", () => { 773 | bench("pg", async () => { 774 | for (const id of productIds) { 775 | await pg.query( 776 | `select "products".*, "suppliers".* 777 | from "products" left join "suppliers" on "products"."supplier_id" = "suppliers"."id" where "products"."id" = $1`, 778 | [id] 779 | ); 780 | } 781 | }); 782 | 783 | const query = { 784 | name: "Products-getInfo", 785 | text: `select "products".*, "suppliers".* 786 | from "products" left join "suppliers" on "products"."supplier_id" = "suppliers"."id" where "products"."id" = $1`, 787 | }; 788 | 789 | bench("pg:p", async () => { 790 | for (const id of productIds) { 791 | await pgPrepared.query(query, [id]); 792 | } 793 | }); 794 | 795 | bench("drizzle", async () => { 796 | for (const id of productIds) { 797 | await drizzle 798 | .select() 799 | .from(products) 800 | .leftJoin(suppliers, eq(products.supplierId, suppliers.id)) 801 | .where(eq(products.id, id)); 802 | } 803 | }); 804 | 805 | const prepared = drizzlePrepared 806 | .select() 807 | .from(products) 808 | .leftJoin(suppliers, eq(products.supplierId, suppliers.id)) 809 | .where(eq(products.id, placeholder("productId"))) 810 | .prepare("Products-getInfo-D"); 811 | 812 | bench("drizzle:p", async () => { 813 | for (const id of productIds) { 814 | await prepared.execute({ productId: id }); 815 | } 816 | }); 817 | 818 | bench("knex", async () => { 819 | for (const id of productIds) { 820 | await knexDb("products") 821 | .select([ 822 | "products.*", 823 | "suppliers.id as s_id", 824 | "company_name", 825 | "contact_name", 826 | "contact_title", 827 | "address", 828 | "city", 829 | "region", 830 | "postal_code", 831 | "country", 832 | "phone", 833 | ]) 834 | .where("products.id", "=", id) 835 | .leftJoin("suppliers", "suppliers.id", "products.supplier_id"); 836 | } 837 | }); 838 | 839 | bench("kysely", async () => { 840 | for (const id of productIds) { 841 | await kysely 842 | .selectFrom("products") 843 | .selectAll() 844 | .where("products.id", "=", id) 845 | .leftJoin( 846 | kysely 847 | .selectFrom("suppliers") 848 | .select([ 849 | "id as s_id", 850 | "company_name", 851 | "contact_name", 852 | "contact_title", 853 | "address", 854 | "city", 855 | "region", 856 | "postal_code", 857 | "country", 858 | "phone", 859 | ]) 860 | .as("s1"), 861 | "s1.s_id", 862 | "products.supplier_id" 863 | ) 864 | .execute(); 865 | } 866 | }); 867 | 868 | bench("mikro", async () => { 869 | for (const id of productIds) { 870 | await mikro.findOne(m_Product, { id }, { populate: ["supplier"] }); 871 | } 872 | mikro.clear(); 873 | }); 874 | 875 | bench("typeorm", async () => { 876 | for (const id of productIds) { 877 | await typeorm.getRepository(Product).findOne({ 878 | where: { 879 | id, 880 | }, 881 | relations: ["supplier"], 882 | }); 883 | } 884 | }); 885 | 886 | bench("prisma", async () => { 887 | for (const id of productIds) { 888 | await prisma.product.findUnique({ 889 | where: { 890 | id, 891 | }, 892 | include: { 893 | supplier: true, 894 | }, 895 | }); 896 | } 897 | }); 898 | }); 899 | 900 | // 901 | group("SELECT * FROM product WHERE product.name ILIKE ?", () => { 902 | bench("pg", async () => { 903 | for (const it of productSearches) { 904 | await pg.query( 905 | 'select * from "products" where "products"."name" ilike $1', 906 | [`%${it}%`] 907 | ); 908 | } 909 | }); 910 | 911 | const query = { 912 | name: "Products-search", 913 | text: 'select * from "products" where "products"."name" ilike $1', 914 | }; 915 | 916 | bench("pg:p", async () => { 917 | for (const it of productSearches) { 918 | await pgPrepared.query(query, [`%${it}%`]); 919 | } 920 | }); 921 | 922 | bench("drizzle", async () => { 923 | for (const it of productSearches) { 924 | await drizzle 925 | .select() 926 | .from(products) 927 | .where(ilike(products.name, `%${it}%`)); 928 | } 929 | }); 930 | 931 | const prepared = drizzlePrepared 932 | .select() 933 | .from(products) 934 | .where(sql`${products.name} ilike ${placeholder("name")}`) 935 | .prepare("Products-search-D"); 936 | 937 | bench("drizzle:p", async () => { 938 | for (const it of productSearches) { 939 | await prepared.execute({ name: `%${it}%` }); 940 | } 941 | }); 942 | 943 | bench("knex", async () => { 944 | for (const it of productSearches) { 945 | await knexDb("products").whereILike("name", `%${it}%`); 946 | } 947 | }); 948 | 949 | bench("kysely", async () => { 950 | for (const it of productSearches) { 951 | await kysely 952 | .selectFrom("products") 953 | .selectAll() 954 | .where(k_sql`name`, "ilike", `%${it}%`) 955 | .execute(); 956 | } 957 | }); 958 | 959 | bench("mikro", async () => { 960 | for (const it of productSearches) { 961 | await mikro.find(m_Product, { 962 | name: { $ilike: `%${it}%` }, 963 | }); 964 | } 965 | mikro.clear(); 966 | }); 967 | 968 | bench("typeorm", async () => { 969 | for (const it of productSearches) { 970 | await typeorm.getRepository(Product).find({ 971 | where: { 972 | name: ILike(`%${it}%`), 973 | }, 974 | }); 975 | } 976 | }); 977 | 978 | bench("prisma", async () => { 979 | for (const it of productSearches) { 980 | await prisma.product.findMany({ 981 | where: { 982 | name: { 983 | contains: it, 984 | mode: "insensitive", 985 | }, 986 | }, 987 | }); 988 | } 989 | }); 990 | }); 991 | 992 | group("select all order with sum and count", () => { 993 | bench("pg", async () => { 994 | await pg.query(`select "id", "shipped_date", "ship_name", "ship_city", "ship_country", count("product_id") as "products", 995 | sum("quantity") as "quantity", sum("quantity" * "unit_price") as "total_price" 996 | from "orders" as "o" left join "order_details" as "od" on "od"."order_id" = "o"."id" group by "o"."id"`); 997 | }); 998 | 999 | const query = { 1000 | name: "Orders-getAll", 1001 | text: `select "id", "shipped_date", "ship_name", "ship_city", "ship_country", count("product_id") as "products", 1002 | sum("quantity") as "quantity", sum("quantity" * "unit_price") as "total_price" 1003 | from "orders" as "o" left join "order_details" as "od" on "od"."order_id" = "o"."id" group by "o"."id"`, 1004 | }; 1005 | bench("pg:p", async () => { 1006 | await pgPrepared.query(query); 1007 | }); 1008 | 1009 | bench("drizzle", async () => { 1010 | await drizzle 1011 | .select({ 1012 | id: orders.id, 1013 | shippedDate: orders.shippedDate, 1014 | shipName: orders.shipName, 1015 | shipCity: orders.shipCity, 1016 | shipCountry: orders.shipCountry, 1017 | productsCount: sql`count(${details.productId})`.as(), 1018 | quantitySum: sql`sum(${details.quantity})`.as(), 1019 | totalPrice: 1020 | sql`sum(${details.quantity} * ${details.unitPrice})`.as(), 1021 | }) 1022 | .from(orders) 1023 | .leftJoin(details, eq(orders.id, details.orderId)) 1024 | .groupBy(orders.id); 1025 | }); 1026 | 1027 | const prepared = drizzlePrepared 1028 | .select({ 1029 | id: orders.id, 1030 | shippedDate: orders.shippedDate, 1031 | shipName: orders.shipName, 1032 | shipCity: orders.shipCity, 1033 | shipCountry: orders.shipCountry, 1034 | productsCount: sql`count(${details.productId})`.as(), 1035 | quantitySum: sql`sum(${details.quantity})`.as(), 1036 | totalPrice: 1037 | sql`sum(${details.quantity} * ${details.unitPrice})`.as(), 1038 | }) 1039 | .from(orders) 1040 | .leftJoin(details, eq(orders.id, details.orderId)) 1041 | .groupBy(orders.id) 1042 | .prepare("Orders-getAll-D"); 1043 | 1044 | bench("drizzle:p", async () => { 1045 | await prepared.execute(); 1046 | }); 1047 | 1048 | bench("knex", async () => { 1049 | await knexDb("orders") 1050 | .select([ 1051 | "orders.id", 1052 | "orders.shipped_date", 1053 | "orders.ship_name", 1054 | "orders.ship_city", 1055 | "orders.ship_country", 1056 | ]) 1057 | .leftJoin("order_details", "order_details.order_id", "orders.id") 1058 | .count("product_id as products_count") 1059 | .sum("quantity as quantity_sum") 1060 | .sum({ total_price: knexDb.raw("?? * ??", ["quantity", "unit_price"]) }) 1061 | .groupBy("orders.id"); 1062 | }); 1063 | 1064 | bench("kysely", async () => { 1065 | await kysely 1066 | .selectFrom("orders") 1067 | .select([ 1068 | "orders.id", 1069 | "orders.shipped_date", 1070 | "orders.ship_name", 1071 | "orders.ship_city", 1072 | "orders.ship_country", 1073 | kysely.fn.count("product_id").as("products_count"), 1074 | kysely.fn.sum("quantity").as("quantity_sum"), 1075 | k_sql`SUM(quantity * unit_price)`.as("total_price"), 1076 | ]) 1077 | .leftJoin("order_details", "order_details.order_id", "orders.id") 1078 | .groupBy("orders.id") 1079 | .execute(); 1080 | }); 1081 | 1082 | bench("mikro", async () => { 1083 | const result = await mikro.find(m_Order, {}, { populate: ["details"] }); 1084 | const orders = result.map((item) => { 1085 | const details = item.details.getItems(); 1086 | return { 1087 | id: item.id, 1088 | shippedDate: item.shippedDate, 1089 | shipName: item.shipName, 1090 | shipCity: item.shipCity, 1091 | shipCountry: item.shipCountry, 1092 | productsCount: item.details.length, 1093 | quantitySum: details.reduce( 1094 | (sum, deteil) => (sum += +deteil.quantity), 1095 | 0 1096 | ), 1097 | totalPrice: details.reduce( 1098 | (sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 1099 | 0 1100 | ), 1101 | }; 1102 | }); 1103 | mikro.clear(); 1104 | }); 1105 | 1106 | bench("typeorm", async () => { 1107 | const result = await typeorm.getRepository(Order).find({ 1108 | relations: { 1109 | details: true, 1110 | }, 1111 | }); 1112 | const orders = result.map((item) => { 1113 | return { 1114 | id: item.id, 1115 | shippedDate: item.shippedDate, 1116 | shipName: item.shipName, 1117 | shipCity: item.shipCity, 1118 | shipCountry: item.shipCountry, 1119 | productsCount: item.details.length, 1120 | quantitySum: item.details.reduce( 1121 | (sum, deteil) => (sum += +deteil.quantity), 1122 | 0 1123 | ), 1124 | totalPrice: item.details.reduce( 1125 | (sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 1126 | 0 1127 | ), 1128 | }; 1129 | }); 1130 | }); 1131 | 1132 | bench("prisma", async () => { 1133 | const result = await prisma.order.findMany({ 1134 | include: { 1135 | details: true, 1136 | }, 1137 | }); 1138 | const orders = result.map((item) => { 1139 | return { 1140 | id: item.id, 1141 | shippedDate: item.shippedDate, 1142 | shipName: item.shipName, 1143 | shipCity: item.shipCity, 1144 | shipCountry: item.shipCountry, 1145 | productsCount: item.details.length, 1146 | quantitySum: item.details.reduce( 1147 | (sum, deteil) => (sum += +deteil.quantity), 1148 | 0 1149 | ), 1150 | totalPrice: item.details.reduce( 1151 | (sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 1152 | 0 1153 | ), 1154 | }; 1155 | }); 1156 | }); 1157 | }); 1158 | 1159 | group("select order with sum and count using limit with offset", () => { 1160 | const limit = 50; 1161 | 1162 | bench("pg", async () => { 1163 | let offset = 0; 1164 | while (true) { 1165 | const result = await pg.query( 1166 | `select "id", "shipped_date", "ship_name", "ship_city", "ship_country", count("product_id") as "products", 1167 | sum("quantity") as "quantity", sum("quantity" * "unit_price") as "total_price" 1168 | from "orders" as "o" left join "order_details" as "od" on "od"."order_id" = "o"."id" group by "o"."id" ORDER BY o.id ASC limit $1 offset $2`, 1169 | [limit, offset] 1170 | ); 1171 | 1172 | offset += limit; 1173 | if (result.rowCount < limit) break; 1174 | } 1175 | }); 1176 | 1177 | const query = { 1178 | name: "Orders-getLimit-withOffset", 1179 | text: `select "id", "shipped_date", "ship_name", "ship_city", "ship_country", count("product_id") as "products", 1180 | sum("quantity") as "quantity", sum("quantity" * "unit_price") as "total_price" 1181 | from "orders" as "o" left join "order_details" as "od" on "od"."order_id" = "o"."id" group by "o"."id" ORDER BY o.id ASC limit $1 offset $2`, 1182 | }; 1183 | 1184 | bench("pg:p", async () => { 1185 | let offset = 0; 1186 | while (true) { 1187 | const result = await pgPrepared.query(query, [limit, offset]); 1188 | offset += limit; 1189 | if (result.rowCount < limit) break; 1190 | } 1191 | }); 1192 | 1193 | bench("drizzle", async () => { 1194 | let offset = 0; 1195 | while (true) { 1196 | const result = await drizzle 1197 | .select({ 1198 | id: orders.id, 1199 | shippedDate: orders.shippedDate, 1200 | shipName: orders.shipName, 1201 | shipCity: orders.shipCity, 1202 | shipCountry: orders.shipCountry, 1203 | productsCount: sql`count(${details.productId})`.as(), 1204 | quantitySum: sql`sum(${details.quantity})`.as(), 1205 | totalPrice: 1206 | sql`sum(${details.quantity} * ${details.unitPrice})`.as(), 1207 | }) 1208 | .from(orders) 1209 | .leftJoin(details, eq(orders.id, details.orderId)) 1210 | .orderBy(orders.id) 1211 | .groupBy(orders.id) 1212 | .limit(limit) 1213 | .offset(offset); 1214 | 1215 | offset += limit; 1216 | if (result.length < limit) break; 1217 | } 1218 | }); 1219 | 1220 | const prepared = drizzlePrepared 1221 | .select({ 1222 | id: orders.id, 1223 | shippedDate: orders.shippedDate, 1224 | shipName: orders.shipName, 1225 | shipCity: orders.shipCity, 1226 | shipCountry: orders.shipCountry, 1227 | productsCount: sql`count(${details.productId})`.as(), 1228 | quantitySum: sql`sum(${details.quantity})`.as(), 1229 | totalPrice: 1230 | sql`sum(${details.quantity} * ${details.unitPrice})`.as(), 1231 | }) 1232 | .from(orders) 1233 | .leftJoin(details, eq(orders.id, details.orderId)) 1234 | .orderBy(orders.id) 1235 | .groupBy(orders.id) 1236 | .limit(placeholder("limit")) 1237 | .offset(placeholder("offset")) 1238 | .prepare("Orders-getLimit-withOffset-D"); 1239 | 1240 | bench("drizle:p", async () => { 1241 | let offset = 0; 1242 | while (true) { 1243 | const result = await prepared.execute({ limit, offset }); 1244 | offset += limit; 1245 | if (result.length < limit) break; 1246 | } 1247 | }); 1248 | 1249 | bench("knex", async () => { 1250 | let offset = 0; 1251 | while (true) { 1252 | const result = await knexDb("orders") 1253 | .select([ 1254 | "orders.id", 1255 | "orders.shipped_date", 1256 | "orders.ship_name", 1257 | "orders.ship_city", 1258 | "orders.ship_country", 1259 | ]) 1260 | .leftJoin("order_details", "order_details.order_id", "orders.id") 1261 | .count("product_id as products_count") 1262 | .sum("quantity as quantity_sum") 1263 | .sum({ total_price: knexDb.raw("?? * ??", ["quantity", "unit_price"]) }) 1264 | .groupBy("orders.id") 1265 | .orderBy("orders.id") 1266 | .limit(limit) 1267 | .offset(offset); 1268 | 1269 | offset += limit; 1270 | if (result.length < limit) break; 1271 | } 1272 | }); 1273 | 1274 | bench("kysely", async () => { 1275 | let offset = 0; 1276 | while (true) { 1277 | const result = await kysely 1278 | .selectFrom("orders") 1279 | .select([ 1280 | "orders.id", 1281 | "orders.shipped_date", 1282 | "orders.ship_name", 1283 | "orders.ship_city", 1284 | "orders.ship_country", 1285 | kysely.fn.count("product_id").as("products_count"), 1286 | kysely.fn.sum("quantity").as("quantity_sum"), 1287 | k_sql`SUM(quantity * unit_price)`.as("total_price"), 1288 | ]) 1289 | .leftJoin("order_details", "order_details.order_id", "orders.id") 1290 | .groupBy("orders.id") 1291 | .orderBy("orders.id") 1292 | .limit(limit) 1293 | .offset(offset) 1294 | .execute(); 1295 | 1296 | offset += limit; 1297 | if (result.length < limit) break; 1298 | } 1299 | }); 1300 | 1301 | bench("mikro", async () => { 1302 | let offset = 0; 1303 | while (true) { 1304 | const result = await mikro.find( 1305 | m_Order, 1306 | {}, 1307 | { populate: ["details"], limit, offset, orderBy: { id: "ASC" } } 1308 | ); 1309 | const orders = result.map((item) => { 1310 | const details = item.details.getItems(); 1311 | return { 1312 | id: item.id, 1313 | shippedDate: item.shippedDate, 1314 | shipName: item.shipName, 1315 | shipCity: item.shipCity, 1316 | shipCountry: item.shipCountry, 1317 | productsCount: item.details.length, 1318 | quantitySum: details.reduce( 1319 | (sum, deteil) => (sum += +deteil.quantity), 1320 | 0 1321 | ), 1322 | totalPrice: details.reduce( 1323 | (sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 1324 | 0 1325 | ), 1326 | }; 1327 | }); 1328 | offset += limit; 1329 | if (result.length < limit) break; 1330 | } 1331 | mikro.clear(); 1332 | }); 1333 | 1334 | bench("typeorm", async () => { 1335 | let offset = 0; 1336 | while (true) { 1337 | const result = await typeorm.getRepository(Order).find({ 1338 | relations: { 1339 | details: true, 1340 | }, 1341 | order: { 1342 | id: "ASC", 1343 | }, 1344 | take: limit, 1345 | skip: offset, 1346 | }); 1347 | const orders = result.map((item) => { 1348 | return { 1349 | id: item.id, 1350 | shippedDate: item.shippedDate, 1351 | shipName: item.shipName, 1352 | shipCity: item.shipCity, 1353 | shipCountry: item.shipCountry, 1354 | productsCount: item.details.length, 1355 | quantitySum: item.details.reduce( 1356 | (sum, deteil) => (sum += +deteil.quantity), 1357 | 0 1358 | ), 1359 | totalPrice: item.details.reduce( 1360 | (sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 1361 | 0 1362 | ), 1363 | }; 1364 | }); 1365 | 1366 | offset += limit; 1367 | if (result.length < limit) break; 1368 | } 1369 | }); 1370 | 1371 | bench("prisma", async () => { 1372 | let offset = 0; 1373 | while (true) { 1374 | const result = await prisma.order.findMany({ 1375 | include: { 1376 | details: true, 1377 | }, 1378 | orderBy: { 1379 | id: "asc", 1380 | }, 1381 | take: limit, 1382 | skip: offset, 1383 | }); 1384 | const orders = result.map((item) => { 1385 | return { 1386 | id: item.id, 1387 | shippedDate: item.shippedDate, 1388 | shipName: item.shipName, 1389 | shipCity: item.shipCity, 1390 | shipCountry: item.shipCountry, 1391 | productsCount: item.details.length, 1392 | quantitySum: item.details.reduce( 1393 | (sum, deteil) => (sum += +deteil.quantity), 1394 | 0 1395 | ), 1396 | totalPrice: item.details.reduce( 1397 | (sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 1398 | 0 1399 | ), 1400 | }; 1401 | }); 1402 | 1403 | offset += limit; 1404 | if (result.length < limit) break; 1405 | } 1406 | }); 1407 | }); 1408 | 1409 | group("select order where order.id = ? with sum and count", () => { 1410 | bench("pg", async () => { 1411 | await Promise.all( 1412 | orderIds.map(async (id) => { 1413 | await pg.query( 1414 | `select "id", "shipped_date", "ship_name", "ship_city", "ship_country", count("product_id") as "products", 1415 | sum("quantity") as "quantity", sum("quantity" * "unit_price") as "total_price" 1416 | from "orders" as "o" left join "order_details" as "od" on "od"."order_id" = "o"."id" where "o"."id" = $1 group by "o"."id"`, 1417 | [id] 1418 | ); 1419 | }) 1420 | ); 1421 | // for (const id of orderIds) { 1422 | // await pg.query( 1423 | // `select "id", "shipped_date", "ship_name", "ship_city", "ship_country", count("product_id") as "products", 1424 | // sum("quantity") as "quantity", sum("quantity" * "unit_price") as "total_price" 1425 | // from "orders" as "o" left join "order_details" as "od" on "od"."order_id" = "o"."id" where "o"."id" = $1 group by "o"."id"`, 1426 | // [id] 1427 | // ); 1428 | // } 1429 | }); 1430 | 1431 | const query = { 1432 | name: "Orders-getById", 1433 | text: `select "id", "shipped_date", "ship_name", "ship_city", "ship_country", count("product_id") as "products", 1434 | sum("quantity") as "quantity", sum("quantity" * "unit_price") as "total_price" 1435 | from "orders" as "o" left join "order_details" as "od" on "od"."order_id" = "o"."id" where "o"."id" = $1 group by "o"."id"`, 1436 | }; 1437 | 1438 | bench("pg:p", async () => { 1439 | await Promise.all( 1440 | orderIds.map(async (id) => { 1441 | await pgPrepared.query(query, [id]); 1442 | }) 1443 | ); 1444 | // for (const id of orderIds) { 1445 | // await pg.query(query, [id]); 1446 | // } 1447 | }); 1448 | 1449 | bench("drizzle", async () => { 1450 | await Promise.all( 1451 | orderIds.map(async (id) => { 1452 | await drizzle 1453 | .select({ 1454 | id: orders.id, 1455 | shippedDate: orders.shippedDate, 1456 | shipName: orders.shipName, 1457 | shipCity: orders.shipCity, 1458 | shipCountry: orders.shipCountry, 1459 | productsCount: sql`count(${details.productId})`.as(), 1460 | quantitySum: sql`sum(${details.quantity})`.as(), 1461 | totalPrice: 1462 | sql`sum(${details.quantity} * ${details.unitPrice})`.as(), 1463 | }) 1464 | .from(orders) 1465 | .leftJoin(details, eq(orders.id, details.orderId)) 1466 | .where(eq(orders.id, id)) 1467 | .groupBy(orders.id); 1468 | }) 1469 | ); 1470 | // for (const id of orderIds) { 1471 | // await drizzle 1472 | // .select(orders) 1473 | // .fields({ 1474 | // id: orders.id, 1475 | // shippedDate: orders.shippedDate, 1476 | // shipName: orders.shipName, 1477 | // shipCity: orders.shipCity, 1478 | // shipCountry: orders.shipCountry, 1479 | // productsCount: sql`count(${details.productId})`.as(), 1480 | // quantitySum: sql`sum(${details.quantity})`.as(), 1481 | // totalPrice: 1482 | // sql`sum(${details.quantity} * ${details.unitPrice})`.as(), 1483 | // }) 1484 | // .leftJoin(details, eq(orders.id, details.orderId), {}) 1485 | // .where(eq(orders.id, id)) 1486 | // .groupBy(orders.id) 1487 | // } 1488 | }); 1489 | 1490 | const prepared = drizzlePrepared 1491 | .select({ 1492 | id: orders.id, 1493 | shippedDate: orders.shippedDate, 1494 | shipName: orders.shipName, 1495 | shipCity: orders.shipCity, 1496 | shipCountry: orders.shipCountry, 1497 | productsCount: sql`count(${details.productId})`.as(), 1498 | quantitySum: sql`sum(${details.quantity})`.as(), 1499 | totalPrice: 1500 | sql`sum(${details.quantity} * ${details.unitPrice})`.as(), 1501 | }) 1502 | .from(orders) 1503 | .leftJoin(details, eq(orders.id, details.orderId)) 1504 | .where(eq(orders.id, placeholder("orderId"))) 1505 | .groupBy(orders.id) 1506 | .prepare("Orders-getById-D"); 1507 | 1508 | bench("drizzle:p", async () => { 1509 | await Promise.all( 1510 | orderIds.map(async (id) => { 1511 | await prepared.execute({ orderId: id }); 1512 | }) 1513 | ); 1514 | }); 1515 | 1516 | bench("knex", async () => { 1517 | await Promise.all( 1518 | orderIds.map(async (id) => { 1519 | await knexDb("orders") 1520 | .select([ 1521 | "orders.id", 1522 | "orders.shipped_date", 1523 | "orders.ship_name", 1524 | "orders.ship_city", 1525 | "orders.ship_country", 1526 | ]) 1527 | .leftJoin("order_details", "order_details.order_id", "orders.id") 1528 | .where("orders.id", "=", id) 1529 | .count("product_id as products_count") 1530 | .sum("quantity as quantity_sum") 1531 | .sum({ 1532 | total_price: knexDb.raw("?? * ??", ["quantity", "unit_price"]), 1533 | }) 1534 | .groupBy("orders.id"); 1535 | }) 1536 | ); 1537 | // for (const id of orderIds) { 1538 | // await knexDb("orders") 1539 | // .select([ 1540 | // "orders.id", 1541 | // "orders.shipped_date", 1542 | // "orders.ship_name", 1543 | // "orders.ship_city", 1544 | // "orders.ship_country", 1545 | // ]) 1546 | // .leftJoin("order_details", "order_details.order_id", "orders.id") 1547 | // .where("orders.id", "=", id) 1548 | // .count("product_id as products_count") 1549 | // .sum("quantity as quantity_sum") 1550 | // .sum({ total_price: knexDb.raw("?? * ??", ["quantity", "unit_price"]) }) 1551 | // .groupBy("orders.id") 1552 | // } 1553 | }); 1554 | 1555 | bench("kysely", async () => { 1556 | await Promise.all( 1557 | orderIds.map(async (id) => { 1558 | await kysely 1559 | .selectFrom("orders") 1560 | .select([ 1561 | "orders.id", 1562 | "orders.shipped_date", 1563 | "orders.ship_name", 1564 | "orders.ship_city", 1565 | "orders.ship_country", 1566 | kysely.fn.count("product_id").as("products_count"), 1567 | kysely.fn.sum("quantity").as("quantity_sum"), 1568 | k_sql`SUM(quantity * unit_price)`.as("total_price"), 1569 | ]) 1570 | .leftJoin("order_details", "order_details.order_id", "orders.id") 1571 | .where("orders.id", "=", id) 1572 | .groupBy("orders.id") 1573 | .execute(); 1574 | }) 1575 | ); 1576 | 1577 | // for (const id of orderIds) { 1578 | // await kysely 1579 | // .selectFrom("orders") 1580 | // .select([ 1581 | // "orders.id", 1582 | // "orders.shipped_date", 1583 | // "orders.ship_name", 1584 | // "orders.ship_city", 1585 | // "orders.ship_country", 1586 | // kysely.fn.count("product_id").as("products_count"), 1587 | // kysely.fn.sum("quantity").as("quantity_sum"), 1588 | // k_sql`SUM(quantity * unit_price)`.as("total_price"), 1589 | // ]) 1590 | // .leftJoin("order_details", "order_details.order_id", "orders.id") 1591 | // .where("orders.id", "=", id) 1592 | // .groupBy("orders.id") 1593 | // .execute(); 1594 | // } 1595 | }); 1596 | 1597 | bench("mikro", async () => { 1598 | await Promise.all( 1599 | orderIds.map(async (id) => { 1600 | const result = await mikro.findOne( 1601 | m_Order, 1602 | { id }, 1603 | { populate: ["details"] } 1604 | ); 1605 | const details = result!.details.getItems(); 1606 | const order = { 1607 | id: result!.id, 1608 | shippedDate: result!.shippedDate, 1609 | shipName: result!.shipName, 1610 | shipCity: result!.shipCity, 1611 | shipCountry: result!.shipCountry, 1612 | productsCount: result!.details.length, 1613 | quantitySum: details.reduce( 1614 | (sum, deteil) => (sum += +deteil.quantity), 1615 | 0 1616 | ), 1617 | totalPrice: details.reduce( 1618 | (sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 1619 | 0 1620 | ), 1621 | }; 1622 | }) 1623 | ); 1624 | // for (const id of orderIds) { 1625 | // const result = await mikro.findOne( 1626 | // m_Order, 1627 | // { id }, 1628 | // { populate: ["details"] } 1629 | // ); 1630 | // const details = result!.details.getItems() 1631 | // const order = { 1632 | // id: result!.id, 1633 | // shippedDate: result!.shippedDate, 1634 | // shipName: result!.shipName, 1635 | // shipCity: result!.shipCity, 1636 | // shipCountry: result!.shipCountry, 1637 | // productsCount: result!.details.length, 1638 | // quantitySum: details 1639 | // .reduce((sum, deteil) => (sum += +deteil.quantity), 0), 1640 | // totalPrice: details 1641 | // .reduce( 1642 | // (sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 1643 | // 0 1644 | // ), 1645 | // }; 1646 | // } 1647 | mikro.clear(); 1648 | }); 1649 | 1650 | bench("prisma", async () => { 1651 | await Promise.all( 1652 | orderIds.map(async (id) => { 1653 | const result = await prisma.order.findFirst({ 1654 | include: { 1655 | details: true, 1656 | }, 1657 | where: { 1658 | id, 1659 | }, 1660 | }); 1661 | const order = { 1662 | id: result!.id, 1663 | shippedDate: result!.shippedDate, 1664 | shipName: result!.shipName, 1665 | shipCity: result!.shipCity, 1666 | shipCountry: result!.shipCountry, 1667 | productsCount: result!.details.length, 1668 | quantitySum: result!.details.reduce( 1669 | (sum, deteil) => (sum += +deteil.quantity), 1670 | 0 1671 | ), 1672 | totalPrice: result!.details.reduce( 1673 | (sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 1674 | 0 1675 | ), 1676 | }; 1677 | }) 1678 | ); 1679 | // for (const id of orderIds) { 1680 | // const result = await prisma.order.findFirst({ 1681 | // include: { 1682 | // details: true, 1683 | // }, 1684 | // where: { 1685 | // id, 1686 | // }, 1687 | // }); 1688 | // const order = { 1689 | // id: result!.id, 1690 | // shippedDate: result!.shippedDate, 1691 | // shipName: result!.shipName, 1692 | // shipCity: result!.shipCity, 1693 | // shipCountry: result!.shipCountry, 1694 | // productsCount: result!.details.length, 1695 | // quantitySum: result!.details.reduce( 1696 | // (sum, deteil) => (sum += +deteil.quantity), 1697 | // 0 1698 | // ), 1699 | // totalPrice: result!.details.reduce( 1700 | // (sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 1701 | // 0 1702 | // ), 1703 | // }; 1704 | // } 1705 | }); 1706 | 1707 | bench("typeorm", async () => { 1708 | await Promise.all( 1709 | orderIds.map(async (id) => { 1710 | const result = await typeorm.getRepository(Order).findOne({ 1711 | relations: { 1712 | details: true, 1713 | }, 1714 | where: { 1715 | id, 1716 | }, 1717 | }); 1718 | const order = { 1719 | id: result!.id, 1720 | shippedDate: result!.shippedDate, 1721 | shipName: result!.shipName, 1722 | shipCity: result!.shipCity, 1723 | shipCountry: result!.shipCountry, 1724 | productsCount: result!.details.length, 1725 | quantitySum: result!.details.reduce( 1726 | (sum, deteil) => (sum += +deteil.quantity), 1727 | 0 1728 | ), 1729 | totalPrice: result!.details.reduce( 1730 | (sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 1731 | 0 1732 | ), 1733 | }; 1734 | }) 1735 | ); 1736 | // for (const id of orderIds) { 1737 | // const result = await typeorm.getRepository(Order).findOne({ 1738 | // relations: { 1739 | // details: true, 1740 | // }, 1741 | // where: { 1742 | // id, 1743 | // }, 1744 | // }); 1745 | // const order = { 1746 | // id: result!.id, 1747 | // shippedDate: result!.shippedDate, 1748 | // shipName: result!.shipName, 1749 | // shipCity: result!.shipCity, 1750 | // shipCountry: result!.shipCountry, 1751 | // productsCount: result!.details.length, 1752 | // quantitySum: result!.details.reduce( 1753 | // (sum, deteil) => (sum += +deteil.quantity), 1754 | // 0 1755 | // ), 1756 | // totalPrice: result!.details.reduce( 1757 | // (sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 1758 | // 0 1759 | // ), 1760 | // }; 1761 | // } 1762 | }); 1763 | }); 1764 | 1765 | // 1766 | group("SELECT * FROM order_detail WHERE order_id = ?", () => { 1767 | bench("pg", async () => { 1768 | for (const id of orderIds) { 1769 | await pg.query( 1770 | `SELECT * FROM "orders" AS o 1771 | LEFT JOIN "order_details" AS od ON o.id = od.order_id 1772 | LEFT JOIN "products" AS p ON od.product_id = p.id 1773 | WHERE o.id = $1`, 1774 | [id] 1775 | ); 1776 | } 1777 | }); 1778 | 1779 | const query = { 1780 | name: "Orders-getInfo", 1781 | text: `SELECT * FROM "orders" AS o 1782 | LEFT JOIN "order_details" AS od ON o.id = od.order_id 1783 | LEFT JOIN "products" AS p ON od.product_id = p.id 1784 | WHERE o.id = $1`, 1785 | }; 1786 | 1787 | bench("pg:p", async () => { 1788 | for await (const id of orderIds) { 1789 | await pgPrepared.query(query, [id]); 1790 | } 1791 | }); 1792 | 1793 | bench("drizzle", async () => { 1794 | for (const id of orderIds) { 1795 | await drizzle 1796 | .select() 1797 | .from(orders) 1798 | .leftJoin(details, eq(orders.id, details.orderId)) 1799 | .leftJoin(products, eq(details.productId, products.id)) 1800 | .where(eq(orders.id, id)); 1801 | } 1802 | }); 1803 | 1804 | const prepared = drizzlePrepared 1805 | .select() 1806 | .from(orders) 1807 | .leftJoin(details, eq(orders.id, details.orderId)) 1808 | .leftJoin(products, eq(details.productId, products.id)) 1809 | .where(eq(orders.id, placeholder("orderId"))) 1810 | .prepare("Orders-getInfo-D"); 1811 | 1812 | bench("drizzle:p", async () => { 1813 | for (const id of orderIds) { 1814 | await prepared.execute({ orderId: id }); 1815 | } 1816 | }); 1817 | 1818 | bench("knex", async () => { 1819 | for (const id of orderIds) { 1820 | await knexDb("orders") 1821 | .select([ 1822 | "order_details.*", 1823 | "orders.id as o_id", 1824 | "order_date", 1825 | "required_date", 1826 | "shipped_date", 1827 | "ship_via", 1828 | "freight", 1829 | "ship_name", 1830 | "ship_city", 1831 | "ship_region", 1832 | "ship_postal_code", 1833 | "ship_country", 1834 | "customer_id", 1835 | "employee_id", 1836 | "products.id as p_id", 1837 | "name", 1838 | "qt_per_unit", 1839 | "products.unit_price as p_unit_price", 1840 | "units_in_stock", 1841 | "units_on_order", 1842 | "reorder_level", 1843 | "discontinued", 1844 | "supplier_id", 1845 | ]) 1846 | .where("orders.id", "=", id) 1847 | .leftJoin("order_details", "order_details.order_id", "orders.id") 1848 | .leftJoin("products", "products.id", "order_details.product_id"); 1849 | } 1850 | }); 1851 | 1852 | bench("kysely", async () => { 1853 | for (const id of orderIds) { 1854 | await kysely 1855 | .selectFrom("orders") 1856 | .selectAll() 1857 | .where("id", "=", id) 1858 | .leftJoin( 1859 | kysely 1860 | .selectFrom("order_details") 1861 | .select([ 1862 | "discount", 1863 | "order_id", 1864 | "product_id", 1865 | "unit_price", 1866 | "quantity", 1867 | ]) 1868 | .as("od"), 1869 | "od.order_id", 1870 | "orders.id" 1871 | ) 1872 | .leftJoin( 1873 | kysely 1874 | .selectFrom("products") 1875 | .select([ 1876 | "products.id as p_id", 1877 | "name", 1878 | "qt_per_unit", 1879 | "products.unit_price as p_unit_price", 1880 | "units_in_stock", 1881 | "units_on_order", 1882 | "reorder_level", 1883 | "discontinued", 1884 | "supplier_id", 1885 | ]) 1886 | .as("p"), 1887 | "p.p_id", 1888 | "od.product_id" 1889 | ) 1890 | .execute(); 1891 | } 1892 | }); 1893 | 1894 | bench("mikro", async () => { 1895 | for (const id of orderIds) { 1896 | await mikro.find( 1897 | m_Order, 1898 | { id }, 1899 | { populate: ["details", "details.product"] } 1900 | ); 1901 | } 1902 | mikro.clear(); 1903 | }); 1904 | 1905 | bench("typeorm", async () => { 1906 | for (const id of orderIds) { 1907 | await typeorm.getRepository(Order).find({ 1908 | relations: ["details", "details.product"], 1909 | where: { 1910 | id, 1911 | }, 1912 | }); 1913 | } 1914 | }); 1915 | 1916 | bench("prisma", async () => { 1917 | for (const id of orderIds) { 1918 | await prisma.order.findMany({ 1919 | where: { 1920 | id, 1921 | }, 1922 | include: { 1923 | details: { 1924 | include: { 1925 | product: true, 1926 | }, 1927 | }, 1928 | }, 1929 | }); 1930 | } 1931 | }); 1932 | }); 1933 | 1934 | const main = async () => { 1935 | try { 1936 | await run(); 1937 | } catch (e) { 1938 | console.error(e); 1939 | } 1940 | 1941 | await deleteDockerDBs(dockersDbs); 1942 | process.exit(0); 1943 | }; 1944 | 1945 | main(); 1946 | --------------------------------------------------------------------------------