├── .env ├── .gitignore ├── README.md ├── bench ├── bench.js ├── cpu-usage.ts ├── index.ts └── prepare.ts ├── build.ts ├── data └── requests.json ├── drizzle.config.ts ├── drizzle.sqlite.config.ts ├── drizzle ├── 0000_flat_master_mold.sql ├── 0001_concerned_mother_askani.sql └── meta │ ├── 0000_snapshot.json │ ├── 0001_snapshot.json │ └── _journal.json ├── package.json ├── pnpm-lock.yaml ├── src ├── cpu-usage.ts ├── docker.ts ├── drizzle-server-bun.ts ├── drizzle-server-node.ts ├── generate.ts ├── prisma-joins-server-bun.ts ├── prisma-joins-server-node.ts ├── prisma-server-bun.ts ├── prisma-server-node.ts ├── schema.prisma ├── schema.ts ├── seed.ts ├── sqlite │ ├── drizzle-server-bun.ts │ ├── drizzle-server-node.ts │ ├── drizzle │ │ ├── 0000_glamorous_skaar.sql │ │ └── meta │ │ │ ├── 0000_snapshot.json │ │ │ └── _journal.json │ ├── prisma-server-bun.ts │ ├── prisma-server-node.ts │ ├── schema.prisma │ ├── schema.ts │ └── seed.ts └── test.ts └── tsconfig.json /.env: -------------------------------------------------------------------------------- 1 | DATABASE_URL="postgres://postgres:postgres@localhost:5434/postgres" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test 3 | results 4 | dist 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Drizzle Benchmarks 2 | Drizzle has always been fast, we just wanted you to have a meaningful [benchmarks experience](orm.drizzle.team#benchmarks) 3 | 4 | We ran our benchmarks on 2 separate machines, so that observer does not influence results. For database we're using PostgreSQL instance with 42MB of E-commerce data(~370k records). 5 | K6 benchmarking instance lives on MacBook Air and makes [1M prepared requests](./data/requests.json) through 1GB ethernet to Lenovo M720q with Intel Core i3-9100T and 32GB of RAM. 6 | 7 | ![image](https://github.com/drizzle-team/drizzle-benchmarks/assets/4045375/103ae551-7708-4752-b3ed-5734adfe897f) 8 | 9 | 10 | To run your own tests - follow instructions below! 11 | 12 | ## Prepare test machine 13 | 1. Spin up a docker container with PostgreSQL using `pnpm start:docker` command. You can configure a desired database port in `./src/docker.ts` file: 14 | ```ts 15 | ... 16 | } 17 | 18 | const desiredPostgresPort = 5432; // change here 19 | main(); 20 | ``` 21 | 2. Update `DATABASE_URL` with allocated database port in .env file: 22 | ```env 23 | DATABASE_URL="postgres://postgres:postgres@localhost:5432/postgres" 24 | ``` 25 | 3. Seed your database with test data using `pnpm start:seed` command, you can change the size of the database in `./src/seed.ts` file: 26 | ```ts 27 | ... 28 | } 29 | 30 | main("micro"); // nano | micro 31 | ``` 32 | 4. Make sure you have Node version 18 installed or above, we've used Node v22. You can use [`nvm use 22`](https://github.com/nvm-sh/nvm) command 33 | 5. Start Drizzle/Prisma server: 34 | ```bash 35 | ## Drizzle 36 | pnpm start:drizzle 37 | 38 | ## Prisma 39 | pnpm prepare:prisma 40 | pnpm start:prisma 41 | ``` 42 | 43 | ## Prepare testing machine 44 | 1. Generate a list of http requests with `pnpm start:generate`. It will output a list of http requests to be run on the tested server | `./data/requests.json` 45 | 2. Install [k6 load tester](https://k6.io/) 46 | 3. Configure tested server url in `./k6.js` file 47 | ```js 48 | // const host = `http://192.168.31.144:3000`; // drizzle 49 | const host = `http://192.168.31.144:3001`; // prisma 50 | ``` 51 | 4. Run tests with `k6 run bench.js` 🚀 52 | -------------------------------------------------------------------------------- /bench/bench.js: -------------------------------------------------------------------------------- 1 | import { sleep } from 'k6'; 2 | import { SharedArray } from 'k6/data'; 3 | import { scenario } from 'k6/execution'; 4 | import http from 'k6/http'; 5 | 6 | const data = new SharedArray('requests', function () { 7 | // return JSON.parse(open('./data/requests.json')); 8 | return JSON.parse(open('../data/requests.json')).filter((it) => !it.startsWith('/search')); 9 | }); 10 | 11 | const host = __ENV.HOST || `http://192.168.31.144:3000`; // drizzle 12 | // const host = `http://192.168.31.144:3001`; // prisma 13 | 14 | export const options = { 15 | stages: [ 16 | { duration: '5s', target: 200 }, 17 | { duration: '15s', target: 200 }, 18 | { duration: '5s', target: 400 }, 19 | { duration: '15s', target: 400 }, 20 | { duration: '5s', target: 600 }, 21 | { duration: '15s', target: 600 }, 22 | { duration: '5s', target: 800 }, 23 | { duration: '15s', target: 800 }, 24 | { duration: '5s', target: 1000 }, 25 | { duration: '15s', target: 1000 }, 26 | { duration: '5s', target: 1200 }, 27 | { duration: '15s', target: 1200 }, 28 | { duration: '5s', target: 1400 }, 29 | { duration: '15s', target: 1400 }, 30 | { duration: '5s', target: 1600 }, 31 | { duration: '15s', target: 1600 }, 32 | { duration: '5s', target: 1800 }, 33 | { duration: '15s', target: 1800 }, 34 | { duration: '5s', target: 2000 }, 35 | { duration: '15s', target: 2000 }, 36 | { duration: '5s', target: 2200 }, 37 | { duration: '15s', target: 2200 }, 38 | { duration: '5s', target: 2400 }, 39 | { duration: '15s', target: 2400 }, 40 | { duration: '5s', target: 2600 }, 41 | { duration: '15s', target: 2600 }, 42 | { duration: '5s', target: 2800 }, 43 | { duration: '15s', target: 2800 }, 44 | { duration: '5s', target: 3000 }, 45 | { duration: '55s', target: 3000 }, 46 | ], 47 | 48 | // vus: 2600, 49 | // duration: '60s', 50 | // iterations: 600000, 51 | }; 52 | 53 | export default function () { 54 | const params = data[scenario.iterationInTest % data.length]; 55 | const url = `${host}${params}`; 56 | // const url = `${host}${params}`; 57 | 58 | http.get(url, { 59 | headers: { 60 | Connection: 'keep-alive', 61 | 'Keep-Alive': 'timeout=5, max=1000', 62 | }, 63 | tags: { name: 'fetch' }, 64 | timeout: '30s', 65 | }); 66 | 67 | sleep(0.1 * (scenario.iterationInTest % 6)); 68 | } 69 | -------------------------------------------------------------------------------- /bench/cpu-usage.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { parseArgs } from 'util'; 3 | 4 | const { 5 | values: { host, name, folder }, 6 | } = parseArgs({ 7 | args: process.argv, 8 | options: { 9 | host: { 10 | type: 'string', 11 | }, 12 | name: { 13 | type: 'string', 14 | }, 15 | folder: { 16 | type: 'string', 17 | default: 'results', 18 | }, 19 | }, 20 | strict: true, 21 | allowPositionals: true, 22 | }); 23 | 24 | if (!host) { 25 | throw new Error('host is required'); 26 | } 27 | 28 | if (!name) { 29 | throw new Error('name is required'); 30 | } 31 | 32 | if (!folder) { 33 | throw new Error('folder is required'); 34 | } 35 | 36 | const filename = `${folder}/cpu-usage-${name}.csv`; 37 | 38 | fs.writeFileSync(filename, 'core1,core2,core3,core4,timestamp\n', { 39 | flag: 'w', // 'w' means create a new file only if it does not exist 40 | }); 41 | 42 | setInterval(() => { 43 | fetch(`${host}/stats`) 44 | .then((res) => res.json() as Promise) 45 | .then((data) => { 46 | const [core1, core2, core3, core4] = data; 47 | 48 | if ( 49 | core1 === undefined || 50 | core2 === undefined || 51 | core3 === undefined || 52 | core4 === undefined || 53 | core1 === null || 54 | core2 === null || 55 | core3 === null || 56 | core4 === null 57 | ) { 58 | return; 59 | } 60 | 61 | fs.appendFileSync(filename, `${core1},${core2},${core3},${core4},${new Date().getTime()}\n`); 62 | }); 63 | }, 200); 64 | -------------------------------------------------------------------------------- /bench/index.ts: -------------------------------------------------------------------------------- 1 | import concurrently from 'concurrently'; 2 | import fs from 'fs'; 3 | import { parseArgs } from 'util'; 4 | 5 | // const host = `http://192.168.31.144:3000`; // drizzle 6 | // const host = `http://192.168.31.144:3001`; // prisma 7 | 8 | const { 9 | values: { host, name, folder }, 10 | } = parseArgs({ 11 | args: process.argv, 12 | options: { 13 | host: { 14 | type: 'string', 15 | }, 16 | name: { 17 | type: 'string', 18 | }, 19 | folder: { 20 | type: 'string', 21 | default: 'results', 22 | }, 23 | }, 24 | strict: true, 25 | allowPositionals: true, 26 | }); 27 | 28 | if (!host) { 29 | throw new Error('host is required'); 30 | } 31 | 32 | if (!name) { 33 | throw new Error('name is required'); 34 | } 35 | 36 | if (!folder) { 37 | throw new Error('folder is required'); 38 | } 39 | 40 | fs.mkdirSync(folder, { recursive: true }); 41 | 42 | const { result } = concurrently( 43 | [ 44 | { command: `tsx bench/cpu-usage.ts --host ${host} --name ${name} --folder ${folder}`, name: 'cpu-usage' }, 45 | { 46 | command: `sleep 1 && k6 run -e HOST=${host} bench/bench.js --out csv=${folder}/${name}.csv && duckdb :memory: "COPY (SELECT * FROM '${folder}/${name}.csv') TO '${folder}/${name}.parquet' (FORMAT 'parquet');" && rm ${folder}/${name}.csv`, 47 | name: 'bench', 48 | }, 49 | ], 50 | { 51 | prefix: 'name', 52 | killOthers: ['failure', 'success'], 53 | }, 54 | ); 55 | result.then(() => console.log('All done!')); 56 | -------------------------------------------------------------------------------- /bench/prepare.ts: -------------------------------------------------------------------------------- 1 | import { Database } from 'duckdb'; 2 | import fs from 'fs'; 3 | import { parseArgs } from 'util'; 4 | 5 | const { 6 | values: { folder }, 7 | } = parseArgs({ 8 | args: process.argv, 9 | options: { 10 | folder: { 11 | type: 'string', 12 | default: 'results', 13 | }, 14 | }, 15 | strict: true, 16 | allowPositionals: true, 17 | }); 18 | 19 | if (!folder) { 20 | throw new Error('folder is required'); 21 | } 22 | 23 | const db = new Database(':memory:'); 24 | 25 | const files = fs 26 | .readdirSync(folder) 27 | .filter((file) => file.endsWith('.parquet')) 28 | .map((file) => file.replace('.parquet', '')); 29 | 30 | const data: Record = {}; 31 | files.forEach((testName) => { 32 | db.all( 33 | ` 34 | WITH cpu_usage AS ( 35 | SELECT 36 | time_bucket(INTERVAL '1s', epoch_ms(timestamp)) AS "time", 37 | AVG(core1) AS "core1", 38 | AVG(core2) AS "core2", 39 | AVG(core3) AS "core3", 40 | AVG(core4) AS "core4" 41 | FROM 42 | read_csv('${folder}/cpu-usage-${testName}.csv') 43 | GROUP BY time 44 | ORDER BY time ASC 45 | ), reqs_per_sec AS ( 46 | SELECT 47 | time_bucket(INTERVAL '1s', to_timestamp(timestamp)) AS "time", 48 | SUM(metric_value) AS "reqs_per_sec" 49 | FROM 50 | "${folder}/${testName}.parquet" 51 | WHERE metric_name = 'http_reqs' 52 | GROUP BY time 53 | ), fail_reqs_per_sec AS ( 54 | SELECT 55 | time_bucket(INTERVAL '1s', to_timestamp(timestamp)) AS "time", 56 | SUM(metric_value) AS "fail_reqs_per_sec" 57 | FROM 58 | "${folder}/${testName}.parquet" 59 | WHERE 60 | metric_name = 'http_req_failed' 61 | GROUP BY time 62 | ), req_duration AS ( 63 | SELECT 64 | time_bucket(INTERVAL '1s', to_timestamp(timestamp)) AS "time", 65 | percentile_cont(0.95) WITHIN GROUP (ORDER BY metric_value) AS "latency_95", 66 | percentile_cont(0.90) WITHIN GROUP (ORDER BY metric_value) AS "latency_90", 67 | percentile_cont(0.99) WITHIN GROUP (ORDER BY metric_value) AS "latency_99", 68 | AVG(metric_value) AS "latency_average" 69 | FROM 70 | "${folder}/${testName}.parquet" 71 | WHERE metric_name = 'http_req_duration' 72 | AND status < 400 73 | GROUP BY time 74 | ) 75 | SELECT 76 | cpu_usage.time, 77 | cpu_usage.core1, 78 | cpu_usage.core2, 79 | cpu_usage.core3, 80 | cpu_usage.core4, 81 | reqs_per_sec.reqs_per_sec, 82 | fail_reqs_per_sec.fail_reqs_per_sec, 83 | req_duration.latency_95, 84 | req_duration.latency_90, 85 | req_duration.latency_99, 86 | req_duration.latency_average 87 | FROM 88 | cpu_usage 89 | JOIN reqs_per_sec ON epoch_ms(cpu_usage.time) = epoch_ms(reqs_per_sec.time) 90 | JOIN fail_reqs_per_sec ON epoch_ms(cpu_usage.time) = epoch_ms(fail_reqs_per_sec.time) 91 | JOIN req_duration ON epoch_ms(cpu_usage.time) = epoch_ms(req_duration.time) 92 | ORDER BY cpu_usage.time ASC; 93 | `, 94 | (err, res) => { 95 | console.log(`Processing ${testName}...`); 96 | if (err) { 97 | console.error(err); 98 | return; 99 | } 100 | data[testName] = res; 101 | 102 | if (Object.keys(data).length === files.length) { 103 | console.log('All data processed'); 104 | // Do something with the data 105 | fs.writeFileSync('data.json', JSON.stringify(data, null, 2)); 106 | } 107 | }, 108 | ); 109 | }); 110 | -------------------------------------------------------------------------------- /build.ts: -------------------------------------------------------------------------------- 1 | import * as esbuild from 'esbuild'; 2 | 3 | esbuild.buildSync({ 4 | entryPoints: ['./src/drizzle-server-node.ts'], 5 | bundle: true, 6 | minify: true, 7 | outfile: 'dist/drizzle.js', 8 | format: 'cjs', 9 | target: 'node22', 10 | platform: 'node', 11 | external: ['esbuild', 'pg-native'], 12 | }); 13 | 14 | esbuild.buildSync({ 15 | entryPoints: ['./src/prisma-server-node.ts'], 16 | bundle: true, 17 | minify: true, 18 | outfile: 'dist/prisma.js', 19 | format: 'cjs', 20 | target: 'node22', 21 | platform: 'node', 22 | external: ['esbuild', 'pg-native'], 23 | }); 24 | 25 | esbuild.buildSync({ 26 | entryPoints: ['./src/prisma-joins-server-node.ts'], 27 | bundle: true, 28 | minify: true, 29 | outfile: 'dist/prisma-joins.js', 30 | format: 'cjs', 31 | target: 'node22', 32 | platform: 'node', 33 | external: ['esbuild', 'pg-native'], 34 | }); 35 | 36 | esbuild.buildSync({ 37 | entryPoints: ['./src/sqlite/drizzle-server-node.ts'], 38 | bundle: true, 39 | minify: true, 40 | outfile: 'dist/sqlite/drizzle.js', 41 | format: 'cjs', 42 | target: 'node22', 43 | platform: 'node', 44 | external: ['esbuild', 'better-sqlite3'], 45 | }); 46 | 47 | esbuild.buildSync({ 48 | entryPoints: ['./src/sqlite/prisma-server-node.ts'], 49 | bundle: true, 50 | minify: true, 51 | outfile: 'dist/sqlite/prisma.js', 52 | format: 'cjs', 53 | target: 'node22', 54 | platform: 'node', 55 | external: ['esbuild', 'better-sqlite3'], 56 | }); 57 | -------------------------------------------------------------------------------- /drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'drizzle-kit'; 2 | import 'dotenv/config'; 3 | 4 | export default { 5 | schema: './src/schema.ts', 6 | dialect: 'postgresql', 7 | dbCredentials: { 8 | url: process.env.DATABASE_URL!, 9 | }, 10 | } satisfies Config; 11 | -------------------------------------------------------------------------------- /drizzle.sqlite.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'drizzle-kit'; 2 | import 'dotenv/config'; 3 | 4 | export default { 5 | schema: 'src/sqlite/schema.ts', 6 | out: 'src/sqlite/drizzle', 7 | dialect: 'sqlite', 8 | dbCredentials: { 9 | url: 'src/sqlite/northwind.db', 10 | }, 11 | } satisfies Config; 12 | -------------------------------------------------------------------------------- /drizzle/0000_flat_master_mold.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "customers" ( 2 | "id" serial PRIMARY KEY NOT NULL, 3 | "company_name" text NOT NULL, 4 | "contact_name" varchar NOT NULL, 5 | "contact_title" varchar NOT NULL, 6 | "address" varchar NOT NULL, 7 | "city" varchar NOT NULL, 8 | "postal_code" varchar, 9 | "region" varchar, 10 | "country" varchar NOT NULL, 11 | "phone" varchar NOT NULL, 12 | "fax" varchar 13 | ); 14 | --> statement-breakpoint 15 | CREATE TABLE IF NOT EXISTS "order_details" ( 16 | "unit_price" double precision NOT NULL, 17 | "quantity" integer NOT NULL, 18 | "discount" double precision NOT NULL, 19 | "order_id" integer NOT NULL, 20 | "product_id" integer NOT NULL 21 | ); 22 | --> statement-breakpoint 23 | CREATE TABLE IF NOT EXISTS "employees" ( 24 | "id" serial PRIMARY KEY NOT NULL, 25 | "last_name" varchar NOT NULL, 26 | "first_name" varchar, 27 | "title" varchar NOT NULL, 28 | "title_of_courtesy" varchar NOT NULL, 29 | "birth_date" date NOT NULL, 30 | "hire_date" date NOT NULL, 31 | "address" varchar NOT NULL, 32 | "city" varchar NOT NULL, 33 | "postal_code" varchar NOT NULL, 34 | "country" varchar NOT NULL, 35 | "home_phone" varchar NOT NULL, 36 | "extension" integer NOT NULL, 37 | "notes" text NOT NULL, 38 | "recipient_id" integer 39 | ); 40 | --> statement-breakpoint 41 | CREATE TABLE IF NOT EXISTS "orders" ( 42 | "id" serial PRIMARY KEY NOT NULL, 43 | "order_date" date NOT NULL, 44 | "required_date" date NOT NULL, 45 | "shipped_date" date, 46 | "ship_via" integer NOT NULL, 47 | "freight" double precision NOT NULL, 48 | "ship_name" varchar NOT NULL, 49 | "ship_city" varchar NOT NULL, 50 | "ship_region" varchar, 51 | "ship_postal_code" varchar, 52 | "ship_country" varchar NOT NULL, 53 | "customer_id" integer NOT NULL, 54 | "employee_id" integer NOT NULL 55 | ); 56 | --> statement-breakpoint 57 | CREATE TABLE IF NOT EXISTS "products" ( 58 | "id" serial PRIMARY KEY NOT NULL, 59 | "name" text NOT NULL, 60 | "qt_per_unit" varchar NOT NULL, 61 | "unit_price" double precision NOT NULL, 62 | "units_in_stock" integer NOT NULL, 63 | "units_on_order" integer NOT NULL, 64 | "reorder_level" integer NOT NULL, 65 | "discontinued" integer NOT NULL, 66 | "supplier_id" serial NOT NULL 67 | ); 68 | --> statement-breakpoint 69 | CREATE TABLE IF NOT EXISTS "suppliers" ( 70 | "id" serial PRIMARY KEY NOT NULL, 71 | "company_name" varchar NOT NULL, 72 | "contact_name" varchar NOT NULL, 73 | "contact_title" varchar NOT NULL, 74 | "address" varchar NOT NULL, 75 | "city" varchar NOT NULL, 76 | "region" varchar, 77 | "postal_code" varchar NOT NULL, 78 | "country" varchar NOT NULL, 79 | "phone" varchar NOT NULL 80 | ); 81 | --> statement-breakpoint 82 | CREATE INDEX IF NOT EXISTS "order_id_idx" ON "order_details" ("order_id");--> statement-breakpoint 83 | CREATE INDEX IF NOT EXISTS "product_id_idx" ON "order_details" ("product_id");--> statement-breakpoint 84 | CREATE INDEX IF NOT EXISTS "recepient_idx" ON "employees" ("recipient_id");--> statement-breakpoint 85 | CREATE INDEX IF NOT EXISTS "supplier_idx" ON "products" ("supplier_id");--> statement-breakpoint 86 | 87 | DO $$ BEGIN 88 | ALTER TABLE "order_details" ADD CONSTRAINT "order_details_order_id_orders_id_fk" FOREIGN KEY ("order_id") REFERENCES "orders"("id") ON DELETE cascade ON UPDATE no action; 89 | EXCEPTION 90 | WHEN duplicate_object THEN null; 91 | END $$; 92 | --> statement-breakpoint 93 | DO $$ BEGIN 94 | ALTER TABLE "order_details" ADD CONSTRAINT "order_details_product_id_products_id_fk" FOREIGN KEY ("product_id") REFERENCES "products"("id") ON DELETE cascade ON UPDATE no action; 95 | EXCEPTION 96 | WHEN duplicate_object THEN null; 97 | END $$; 98 | --> statement-breakpoint 99 | DO $$ BEGIN 100 | ALTER TABLE "employees" ADD CONSTRAINT "employees_recipient_id_employees_id_fk" FOREIGN KEY ("recipient_id") REFERENCES "employees"("id") ON DELETE no action ON UPDATE no action; 101 | EXCEPTION 102 | WHEN duplicate_object THEN null; 103 | END $$; 104 | --> statement-breakpoint 105 | DO $$ BEGIN 106 | ALTER TABLE "orders" ADD CONSTRAINT "orders_customer_id_customers_id_fk" FOREIGN KEY ("customer_id") REFERENCES "customers"("id") ON DELETE cascade ON UPDATE no action; 107 | EXCEPTION 108 | WHEN duplicate_object THEN null; 109 | END $$; 110 | --> statement-breakpoint 111 | DO $$ BEGIN 112 | ALTER TABLE "orders" ADD CONSTRAINT "orders_employee_id_employees_id_fk" FOREIGN KEY ("employee_id") REFERENCES "employees"("id") ON DELETE cascade ON UPDATE no action; 113 | EXCEPTION 114 | WHEN duplicate_object THEN null; 115 | END $$; 116 | --> statement-breakpoint 117 | DO $$ BEGIN 118 | ALTER TABLE "products" ADD CONSTRAINT "products_supplier_id_suppliers_id_fk" FOREIGN KEY ("supplier_id") REFERENCES "suppliers"("id") ON DELETE cascade ON UPDATE no action; 119 | EXCEPTION 120 | WHEN duplicate_object THEN null; 121 | END $$; 122 | -------------------------------------------------------------------------------- /drizzle/0001_concerned_mother_askani.sql: -------------------------------------------------------------------------------- 1 | -- Custom SQL migration file, put you code below! -- 2 | 3 | CREATE INDEX IF NOT EXISTS "customers_company_name_idx" ON "customers" USING GIN (to_tsvector('english', "company_name"));--> statement-breakpoint 4 | CREATE INDEX IF NOT EXISTS "products_name_idx" ON "products" USING GIN (to_tsvector('english', "name"));--> statement-breakpoint -------------------------------------------------------------------------------- /drizzle/meta/0000_snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "5", 3 | "dialect": "pg", 4 | "id": "d28884aa-2629-4191-94a9-bfe978f3fcd7", 5 | "prevId": "00000000-0000-0000-0000-000000000000", 6 | "tables": { 7 | "customers": { 8 | "name": "customers", 9 | "schema": "", 10 | "columns": { 11 | "id": { 12 | "name": "id", 13 | "type": "serial", 14 | "primaryKey": true, 15 | "notNull": true 16 | }, 17 | "company_name": { 18 | "name": "company_name", 19 | "type": "text", 20 | "primaryKey": false, 21 | "notNull": true 22 | }, 23 | "contact_name": { 24 | "name": "contact_name", 25 | "type": "varchar", 26 | "primaryKey": false, 27 | "notNull": true 28 | }, 29 | "contact_title": { 30 | "name": "contact_title", 31 | "type": "varchar", 32 | "primaryKey": false, 33 | "notNull": true 34 | }, 35 | "address": { 36 | "name": "address", 37 | "type": "varchar", 38 | "primaryKey": false, 39 | "notNull": true 40 | }, 41 | "city": { 42 | "name": "city", 43 | "type": "varchar", 44 | "primaryKey": false, 45 | "notNull": true 46 | }, 47 | "postal_code": { 48 | "name": "postal_code", 49 | "type": "varchar", 50 | "primaryKey": false, 51 | "notNull": false 52 | }, 53 | "region": { 54 | "name": "region", 55 | "type": "varchar", 56 | "primaryKey": false, 57 | "notNull": false 58 | }, 59 | "country": { 60 | "name": "country", 61 | "type": "varchar", 62 | "primaryKey": false, 63 | "notNull": true 64 | }, 65 | "phone": { 66 | "name": "phone", 67 | "type": "varchar", 68 | "primaryKey": false, 69 | "notNull": true 70 | }, 71 | "fax": { 72 | "name": "fax", 73 | "type": "varchar", 74 | "primaryKey": false, 75 | "notNull": false 76 | } 77 | }, 78 | "indexes": {}, 79 | "foreignKeys": {}, 80 | "compositePrimaryKeys": {}, 81 | "uniqueConstraints": {} 82 | }, 83 | "order_details": { 84 | "name": "order_details", 85 | "schema": "", 86 | "columns": { 87 | "unit_price": { 88 | "name": "unit_price", 89 | "type": "double precision", 90 | "primaryKey": false, 91 | "notNull": true 92 | }, 93 | "quantity": { 94 | "name": "quantity", 95 | "type": "integer", 96 | "primaryKey": false, 97 | "notNull": true 98 | }, 99 | "discount": { 100 | "name": "discount", 101 | "type": "double precision", 102 | "primaryKey": false, 103 | "notNull": true 104 | }, 105 | "order_id": { 106 | "name": "order_id", 107 | "type": "integer", 108 | "primaryKey": false, 109 | "notNull": true 110 | }, 111 | "product_id": { 112 | "name": "product_id", 113 | "type": "integer", 114 | "primaryKey": false, 115 | "notNull": true 116 | } 117 | }, 118 | "indexes": { 119 | "order_id_idx": { 120 | "name": "order_id_idx", 121 | "columns": [ 122 | "order_id" 123 | ], 124 | "isUnique": false 125 | }, 126 | "product_id_idx": { 127 | "name": "product_id_idx", 128 | "columns": [ 129 | "product_id" 130 | ], 131 | "isUnique": false 132 | } 133 | }, 134 | "foreignKeys": { 135 | "order_details_order_id_orders_id_fk": { 136 | "name": "order_details_order_id_orders_id_fk", 137 | "tableFrom": "order_details", 138 | "tableTo": "orders", 139 | "columnsFrom": [ 140 | "order_id" 141 | ], 142 | "columnsTo": [ 143 | "id" 144 | ], 145 | "onDelete": "cascade", 146 | "onUpdate": "no action" 147 | }, 148 | "order_details_product_id_products_id_fk": { 149 | "name": "order_details_product_id_products_id_fk", 150 | "tableFrom": "order_details", 151 | "tableTo": "products", 152 | "columnsFrom": [ 153 | "product_id" 154 | ], 155 | "columnsTo": [ 156 | "id" 157 | ], 158 | "onDelete": "cascade", 159 | "onUpdate": "no action" 160 | } 161 | }, 162 | "compositePrimaryKeys": {}, 163 | "uniqueConstraints": {} 164 | }, 165 | "employees": { 166 | "name": "employees", 167 | "schema": "", 168 | "columns": { 169 | "id": { 170 | "name": "id", 171 | "type": "serial", 172 | "primaryKey": true, 173 | "notNull": true 174 | }, 175 | "last_name": { 176 | "name": "last_name", 177 | "type": "varchar", 178 | "primaryKey": false, 179 | "notNull": true 180 | }, 181 | "first_name": { 182 | "name": "first_name", 183 | "type": "varchar", 184 | "primaryKey": false, 185 | "notNull": false 186 | }, 187 | "title": { 188 | "name": "title", 189 | "type": "varchar", 190 | "primaryKey": false, 191 | "notNull": true 192 | }, 193 | "title_of_courtesy": { 194 | "name": "title_of_courtesy", 195 | "type": "varchar", 196 | "primaryKey": false, 197 | "notNull": true 198 | }, 199 | "birth_date": { 200 | "name": "birth_date", 201 | "type": "date", 202 | "primaryKey": false, 203 | "notNull": true 204 | }, 205 | "hire_date": { 206 | "name": "hire_date", 207 | "type": "date", 208 | "primaryKey": false, 209 | "notNull": true 210 | }, 211 | "address": { 212 | "name": "address", 213 | "type": "varchar", 214 | "primaryKey": false, 215 | "notNull": true 216 | }, 217 | "city": { 218 | "name": "city", 219 | "type": "varchar", 220 | "primaryKey": false, 221 | "notNull": true 222 | }, 223 | "postal_code": { 224 | "name": "postal_code", 225 | "type": "varchar", 226 | "primaryKey": false, 227 | "notNull": true 228 | }, 229 | "country": { 230 | "name": "country", 231 | "type": "varchar", 232 | "primaryKey": false, 233 | "notNull": true 234 | }, 235 | "home_phone": { 236 | "name": "home_phone", 237 | "type": "varchar", 238 | "primaryKey": false, 239 | "notNull": true 240 | }, 241 | "extension": { 242 | "name": "extension", 243 | "type": "integer", 244 | "primaryKey": false, 245 | "notNull": true 246 | }, 247 | "notes": { 248 | "name": "notes", 249 | "type": "text", 250 | "primaryKey": false, 251 | "notNull": true 252 | }, 253 | "recipient_id": { 254 | "name": "recipient_id", 255 | "type": "integer", 256 | "primaryKey": false, 257 | "notNull": false 258 | } 259 | }, 260 | "indexes": { 261 | "recepient_idx": { 262 | "name": "recepient_idx", 263 | "columns": [ 264 | "recipient_id" 265 | ], 266 | "isUnique": false 267 | } 268 | }, 269 | "foreignKeys": { 270 | "employees_recipient_id_employees_id_fk": { 271 | "name": "employees_recipient_id_employees_id_fk", 272 | "tableFrom": "employees", 273 | "tableTo": "employees", 274 | "columnsFrom": [ 275 | "recipient_id" 276 | ], 277 | "columnsTo": [ 278 | "id" 279 | ], 280 | "onDelete": "no action", 281 | "onUpdate": "no action" 282 | } 283 | }, 284 | "compositePrimaryKeys": {}, 285 | "uniqueConstraints": {} 286 | }, 287 | "orders": { 288 | "name": "orders", 289 | "schema": "", 290 | "columns": { 291 | "id": { 292 | "name": "id", 293 | "type": "serial", 294 | "primaryKey": true, 295 | "notNull": true 296 | }, 297 | "order_date": { 298 | "name": "order_date", 299 | "type": "date", 300 | "primaryKey": false, 301 | "notNull": true 302 | }, 303 | "required_date": { 304 | "name": "required_date", 305 | "type": "date", 306 | "primaryKey": false, 307 | "notNull": true 308 | }, 309 | "shipped_date": { 310 | "name": "shipped_date", 311 | "type": "date", 312 | "primaryKey": false, 313 | "notNull": false 314 | }, 315 | "ship_via": { 316 | "name": "ship_via", 317 | "type": "integer", 318 | "primaryKey": false, 319 | "notNull": true 320 | }, 321 | "freight": { 322 | "name": "freight", 323 | "type": "double precision", 324 | "primaryKey": false, 325 | "notNull": true 326 | }, 327 | "ship_name": { 328 | "name": "ship_name", 329 | "type": "varchar", 330 | "primaryKey": false, 331 | "notNull": true 332 | }, 333 | "ship_city": { 334 | "name": "ship_city", 335 | "type": "varchar", 336 | "primaryKey": false, 337 | "notNull": true 338 | }, 339 | "ship_region": { 340 | "name": "ship_region", 341 | "type": "varchar", 342 | "primaryKey": false, 343 | "notNull": false 344 | }, 345 | "ship_postal_code": { 346 | "name": "ship_postal_code", 347 | "type": "varchar", 348 | "primaryKey": false, 349 | "notNull": false 350 | }, 351 | "ship_country": { 352 | "name": "ship_country", 353 | "type": "varchar", 354 | "primaryKey": false, 355 | "notNull": true 356 | }, 357 | "customer_id": { 358 | "name": "customer_id", 359 | "type": "integer", 360 | "primaryKey": false, 361 | "notNull": true 362 | }, 363 | "employee_id": { 364 | "name": "employee_id", 365 | "type": "integer", 366 | "primaryKey": false, 367 | "notNull": true 368 | } 369 | }, 370 | "indexes": {}, 371 | "foreignKeys": { 372 | "orders_customer_id_customers_id_fk": { 373 | "name": "orders_customer_id_customers_id_fk", 374 | "tableFrom": "orders", 375 | "tableTo": "customers", 376 | "columnsFrom": [ 377 | "customer_id" 378 | ], 379 | "columnsTo": [ 380 | "id" 381 | ], 382 | "onDelete": "cascade", 383 | "onUpdate": "no action" 384 | }, 385 | "orders_employee_id_employees_id_fk": { 386 | "name": "orders_employee_id_employees_id_fk", 387 | "tableFrom": "orders", 388 | "tableTo": "employees", 389 | "columnsFrom": [ 390 | "employee_id" 391 | ], 392 | "columnsTo": [ 393 | "id" 394 | ], 395 | "onDelete": "cascade", 396 | "onUpdate": "no action" 397 | } 398 | }, 399 | "compositePrimaryKeys": {}, 400 | "uniqueConstraints": {} 401 | }, 402 | "products": { 403 | "name": "products", 404 | "schema": "", 405 | "columns": { 406 | "id": { 407 | "name": "id", 408 | "type": "serial", 409 | "primaryKey": true, 410 | "notNull": true 411 | }, 412 | "name": { 413 | "name": "name", 414 | "type": "text", 415 | "primaryKey": false, 416 | "notNull": true 417 | }, 418 | "qt_per_unit": { 419 | "name": "qt_per_unit", 420 | "type": "varchar", 421 | "primaryKey": false, 422 | "notNull": true 423 | }, 424 | "unit_price": { 425 | "name": "unit_price", 426 | "type": "double precision", 427 | "primaryKey": false, 428 | "notNull": true 429 | }, 430 | "units_in_stock": { 431 | "name": "units_in_stock", 432 | "type": "integer", 433 | "primaryKey": false, 434 | "notNull": true 435 | }, 436 | "units_on_order": { 437 | "name": "units_on_order", 438 | "type": "integer", 439 | "primaryKey": false, 440 | "notNull": true 441 | }, 442 | "reorder_level": { 443 | "name": "reorder_level", 444 | "type": "integer", 445 | "primaryKey": false, 446 | "notNull": true 447 | }, 448 | "discontinued": { 449 | "name": "discontinued", 450 | "type": "integer", 451 | "primaryKey": false, 452 | "notNull": true 453 | }, 454 | "supplier_id": { 455 | "name": "supplier_id", 456 | "type": "serial", 457 | "primaryKey": false, 458 | "notNull": true 459 | } 460 | }, 461 | "indexes": { 462 | "supplier_idx": { 463 | "name": "supplier_idx", 464 | "columns": [ 465 | "supplier_id" 466 | ], 467 | "isUnique": false 468 | } 469 | }, 470 | "foreignKeys": { 471 | "products_supplier_id_suppliers_id_fk": { 472 | "name": "products_supplier_id_suppliers_id_fk", 473 | "tableFrom": "products", 474 | "tableTo": "suppliers", 475 | "columnsFrom": [ 476 | "supplier_id" 477 | ], 478 | "columnsTo": [ 479 | "id" 480 | ], 481 | "onDelete": "cascade", 482 | "onUpdate": "no action" 483 | } 484 | }, 485 | "compositePrimaryKeys": {}, 486 | "uniqueConstraints": {} 487 | }, 488 | "suppliers": { 489 | "name": "suppliers", 490 | "schema": "", 491 | "columns": { 492 | "id": { 493 | "name": "id", 494 | "type": "serial", 495 | "primaryKey": true, 496 | "notNull": true 497 | }, 498 | "company_name": { 499 | "name": "company_name", 500 | "type": "varchar", 501 | "primaryKey": false, 502 | "notNull": true 503 | }, 504 | "contact_name": { 505 | "name": "contact_name", 506 | "type": "varchar", 507 | "primaryKey": false, 508 | "notNull": true 509 | }, 510 | "contact_title": { 511 | "name": "contact_title", 512 | "type": "varchar", 513 | "primaryKey": false, 514 | "notNull": true 515 | }, 516 | "address": { 517 | "name": "address", 518 | "type": "varchar", 519 | "primaryKey": false, 520 | "notNull": true 521 | }, 522 | "city": { 523 | "name": "city", 524 | "type": "varchar", 525 | "primaryKey": false, 526 | "notNull": true 527 | }, 528 | "region": { 529 | "name": "region", 530 | "type": "varchar", 531 | "primaryKey": false, 532 | "notNull": false 533 | }, 534 | "postal_code": { 535 | "name": "postal_code", 536 | "type": "varchar", 537 | "primaryKey": false, 538 | "notNull": true 539 | }, 540 | "country": { 541 | "name": "country", 542 | "type": "varchar", 543 | "primaryKey": false, 544 | "notNull": true 545 | }, 546 | "phone": { 547 | "name": "phone", 548 | "type": "varchar", 549 | "primaryKey": false, 550 | "notNull": true 551 | } 552 | }, 553 | "indexes": {}, 554 | "foreignKeys": {}, 555 | "compositePrimaryKeys": {}, 556 | "uniqueConstraints": {} 557 | } 558 | }, 559 | "enums": {}, 560 | "schemas": {}, 561 | "_meta": { 562 | "schemas": {}, 563 | "tables": {}, 564 | "columns": {} 565 | } 566 | } -------------------------------------------------------------------------------- /drizzle/meta/0001_snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "d4c63406-3ff1-47ba-8c7a-76a9568b091d", 3 | "prevId": "d28884aa-2629-4191-94a9-bfe978f3fcd7", 4 | "version": "5", 5 | "dialect": "pg", 6 | "tables": { 7 | "customers": { 8 | "name": "customers", 9 | "schema": "", 10 | "columns": { 11 | "id": { 12 | "name": "id", 13 | "type": "serial", 14 | "primaryKey": true, 15 | "notNull": true 16 | }, 17 | "company_name": { 18 | "name": "company_name", 19 | "type": "text", 20 | "primaryKey": false, 21 | "notNull": true 22 | }, 23 | "contact_name": { 24 | "name": "contact_name", 25 | "type": "varchar", 26 | "primaryKey": false, 27 | "notNull": true 28 | }, 29 | "contact_title": { 30 | "name": "contact_title", 31 | "type": "varchar", 32 | "primaryKey": false, 33 | "notNull": true 34 | }, 35 | "address": { 36 | "name": "address", 37 | "type": "varchar", 38 | "primaryKey": false, 39 | "notNull": true 40 | }, 41 | "city": { 42 | "name": "city", 43 | "type": "varchar", 44 | "primaryKey": false, 45 | "notNull": true 46 | }, 47 | "postal_code": { 48 | "name": "postal_code", 49 | "type": "varchar", 50 | "primaryKey": false, 51 | "notNull": false 52 | }, 53 | "region": { 54 | "name": "region", 55 | "type": "varchar", 56 | "primaryKey": false, 57 | "notNull": false 58 | }, 59 | "country": { 60 | "name": "country", 61 | "type": "varchar", 62 | "primaryKey": false, 63 | "notNull": true 64 | }, 65 | "phone": { 66 | "name": "phone", 67 | "type": "varchar", 68 | "primaryKey": false, 69 | "notNull": true 70 | }, 71 | "fax": { 72 | "name": "fax", 73 | "type": "varchar", 74 | "primaryKey": false, 75 | "notNull": false 76 | } 77 | }, 78 | "indexes": {}, 79 | "foreignKeys": {}, 80 | "compositePrimaryKeys": {}, 81 | "uniqueConstraints": {} 82 | }, 83 | "order_details": { 84 | "name": "order_details", 85 | "schema": "", 86 | "columns": { 87 | "unit_price": { 88 | "name": "unit_price", 89 | "type": "double precision", 90 | "primaryKey": false, 91 | "notNull": true 92 | }, 93 | "quantity": { 94 | "name": "quantity", 95 | "type": "integer", 96 | "primaryKey": false, 97 | "notNull": true 98 | }, 99 | "discount": { 100 | "name": "discount", 101 | "type": "double precision", 102 | "primaryKey": false, 103 | "notNull": true 104 | }, 105 | "order_id": { 106 | "name": "order_id", 107 | "type": "integer", 108 | "primaryKey": false, 109 | "notNull": true 110 | }, 111 | "product_id": { 112 | "name": "product_id", 113 | "type": "integer", 114 | "primaryKey": false, 115 | "notNull": true 116 | } 117 | }, 118 | "indexes": { 119 | "order_id_idx": { 120 | "name": "order_id_idx", 121 | "columns": [ 122 | "order_id" 123 | ], 124 | "isUnique": false 125 | }, 126 | "product_id_idx": { 127 | "name": "product_id_idx", 128 | "columns": [ 129 | "product_id" 130 | ], 131 | "isUnique": false 132 | } 133 | }, 134 | "foreignKeys": { 135 | "order_details_order_id_orders_id_fk": { 136 | "name": "order_details_order_id_orders_id_fk", 137 | "tableFrom": "order_details", 138 | "columnsFrom": [ 139 | "order_id" 140 | ], 141 | "tableTo": "orders", 142 | "columnsTo": [ 143 | "id" 144 | ], 145 | "onUpdate": "no action", 146 | "onDelete": "cascade" 147 | }, 148 | "order_details_product_id_products_id_fk": { 149 | "name": "order_details_product_id_products_id_fk", 150 | "tableFrom": "order_details", 151 | "columnsFrom": [ 152 | "product_id" 153 | ], 154 | "tableTo": "products", 155 | "columnsTo": [ 156 | "id" 157 | ], 158 | "onUpdate": "no action", 159 | "onDelete": "cascade" 160 | } 161 | }, 162 | "compositePrimaryKeys": {}, 163 | "uniqueConstraints": {} 164 | }, 165 | "employees": { 166 | "name": "employees", 167 | "schema": "", 168 | "columns": { 169 | "id": { 170 | "name": "id", 171 | "type": "serial", 172 | "primaryKey": true, 173 | "notNull": true 174 | }, 175 | "last_name": { 176 | "name": "last_name", 177 | "type": "varchar", 178 | "primaryKey": false, 179 | "notNull": true 180 | }, 181 | "first_name": { 182 | "name": "first_name", 183 | "type": "varchar", 184 | "primaryKey": false, 185 | "notNull": false 186 | }, 187 | "title": { 188 | "name": "title", 189 | "type": "varchar", 190 | "primaryKey": false, 191 | "notNull": true 192 | }, 193 | "title_of_courtesy": { 194 | "name": "title_of_courtesy", 195 | "type": "varchar", 196 | "primaryKey": false, 197 | "notNull": true 198 | }, 199 | "birth_date": { 200 | "name": "birth_date", 201 | "type": "date", 202 | "primaryKey": false, 203 | "notNull": true 204 | }, 205 | "hire_date": { 206 | "name": "hire_date", 207 | "type": "date", 208 | "primaryKey": false, 209 | "notNull": true 210 | }, 211 | "address": { 212 | "name": "address", 213 | "type": "varchar", 214 | "primaryKey": false, 215 | "notNull": true 216 | }, 217 | "city": { 218 | "name": "city", 219 | "type": "varchar", 220 | "primaryKey": false, 221 | "notNull": true 222 | }, 223 | "postal_code": { 224 | "name": "postal_code", 225 | "type": "varchar", 226 | "primaryKey": false, 227 | "notNull": true 228 | }, 229 | "country": { 230 | "name": "country", 231 | "type": "varchar", 232 | "primaryKey": false, 233 | "notNull": true 234 | }, 235 | "home_phone": { 236 | "name": "home_phone", 237 | "type": "varchar", 238 | "primaryKey": false, 239 | "notNull": true 240 | }, 241 | "extension": { 242 | "name": "extension", 243 | "type": "integer", 244 | "primaryKey": false, 245 | "notNull": true 246 | }, 247 | "notes": { 248 | "name": "notes", 249 | "type": "text", 250 | "primaryKey": false, 251 | "notNull": true 252 | }, 253 | "recipient_id": { 254 | "name": "recipient_id", 255 | "type": "integer", 256 | "primaryKey": false, 257 | "notNull": false 258 | } 259 | }, 260 | "indexes": { 261 | "recepient_idx": { 262 | "name": "recepient_idx", 263 | "columns": [ 264 | "recipient_id" 265 | ], 266 | "isUnique": false 267 | } 268 | }, 269 | "foreignKeys": { 270 | "employees_recipient_id_employees_id_fk": { 271 | "name": "employees_recipient_id_employees_id_fk", 272 | "tableFrom": "employees", 273 | "columnsFrom": [ 274 | "recipient_id" 275 | ], 276 | "tableTo": "employees", 277 | "columnsTo": [ 278 | "id" 279 | ], 280 | "onUpdate": "no action", 281 | "onDelete": "no action" 282 | } 283 | }, 284 | "compositePrimaryKeys": {}, 285 | "uniqueConstraints": {} 286 | }, 287 | "orders": { 288 | "name": "orders", 289 | "schema": "", 290 | "columns": { 291 | "id": { 292 | "name": "id", 293 | "type": "serial", 294 | "primaryKey": true, 295 | "notNull": true 296 | }, 297 | "order_date": { 298 | "name": "order_date", 299 | "type": "date", 300 | "primaryKey": false, 301 | "notNull": true 302 | }, 303 | "required_date": { 304 | "name": "required_date", 305 | "type": "date", 306 | "primaryKey": false, 307 | "notNull": true 308 | }, 309 | "shipped_date": { 310 | "name": "shipped_date", 311 | "type": "date", 312 | "primaryKey": false, 313 | "notNull": false 314 | }, 315 | "ship_via": { 316 | "name": "ship_via", 317 | "type": "integer", 318 | "primaryKey": false, 319 | "notNull": true 320 | }, 321 | "freight": { 322 | "name": "freight", 323 | "type": "double precision", 324 | "primaryKey": false, 325 | "notNull": true 326 | }, 327 | "ship_name": { 328 | "name": "ship_name", 329 | "type": "varchar", 330 | "primaryKey": false, 331 | "notNull": true 332 | }, 333 | "ship_city": { 334 | "name": "ship_city", 335 | "type": "varchar", 336 | "primaryKey": false, 337 | "notNull": true 338 | }, 339 | "ship_region": { 340 | "name": "ship_region", 341 | "type": "varchar", 342 | "primaryKey": false, 343 | "notNull": false 344 | }, 345 | "ship_postal_code": { 346 | "name": "ship_postal_code", 347 | "type": "varchar", 348 | "primaryKey": false, 349 | "notNull": false 350 | }, 351 | "ship_country": { 352 | "name": "ship_country", 353 | "type": "varchar", 354 | "primaryKey": false, 355 | "notNull": true 356 | }, 357 | "customer_id": { 358 | "name": "customer_id", 359 | "type": "integer", 360 | "primaryKey": false, 361 | "notNull": true 362 | }, 363 | "employee_id": { 364 | "name": "employee_id", 365 | "type": "integer", 366 | "primaryKey": false, 367 | "notNull": true 368 | } 369 | }, 370 | "indexes": {}, 371 | "foreignKeys": { 372 | "orders_customer_id_customers_id_fk": { 373 | "name": "orders_customer_id_customers_id_fk", 374 | "tableFrom": "orders", 375 | "columnsFrom": [ 376 | "customer_id" 377 | ], 378 | "tableTo": "customers", 379 | "columnsTo": [ 380 | "id" 381 | ], 382 | "onUpdate": "no action", 383 | "onDelete": "cascade" 384 | }, 385 | "orders_employee_id_employees_id_fk": { 386 | "name": "orders_employee_id_employees_id_fk", 387 | "tableFrom": "orders", 388 | "columnsFrom": [ 389 | "employee_id" 390 | ], 391 | "tableTo": "employees", 392 | "columnsTo": [ 393 | "id" 394 | ], 395 | "onUpdate": "no action", 396 | "onDelete": "cascade" 397 | } 398 | }, 399 | "compositePrimaryKeys": {}, 400 | "uniqueConstraints": {} 401 | }, 402 | "products": { 403 | "name": "products", 404 | "schema": "", 405 | "columns": { 406 | "id": { 407 | "name": "id", 408 | "type": "serial", 409 | "primaryKey": true, 410 | "notNull": true 411 | }, 412 | "name": { 413 | "name": "name", 414 | "type": "text", 415 | "primaryKey": false, 416 | "notNull": true 417 | }, 418 | "qt_per_unit": { 419 | "name": "qt_per_unit", 420 | "type": "varchar", 421 | "primaryKey": false, 422 | "notNull": true 423 | }, 424 | "unit_price": { 425 | "name": "unit_price", 426 | "type": "double precision", 427 | "primaryKey": false, 428 | "notNull": true 429 | }, 430 | "units_in_stock": { 431 | "name": "units_in_stock", 432 | "type": "integer", 433 | "primaryKey": false, 434 | "notNull": true 435 | }, 436 | "units_on_order": { 437 | "name": "units_on_order", 438 | "type": "integer", 439 | "primaryKey": false, 440 | "notNull": true 441 | }, 442 | "reorder_level": { 443 | "name": "reorder_level", 444 | "type": "integer", 445 | "primaryKey": false, 446 | "notNull": true 447 | }, 448 | "discontinued": { 449 | "name": "discontinued", 450 | "type": "integer", 451 | "primaryKey": false, 452 | "notNull": true 453 | }, 454 | "supplier_id": { 455 | "name": "supplier_id", 456 | "type": "serial", 457 | "primaryKey": false, 458 | "notNull": true 459 | } 460 | }, 461 | "indexes": { 462 | "supplier_idx": { 463 | "name": "supplier_idx", 464 | "columns": [ 465 | "supplier_id" 466 | ], 467 | "isUnique": false 468 | } 469 | }, 470 | "foreignKeys": { 471 | "products_supplier_id_suppliers_id_fk": { 472 | "name": "products_supplier_id_suppliers_id_fk", 473 | "tableFrom": "products", 474 | "columnsFrom": [ 475 | "supplier_id" 476 | ], 477 | "tableTo": "suppliers", 478 | "columnsTo": [ 479 | "id" 480 | ], 481 | "onUpdate": "no action", 482 | "onDelete": "cascade" 483 | } 484 | }, 485 | "compositePrimaryKeys": {}, 486 | "uniqueConstraints": {} 487 | }, 488 | "suppliers": { 489 | "name": "suppliers", 490 | "schema": "", 491 | "columns": { 492 | "id": { 493 | "name": "id", 494 | "type": "serial", 495 | "primaryKey": true, 496 | "notNull": true 497 | }, 498 | "company_name": { 499 | "name": "company_name", 500 | "type": "varchar", 501 | "primaryKey": false, 502 | "notNull": true 503 | }, 504 | "contact_name": { 505 | "name": "contact_name", 506 | "type": "varchar", 507 | "primaryKey": false, 508 | "notNull": true 509 | }, 510 | "contact_title": { 511 | "name": "contact_title", 512 | "type": "varchar", 513 | "primaryKey": false, 514 | "notNull": true 515 | }, 516 | "address": { 517 | "name": "address", 518 | "type": "varchar", 519 | "primaryKey": false, 520 | "notNull": true 521 | }, 522 | "city": { 523 | "name": "city", 524 | "type": "varchar", 525 | "primaryKey": false, 526 | "notNull": true 527 | }, 528 | "region": { 529 | "name": "region", 530 | "type": "varchar", 531 | "primaryKey": false, 532 | "notNull": false 533 | }, 534 | "postal_code": { 535 | "name": "postal_code", 536 | "type": "varchar", 537 | "primaryKey": false, 538 | "notNull": true 539 | }, 540 | "country": { 541 | "name": "country", 542 | "type": "varchar", 543 | "primaryKey": false, 544 | "notNull": true 545 | }, 546 | "phone": { 547 | "name": "phone", 548 | "type": "varchar", 549 | "primaryKey": false, 550 | "notNull": true 551 | } 552 | }, 553 | "indexes": {}, 554 | "foreignKeys": {}, 555 | "compositePrimaryKeys": {}, 556 | "uniqueConstraints": {} 557 | } 558 | }, 559 | "enums": {}, 560 | "schemas": {}, 561 | "_meta": { 562 | "columns": {}, 563 | "schemas": {}, 564 | "tables": {} 565 | } 566 | } -------------------------------------------------------------------------------- /drizzle/meta/_journal.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "5", 3 | "dialect": "pg", 4 | "entries": [ 5 | { 6 | "idx": 0, 7 | "version": "5", 8 | "when": 1691926408243, 9 | "tag": "0000_flat_master_mold", 10 | "breakpoints": true 11 | }, 12 | { 13 | "idx": 1, 14 | "version": "5", 15 | "when": 1691941522899, 16 | "tag": "0001_concerned_mother_askani", 17 | "breakpoints": true 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "perf-drizzle", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "bench": "tsx bench/index.ts --host http://localhost:3000 --name my-bench --folder results", 8 | "start:docker": "tsx ./src/docker.ts", 9 | "start:seed": "tsx ./src/seed.ts", 10 | "start:seed:sqlite": "tsx ./src/sqlite/seed.ts", 11 | "start:generate": "tsx ./src/generate.ts", 12 | "prepare:prisma": "prisma generate --schema src/schema.prisma", 13 | "prepare:prisma:sqlite": "prisma generate --schema src/sqlite/schema.prisma", 14 | "start:prisma": "tsx ./src/prisma-server.ts", 15 | "start:drizzle": "tsx ./src/drizzle-server.ts" 16 | }, 17 | "keywords": [], 18 | "author": "", 19 | "license": "ISC", 20 | "devDependencies": { 21 | "@balena/dockerignore": "^1.0.2", 22 | "@faker-js/faker": "^8.0.2", 23 | "@types/better-sqlite3": "^7.6.11", 24 | "@types/bun": "^1.1.6", 25 | "@types/deep-diff": "^1.0.2", 26 | "@types/dockerode": "^3.3.19", 27 | "@types/k6": "^0.45.2", 28 | "@types/pg": "^8.10.2", 29 | "@types/pidusage": "^2.0.2", 30 | "@types/ramda": "^0.29.3", 31 | "deep-diff": "^1.0.2", 32 | "dockerode": "^3.3.5", 33 | "drizzle-kit": "^0.24.0", 34 | "esbuild": "^0.19.2", 35 | "get-port": "^7.0.0", 36 | "k6": "^0.0.0", 37 | "postgres": "^3.3.5", 38 | "tsx": "^3.12.7", 39 | "typescript": "^5.1.6" 40 | }, 41 | "dependencies": { 42 | "@hono/node-server": "^1.1.1", 43 | "@prisma/client": "5.18.0", 44 | "axios": "^1.4.0", 45 | "better-sqlite3": "^11.2.1", 46 | "concurrently": "^8.2.2", 47 | "dotenv": "^16.3.1", 48 | "drizzle-orm": "^0.33.0", 49 | "duckdb": "^1.0.0", 50 | "hono": "^3.4.1", 51 | "pg": "^8.11.2", 52 | "pg-native": "^3.1.0", 53 | "pidusage": "^3.0.2", 54 | "prisma": "^5.18.0", 55 | "ramda": "^0.29.0" 56 | } 57 | } -------------------------------------------------------------------------------- /src/cpu-usage.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from 'hono'; 2 | import os from 'os'; 3 | 4 | interface CpuUsage { 5 | usage: number; 6 | total: number; 7 | } 8 | 9 | const app = new Hono(); 10 | 11 | let temp: CpuUsage[] = []; 12 | 13 | app.get('/stats', (c) => { 14 | const cpus = os.cpus(); 15 | const cpuUsage = cpus.map((cpu) => { 16 | const { user, nice, sys, irq, idle } = cpu.times; 17 | const total = user + nice + sys + irq + idle; 18 | const usage = user + nice + sys + irq; 19 | return { usage, total }; 20 | }); 21 | 22 | let result: number[] = []; 23 | if (temp.length > 0) { 24 | result = cpuUsage.map((cpu, index) => { 25 | const usageDiff = cpu.usage - temp[index].usage; 26 | const totalDiff = cpu.total - temp[index].total; 27 | return parseInt(((100 * usageDiff) / totalDiff).toFixed()); 28 | }); 29 | } 30 | temp = cpuUsage; 31 | 32 | return c.json(result); 33 | }); 34 | 35 | export default app; 36 | -------------------------------------------------------------------------------- /src/docker.ts: -------------------------------------------------------------------------------- 1 | import Docker from "dockerode"; 2 | import getPort from "get-port"; 3 | import { Pool } from "pg"; 4 | 5 | async function main() { 6 | const docker = new Docker(); 7 | const port = await getPort({ port: desiredPostgresPort }); 8 | if (desiredPostgresPort !== port) { 9 | throw new Error(`${desiredPostgresPort} port is taken`); 10 | } 11 | const image = "postgres"; 12 | 13 | await docker.pull(image); 14 | 15 | const pgContainer = await docker.createContainer({ 16 | Image: image, 17 | Env: [ 18 | "POSTGRES_PASSWORD=postgres", 19 | "POSTGRES_USER=postgres", 20 | "POSTGRES_DB=postgres", 21 | ], 22 | name: `drizzle-benchmarks-pg2`, 23 | HostConfig: { 24 | AutoRemove: true, 25 | PortBindings: { 26 | "5432/tcp": [{ HostPort: `${port}` }], 27 | }, 28 | }, 29 | }); 30 | 31 | await pgContainer.start(); 32 | 33 | let sleep = 250; 34 | let timeLeft = 5000; 35 | let connected = false; 36 | let lastError: unknown | undefined; 37 | 38 | const dburl = `postgres://postgres:postgres@localhost:${port}/postgres`; 39 | const pool = new Pool({ connectionString: dburl }); 40 | 41 | do { 42 | try { 43 | await pool.connect(); 44 | connected = true; 45 | break; 46 | } catch (e) { 47 | lastError = e; 48 | await new Promise((resolve) => setTimeout(resolve, sleep)); 49 | timeLeft -= sleep; 50 | } 51 | } while (timeLeft > 0); 52 | if (!connected) { 53 | console.error("Cannot connect to Postgres"); 54 | throw lastError; 55 | } 56 | // const sql_script = fs.readFileSync(path.resolve("data/init-db.sql"), "utf-8"); 57 | // await pool.query(sql_script); 58 | console.log("db is up and running..."); 59 | process.exit(0); 60 | } 61 | 62 | const desiredPostgresPort = 5434; 63 | main(); 64 | -------------------------------------------------------------------------------- /src/drizzle-server-bun.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import { drizzle } from "drizzle-orm/node-postgres"; 3 | import * as schema from "./schema"; 4 | import { Pool } from "pg"; 5 | import { eq, sql, asc } from "drizzle-orm"; 6 | import cpuUsage from "./cpu-usage"; 7 | import { 8 | customers, 9 | details, 10 | employees, 11 | orders, 12 | products, 13 | suppliers, 14 | } from "./schema"; 15 | import "dotenv/config"; 16 | import cluster from "cluster"; 17 | import os from "os"; 18 | 19 | const numCPUs = os.cpus().length; 20 | 21 | const pool = new Pool({ 22 | connectionString: process.env.DATABASE_URL, 23 | max: numCPUs * 2, 24 | min: numCPUs * 2, 25 | }); 26 | const db = drizzle(pool, { schema, logger: false }); 27 | 28 | const p1 = db.query.customers 29 | .findMany({ 30 | limit: sql.placeholder("limit"), 31 | offset: sql.placeholder("offset"), 32 | orderBy: customers.id, 33 | }) 34 | .prepare("p1"); 35 | 36 | const p2 = db.query.customers 37 | .findFirst({ 38 | where: eq(customers.id, sql.placeholder("id")), 39 | }) 40 | .prepare("p2"); 41 | 42 | const p3 = db.query.customers 43 | .findMany({ 44 | where: sql`to_tsvector('english', ${ 45 | customers.companyName 46 | }) @@ to_tsquery('english', ${sql.placeholder("term")})`, 47 | }) 48 | .prepare("p3"); 49 | 50 | const p4 = db.query.employees 51 | .findMany({ 52 | limit: sql.placeholder("limit"), 53 | offset: sql.placeholder("offset"), 54 | orderBy: employees.id, 55 | }) 56 | .prepare("p4"); 57 | 58 | const p5 = db.query.employees 59 | .findMany({ 60 | with: { 61 | recipient: true, 62 | }, 63 | where: eq(employees.id, sql.placeholder("id")), 64 | }) 65 | .prepare("p5"); 66 | 67 | const p6 = db.query.suppliers 68 | .findMany({ 69 | limit: sql.placeholder("limit"), 70 | offset: sql.placeholder("offset"), 71 | orderBy: suppliers.id, 72 | }) 73 | .prepare("p6"); 74 | 75 | const p7 = db.query.suppliers 76 | .findFirst({ 77 | where: eq(suppliers.id, sql.placeholder("id")), 78 | }) 79 | .prepare("p7"); 80 | 81 | const p8 = db.query.products 82 | .findMany({ 83 | limit: sql.placeholder("limit"), 84 | offset: sql.placeholder("offset"), 85 | orderBy: products.id, 86 | }) 87 | .prepare("p8"); 88 | 89 | const p9 = db.query.products 90 | .findMany({ 91 | where: eq(products.id, sql.placeholder("id")), 92 | with: { 93 | supplier: true, 94 | }, 95 | }) 96 | .prepare("p9"); 97 | 98 | const p10 = db.query.products 99 | .findMany({ 100 | where: sql`to_tsvector('english', ${ 101 | products.name 102 | }) @@ to_tsquery('english', ${sql.placeholder("term")})`, 103 | }) 104 | .prepare("p10"); 105 | 106 | const p11 = db 107 | .select({ 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})::int`, 114 | quantitySum: sql`sum(${details.quantity})::int`, 115 | totalPrice: sql`sum(${details.quantity} * ${details.unitPrice})::real`, 116 | }) 117 | .from(orders) 118 | .leftJoin(details, eq(details.orderId, orders.id)) 119 | .groupBy(orders.id) 120 | .orderBy(asc(orders.id)) 121 | .limit(sql.placeholder("limit")) 122 | .offset(sql.placeholder("offset")) 123 | .prepare("p11"); 124 | 125 | const p12 = db 126 | .select({ 127 | id: orders.id, 128 | shippedDate: orders.shippedDate, 129 | shipName: orders.shipName, 130 | shipCity: orders.shipCity, 131 | shipCountry: orders.shipCountry, 132 | productsCount: sql`count(${details.productId})::int`, 133 | quantitySum: sql`sum(${details.quantity})::int`, 134 | totalPrice: sql`sum(${details.quantity} * ${details.unitPrice})::real`, 135 | }) 136 | .from(orders) 137 | .leftJoin(details, eq(details.orderId, orders.id)) 138 | .where(eq(orders.id, sql.placeholder("id"))) 139 | .groupBy(orders.id) 140 | .orderBy(asc(orders.id)) 141 | .prepare("p12"); 142 | 143 | const p13 = db.query.orders 144 | .findMany({ 145 | with: { 146 | details: { 147 | with: { 148 | product: true, 149 | }, 150 | }, 151 | }, 152 | where: eq(orders.id, sql.placeholder("id")), 153 | }) 154 | .prepare("p13"); 155 | 156 | const app = new Hono(); 157 | app.route("", cpuUsage); 158 | app.get("/customers", async (c) => { 159 | const limit = Number(c.req.query("limit")); 160 | const offset = Number(c.req.query("offset")); 161 | const result = await p1.execute({ limit, offset }); 162 | return c.json(result); 163 | }); 164 | 165 | app.get("/customer-by-id", async (c) => { 166 | const result = await p2.execute({ id: c.req.query("id") }); 167 | return c.json(result); 168 | }); 169 | 170 | app.get("/search-customer", async (c) => { 171 | // const term = `%${c.req.query("term")}%`; 172 | const term = `${c.req.query("term")}:*`; 173 | const result = await p3.execute({ term }); 174 | return c.json(result); 175 | }); 176 | 177 | app.get("/employees", async (c) => { 178 | const limit = Number(c.req.query("limit")); 179 | const offset = Number(c.req.query("offset")); 180 | const result = await p4.execute({ limit, offset }); 181 | return c.json(result); 182 | }); 183 | 184 | app.get("/employee-with-recipient", async (c) => { 185 | const result = await p5.execute({ id: c.req.query("id") }); 186 | return c.json(result); 187 | }); 188 | 189 | app.get("/suppliers", async (c) => { 190 | const limit = Number(c.req.query("limit")); 191 | const offset = Number(c.req.query("offset")); 192 | 193 | const result = await p6.execute({ limit, offset }); 194 | return c.json(result); 195 | }); 196 | 197 | app.get("/supplier-by-id", async (c) => { 198 | const result = await p7.execute({ id: c.req.query("id") }); 199 | return c.json(result); 200 | }); 201 | 202 | app.get("/products", async (c) => { 203 | const limit = Number(c.req.query("limit")); 204 | const offset = Number(c.req.query("offset")); 205 | 206 | const result = await p8.execute({ limit, offset }); 207 | return c.json(result); 208 | }); 209 | 210 | app.get("/product-with-supplier", async (c) => { 211 | const result = await p9.execute({ id: c.req.query("id") }); 212 | return c.json(result); 213 | }); 214 | 215 | app.get("/search-product", async (c) => { 216 | // const term = `%${c.req.query("term")}%`; 217 | const term = `${c.req.query("term")}:*`; 218 | const result = await p10.execute({ term }); 219 | return c.json(result); 220 | }); 221 | 222 | app.get("/orders-with-details", async (c) => { 223 | const limit = Number(c.req.query("limit")); 224 | const offset = Number(c.req.query("offset")); 225 | 226 | const result = await p11.execute({ limit, offset }); 227 | return c.json(result); 228 | }); 229 | 230 | app.get("/order-with-details", async (c) => { 231 | const result = await p12.execute({ id: c.req.query("id") }); 232 | return c.json(result); 233 | }); 234 | 235 | app.get("/order-with-details-and-products", async (c) => { 236 | const result = await p13.execute({ id: c.req.query("id") }); 237 | return c.json(result); 238 | }); 239 | 240 | if (cluster.isPrimary) { 241 | console.log(`Primary ${process.pid} is running`); 242 | //Fork workers 243 | for (let i = 0; i < numCPUs; i++) { 244 | cluster.fork(); 245 | } 246 | 247 | cluster.on("exit", (worker) => { 248 | console.log(`worker ${worker.process.pid} died`); 249 | }); 250 | } else { 251 | Bun.serve({ 252 | fetch: app.fetch, 253 | port: 3000, 254 | reusePort: true, 255 | }); 256 | console.log(`Worker ${process.pid} started`); 257 | } 258 | -------------------------------------------------------------------------------- /src/drizzle-server-node.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "@hono/node-server"; 2 | import { Hono } from "hono"; 3 | import { drizzle } from "drizzle-orm/node-postgres"; 4 | import * as schema from "./schema"; 5 | import pg from "pg"; 6 | import { eq, sql, asc } from "drizzle-orm"; 7 | import cpuUsage from "./cpu-usage"; 8 | import { 9 | customers, 10 | details, 11 | employees, 12 | orders, 13 | products, 14 | suppliers, 15 | } from "./schema"; 16 | import "dotenv/config"; 17 | import cluster from 'cluster'; 18 | import os from "os" 19 | 20 | const numCPUs = os.cpus().length; 21 | 22 | const pool = new pg.native.Pool({ connectionString: process.env.DATABASE_URL, max: 8, min: 8 }); 23 | const db = drizzle(pool, { schema, logger: false }); 24 | 25 | const p1 = db.query.customers 26 | .findMany({ 27 | limit: sql.placeholder("limit"), 28 | offset: sql.placeholder("offset"), 29 | orderBy: customers.id, 30 | }) 31 | .prepare("p1"); 32 | 33 | const p2 = db.query.customers 34 | .findFirst({ 35 | where: eq(customers.id, sql.placeholder("id")), 36 | }) 37 | .prepare("p2"); 38 | 39 | const p3 = db.query.customers 40 | .findMany({ 41 | where: sql`to_tsvector('english', ${customers.companyName 42 | }) @@ to_tsquery('english', ${sql.placeholder("term")})`, 43 | }) 44 | .prepare("p3"); 45 | 46 | const p4 = db.query.employees 47 | .findMany({ 48 | limit: sql.placeholder("limit"), 49 | offset: sql.placeholder("offset"), 50 | orderBy: employees.id, 51 | }) 52 | .prepare("p4"); 53 | 54 | const p5 = db.query.employees 55 | .findMany({ 56 | with: { 57 | recipient: true, 58 | }, 59 | where: eq(employees.id, sql.placeholder("id")), 60 | }) 61 | .prepare("p5"); 62 | 63 | const p6 = db.query.suppliers 64 | .findMany({ 65 | limit: sql.placeholder("limit"), 66 | offset: sql.placeholder("offset"), 67 | orderBy: suppliers.id, 68 | }) 69 | .prepare("p6"); 70 | 71 | const p7 = db.query.suppliers 72 | .findFirst({ 73 | where: eq(suppliers.id, sql.placeholder("id")), 74 | }) 75 | .prepare("p7"); 76 | 77 | const p8 = db.query.products 78 | .findMany({ 79 | limit: sql.placeholder("limit"), 80 | offset: sql.placeholder("offset"), 81 | orderBy: products.id, 82 | }) 83 | .prepare("p8"); 84 | 85 | const p9 = db.query.products 86 | .findMany({ 87 | where: eq(products.id, sql.placeholder("id")), 88 | with: { 89 | supplier: true, 90 | }, 91 | }) 92 | .prepare("p9"); 93 | 94 | const p10 = db.query.products 95 | .findMany({ 96 | where: sql`to_tsvector('english', ${products.name 97 | }) @@ to_tsquery('english', ${sql.placeholder("term")})`, 98 | }) 99 | .prepare("p10"); 100 | 101 | const p11 = db 102 | .select({ 103 | id: orders.id, 104 | shippedDate: orders.shippedDate, 105 | shipName: orders.shipName, 106 | shipCity: orders.shipCity, 107 | shipCountry: orders.shipCountry, 108 | productsCount: sql`count(${details.productId})::int`, 109 | quantitySum: sql`sum(${details.quantity})::int`, 110 | totalPrice: sql`sum(${details.quantity} * ${details.unitPrice})::real`, 111 | }) 112 | .from(orders) 113 | .leftJoin(details, eq(details.orderId, orders.id)) 114 | .groupBy(orders.id) 115 | .orderBy(asc(orders.id)) 116 | .limit(sql.placeholder("limit")) 117 | .offset(sql.placeholder("offset")) 118 | .prepare("p11"); 119 | 120 | const p12 = db 121 | .select({ 122 | id: orders.id, 123 | shippedDate: orders.shippedDate, 124 | shipName: orders.shipName, 125 | shipCity: orders.shipCity, 126 | shipCountry: orders.shipCountry, 127 | productsCount: sql`count(${details.productId})::int`, 128 | quantitySum: sql`sum(${details.quantity})::int`, 129 | totalPrice: sql`sum(${details.quantity} * ${details.unitPrice})::real`, 130 | }) 131 | .from(orders) 132 | .leftJoin(details, eq(details.orderId, orders.id)) 133 | .where(eq(orders.id, sql.placeholder("id"))) 134 | .groupBy(orders.id) 135 | .orderBy(asc(orders.id)) 136 | .prepare("p12"); 137 | 138 | const p13 = db.query.orders 139 | .findMany({ 140 | with: { 141 | details: { 142 | with: { 143 | product: true, 144 | }, 145 | }, 146 | }, 147 | where: eq(orders.id, sql.placeholder("id")), 148 | }) 149 | .prepare("p13"); 150 | 151 | const app = new Hono(); 152 | app.route('', cpuUsage); 153 | app.get("/customers", async (c) => { 154 | const limit = Number(c.req.query("limit")); 155 | const offset = Number(c.req.query("offset")); 156 | const result = await p1.execute({ limit, offset }); 157 | return c.json(result); 158 | }); 159 | 160 | app.get("/customer-by-id", async (c) => { 161 | const result = await p2.execute({ id: c.req.query("id") }); 162 | return c.json(result); 163 | }); 164 | 165 | app.get("/search-customer", async (c) => { 166 | // const term = `%${c.req.query("term")}%`; 167 | const term = `${c.req.query("term")}:*`; 168 | const result = await p3.execute({ term }); 169 | return c.json(result); 170 | }); 171 | 172 | app.get("/employees", async (c) => { 173 | const limit = Number(c.req.query("limit")); 174 | const offset = Number(c.req.query("offset")); 175 | const result = await p4.execute({ limit, offset }); 176 | return c.json(result); 177 | }); 178 | 179 | app.get("/employee-with-recipient", async (c) => { 180 | const result = await p5.execute({ id: c.req.query("id") }); 181 | return c.json(result); 182 | }); 183 | 184 | app.get("/suppliers", async (c) => { 185 | const limit = Number(c.req.query("limit")); 186 | const offset = Number(c.req.query("offset")); 187 | 188 | const result = await p6.execute({ limit, offset }); 189 | return c.json(result); 190 | }); 191 | 192 | app.get("/supplier-by-id", async (c) => { 193 | const result = await p7.execute({ id: c.req.query("id") }); 194 | return c.json(result); 195 | }); 196 | 197 | app.get("/products", async (c) => { 198 | const limit = Number(c.req.query("limit")); 199 | const offset = Number(c.req.query("offset")); 200 | 201 | const result = await p8.execute({ limit, offset }); 202 | return c.json(result); 203 | }); 204 | 205 | app.get("/product-with-supplier", async (c) => { 206 | const result = await p9.execute({ id: c.req.query("id") }); 207 | return c.json(result); 208 | }); 209 | 210 | app.get("/search-product", async (c) => { 211 | // const term = `%${c.req.query("term")}%`; 212 | const term = `${c.req.query("term")}:*`; 213 | const result = await p10.execute({ term }); 214 | return c.json(result); 215 | }); 216 | 217 | app.get("/orders-with-details", async (c) => { 218 | const limit = Number(c.req.query("limit")); 219 | const offset = Number(c.req.query("offset")); 220 | 221 | const result = await p11.execute({ limit, offset }); 222 | return c.json(result); 223 | }); 224 | 225 | app.get("/order-with-details", async (c) => { 226 | const result = await p12.execute({ id: c.req.query("id") }); 227 | return c.json(result); 228 | }); 229 | 230 | app.get("/order-with-details-and-products", async (c) => { 231 | const result = await p13.execute({ id: c.req.query("id") }); 232 | return c.json(result); 233 | }); 234 | 235 | if (cluster.isPrimary) { 236 | console.log(`Primary ${process.pid} is running`); 237 | 238 | //Fork workers 239 | for (let i = 0; i < numCPUs; i++) { 240 | cluster.fork(); 241 | } 242 | 243 | cluster.on('exit', (worker) => { 244 | console.log(`worker ${worker.process.pid} died`); 245 | }) 246 | } else { 247 | serve({ 248 | fetch: app.fetch, 249 | port: 3000, 250 | }); 251 | console.log(`Worker ${process.pid} started`); 252 | } 253 | -------------------------------------------------------------------------------- /src/generate.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { drizzle } from "drizzle-orm/postgres-js"; 3 | import postgres from "postgres"; 4 | import { customers, employees, orders, products, suppliers } from "./schema"; 5 | import { sql } from "drizzle-orm"; 6 | import "dotenv/config"; 7 | 8 | // prettier-ignore 9 | export const customerSearches = [ 10 | "ve", "ey", "or", "bb", "te", 11 | "ab", "ca", "ki", "ap", "be", 12 | "ct", "hi", "er", "pr", "pi", 13 | "en", "au", "ra", "ti", "ke", 14 | "ou", "ur", "me", "ea", "op", 15 | "at", "ne", "na", "os", "ri", 16 | "on", "ha", "il", "to", "as", 17 | "io", "di", "zy", "az", "la", 18 | "ko", "st", "gh", "ug", "ac", 19 | "cc", "ch", "hu", "re", "an", 20 | ]; 21 | 22 | // prettier-ignore 23 | export const productSearches = [ 24 | "ha", "ey", "or", "po", "te", 25 | "ab", "er", "ke", "ap", "be", 26 | "en", "au", "ra", "ti", "su", 27 | "sa", "hi", "nu", "ge", "pi", 28 | "ou", "ur", "me", "ea", "tu", 29 | "at", "ne", "na", "os", "ri", 30 | "on", "ka", "il", "to", "as", 31 | "io", "di", "za", "fa", "la", 32 | "ko", "st", "gh", "ug", "ac", 33 | "cc", "ch", "pa", "re", "an", 34 | ]; 35 | 36 | export const generateIds = (from: number, to: number) => { 37 | const ids = Array.from({ length: to - from + 1 }, (_, i) => i + from); 38 | return ids; 39 | }; 40 | 41 | const rand = (idx: number) => 0 | (Math.random() * idx); 42 | 43 | function shuffle(arr: any[]) { 44 | let last = arr.length; 45 | while (last > 0) { 46 | const n = rand(last); 47 | const m = --last; 48 | let tmp = arr[n]; 49 | arr[n] = arr[m]; 50 | arr[m] = tmp; 51 | } 52 | } 53 | 54 | const main = async () => { 55 | const client = postgres(process.env.DATABASE_URL); 56 | const db = drizzle(client, { logger: false }); 57 | 58 | const [ 59 | { minId: employeesMinId, maxId: employeesMaxId, count: employeesCount }, 60 | ] = await db 61 | .select({ 62 | minId: sql`min(${employees.id})::int`, 63 | maxId: sql`max(${employees.id})::int`, 64 | count: sql`count(*)::int`, 65 | }) 66 | .from(employees); 67 | 68 | const [ 69 | { minId: customersMinId, maxId: customersMaxId, count: customersCount }, 70 | ] = await db 71 | .select({ 72 | minId: sql`min(${customers.id})::int`, 73 | maxId: sql`max(${customers.id})::int`, 74 | count: sql`count(*)::int`, 75 | }) 76 | .from(customers); 77 | 78 | const [ 79 | { minId: suppliersMinId, maxId: suppliersMaxId, count: suppliersCount }, 80 | ] = await db 81 | .select({ 82 | minId: sql`min(${suppliers.id})::int`, 83 | maxId: sql`max(${suppliers.id})::int`, 84 | count: sql`count(*)::int`, 85 | }) 86 | .from(suppliers); 87 | 88 | const [{ minId: productsMinId, maxId: productsMaxId, count: productsCount }] = 89 | await db 90 | .select({ 91 | minId: sql`min(${products.id})::int`, 92 | maxId: sql`max(${products.id})::int`, 93 | count: sql`count(*)::int`, 94 | }) 95 | .from(products); 96 | 97 | const [{ minId: ordersMinId, maxId: ordersMaxId, count: ordersCount }] = 98 | await db 99 | .select({ 100 | minId: sql`min(${orders.id})::int`, 101 | maxId: sql`max(${orders.id})::int`, 102 | count: sql`count(*)::int`, 103 | }) 104 | .from(orders); 105 | 106 | const employeeIds = generateIds(employeesMinId, employeesMaxId); 107 | const customerIds = generateIds(customersMinId, customersMaxId); 108 | const supplierIds = generateIds(suppliersMinId, suppliersMaxId); 109 | const productIds = generateIds(productsMinId, productsMaxId); 110 | const orderIds = generateIds(ordersMinId, ordersMaxId); 111 | 112 | const requests: string[] = []; 113 | const requests2: string[] = []; 114 | 115 | // 20k requests to fetch customers by id 116 | for (let i = 1; i < 2e4; i += 1) { 117 | const idx = i % customerIds.length; 118 | const id = customerIds[idx]; 119 | requests.push(`/customer-by-id?id=${id}`); 120 | // requests2.push(`/customer-by-id?id=${id}`); 121 | } 122 | shuffle(requests); 123 | shuffle(requests2); 124 | 125 | // 5k requests to serch customers 126 | const searchCustomersRequests = []; 127 | for (let i = 0; i < 5e3; i++) { 128 | const idx = i % customerSearches.length; 129 | const term = customerSearches[idx]; 130 | requests.push(`/search-customer?term=${term}`); 131 | searchCustomersRequests.push(`/search-customer?term=${term}`); 132 | } 133 | shuffle(requests); 134 | shuffle(requests2); 135 | 136 | // 50k requests to search products 137 | const searchProductRequests = []; 138 | for (let i = 0; i < 5e4; i++) { 139 | const idx = i % productSearches.length; 140 | const term = productSearches[idx]; 141 | requests.push(`/search-product?term=${term}`); 142 | searchProductRequests.push(`/search-product?term=${term}`); 143 | } 144 | shuffle(requests); 145 | 146 | // 5k requests to get employee by id with recipient 147 | for (let i = 0; i < 5e3; i++) { 148 | const idx = i % employeeIds.length; 149 | const id = employeeIds[idx]; 150 | requests.push(`/employee-with-recipient?id=${id}`); 151 | // requests2.push(`/employee-with-recipient?id=${id}`); 152 | } 153 | shuffle(requests); 154 | shuffle(requests2); 155 | 156 | // 30k requests to get suppliers 157 | for (let i = 0; i < 3e4; i++) { 158 | const idx = i % supplierIds.length; 159 | const id = supplierIds[idx]; 160 | requests.push(`/supplier-by-id?id=${id}`); 161 | // requests2.push(`/supplier-by-id?id=${id}`); 162 | } 163 | shuffle(requests2); 164 | 165 | const productsWithSuppliers = []; 166 | // 100k requests to get product by id with supplier 167 | for (let i = 0; i < 1e5; i++) { 168 | const idx = i % productIds.length; 169 | const id = productIds[idx]; 170 | requests.push(`/product-with-supplier?id=${id}`); 171 | productsWithSuppliers.push(`/product-with-supplier?id=${id}`); 172 | // requests2.push(`/product-with-supplier?id=${id}`) 173 | } 174 | shuffle(requests); 175 | shuffle(requests2); 176 | 177 | // 100k requests to get order with details 178 | for (let i = 0; i < 1e5; i++) { 179 | const idx = i % orderIds.length; 180 | const id = orderIds[idx]; 181 | requests.push(`/order-with-details?id=${id}`); 182 | // requests2.push(`/order-with-details?id=${id}`); 183 | } 184 | shuffle(requests); 185 | shuffle(requests2); 186 | 187 | const ordersFull = []; 188 | // 100k requests to get order with details and products 189 | for (let i = 0; i < 1e5; i++) { 190 | const idx = i % orderIds.length; 191 | const id = orderIds[idx]; 192 | requests.push(`/order-with-details-and-products?id=${id}`); 193 | ordersFull.push(`/order-with-details-and-products?id=${id}`); 194 | // requests2.push(`/order-with-details-and-products?id=${id}`) 195 | } 196 | shuffle(requests); 197 | shuffle(requests2); 198 | 199 | // 2k paginated customers 200 | const totalCount = 10000; 201 | for (let i = 0; i < 2e3; i++) { 202 | const limit = 50; 203 | const pages = totalCount / limit; 204 | const page = 1 + Math.floor(pages * Math.random()); 205 | const offset = page * limit - limit; 206 | 207 | requests.push(`/customers?limit=${limit}&offset=${offset}`); 208 | // requests2.push(`/customers?limit=${limit}&offset=${offset}`); 209 | } 210 | shuffle(requests); 211 | shuffle(requests2); 212 | 213 | // 1k paginated employees 214 | const totalCount2 = 100; 215 | for (let i = 0; i < 1e3; i++) { 216 | const limit = 20; 217 | const pages = totalCount2 / limit; 218 | const page = 1 + Math.floor(pages * Math.random()); 219 | const offset = page * limit - limit; 220 | 221 | requests.push(`/employees?limit=${limit}&offset=${offset}`); 222 | // requests2.push(`/employees?limit=${limit}&offset=${offset}`); 223 | } 224 | shuffle(requests); 225 | shuffle(requests2); 226 | 227 | // 1k paginated suppliers 228 | const totalCount3 = 10000; 229 | for (let i = 0; i < 1e3; i++) { 230 | const limit = 50; 231 | const pages = totalCount3 / limit; 232 | const page = 1 + Math.floor(pages * Math.random()); 233 | const offset = page * limit - limit; 234 | 235 | requests.push(`/suppliers?limit=${limit}&offset=${offset}`); 236 | // requests2.push(`/suppliers?limit=${limit}&offset=${offset}`); 237 | } 238 | shuffle(requests2); 239 | 240 | // 3k paginated products 241 | const totalCount4 = 1000; 242 | for (let i = 0; i < 3e3; i++) { 243 | const limit = 50; 244 | const pages = totalCount4 / limit; 245 | const page = 1 + Math.floor(pages * Math.random()); 246 | const offset = page * limit - limit; 247 | 248 | requests.push(`/products?limit=${limit}&offset=${offset}`); 249 | // requests2.push(`/products?limit=${limit}&offset=${offset}`); 250 | } 251 | shuffle(requests); 252 | shuffle(requests2); 253 | 254 | // 10k paginated orders-with-details 255 | const totalCount5 = 830; 256 | for (let i = 0; i < 1e4; i++) { 257 | const limit = 50; 258 | const pages = totalCount5 / limit; 259 | const page = 1 + Math.floor(pages * Math.random()); 260 | const offset = page * limit - limit; 261 | 262 | requests.push(`/orders-with-details?limit=${limit}&offset=${offset}`); 263 | requests2.push(`/orders-with-details?limit=${limit}&offset=${offset}`); 264 | } 265 | shuffle(requests); 266 | shuffle(requests2); 267 | 268 | shuffle(requests); 269 | shuffle(requests); 270 | shuffle(requests); 271 | shuffle(requests); 272 | shuffle(requests); 273 | shuffle(requests); 274 | shuffle(requests); 275 | shuffle(requests); 276 | shuffle(requests); 277 | 278 | console.log(requests.length, "shuffled requests"); 279 | 280 | fs.writeFileSync("./data/requests.json", JSON.stringify(requests)); 281 | fs.writeFileSync("./data/requests2.json", JSON.stringify(requests2)); 282 | fs.writeFileSync( 283 | "./data/search-customers.json", 284 | JSON.stringify(searchCustomersRequests) 285 | ); 286 | fs.writeFileSync( 287 | "./data/serch-products.json", 288 | JSON.stringify(searchProductRequests) 289 | ); 290 | fs.writeFileSync("./data/orders.json", JSON.stringify(ordersFull)); 291 | fs.writeFileSync( 292 | "./data/products.json", 293 | JSON.stringify(productsWithSuppliers) 294 | ); 295 | 296 | process.exit(0); 297 | }; 298 | main(); 299 | -------------------------------------------------------------------------------- /src/prisma-joins-server-bun.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import { PrismaClient } from "@prisma/client"; 3 | import cpuUsage from "./cpu-usage"; 4 | import cluster from 'cluster'; 5 | import os from "os" 6 | const numCPUs = os.cpus().length; 7 | 8 | const prisma = new PrismaClient(); 9 | 10 | const app = new Hono(); 11 | app.route('', cpuUsage); 12 | app.get("/customers", async (c) => { 13 | const limit = Number(c.req.query("limit")); 14 | const offset = Number(c.req.query("offset")); 15 | 16 | const result = await prisma.customer.findMany({ 17 | take: limit, 18 | skip: offset, 19 | }); 20 | 21 | return c.json(result); 22 | }); 23 | 24 | app.get("/customer-by-id", async (c) => { 25 | const result = await prisma.customer.findFirst({ 26 | where: { 27 | id: Number(c.req.query("id")!), 28 | }, 29 | }); 30 | return c.json(result); 31 | }); 32 | 33 | app.get("/search-customer", async (c) => { 34 | const result = await prisma.customer.findMany({ 35 | where: { 36 | companyName: { 37 | search: `${c.req.query("term")}:*`, 38 | }, 39 | }, 40 | }); 41 | 42 | return c.json(result); 43 | }); 44 | 45 | app.get("/employees", async (c) => { 46 | const limit = Number(c.req.query("limit")); 47 | const offset = Number(c.req.query("offset")); 48 | 49 | const result = await prisma.employee.findMany({ 50 | take: limit, 51 | skip: offset, 52 | }); 53 | return c.json(result); 54 | }); 55 | 56 | app.get("/employee-with-recipient", async (c) => { 57 | const result = await prisma.employee.findUnique({ 58 | where: { 59 | id: Number(c.req.query("id")!), 60 | }, 61 | relationLoadStrategy: "join", 62 | include: { 63 | recipient: true, 64 | }, 65 | }); 66 | return c.json([result]); 67 | }); 68 | 69 | app.get("/suppliers", async (c) => { 70 | const limit = Number(c.req.query("limit")); 71 | const offset = Number(c.req.query("offset")); 72 | 73 | const result = await prisma.supplier.findMany({ 74 | take: limit, 75 | skip: offset, 76 | }); 77 | return c.json(result); 78 | }); 79 | 80 | app.get("/supplier-by-id", async (c) => { 81 | const result = await prisma.supplier.findUnique({ 82 | where: { 83 | id: Number(c.req.query("id")!), 84 | }, 85 | }); 86 | return c.json(result); 87 | }); 88 | 89 | app.get("/products", async (c) => { 90 | const limit = Number(c.req.query("limit")); 91 | const offset = Number(c.req.query("offset")); 92 | 93 | const result = await prisma.product.findMany({ 94 | take: limit, 95 | skip: offset, 96 | }); 97 | return c.json(result); 98 | }); 99 | 100 | app.get("/product-with-supplier", async (c) => { 101 | const result = await prisma.product.findUnique({ 102 | where: { 103 | id: Number(c.req.query("id")!), 104 | }, 105 | relationLoadStrategy: "join", 106 | include: { 107 | supplier: true, 108 | }, 109 | }); 110 | return c.json([result]); 111 | }); 112 | 113 | app.get("/search-product", async (c) => { 114 | const result = await prisma.product.findMany({ 115 | where: { 116 | name: { 117 | search: `${c.req.query("term")}:*`, 118 | }, 119 | }, 120 | }); 121 | 122 | return c.json(result); 123 | }); 124 | 125 | app.get("/orders-with-details", async (c) => { 126 | const limit = Number(c.req.query("limit")); 127 | const offset = Number(c.req.query("offset")); 128 | 129 | const res = await prisma.order.findMany({ 130 | relationLoadStrategy: "join", 131 | include: { 132 | details: true, 133 | }, 134 | take: limit, 135 | skip: offset, 136 | orderBy: { 137 | id: "asc", 138 | }, 139 | }); 140 | 141 | const result = res.map((item) => { 142 | return { 143 | id: item.id, 144 | shippedDate: item.shippedDate, 145 | shipName: item.shipName, 146 | shipCity: item.shipCity, 147 | shipCountry: item.shipCountry, 148 | productsCount: item.details.length, 149 | quantitySum: item.details.reduce( 150 | (sum, deteil) => (sum += +deteil.quantity), 151 | 0 152 | ), 153 | totalPrice: item.details.reduce( 154 | (sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 155 | 0 156 | ), 157 | }; 158 | }); 159 | return c.json(result); 160 | }); 161 | 162 | app.get("/order-with-details", async (c) => { 163 | const res = await prisma.order.findMany({ 164 | relationLoadStrategy: "join", 165 | include: { 166 | details: true, 167 | }, 168 | where: { 169 | id: Number(c.req.query("id")!), 170 | }, 171 | }); 172 | 173 | const result = res.map((item) => { 174 | return { 175 | id: item.id, 176 | shippedDate: item.shippedDate, 177 | shipName: item.shipName, 178 | shipCity: item.shipCity, 179 | shipCountry: item.shipCountry, 180 | productsCount: item.details.length, 181 | quantitySum: item.details.reduce( 182 | (sum, detail) => (sum += detail.quantity), 183 | 0 184 | ), 185 | totalPrice: item.details.reduce( 186 | (sum, detail) => (sum += detail.quantity * detail.unitPrice), 187 | 0 188 | ), 189 | }; 190 | }); 191 | 192 | return c.json(result); 193 | }); 194 | 195 | app.get("/order-with-details-and-products", async (c) => { 196 | const result = await prisma.order.findMany({ 197 | where: { 198 | id: Number(c.req.query("id")!), 199 | }, 200 | relationLoadStrategy: "join", 201 | include: { 202 | details: { 203 | include: { 204 | product: true, 205 | }, 206 | }, 207 | }, 208 | }); 209 | 210 | return c.json(result); 211 | }); 212 | 213 | if (cluster.isPrimary) { 214 | console.log(`Primary ${process.pid} is running`); 215 | // Fork workers, 216 | for (let i = 0; i < numCPUs / 2; i++) { 217 | cluster.fork(); 218 | } 219 | 220 | cluster.on("exit", (worker) => { 221 | console.log(`worker ${worker.process.pid} died`); 222 | }); 223 | } else { 224 | Bun.serve({ 225 | fetch: app.fetch, 226 | port: 3001, 227 | reusePort: true, 228 | }); 229 | console.log(`Worker ${process.pid} started`); 230 | } -------------------------------------------------------------------------------- /src/prisma-joins-server-node.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "@hono/node-server"; 2 | import { Hono } from "hono"; 3 | import { PrismaClient } from "@prisma/client"; 4 | import cpuUsage from "./cpu-usage"; 5 | 6 | import cluster from 'cluster'; 7 | import os from "os" 8 | const numCPUs = os.cpus().length; 9 | 10 | const prisma = new PrismaClient(); 11 | 12 | const app = new Hono(); 13 | app.route('', cpuUsage); 14 | app.get("/customers", async (c) => { 15 | const limit = Number(c.req.query("limit")); 16 | const offset = Number(c.req.query("offset")); 17 | 18 | const result = await prisma.customer.findMany({ 19 | take: limit, 20 | skip: offset, 21 | }); 22 | 23 | return c.json(result); 24 | }); 25 | 26 | app.get("/customer-by-id", async (c) => { 27 | const result = await prisma.customer.findFirst({ 28 | where: { 29 | id: Number(c.req.query("id")!), 30 | }, 31 | }); 32 | return c.json(result); 33 | }); 34 | 35 | app.get("/search-customer", async (c) => { 36 | const result = await prisma.customer.findMany({ 37 | where: { 38 | companyName: { 39 | search: `${c.req.query("term")}:*`, 40 | }, 41 | }, 42 | }); 43 | 44 | return c.json(result); 45 | }); 46 | 47 | app.get("/employees", async (c) => { 48 | const limit = Number(c.req.query("limit")); 49 | const offset = Number(c.req.query("offset")); 50 | 51 | const result = await prisma.employee.findMany({ 52 | take: limit, 53 | skip: offset, 54 | }); 55 | return c.json(result); 56 | }); 57 | 58 | app.get("/employee-with-recipient", async (c) => { 59 | const result = await prisma.employee.findUnique({ 60 | where: { 61 | id: Number(c.req.query("id")!), 62 | }, 63 | relationLoadStrategy: "join", 64 | include: { 65 | recipient: true, 66 | }, 67 | }); 68 | return c.json([result]); 69 | }); 70 | 71 | app.get("/suppliers", async (c) => { 72 | const limit = Number(c.req.query("limit")); 73 | const offset = Number(c.req.query("offset")); 74 | 75 | const result = await prisma.supplier.findMany({ 76 | take: limit, 77 | skip: offset, 78 | }); 79 | return c.json(result); 80 | }); 81 | 82 | app.get("/supplier-by-id", async (c) => { 83 | const result = await prisma.supplier.findUnique({ 84 | where: { 85 | id: Number(c.req.query("id")!), 86 | }, 87 | }); 88 | return c.json(result); 89 | }); 90 | 91 | app.get("/products", async (c) => { 92 | const limit = Number(c.req.query("limit")); 93 | const offset = Number(c.req.query("offset")); 94 | 95 | const result = await prisma.product.findMany({ 96 | take: limit, 97 | skip: offset, 98 | }); 99 | return c.json(result); 100 | }); 101 | 102 | app.get("/product-with-supplier", async (c) => { 103 | const result = await prisma.product.findUnique({ 104 | where: { 105 | id: Number(c.req.query("id")!), 106 | }, 107 | relationLoadStrategy: "join", 108 | include: { 109 | supplier: true, 110 | }, 111 | }); 112 | return c.json([result]); 113 | }); 114 | 115 | app.get("/search-product", async (c) => { 116 | const result = await prisma.product.findMany({ 117 | where: { 118 | name: { 119 | search: `${c.req.query("term")}:*`, 120 | }, 121 | }, 122 | }); 123 | 124 | return c.json(result); 125 | }); 126 | 127 | app.get("/orders-with-details", async (c) => { 128 | const limit = Number(c.req.query("limit")); 129 | const offset = Number(c.req.query("offset")); 130 | 131 | const res = await prisma.order.findMany({ 132 | relationLoadStrategy: "join", 133 | include: { 134 | details: true, 135 | }, 136 | take: limit, 137 | skip: offset, 138 | orderBy: { 139 | id: "asc", 140 | }, 141 | }); 142 | 143 | const result = res.map((item) => { 144 | return { 145 | id: item.id, 146 | shippedDate: item.shippedDate, 147 | shipName: item.shipName, 148 | shipCity: item.shipCity, 149 | shipCountry: item.shipCountry, 150 | productsCount: item.details.length, 151 | quantitySum: item.details.reduce( 152 | (sum, deteil) => (sum += +deteil.quantity), 153 | 0 154 | ), 155 | totalPrice: item.details.reduce( 156 | (sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 157 | 0 158 | ), 159 | }; 160 | }); 161 | return c.json(result); 162 | }); 163 | 164 | app.get("/order-with-details", async (c) => { 165 | const res = await prisma.order.findMany({ 166 | relationLoadStrategy: "join", 167 | include: { 168 | details: true, 169 | }, 170 | where: { 171 | id: Number(c.req.query("id")!), 172 | }, 173 | }); 174 | 175 | const result = res.map((item) => { 176 | return { 177 | id: item.id, 178 | shippedDate: item.shippedDate, 179 | shipName: item.shipName, 180 | shipCity: item.shipCity, 181 | shipCountry: item.shipCountry, 182 | productsCount: item.details.length, 183 | quantitySum: item.details.reduce( 184 | (sum, detail) => (sum += detail.quantity), 185 | 0 186 | ), 187 | totalPrice: item.details.reduce( 188 | (sum, detail) => (sum += detail.quantity * detail.unitPrice), 189 | 0 190 | ), 191 | }; 192 | }); 193 | 194 | return c.json(result); 195 | }); 196 | 197 | app.get("/order-with-details-and-products", async (c) => { 198 | const result = await prisma.order.findMany({ 199 | where: { 200 | id: Number(c.req.query("id")!), 201 | }, 202 | relationLoadStrategy: "join", 203 | include: { 204 | details: { 205 | include: { 206 | product: true, 207 | }, 208 | }, 209 | }, 210 | }); 211 | 212 | return c.json(result); 213 | }); 214 | 215 | if (cluster.isPrimary) { 216 | console.log(`Primary ${process.pid} is running`); 217 | 218 | //Fork workers 219 | for (let i=0; i < 2; i++) { 220 | cluster.fork(); 221 | } 222 | 223 | cluster.on('exit', (worker) => { 224 | console.log(`worker ${worker.process.pid} died`); 225 | }) 226 | } else { 227 | serve({ 228 | fetch: app.fetch, 229 | port: 3001, 230 | }); 231 | 232 | console.log(`Worker ${process.pid} started`); 233 | } 234 | -------------------------------------------------------------------------------- /src/prisma-server-bun.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Hono } from "hono"; 3 | import { PrismaClient } from "@prisma/client"; 4 | import cpuUsage from "./cpu-usage"; 5 | import os from "os" 6 | 7 | const prisma = new PrismaClient(); 8 | 9 | const app = new Hono(); 10 | app.route('', cpuUsage); 11 | app.get("/customers", async (c) => { 12 | const limit = Number(c.req.query("limit")); 13 | const offset = Number(c.req.query("offset")); 14 | 15 | const result = await prisma.customer.findMany({ 16 | take: limit, 17 | skip: offset, 18 | }); 19 | 20 | return c.json(result); 21 | }); 22 | 23 | app.get("/customer-by-id", async (c) => { 24 | const result = await prisma.customer.findFirst({ 25 | where: { 26 | id: Number(c.req.query("id")!), 27 | }, 28 | }); 29 | return c.json(result); 30 | }); 31 | 32 | app.get("/search-customer", async (c) => { 33 | const result = await prisma.customer.findMany({ 34 | where: { 35 | companyName: { 36 | search: `${c.req.query("term")}:*`, 37 | }, 38 | }, 39 | }); 40 | 41 | return c.json(result); 42 | }); 43 | 44 | app.get("/employees", async (c) => { 45 | const limit = Number(c.req.query("limit")); 46 | const offset = Number(c.req.query("offset")); 47 | 48 | const result = await prisma.employee.findMany({ 49 | take: limit, 50 | skip: offset, 51 | }); 52 | return c.json(result); 53 | }); 54 | 55 | app.get("/employee-with-recipient", async (c) => { 56 | const result = await prisma.employee.findUnique({ 57 | where: { 58 | id: Number(c.req.query("id")!), 59 | }, 60 | include: { 61 | recipient: true, 62 | }, 63 | }); 64 | return c.json([result]); 65 | }); 66 | 67 | app.get("/suppliers", async (c) => { 68 | const limit = Number(c.req.query("limit")); 69 | const offset = Number(c.req.query("offset")); 70 | 71 | const result = await prisma.supplier.findMany({ 72 | take: limit, 73 | skip: offset, 74 | }); 75 | return c.json(result); 76 | }); 77 | 78 | app.get("/supplier-by-id", async (c) => { 79 | const result = await prisma.supplier.findUnique({ 80 | where: { 81 | id: Number(c.req.query("id")!), 82 | }, 83 | }); 84 | return c.json(result); 85 | }); 86 | 87 | app.get("/products", async (c) => { 88 | const limit = Number(c.req.query("limit")); 89 | const offset = Number(c.req.query("offset")); 90 | 91 | const result = await prisma.product.findMany({ 92 | take: limit, 93 | skip: offset, 94 | }); 95 | return c.json(result); 96 | }); 97 | 98 | app.get("/product-with-supplier", async (c) => { 99 | const result = await prisma.product.findUnique({ 100 | where: { 101 | id: Number(c.req.query("id")!), 102 | }, 103 | include: { 104 | supplier: true, 105 | }, 106 | }); 107 | return c.json([result]); 108 | }); 109 | 110 | app.get("/search-product", async (c) => { 111 | const result = await prisma.product.findMany({ 112 | where: { 113 | name: { 114 | search: `${c.req.query("term")}:*`, 115 | }, 116 | }, 117 | }); 118 | 119 | return c.json(result); 120 | }); 121 | 122 | app.get("/orders-with-details", async (c) => { 123 | const limit = Number(c.req.query("limit")); 124 | const offset = Number(c.req.query("offset")); 125 | 126 | const res = await prisma.order.findMany({ 127 | include: { 128 | details: true, 129 | }, 130 | take: limit, 131 | skip: offset, 132 | orderBy: { 133 | id: "asc", 134 | }, 135 | }); 136 | 137 | const result = res.map((item) => { 138 | return { 139 | id: item.id, 140 | shippedDate: item.shippedDate, 141 | shipName: item.shipName, 142 | shipCity: item.shipCity, 143 | shipCountry: item.shipCountry, 144 | productsCount: item.details.length, 145 | quantitySum: item.details.reduce( 146 | (sum, deteil) => (sum += +deteil.quantity), 147 | 0 148 | ), 149 | totalPrice: item.details.reduce( 150 | (sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 151 | 0 152 | ), 153 | }; 154 | }); 155 | return c.json(result); 156 | }); 157 | 158 | app.get("/order-with-details", async (c) => { 159 | const res = await prisma.order.findMany({ 160 | include: { 161 | details: true, 162 | }, 163 | where: { 164 | id: Number(c.req.query("id")!), 165 | }, 166 | }); 167 | 168 | const result = res.map((item) => { 169 | return { 170 | id: item.id, 171 | shippedDate: item.shippedDate, 172 | shipName: item.shipName, 173 | shipCity: item.shipCity, 174 | shipCountry: item.shipCountry, 175 | productsCount: item.details.length, 176 | quantitySum: item.details.reduce( 177 | (sum, detail) => (sum += detail.quantity), 178 | 0 179 | ), 180 | totalPrice: item.details.reduce( 181 | (sum, detail) => (sum += detail.quantity * detail.unitPrice), 182 | 0 183 | ), 184 | }; 185 | }); 186 | 187 | return c.json(result); 188 | }); 189 | 190 | app.get("/order-with-details-and-products", async (c) => { 191 | const result = await prisma.order.findMany({ 192 | where: { 193 | id: Number(c.req.query("id")!), 194 | }, 195 | include: { 196 | details: { 197 | include: { 198 | product: true, 199 | }, 200 | }, 201 | }, 202 | }); 203 | 204 | return c.json(result); 205 | }); 206 | 207 | export default { 208 | fetch: app.fetch, 209 | port: 3001, 210 | reusePort: true, 211 | } 212 | -------------------------------------------------------------------------------- /src/prisma-server-node.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "@hono/node-server"; 2 | import { Hono } from "hono"; 3 | import { PrismaClient } from "@prisma/client"; 4 | import cpuUsage from "./cpu-usage"; 5 | 6 | import cluster from 'cluster'; 7 | import os from "os" 8 | const numCPUs = os.cpus().length; 9 | 10 | const prisma = new PrismaClient(); 11 | 12 | const app = new Hono(); 13 | app.route('', cpuUsage); 14 | app.get("/customers", async (c) => { 15 | const limit = Number(c.req.query("limit")); 16 | const offset = Number(c.req.query("offset")); 17 | 18 | const result = await prisma.customer.findMany({ 19 | take: limit, 20 | skip: offset, 21 | }); 22 | 23 | return c.json(result); 24 | }); 25 | 26 | app.get("/customer-by-id", async (c) => { 27 | const result = await prisma.customer.findFirst({ 28 | where: { 29 | id: Number(c.req.query("id")!), 30 | }, 31 | }); 32 | return c.json(result); 33 | }); 34 | 35 | app.get("/search-customer", async (c) => { 36 | const result = await prisma.customer.findMany({ 37 | where: { 38 | companyName: { 39 | search: `${c.req.query("term")}:*`, 40 | }, 41 | }, 42 | }); 43 | 44 | return c.json(result); 45 | }); 46 | 47 | app.get("/employees", async (c) => { 48 | const limit = Number(c.req.query("limit")); 49 | const offset = Number(c.req.query("offset")); 50 | 51 | const result = await prisma.employee.findMany({ 52 | take: limit, 53 | skip: offset, 54 | }); 55 | return c.json(result); 56 | }); 57 | 58 | app.get("/employee-with-recipient", async (c) => { 59 | const result = await prisma.employee.findUnique({ 60 | where: { 61 | id: Number(c.req.query("id")!), 62 | }, 63 | include: { 64 | recipient: true, 65 | }, 66 | }); 67 | return c.json([result]); 68 | }); 69 | 70 | app.get("/suppliers", async (c) => { 71 | const limit = Number(c.req.query("limit")); 72 | const offset = Number(c.req.query("offset")); 73 | 74 | const result = await prisma.supplier.findMany({ 75 | take: limit, 76 | skip: offset, 77 | }); 78 | return c.json(result); 79 | }); 80 | 81 | app.get("/supplier-by-id", async (c) => { 82 | const result = await prisma.supplier.findUnique({ 83 | where: { 84 | id: Number(c.req.query("id")!), 85 | }, 86 | }); 87 | return c.json(result); 88 | }); 89 | 90 | app.get("/products", async (c) => { 91 | const limit = Number(c.req.query("limit")); 92 | const offset = Number(c.req.query("offset")); 93 | 94 | const result = await prisma.product.findMany({ 95 | take: limit, 96 | skip: offset, 97 | }); 98 | return c.json(result); 99 | }); 100 | 101 | app.get("/product-with-supplier", async (c) => { 102 | const result = await prisma.product.findUnique({ 103 | where: { 104 | id: Number(c.req.query("id")!), 105 | }, 106 | include: { 107 | supplier: true, 108 | }, 109 | }); 110 | return c.json([result]); 111 | }); 112 | 113 | app.get("/search-product", async (c) => { 114 | const result = await prisma.product.findMany({ 115 | where: { 116 | name: { 117 | search: `${c.req.query("term")}:*`, 118 | }, 119 | }, 120 | }); 121 | 122 | return c.json(result); 123 | }); 124 | 125 | app.get("/orders-with-details", async (c) => { 126 | const limit = Number(c.req.query("limit")); 127 | const offset = Number(c.req.query("offset")); 128 | 129 | const res = await prisma.order.findMany({ 130 | include: { 131 | details: true, 132 | }, 133 | take: limit, 134 | skip: offset, 135 | orderBy: { 136 | id: "asc", 137 | }, 138 | }); 139 | 140 | const result = res.map((item) => { 141 | return { 142 | id: item.id, 143 | shippedDate: item.shippedDate, 144 | shipName: item.shipName, 145 | shipCity: item.shipCity, 146 | shipCountry: item.shipCountry, 147 | productsCount: item.details.length, 148 | quantitySum: item.details.reduce( 149 | (sum, deteil) => (sum += +deteil.quantity), 150 | 0 151 | ), 152 | totalPrice: item.details.reduce( 153 | (sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 154 | 0 155 | ), 156 | }; 157 | }); 158 | return c.json(result); 159 | }); 160 | 161 | app.get("/order-with-details", async (c) => { 162 | const res = await prisma.order.findMany({ 163 | include: { 164 | details: true, 165 | }, 166 | where: { 167 | id: Number(c.req.query("id")!), 168 | }, 169 | }); 170 | 171 | const result = res.map((item) => { 172 | return { 173 | id: item.id, 174 | shippedDate: item.shippedDate, 175 | shipName: item.shipName, 176 | shipCity: item.shipCity, 177 | shipCountry: item.shipCountry, 178 | productsCount: item.details.length, 179 | quantitySum: item.details.reduce( 180 | (sum, detail) => (sum += detail.quantity), 181 | 0 182 | ), 183 | totalPrice: item.details.reduce( 184 | (sum, detail) => (sum += detail.quantity * detail.unitPrice), 185 | 0 186 | ), 187 | }; 188 | }); 189 | 190 | return c.json(result); 191 | }); 192 | 193 | app.get("/order-with-details-and-products", async (c) => { 194 | const result = await prisma.order.findMany({ 195 | where: { 196 | id: Number(c.req.query("id")!), 197 | }, 198 | include: { 199 | details: { 200 | include: { 201 | product: true, 202 | }, 203 | }, 204 | }, 205 | }); 206 | 207 | return c.json(result); 208 | }); 209 | 210 | if (cluster.isPrimary) { 211 | console.log(`Primary ${process.pid} is running`); 212 | 213 | //Fork workers 214 | for (let i=0; i < 2; i++) { 215 | cluster.fork(); 216 | } 217 | 218 | cluster.on('exit', (worker) => { 219 | console.log(`worker ${worker.process.pid} died`); 220 | }) 221 | } else { 222 | serve({ 223 | fetch: app.fetch, 224 | port: 3001, 225 | }); 226 | 227 | console.log(`Worker ${process.pid} started`); 228 | } 229 | -------------------------------------------------------------------------------- /src/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | previewFeatures = ["referentialIntegrity", "fullTextSearch", "fullTextIndex", "relationJoins"] 4 | binaryTargets = ["native", "rhel-openssl-1.0.x"] 5 | } 6 | 7 | datasource db { 8 | provider = "postgresql" 9 | url = env("DATABASE_URL") 10 | } 11 | 12 | model Customer { 13 | id Int @id @default(autoincrement()) 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 Int @id @default(autoincrement()) 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") @db.Date 36 | hireDate DateTime @map("hire_date") @db.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 44 | recipientId Int? @map("recipient_id") 45 | recipient Employee? @relation("reports", fields: [recipientId], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "employees_recipient_id_employees_id_fk") 46 | reporters Employee[] @relation("reports") 47 | orders Order[] 48 | 49 | @@index([recipientId], map: "recepient_idx") 50 | @@map("employees") 51 | } 52 | 53 | model Order { 54 | id Int @id @default(autoincrement()) 55 | orderDate DateTime @map("order_date") @db.Date 56 | requiredDate DateTime @map("required_date") @db.Date 57 | shippedDate DateTime? @map("shipped_date") @db.Date 58 | shipVia Int @map("ship_via") 59 | freight Float 60 | shipName String @map("ship_name") @db.VarChar 61 | shipCity String @map("ship_city") @db.VarChar 62 | shipRegion String? @map("ship_region") @db.VarChar 63 | shipPostalCode String? @map("ship_postal_code") @db.VarChar 64 | shipCountry String @map("ship_country") @db.VarChar 65 | customerId Int @map("customer_id") 66 | employeeId Int @map("employee_id") 67 | details Detail[] 68 | customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "orders_customer_id_customers_id_fk") 69 | employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "orders_employee_id_employees_id_fk") 70 | 71 | @@map("orders") 72 | } 73 | 74 | model Detail { 75 | unitPrice Float @map("unit_price") 76 | quantity Int 77 | discount Float 78 | orderId Int @map("order_id") 79 | productId Int @map("product_id") 80 | order Order @relation(fields: [orderId], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "order_details_order_id_orders_id_fk") 81 | product Product @relation(fields: [productId], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "order_details_product_id_products_id_fk") 82 | 83 | @@unique([orderId, productId]) 84 | @@map("order_details") 85 | } 86 | 87 | model Product { 88 | id Int @id @default(autoincrement()) 89 | name String @db.VarChar 90 | quantityPerUnit String @map("qt_per_unit") @db.VarChar 91 | unitPrice Float @map("unit_price") 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 Int @map("supplier_id") 97 | details Detail[] 98 | supplier Supplier @relation(fields: [supplierId], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "products_supplier_id_suppliers_id_fk") 99 | 100 | @@index([supplierId], map: "supplier_idx") 101 | @@map("products") 102 | } 103 | 104 | model Supplier { 105 | id Int @id @default(autoincrement()) 106 | companyName String @map("company_name") @db.VarChar 107 | contactName String @map("contact_name") @db.VarChar 108 | contactTitle String @map("contact_title") @db.VarChar 109 | address String @db.VarChar 110 | city String @db.VarChar 111 | region String? @db.VarChar 112 | postalCode String @map("postal_code") @db.VarChar 113 | country String @db.VarChar 114 | phone String @db.VarChar 115 | products Product[] 116 | 117 | @@map("suppliers") 118 | } 119 | -------------------------------------------------------------------------------- /src/schema.ts: -------------------------------------------------------------------------------- 1 | import { InferModel, relations, sql } from "drizzle-orm"; 2 | import { 3 | pgTable, 4 | varchar, 5 | date, 6 | text, 7 | foreignKey, 8 | integer, 9 | doublePrecision, 10 | index, 11 | serial, 12 | } from "drizzle-orm/pg-core"; 13 | 14 | export const customers = pgTable( 15 | "customers", 16 | { 17 | id: serial("id").primaryKey(), 18 | companyName: text("company_name").notNull(), 19 | contactName: varchar("contact_name").notNull(), 20 | contactTitle: varchar("contact_title").notNull(), 21 | address: varchar("address").notNull(), 22 | city: varchar("city").notNull(), 23 | postalCode: varchar("postal_code"), 24 | region: varchar("region"), 25 | country: varchar("country").notNull(), 26 | phone: varchar("phone").notNull(), 27 | fax: varchar("fax"), 28 | } 29 | ); 30 | 31 | export const employees = pgTable( 32 | "employees", 33 | { 34 | id: serial("id").primaryKey(), 35 | lastName: varchar("last_name").notNull(), 36 | firstName: varchar("first_name"), 37 | title: varchar("title").notNull(), 38 | titleOfCourtesy: varchar("title_of_courtesy").notNull(), 39 | birthDate: date("birth_date", { mode: "date" }).notNull(), 40 | hireDate: date("hire_date", { mode: "date" }).notNull(), 41 | address: varchar("address").notNull(), 42 | city: varchar("city").notNull(), 43 | postalCode: varchar("postal_code").notNull(), 44 | country: varchar("country").notNull(), 45 | homePhone: varchar("home_phone").notNull(), 46 | extension: integer("extension").notNull(), 47 | notes: text("notes").notNull(), 48 | recipientId: integer("recipient_id"), 49 | }, 50 | (table) => ({ 51 | recipientFk: foreignKey({ 52 | columns: [table.recipientId], 53 | foreignColumns: [table.id], 54 | }), 55 | recepientIdx: index("recepient_idx").on(table.recipientId), 56 | }) 57 | ); 58 | 59 | export const orders = pgTable("orders", { 60 | id: serial("id").primaryKey(), 61 | orderDate: date("order_date", { mode: "date" }).notNull(), 62 | requiredDate: date("required_date", { mode: "date" }).notNull(), 63 | shippedDate: date("shipped_date", { mode: "date" }), 64 | shipVia: integer("ship_via").notNull(), 65 | freight: doublePrecision("freight").notNull(), 66 | shipName: varchar("ship_name").notNull(), 67 | shipCity: varchar("ship_city").notNull(), 68 | shipRegion: varchar("ship_region"), 69 | shipPostalCode: varchar("ship_postal_code"), 70 | shipCountry: varchar("ship_country").notNull(), 71 | 72 | customerId: integer("customer_id") 73 | .notNull() 74 | .references(() => customers.id, { onDelete: "cascade" }), 75 | 76 | employeeId: integer("employee_id") 77 | .notNull() 78 | .references(() => employees.id, { onDelete: "cascade" }), 79 | }); 80 | 81 | export const suppliers = pgTable("suppliers", { 82 | id: serial("id").primaryKey(), 83 | companyName: varchar("company_name").notNull(), 84 | contactName: varchar("contact_name").notNull(), 85 | contactTitle: varchar("contact_title").notNull(), 86 | address: varchar("address").notNull(), 87 | city: varchar("city").notNull(), 88 | region: varchar("region"), 89 | postalCode: varchar("postal_code").notNull(), 90 | country: varchar("country").notNull(), 91 | phone: varchar("phone").notNull(), 92 | }); 93 | 94 | export const products = pgTable( 95 | "products", 96 | { 97 | id: serial("id").primaryKey(), 98 | name: text("name").notNull(), 99 | quantityPerUnit: varchar("qt_per_unit").notNull(), 100 | unitPrice: doublePrecision("unit_price").notNull(), 101 | unitsInStock: integer("units_in_stock").notNull(), 102 | unitsOnOrder: integer("units_on_order").notNull(), 103 | reorderLevel: integer("reorder_level").notNull(), 104 | discontinued: integer("discontinued").notNull(), 105 | 106 | supplierId: serial("supplier_id") 107 | .notNull() 108 | .references(() => suppliers.id, { onDelete: "cascade" }), 109 | }, 110 | (table) => { 111 | return { 112 | supplierIdx: index("supplier_idx").on(table.supplierId), 113 | }; 114 | } 115 | ); 116 | 117 | export const details = pgTable( 118 | "order_details", 119 | { 120 | unitPrice: doublePrecision("unit_price").notNull(), 121 | quantity: integer("quantity").notNull(), 122 | discount: doublePrecision("discount").notNull(), 123 | 124 | orderId: integer("order_id") 125 | .notNull() 126 | .references(() => orders.id, { onDelete: "cascade" }), 127 | 128 | productId: integer("product_id") 129 | .notNull() 130 | .references(() => products.id, { onDelete: "cascade" }), 131 | }, 132 | (t) => { 133 | return { 134 | orderIdIdx: index("order_id_idx").on(t.orderId), 135 | productIdIdx: index("product_id_idx").on(t.productId), 136 | }; 137 | } 138 | ); 139 | 140 | export const ordersRelations = relations(orders, (r) => { 141 | return { 142 | details: r.many(details), 143 | // products: r.many(products) 144 | }; 145 | }); 146 | 147 | export const detailsRelations = relations(details, (r) => { 148 | return { 149 | order: r.one(orders, { 150 | fields: [details.orderId], 151 | references: [orders.id], 152 | }), 153 | product: r.one(products, { 154 | fields: [details.productId], 155 | references: [products.id], 156 | }), 157 | }; 158 | }); 159 | 160 | export const employeesRelations = relations(employees, (r) => { 161 | return { 162 | recipient: r.one(employees, { 163 | fields: [employees.recipientId], 164 | references: [employees.id], 165 | }), 166 | }; 167 | }); 168 | 169 | export const productsRelations = relations(products, (r) => { 170 | return { 171 | supplier: r.one(suppliers, { 172 | fields: [products.supplierId], 173 | references: [suppliers.id], 174 | }), 175 | }; 176 | }); 177 | 178 | export type Customer = InferModel; 179 | export type CustomerInsert = InferModel; 180 | export type Employee = InferModel; 181 | export type EmployeeInsert = InferModel; 182 | export type Order = InferModel; 183 | export type OrderInsert = InferModel; 184 | export type Supplier = InferModel; 185 | export type SupplierInsert = InferModel; 186 | export type Product = InferModel; 187 | export type ProductInsert = InferModel; 188 | export type Detail = InferModel; 189 | export type DetailInsert = InferModel; 190 | -------------------------------------------------------------------------------- /src/seed.ts: -------------------------------------------------------------------------------- 1 | import postgres from "postgres"; 2 | import { migrate } from "drizzle-orm/postgres-js/migrator"; 3 | import { drizzle, PostgresJsDatabase } from "drizzle-orm/postgres-js"; 4 | import { 5 | CustomerInsert, 6 | customers, 7 | DetailInsert, 8 | details, 9 | employees, 10 | OrderInsert, 11 | orders, 12 | ProductInsert, 13 | products, 14 | SupplierInsert, 15 | suppliers, 16 | } from "./schema"; 17 | import { faker } from "@faker-js/faker"; 18 | import "dotenv/config"; 19 | 20 | const titlesOfCourtesy = ["Ms.", "Mrs.", "Dr."]; 21 | const unitsOnOrders = [0, 10, 20, 30, 50, 60, 70, 80, 100]; 22 | const reorderLevels = [0, 5, 10, 15, 20, 25, 30]; 23 | const quantityPerUnit = [ 24 | "100 - 100 g pieces", 25 | "100 - 250 g bags", 26 | "10 - 200 g glasses", 27 | "10 - 4 oz boxes", 28 | "10 - 500 g pkgs.", 29 | "10 - 500 g pkgs.", 30 | "10 boxes x 12 pieces", 31 | "10 boxes x 20 bags", 32 | "10 boxes x 8 pieces", 33 | "10 kg pkg.", 34 | "10 pkgs.", 35 | "12 - 100 g bars", 36 | "12 - 100 g pkgs", 37 | "12 - 12 oz cans", 38 | "12 - 1 lb pkgs.", 39 | "12 - 200 ml jars", 40 | "12 - 250 g pkgs.", 41 | "12 - 355 ml cans", 42 | "12 - 500 g pkgs.", 43 | "750 cc per bottle", 44 | "5 kg pkg.", 45 | "50 bags x 30 sausgs.", 46 | "500 ml", 47 | "500 g", 48 | "48 pieces", 49 | "48 - 6 oz jars", 50 | "4 - 450 g glasses", 51 | "36 boxes", 52 | "32 - 8 oz bottles", 53 | "32 - 500 g boxes", 54 | ]; 55 | const discounts = [0.05, 0.15, 0.2, 0.25]; 56 | 57 | const getRandomInt = (from: number, to: number): number => { 58 | return Math.round(Math.random() * (to - from) + from); 59 | }; 60 | 61 | const randFromArray = faker.helpers.arrayElement; 62 | 63 | const weightedRandom = ( 64 | config: { weight: number; value: T | T[] }[] 65 | ): (() => T) => { 66 | // probability mass function 67 | const pmf = config.map((it) => it.weight); 68 | 69 | const fn = (sum: number) => (value: number) => { 70 | return (sum += value); 71 | }; 72 | const mapFn = fn(0); 73 | 74 | // cumulative distribution function 75 | const cdf = pmf.map((it) => mapFn(it)); 76 | // [0.6, 0.7, 0.9...] 77 | return () => { 78 | const rand = Math.random(); 79 | for (let i = 0; i < cdf.length; i++) { 80 | if (cdf[i] >= rand) { 81 | const item = config[i]; 82 | if (typeof item.value === "object") { 83 | return randFromArray(item.value as T[]); 84 | } 85 | return item.value; 86 | } 87 | } 88 | throw Error(`no rand for: ${rand} | ${cdf.join(",")}`); 89 | }; 90 | }; 91 | 92 | const sizes = { 93 | nano: { 94 | employees: 50, 95 | customers: 1000, 96 | orders: 5000, 97 | products: 500, 98 | suppliers: 100, 99 | }, 100 | micro: { 101 | employees: 200, 102 | customers: 10000, 103 | orders: 50000, 104 | products: 5000, 105 | suppliers: 1000, 106 | }, 107 | } as const; 108 | 109 | async function main(size: keyof typeof sizes) { 110 | const config = sizes[size]; 111 | 112 | try { 113 | const client = postgres(process.env.DATABASE_URL!); 114 | const db = drizzle(client, { logger: false }); 115 | 116 | const migrationsClient = postgres(process.env.DATABASE_URL!, { max: 1 }); 117 | const migrationDB: PostgresJsDatabase = drizzle(migrationsClient); 118 | await migrate(migrationDB, { migrationsFolder: "drizzle" }); 119 | 120 | console.log("seeding customers..."); 121 | let customerModels: CustomerInsert[] = []; 122 | for (let customerId = 1; customerId <= config.customers; customerId++) { 123 | customerModels.push({ 124 | companyName: faker.company.name(), 125 | contactName: faker.person.fullName(), 126 | contactTitle: faker.person.jobTitle(), 127 | address: faker.location.streetAddress(), 128 | city: faker.location.city(), 129 | postalCode: getRandomInt(0, 1) ? faker.location.zipCode() : null, 130 | region: faker.location.state(), 131 | country: faker.location.country(), 132 | phone: faker.phone.number("(###) ###-####"), 133 | fax: faker.phone.number("(###) ###-####"), 134 | }); 135 | 136 | if (customerModels.length > 5_000) { 137 | await db.insert(customers).values(customerModels).execute(); 138 | customerModels = []; 139 | } 140 | } 141 | 142 | if (customerModels.length) { 143 | await db.insert(customers).values(customerModels).execute(); 144 | } 145 | 146 | console.log("seeding employees..."); 147 | for (let employeeId = 1; employeeId <= config.employees; employeeId++) { 148 | await db 149 | .insert(employees) 150 | .values({ 151 | firstName: faker.person.firstName(), 152 | lastName: faker.person.lastName(), 153 | title: faker.person.jobTitle(), 154 | titleOfCourtesy: faker.helpers.arrayElement(titlesOfCourtesy), 155 | birthDate: faker.date.birthdate(), 156 | hireDate: faker.date.past(), 157 | address: faker.location.streetAddress(), 158 | city: faker.location.city(), 159 | postalCode: faker.location.zipCode(), 160 | country: faker.location.country(), 161 | homePhone: faker.phone.number("(###) ###-####"), 162 | extension: getRandomInt(428, 5467), 163 | notes: faker.person.bio(), 164 | recipientId: employeeId > 1 ? getRandomInt(1, employeeId - 1) : null, 165 | }) 166 | .execute(); 167 | } 168 | 169 | console.log("locading orders..."); 170 | let startOrderDate = new Date("2016"); 171 | 172 | let orderModels: OrderInsert[] = []; 173 | for (let orderId = 1; orderId <= config.orders; orderId++) { 174 | const orderDate = startOrderDate; 175 | 176 | const requiredDate = new Date(orderDate); 177 | requiredDate.setHours(orderDate.getHours() + 30 * 24); 178 | 179 | const shippedDate = new Date(orderDate); 180 | shippedDate.setHours(orderDate.getHours() + 10 * 24); 181 | 182 | orderModels.push({ 183 | orderDate, 184 | requiredDate, 185 | shippedDate, 186 | shipVia: getRandomInt(1, 3), 187 | freight: Number(`${getRandomInt(0, 1000)}.${getRandomInt(10, 99)}`), 188 | shipName: faker.location.streetAddress(), 189 | shipCity: faker.location.city(), 190 | shipRegion: faker.location.state(), 191 | shipPostalCode: faker.location.zipCode(), 192 | shipCountry: faker.location.country(), 193 | customerId: getRandomInt(1, config.customers), 194 | employeeId: getRandomInt(1, config.employees), 195 | }); 196 | 197 | startOrderDate.setMinutes(startOrderDate.getMinutes() + 1); 198 | 199 | if (orderModels.length > 5_000) { 200 | await db.insert(orders).values(orderModels).execute(); 201 | orderModels = []; 202 | } 203 | } 204 | 205 | if (orderModels.length) { 206 | await db.insert(orders).values(orderModels).execute(); 207 | } 208 | 209 | console.log("seeding suppliers..."); 210 | let supplierModels: SupplierInsert[] = []; 211 | for (let supplierId = 1; supplierId <= config.suppliers; supplierId++) { 212 | supplierModels.push({ 213 | companyName: faker.company.name(), 214 | contactName: faker.person.fullName(), 215 | contactTitle: faker.person.jobTitle(), 216 | address: faker.location.streetAddress(), 217 | city: faker.location.city(), 218 | postalCode: faker.location.zipCode(), 219 | region: faker.location.state(), 220 | country: faker.location.country(), 221 | phone: faker.phone.number("(###) ###-####"), 222 | }); 223 | 224 | if (supplierModels.length > 5_000) { 225 | await db.insert(suppliers).values(supplierModels).execute(); 226 | supplierModels = []; 227 | } 228 | } 229 | 230 | if (supplierModels.length) { 231 | await db.insert(suppliers).values(supplierModels).execute(); 232 | } 233 | 234 | console.log("seeding products..."); 235 | let productModels: ProductInsert[] = []; 236 | const productPrices = new Map(); 237 | for (let productId = 1; productId <= config.products; productId++) { 238 | const unitPrice = getRandomInt(0, 1) 239 | ? getRandomInt(3, 300) 240 | : Number(`${getRandomInt(3, 300)}.${getRandomInt(5, 99)}`); 241 | 242 | productModels.push({ 243 | name: faker.company.name(), 244 | quantityPerUnit: faker.helpers.arrayElement(quantityPerUnit), 245 | unitPrice, 246 | unitsInStock: getRandomInt(0, 125), 247 | unitsOnOrder: faker.helpers.arrayElement(unitsOnOrders), 248 | reorderLevel: faker.helpers.arrayElement(reorderLevels), 249 | discontinued: getRandomInt(0, 1), 250 | supplierId: getRandomInt(1, config.suppliers), 251 | }); 252 | 253 | productPrices.set(productId, unitPrice); 254 | 255 | if (productModels.length > 5_000) { 256 | await db.insert(products).values(productModels).execute(); 257 | productModels = []; 258 | } 259 | } 260 | 261 | if (productModels.length) { 262 | await db.insert(products).values(productModels).execute(); 263 | } 264 | 265 | console.log("seeding order details..."); 266 | let detailModels: DetailInsert[] = []; 267 | const productCount = weightedRandom([ 268 | { weight: 0.6, value: [1, 2, 3, 4] }, 269 | { weight: 0.2, value: [5, 6, 7, 8, 9, 10] }, 270 | { weight: 0.15, value: [11, 12, 13, 14, 15, 16, 17] }, 271 | { weight: 0.05, value: [18, 19, 20, 21, 22, 23, 24, 25] }, 272 | ]); 273 | 274 | for (let orderId = 1; orderId <= config.orders; orderId++) { 275 | const count = productCount(); 276 | 277 | for (let i = 0; i < count; i++) { 278 | const productId = getRandomInt(1, config.products); 279 | const unitPrice = productPrices.get(productId); 280 | 281 | detailModels.push({ 282 | unitPrice: unitPrice!, 283 | quantity: getRandomInt(1, 130), 284 | discount: getRandomInt(0, 1) 285 | ? 0 286 | : faker.helpers.arrayElement(discounts), 287 | orderId: orderId, 288 | productId, 289 | }); 290 | 291 | if (detailModels.length > 5_000) { 292 | await db.insert(details).values(detailModels).execute(); 293 | detailModels = []; 294 | } 295 | } 296 | } 297 | 298 | if (detailModels.length) { 299 | await db.insert(details).values(detailModels).execute(); 300 | } 301 | 302 | console.log("done!"); 303 | process.exit(0); 304 | } catch (err: any) { 305 | console.log("FATAL ERROR:", err); 306 | } 307 | } 308 | 309 | main("micro"); 310 | -------------------------------------------------------------------------------- /src/sqlite/drizzle-server-bun.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import { drizzle } from "drizzle-orm/better-sqlite3"; 3 | import * as schema from "../schema"; 4 | import Database from 'better-sqlite3'; 5 | import { eq, sql, asc } from "drizzle-orm"; 6 | import cpuUsage from "../cpu-usage"; 7 | import { 8 | customers, 9 | details, 10 | employees, 11 | orders, 12 | products, 13 | suppliers, 14 | } from "./schema"; 15 | import "dotenv/config"; 16 | import cluster from "cluster"; 17 | import os from "os"; 18 | 19 | const numCPUs = os.cpus().length; 20 | 21 | const client = new Database('src/sqlite/northwind.db'); 22 | const db = drizzle(client, { schema, logger: false }); 23 | 24 | const p1 = db.query.customers 25 | .findMany({ 26 | limit: sql.placeholder("limit"), 27 | offset: sql.placeholder("offset"), 28 | orderBy: customers.id, 29 | }) 30 | .prepare(); 31 | 32 | const p2 = db.query.customers 33 | .findFirst({ 34 | where: eq(customers.id, sql.placeholder("id")), 35 | }) 36 | .prepare(); 37 | 38 | // const p3 = db.query.customers 39 | // .findMany({ 40 | // where: sql`to_tsvector('english', ${ 41 | // customers.companyName 42 | // }) @@ to_tsquery('english', ${sql.placeholder("term")})`, 43 | // }) 44 | // .prepare(); 45 | 46 | const p4 = db.query.employees 47 | .findMany({ 48 | limit: sql.placeholder("limit"), 49 | offset: sql.placeholder("offset"), 50 | orderBy: employees.id, 51 | }) 52 | .prepare(); 53 | 54 | const p5 = db.query.employees 55 | .findMany({ 56 | with: { 57 | recipient: true, 58 | }, 59 | where: eq(employees.id, sql.placeholder("id")), 60 | }) 61 | .prepare(); 62 | 63 | const p6 = db.query.suppliers 64 | .findMany({ 65 | limit: sql.placeholder("limit"), 66 | offset: sql.placeholder("offset"), 67 | orderBy: suppliers.id, 68 | }) 69 | .prepare(); 70 | 71 | const p7 = db.query.suppliers 72 | .findFirst({ 73 | where: eq(suppliers.id, sql.placeholder("id")), 74 | }) 75 | .prepare(); 76 | 77 | const p8 = db.query.products 78 | .findMany({ 79 | limit: sql.placeholder("limit"), 80 | offset: sql.placeholder("offset"), 81 | orderBy: products.id, 82 | }) 83 | .prepare(); 84 | 85 | const p9 = db.query.products 86 | .findMany({ 87 | where: eq(products.id, sql.placeholder("id")), 88 | with: { 89 | supplier: true, 90 | }, 91 | }) 92 | .prepare(); 93 | 94 | // const p10 = db.query.products 95 | // .findMany({ 96 | // where: sql`to_tsvector('english', ${ 97 | // products.name 98 | // }) @@ to_tsquery('english', ${sql.placeholder("term")})`, 99 | // }) 100 | // .prepare(); 101 | 102 | const p11 = db 103 | .select({ 104 | id: orders.id, 105 | shippedDate: orders.shippedDate, 106 | shipName: orders.shipName, 107 | shipCity: orders.shipCity, 108 | shipCountry: orders.shipCountry, 109 | productsCount: sql`count(${details.productId})::int`, 110 | quantitySum: sql`sum(${details.quantity})::int`, 111 | totalPrice: sql`sum(${details.quantity} * ${details.unitPrice})::real`, 112 | }) 113 | .from(orders) 114 | .leftJoin(details, eq(details.orderId, orders.id)) 115 | .groupBy(orders.id) 116 | .orderBy(asc(orders.id)) 117 | .limit(sql.placeholder("limit")) 118 | .offset(sql.placeholder("offset")) 119 | .prepare(); 120 | 121 | const p12 = db 122 | .select({ 123 | id: orders.id, 124 | shippedDate: orders.shippedDate, 125 | shipName: orders.shipName, 126 | shipCity: orders.shipCity, 127 | shipCountry: orders.shipCountry, 128 | productsCount: sql`count(${details.productId})::int`, 129 | quantitySum: sql`sum(${details.quantity})::int`, 130 | totalPrice: sql`sum(${details.quantity} * ${details.unitPrice})::real`, 131 | }) 132 | .from(orders) 133 | .leftJoin(details, eq(details.orderId, orders.id)) 134 | .where(eq(orders.id, sql.placeholder("id"))) 135 | .groupBy(orders.id) 136 | .orderBy(asc(orders.id)) 137 | .prepare(); 138 | 139 | const p13 = db.query.orders 140 | .findMany({ 141 | with: { 142 | details: { 143 | with: { 144 | product: true, 145 | }, 146 | }, 147 | }, 148 | where: eq(orders.id, sql.placeholder("id")), 149 | }) 150 | .prepare(); 151 | 152 | const app = new Hono(); 153 | app.route("", cpuUsage); 154 | app.get("/customers", async (c) => { 155 | const limit = Number(c.req.query("limit")); 156 | const offset = Number(c.req.query("offset")); 157 | const result = await p1.execute({ limit, offset }); 158 | return c.json(result); 159 | }); 160 | 161 | app.get("/customer-by-id", async (c) => { 162 | const result = await p2.execute({ id: c.req.query("id") }); 163 | return c.json(result); 164 | }); 165 | 166 | // app.get("/search-customer", async (c) => { 167 | // // const term = `%${c.req.query("term")}%`; 168 | // const term = `${c.req.query("term")}:*`; 169 | // const result = await p3.execute({ term }); 170 | // return c.json(result); 171 | // }); 172 | 173 | app.get("/employees", async (c) => { 174 | const limit = Number(c.req.query("limit")); 175 | const offset = Number(c.req.query("offset")); 176 | const result = await p4.execute({ limit, offset }); 177 | return c.json(result); 178 | }); 179 | 180 | app.get("/employee-with-recipient", async (c) => { 181 | const result = await p5.execute({ id: c.req.query("id") }); 182 | return c.json(result); 183 | }); 184 | 185 | app.get("/suppliers", async (c) => { 186 | const limit = Number(c.req.query("limit")); 187 | const offset = Number(c.req.query("offset")); 188 | 189 | const result = await p6.execute({ limit, offset }); 190 | return c.json(result); 191 | }); 192 | 193 | app.get("/supplier-by-id", async (c) => { 194 | const result = await p7.execute({ id: c.req.query("id") }); 195 | return c.json(result); 196 | }); 197 | 198 | app.get("/products", async (c) => { 199 | const limit = Number(c.req.query("limit")); 200 | const offset = Number(c.req.query("offset")); 201 | 202 | const result = await p8.execute({ limit, offset }); 203 | return c.json(result); 204 | }); 205 | 206 | app.get("/product-with-supplier", async (c) => { 207 | const result = await p9.execute({ id: c.req.query("id") }); 208 | return c.json(result); 209 | }); 210 | 211 | // app.get("/search-product", async (c) => { 212 | // // const term = `%${c.req.query("term")}%`; 213 | // const term = `${c.req.query("term")}:*`; 214 | // const result = await p10.execute({ term }); 215 | // return c.json(result); 216 | // }); 217 | 218 | app.get("/orders-with-details", async (c) => { 219 | const limit = Number(c.req.query("limit")); 220 | const offset = Number(c.req.query("offset")); 221 | 222 | const result = await p11.execute({ limit, offset }); 223 | return c.json(result); 224 | }); 225 | 226 | app.get("/order-with-details", async (c) => { 227 | const result = await p12.execute({ id: c.req.query("id") }); 228 | return c.json(result); 229 | }); 230 | 231 | app.get("/order-with-details-and-products", async (c) => { 232 | const result = await p13.execute({ id: c.req.query("id") }); 233 | return c.json(result); 234 | }); 235 | 236 | if (cluster.isPrimary) { 237 | console.log(`Primary ${process.pid} is running`); 238 | //Fork workers 239 | for (let i = 0; i < numCPUs; i++) { 240 | cluster.fork(); 241 | } 242 | 243 | cluster.on("exit", (worker) => { 244 | console.log(`worker ${worker.process.pid} died`); 245 | }); 246 | } else { 247 | Bun.serve({ 248 | fetch: app.fetch, 249 | port: 3000, 250 | reusePort: true, 251 | }); 252 | console.log(`Worker ${process.pid} started`); 253 | } 254 | -------------------------------------------------------------------------------- /src/sqlite/drizzle-server-node.ts: -------------------------------------------------------------------------------- 1 | import { serve } from '@hono/node-server'; 2 | import { Hono } from 'hono'; 3 | import { drizzle } from 'drizzle-orm/better-sqlite3'; 4 | import * as schema from './schema'; 5 | import { eq, sql, asc } from 'drizzle-orm'; 6 | import Database from 'better-sqlite3'; 7 | import cpuUsage from '../cpu-usage'; 8 | import { customers, details, employees, orders, products, suppliers } from './schema'; 9 | import 'dotenv/config'; 10 | import cluster from 'cluster'; 11 | import os from 'os'; 12 | 13 | const numCPUs = os.cpus().length; 14 | 15 | const client = new Database('src/sqlite/northwind.db'); 16 | const db = drizzle(client, { schema, logger: false }); 17 | 18 | const p1 = db.query.customers 19 | .findMany({ 20 | limit: sql.placeholder('limit'), 21 | offset: sql.placeholder('offset'), 22 | orderBy: customers.id, 23 | }) 24 | .prepare(); 25 | 26 | const p2 = db.query.customers 27 | .findFirst({ 28 | where: eq(customers.id, sql.placeholder('id')), 29 | }) 30 | .prepare(); 31 | 32 | // const p3 = db.query.customers 33 | // .findMany({ 34 | // where: sql`to_tsvector('english', ${customers.companyName}) @@ to_tsquery('english', ${sql.placeholder('term')})`, 35 | // }) 36 | // .prepare(); 37 | 38 | const p4 = db.query.employees 39 | .findMany({ 40 | limit: sql.placeholder('limit'), 41 | offset: sql.placeholder('offset'), 42 | orderBy: employees.id, 43 | }) 44 | .prepare(); 45 | 46 | const p5 = db.query.employees 47 | .findMany({ 48 | with: { 49 | recipient: true, 50 | }, 51 | where: eq(employees.id, sql.placeholder('id')), 52 | }) 53 | .prepare(); 54 | 55 | const p6 = db.query.suppliers 56 | .findMany({ 57 | limit: sql.placeholder('limit'), 58 | offset: sql.placeholder('offset'), 59 | orderBy: suppliers.id, 60 | }) 61 | .prepare(); 62 | 63 | const p7 = db.query.suppliers 64 | .findFirst({ 65 | where: eq(suppliers.id, sql.placeholder('id')), 66 | }) 67 | .prepare(); 68 | 69 | const p8 = db.query.products 70 | .findMany({ 71 | limit: sql.placeholder('limit'), 72 | offset: sql.placeholder('offset'), 73 | orderBy: products.id, 74 | }) 75 | .prepare(); 76 | 77 | const p9 = db.query.products 78 | .findMany({ 79 | where: eq(products.id, sql.placeholder('id')), 80 | with: { 81 | supplier: true, 82 | }, 83 | }) 84 | .prepare(); 85 | 86 | // const p10 = db.query.products 87 | // .findMany({ 88 | // where: sql`to_tsvector('english', ${products.name}) @@ to_tsquery('english', ${sql.placeholder('term')})`, 89 | // }) 90 | // .prepare(); 91 | 92 | const p11 = db 93 | .select({ 94 | id: orders.id, 95 | shippedDate: orders.shippedDate, 96 | shipName: orders.shipName, 97 | shipCity: orders.shipCity, 98 | shipCountry: orders.shipCountry, 99 | productsCount: sql`count(${details.productId})`, 100 | quantitySum: sql`sum(${details.quantity})`, 101 | totalPrice: sql`sum(${details.quantity} * ${details.unitPrice})`, 102 | }) 103 | .from(orders) 104 | .leftJoin(details, eq(details.orderId, orders.id)) 105 | .groupBy(orders.id) 106 | .orderBy(asc(orders.id)) 107 | .limit(sql.placeholder('limit')) 108 | .offset(sql.placeholder('offset')) 109 | .prepare(); 110 | 111 | const p12 = db 112 | .select({ 113 | id: orders.id, 114 | shippedDate: orders.shippedDate, 115 | shipName: orders.shipName, 116 | shipCity: orders.shipCity, 117 | shipCountry: orders.shipCountry, 118 | productsCount: sql`count(${details.productId})`, 119 | quantitySum: sql`sum(${details.quantity})`, 120 | totalPrice: sql`sum(${details.quantity} * ${details.unitPrice})`, 121 | }) 122 | .from(orders) 123 | .leftJoin(details, eq(details.orderId, orders.id)) 124 | .where(eq(orders.id, sql.placeholder('id'))) 125 | .groupBy(orders.id) 126 | .orderBy(asc(orders.id)) 127 | .prepare(); 128 | 129 | const p13 = db.query.orders 130 | .findMany({ 131 | with: { 132 | details: { 133 | with: { 134 | product: true, 135 | }, 136 | }, 137 | }, 138 | where: eq(orders.id, sql.placeholder('id')), 139 | }) 140 | .prepare(); 141 | 142 | const app = new Hono(); 143 | app.route('', cpuUsage); 144 | app.get('/customers', async (c) => { 145 | const limit = Number(c.req.query('limit')); 146 | const offset = Number(c.req.query('offset')); 147 | const result = await p1.execute({ limit, offset }); 148 | return c.json(result); 149 | }); 150 | 151 | app.get('/customer-by-id', async (c) => { 152 | const result = await p2.execute({ id: c.req.query('id') }); 153 | return c.json(result); 154 | }); 155 | 156 | // app.get('/search-customer', async (c) => { 157 | // // const term = `%${c.req.query("term")}%`; 158 | // const term = `${c.req.query('term')}:*`; 159 | // const result = await p3.execute({ term }); 160 | // return c.json(result); 161 | // }); 162 | 163 | app.get('/employees', async (c) => { 164 | const limit = Number(c.req.query('limit')); 165 | const offset = Number(c.req.query('offset')); 166 | const result = await p4.execute({ limit, offset }); 167 | return c.json(result); 168 | }); 169 | 170 | app.get('/employee-with-recipient', async (c) => { 171 | const result = await p5.execute({ id: c.req.query('id') }); 172 | return c.json(result); 173 | }); 174 | 175 | app.get('/suppliers', async (c) => { 176 | const limit = Number(c.req.query('limit')); 177 | const offset = Number(c.req.query('offset')); 178 | 179 | const result = await p6.execute({ limit, offset }); 180 | return c.json(result); 181 | }); 182 | 183 | app.get('/supplier-by-id', async (c) => { 184 | const result = await p7.execute({ id: c.req.query('id') }); 185 | return c.json(result); 186 | }); 187 | 188 | app.get('/products', async (c) => { 189 | const limit = Number(c.req.query('limit')); 190 | const offset = Number(c.req.query('offset')); 191 | 192 | const result = await p8.execute({ limit, offset }); 193 | return c.json(result); 194 | }); 195 | 196 | app.get('/product-with-supplier', async (c) => { 197 | const result = await p9.execute({ id: c.req.query('id') }); 198 | return c.json(result); 199 | }); 200 | 201 | // app.get('/search-product', async (c) => { 202 | // // const term = `%${c.req.query("term")}%`; 203 | // const term = `${c.req.query('term')}:*`; 204 | // const result = await p10.execute({ term }); 205 | // return c.json(result); 206 | // }); 207 | 208 | app.get('/orders-with-details', async (c) => { 209 | const limit = Number(c.req.query('limit')); 210 | const offset = Number(c.req.query('offset')); 211 | 212 | const result = await p11.execute({ limit, offset }); 213 | return c.json(result); 214 | }); 215 | 216 | app.get('/order-with-details', async (c) => { 217 | const result = await p12.execute({ id: c.req.query('id') }); 218 | return c.json(result); 219 | }); 220 | 221 | app.get('/order-with-details-and-products', async (c) => { 222 | const result = await p13.execute({ id: c.req.query('id') }); 223 | return c.json(result); 224 | }); 225 | 226 | if (cluster.isPrimary) { 227 | console.log(`Primary ${process.pid} is running`); 228 | 229 | //Fork workers 230 | for (let i = 0; i < numCPUs; i++) { 231 | cluster.fork(); 232 | } 233 | 234 | cluster.on('exit', (worker) => { 235 | console.log(`worker ${worker.process.pid} died`); 236 | }); 237 | } else { 238 | serve({ 239 | fetch: app.fetch, 240 | port: 3000, 241 | }); 242 | console.log(`Worker ${process.pid} started`); 243 | } 244 | -------------------------------------------------------------------------------- /src/sqlite/drizzle/0000_glamorous_skaar.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `customers` ( 2 | `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, 3 | `company_name` text NOT NULL, 4 | `contact_name` text NOT NULL, 5 | `contact_title` text NOT NULL, 6 | `address` text NOT NULL, 7 | `city` text NOT NULL, 8 | `postal_code` text, 9 | `region` text, 10 | `country` text NOT NULL, 11 | `phone` text NOT NULL, 12 | `fax` text 13 | ); 14 | --> statement-breakpoint 15 | CREATE TABLE `order_details` ( 16 | `unit_price` numeric NOT NULL, 17 | `quantity` integer NOT NULL, 18 | `discount` numeric NOT NULL, 19 | `order_id` integer NOT NULL, 20 | `product_id` integer NOT NULL, 21 | FOREIGN KEY (`order_id`) REFERENCES `orders`(`id`) ON UPDATE no action ON DELETE cascade, 22 | FOREIGN KEY (`product_id`) REFERENCES `products`(`id`) ON UPDATE no action ON DELETE cascade 23 | ); 24 | --> statement-breakpoint 25 | CREATE TABLE `employees` ( 26 | `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, 27 | `last_name` text NOT NULL, 28 | `first_name` text, 29 | `title` text NOT NULL, 30 | `title_of_courtesy` text NOT NULL, 31 | `birth_date` integer NOT NULL, 32 | `hire_date` integer NOT NULL, 33 | `address` text NOT NULL, 34 | `city` text NOT NULL, 35 | `postal_code` text NOT NULL, 36 | `country` text NOT NULL, 37 | `home_phone` text NOT NULL, 38 | `extension` integer NOT NULL, 39 | `notes` text NOT NULL, 40 | `recipient_id` integer, 41 | FOREIGN KEY (`recipient_id`) REFERENCES `employees`(`id`) ON UPDATE no action ON DELETE no action 42 | ); 43 | --> statement-breakpoint 44 | CREATE TABLE `orders` ( 45 | `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, 46 | `order_date` integer NOT NULL, 47 | `required_date` integer NOT NULL, 48 | `shipped_date` integer, 49 | `ship_via` integer NOT NULL, 50 | `freight` numeric NOT NULL, 51 | `ship_name` text NOT NULL, 52 | `ship_city` text NOT NULL, 53 | `ship_region` text, 54 | `ship_postal_code` text, 55 | `ship_country` text NOT NULL, 56 | `customer_id` integer NOT NULL, 57 | `employee_id` integer NOT NULL, 58 | FOREIGN KEY (`customer_id`) REFERENCES `customers`(`id`) ON UPDATE no action ON DELETE cascade, 59 | FOREIGN KEY (`employee_id`) REFERENCES `employees`(`id`) ON UPDATE no action ON DELETE cascade 60 | ); 61 | --> statement-breakpoint 62 | CREATE TABLE `products` ( 63 | `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, 64 | `name` text NOT NULL, 65 | `qt_per_unit` text NOT NULL, 66 | `unit_price` numeric NOT NULL, 67 | `units_in_stock` integer NOT NULL, 68 | `units_on_order` integer NOT NULL, 69 | `reorder_level` integer NOT NULL, 70 | `discontinued` integer NOT NULL, 71 | `supplier_id` integer NOT NULL, 72 | FOREIGN KEY (`supplier_id`) REFERENCES `suppliers`(`id`) ON UPDATE no action ON DELETE cascade 73 | ); 74 | --> statement-breakpoint 75 | CREATE TABLE `suppliers` ( 76 | `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, 77 | `company_name` text NOT NULL, 78 | `contact_name` text NOT NULL, 79 | `contact_title` text NOT NULL, 80 | `address` text NOT NULL, 81 | `city` text NOT NULL, 82 | `region` text, 83 | `postal_code` text NOT NULL, 84 | `country` text NOT NULL, 85 | `phone` text NOT NULL 86 | ); 87 | --> statement-breakpoint 88 | CREATE INDEX `order_id_idx` ON `order_details` (`order_id`);--> statement-breakpoint 89 | CREATE INDEX `product_id_idx` ON `order_details` (`product_id`);--> statement-breakpoint 90 | CREATE INDEX `recepient_idx` ON `employees` (`recipient_id`);--> statement-breakpoint 91 | CREATE INDEX `supplier_idx` ON `products` (`supplier_id`); -------------------------------------------------------------------------------- /src/sqlite/drizzle/meta/0000_snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "6", 3 | "dialect": "sqlite", 4 | "id": "ffad8a06-81e2-487a-9fa2-bf05b2fe3c16", 5 | "prevId": "00000000-0000-0000-0000-000000000000", 6 | "tables": { 7 | "customers": { 8 | "name": "customers", 9 | "columns": { 10 | "id": { 11 | "name": "id", 12 | "type": "integer", 13 | "primaryKey": true, 14 | "notNull": true, 15 | "autoincrement": true 16 | }, 17 | "company_name": { 18 | "name": "company_name", 19 | "type": "text", 20 | "primaryKey": false, 21 | "notNull": true, 22 | "autoincrement": false 23 | }, 24 | "contact_name": { 25 | "name": "contact_name", 26 | "type": "text", 27 | "primaryKey": false, 28 | "notNull": true, 29 | "autoincrement": false 30 | }, 31 | "contact_title": { 32 | "name": "contact_title", 33 | "type": "text", 34 | "primaryKey": false, 35 | "notNull": true, 36 | "autoincrement": false 37 | }, 38 | "address": { 39 | "name": "address", 40 | "type": "text", 41 | "primaryKey": false, 42 | "notNull": true, 43 | "autoincrement": false 44 | }, 45 | "city": { 46 | "name": "city", 47 | "type": "text", 48 | "primaryKey": false, 49 | "notNull": true, 50 | "autoincrement": false 51 | }, 52 | "postal_code": { 53 | "name": "postal_code", 54 | "type": "text", 55 | "primaryKey": false, 56 | "notNull": false, 57 | "autoincrement": false 58 | }, 59 | "region": { 60 | "name": "region", 61 | "type": "text", 62 | "primaryKey": false, 63 | "notNull": false, 64 | "autoincrement": false 65 | }, 66 | "country": { 67 | "name": "country", 68 | "type": "text", 69 | "primaryKey": false, 70 | "notNull": true, 71 | "autoincrement": false 72 | }, 73 | "phone": { 74 | "name": "phone", 75 | "type": "text", 76 | "primaryKey": false, 77 | "notNull": true, 78 | "autoincrement": false 79 | }, 80 | "fax": { 81 | "name": "fax", 82 | "type": "text", 83 | "primaryKey": false, 84 | "notNull": false, 85 | "autoincrement": false 86 | } 87 | }, 88 | "indexes": {}, 89 | "foreignKeys": {}, 90 | "compositePrimaryKeys": {}, 91 | "uniqueConstraints": {} 92 | }, 93 | "order_details": { 94 | "name": "order_details", 95 | "columns": { 96 | "unit_price": { 97 | "name": "unit_price", 98 | "type": "numeric", 99 | "primaryKey": false, 100 | "notNull": true, 101 | "autoincrement": false 102 | }, 103 | "quantity": { 104 | "name": "quantity", 105 | "type": "integer", 106 | "primaryKey": false, 107 | "notNull": true, 108 | "autoincrement": false 109 | }, 110 | "discount": { 111 | "name": "discount", 112 | "type": "numeric", 113 | "primaryKey": false, 114 | "notNull": true, 115 | "autoincrement": false 116 | }, 117 | "order_id": { 118 | "name": "order_id", 119 | "type": "integer", 120 | "primaryKey": false, 121 | "notNull": true, 122 | "autoincrement": false 123 | }, 124 | "product_id": { 125 | "name": "product_id", 126 | "type": "integer", 127 | "primaryKey": false, 128 | "notNull": true, 129 | "autoincrement": false 130 | } 131 | }, 132 | "indexes": { 133 | "order_id_idx": { 134 | "name": "order_id_idx", 135 | "columns": [ 136 | "order_id" 137 | ], 138 | "isUnique": false 139 | }, 140 | "product_id_idx": { 141 | "name": "product_id_idx", 142 | "columns": [ 143 | "product_id" 144 | ], 145 | "isUnique": false 146 | } 147 | }, 148 | "foreignKeys": { 149 | "order_details_order_id_orders_id_fk": { 150 | "name": "order_details_order_id_orders_id_fk", 151 | "tableFrom": "order_details", 152 | "tableTo": "orders", 153 | "columnsFrom": [ 154 | "order_id" 155 | ], 156 | "columnsTo": [ 157 | "id" 158 | ], 159 | "onDelete": "cascade", 160 | "onUpdate": "no action" 161 | }, 162 | "order_details_product_id_products_id_fk": { 163 | "name": "order_details_product_id_products_id_fk", 164 | "tableFrom": "order_details", 165 | "tableTo": "products", 166 | "columnsFrom": [ 167 | "product_id" 168 | ], 169 | "columnsTo": [ 170 | "id" 171 | ], 172 | "onDelete": "cascade", 173 | "onUpdate": "no action" 174 | } 175 | }, 176 | "compositePrimaryKeys": {}, 177 | "uniqueConstraints": {} 178 | }, 179 | "employees": { 180 | "name": "employees", 181 | "columns": { 182 | "id": { 183 | "name": "id", 184 | "type": "integer", 185 | "primaryKey": true, 186 | "notNull": true, 187 | "autoincrement": true 188 | }, 189 | "last_name": { 190 | "name": "last_name", 191 | "type": "text", 192 | "primaryKey": false, 193 | "notNull": true, 194 | "autoincrement": false 195 | }, 196 | "first_name": { 197 | "name": "first_name", 198 | "type": "text", 199 | "primaryKey": false, 200 | "notNull": false, 201 | "autoincrement": false 202 | }, 203 | "title": { 204 | "name": "title", 205 | "type": "text", 206 | "primaryKey": false, 207 | "notNull": true, 208 | "autoincrement": false 209 | }, 210 | "title_of_courtesy": { 211 | "name": "title_of_courtesy", 212 | "type": "text", 213 | "primaryKey": false, 214 | "notNull": true, 215 | "autoincrement": false 216 | }, 217 | "birth_date": { 218 | "name": "birth_date", 219 | "type": "integer", 220 | "primaryKey": false, 221 | "notNull": true, 222 | "autoincrement": false 223 | }, 224 | "hire_date": { 225 | "name": "hire_date", 226 | "type": "integer", 227 | "primaryKey": false, 228 | "notNull": true, 229 | "autoincrement": false 230 | }, 231 | "address": { 232 | "name": "address", 233 | "type": "text", 234 | "primaryKey": false, 235 | "notNull": true, 236 | "autoincrement": false 237 | }, 238 | "city": { 239 | "name": "city", 240 | "type": "text", 241 | "primaryKey": false, 242 | "notNull": true, 243 | "autoincrement": false 244 | }, 245 | "postal_code": { 246 | "name": "postal_code", 247 | "type": "text", 248 | "primaryKey": false, 249 | "notNull": true, 250 | "autoincrement": false 251 | }, 252 | "country": { 253 | "name": "country", 254 | "type": "text", 255 | "primaryKey": false, 256 | "notNull": true, 257 | "autoincrement": false 258 | }, 259 | "home_phone": { 260 | "name": "home_phone", 261 | "type": "text", 262 | "primaryKey": false, 263 | "notNull": true, 264 | "autoincrement": false 265 | }, 266 | "extension": { 267 | "name": "extension", 268 | "type": "integer", 269 | "primaryKey": false, 270 | "notNull": true, 271 | "autoincrement": false 272 | }, 273 | "notes": { 274 | "name": "notes", 275 | "type": "text", 276 | "primaryKey": false, 277 | "notNull": true, 278 | "autoincrement": false 279 | }, 280 | "recipient_id": { 281 | "name": "recipient_id", 282 | "type": "integer", 283 | "primaryKey": false, 284 | "notNull": false, 285 | "autoincrement": false 286 | } 287 | }, 288 | "indexes": { 289 | "recepient_idx": { 290 | "name": "recepient_idx", 291 | "columns": [ 292 | "recipient_id" 293 | ], 294 | "isUnique": false 295 | } 296 | }, 297 | "foreignKeys": { 298 | "employees_recipient_id_employees_id_fk": { 299 | "name": "employees_recipient_id_employees_id_fk", 300 | "tableFrom": "employees", 301 | "tableTo": "employees", 302 | "columnsFrom": [ 303 | "recipient_id" 304 | ], 305 | "columnsTo": [ 306 | "id" 307 | ], 308 | "onDelete": "no action", 309 | "onUpdate": "no action" 310 | } 311 | }, 312 | "compositePrimaryKeys": {}, 313 | "uniqueConstraints": {} 314 | }, 315 | "orders": { 316 | "name": "orders", 317 | "columns": { 318 | "id": { 319 | "name": "id", 320 | "type": "integer", 321 | "primaryKey": true, 322 | "notNull": true, 323 | "autoincrement": true 324 | }, 325 | "order_date": { 326 | "name": "order_date", 327 | "type": "integer", 328 | "primaryKey": false, 329 | "notNull": true, 330 | "autoincrement": false 331 | }, 332 | "required_date": { 333 | "name": "required_date", 334 | "type": "integer", 335 | "primaryKey": false, 336 | "notNull": true, 337 | "autoincrement": false 338 | }, 339 | "shipped_date": { 340 | "name": "shipped_date", 341 | "type": "integer", 342 | "primaryKey": false, 343 | "notNull": false, 344 | "autoincrement": false 345 | }, 346 | "ship_via": { 347 | "name": "ship_via", 348 | "type": "integer", 349 | "primaryKey": false, 350 | "notNull": true, 351 | "autoincrement": false 352 | }, 353 | "freight": { 354 | "name": "freight", 355 | "type": "numeric", 356 | "primaryKey": false, 357 | "notNull": true, 358 | "autoincrement": false 359 | }, 360 | "ship_name": { 361 | "name": "ship_name", 362 | "type": "text", 363 | "primaryKey": false, 364 | "notNull": true, 365 | "autoincrement": false 366 | }, 367 | "ship_city": { 368 | "name": "ship_city", 369 | "type": "text", 370 | "primaryKey": false, 371 | "notNull": true, 372 | "autoincrement": false 373 | }, 374 | "ship_region": { 375 | "name": "ship_region", 376 | "type": "text", 377 | "primaryKey": false, 378 | "notNull": false, 379 | "autoincrement": false 380 | }, 381 | "ship_postal_code": { 382 | "name": "ship_postal_code", 383 | "type": "text", 384 | "primaryKey": false, 385 | "notNull": false, 386 | "autoincrement": false 387 | }, 388 | "ship_country": { 389 | "name": "ship_country", 390 | "type": "text", 391 | "primaryKey": false, 392 | "notNull": true, 393 | "autoincrement": false 394 | }, 395 | "customer_id": { 396 | "name": "customer_id", 397 | "type": "integer", 398 | "primaryKey": false, 399 | "notNull": true, 400 | "autoincrement": false 401 | }, 402 | "employee_id": { 403 | "name": "employee_id", 404 | "type": "integer", 405 | "primaryKey": false, 406 | "notNull": true, 407 | "autoincrement": false 408 | } 409 | }, 410 | "indexes": {}, 411 | "foreignKeys": { 412 | "orders_customer_id_customers_id_fk": { 413 | "name": "orders_customer_id_customers_id_fk", 414 | "tableFrom": "orders", 415 | "tableTo": "customers", 416 | "columnsFrom": [ 417 | "customer_id" 418 | ], 419 | "columnsTo": [ 420 | "id" 421 | ], 422 | "onDelete": "cascade", 423 | "onUpdate": "no action" 424 | }, 425 | "orders_employee_id_employees_id_fk": { 426 | "name": "orders_employee_id_employees_id_fk", 427 | "tableFrom": "orders", 428 | "tableTo": "employees", 429 | "columnsFrom": [ 430 | "employee_id" 431 | ], 432 | "columnsTo": [ 433 | "id" 434 | ], 435 | "onDelete": "cascade", 436 | "onUpdate": "no action" 437 | } 438 | }, 439 | "compositePrimaryKeys": {}, 440 | "uniqueConstraints": {} 441 | }, 442 | "products": { 443 | "name": "products", 444 | "columns": { 445 | "id": { 446 | "name": "id", 447 | "type": "integer", 448 | "primaryKey": true, 449 | "notNull": true, 450 | "autoincrement": true 451 | }, 452 | "name": { 453 | "name": "name", 454 | "type": "text", 455 | "primaryKey": false, 456 | "notNull": true, 457 | "autoincrement": false 458 | }, 459 | "qt_per_unit": { 460 | "name": "qt_per_unit", 461 | "type": "text", 462 | "primaryKey": false, 463 | "notNull": true, 464 | "autoincrement": false 465 | }, 466 | "unit_price": { 467 | "name": "unit_price", 468 | "type": "numeric", 469 | "primaryKey": false, 470 | "notNull": true, 471 | "autoincrement": false 472 | }, 473 | "units_in_stock": { 474 | "name": "units_in_stock", 475 | "type": "integer", 476 | "primaryKey": false, 477 | "notNull": true, 478 | "autoincrement": false 479 | }, 480 | "units_on_order": { 481 | "name": "units_on_order", 482 | "type": "integer", 483 | "primaryKey": false, 484 | "notNull": true, 485 | "autoincrement": false 486 | }, 487 | "reorder_level": { 488 | "name": "reorder_level", 489 | "type": "integer", 490 | "primaryKey": false, 491 | "notNull": true, 492 | "autoincrement": false 493 | }, 494 | "discontinued": { 495 | "name": "discontinued", 496 | "type": "integer", 497 | "primaryKey": false, 498 | "notNull": true, 499 | "autoincrement": false 500 | }, 501 | "supplier_id": { 502 | "name": "supplier_id", 503 | "type": "integer", 504 | "primaryKey": false, 505 | "notNull": true, 506 | "autoincrement": false 507 | } 508 | }, 509 | "indexes": { 510 | "supplier_idx": { 511 | "name": "supplier_idx", 512 | "columns": [ 513 | "supplier_id" 514 | ], 515 | "isUnique": false 516 | } 517 | }, 518 | "foreignKeys": { 519 | "products_supplier_id_suppliers_id_fk": { 520 | "name": "products_supplier_id_suppliers_id_fk", 521 | "tableFrom": "products", 522 | "tableTo": "suppliers", 523 | "columnsFrom": [ 524 | "supplier_id" 525 | ], 526 | "columnsTo": [ 527 | "id" 528 | ], 529 | "onDelete": "cascade", 530 | "onUpdate": "no action" 531 | } 532 | }, 533 | "compositePrimaryKeys": {}, 534 | "uniqueConstraints": {} 535 | }, 536 | "suppliers": { 537 | "name": "suppliers", 538 | "columns": { 539 | "id": { 540 | "name": "id", 541 | "type": "integer", 542 | "primaryKey": true, 543 | "notNull": true, 544 | "autoincrement": true 545 | }, 546 | "company_name": { 547 | "name": "company_name", 548 | "type": "text", 549 | "primaryKey": false, 550 | "notNull": true, 551 | "autoincrement": false 552 | }, 553 | "contact_name": { 554 | "name": "contact_name", 555 | "type": "text", 556 | "primaryKey": false, 557 | "notNull": true, 558 | "autoincrement": false 559 | }, 560 | "contact_title": { 561 | "name": "contact_title", 562 | "type": "text", 563 | "primaryKey": false, 564 | "notNull": true, 565 | "autoincrement": false 566 | }, 567 | "address": { 568 | "name": "address", 569 | "type": "text", 570 | "primaryKey": false, 571 | "notNull": true, 572 | "autoincrement": false 573 | }, 574 | "city": { 575 | "name": "city", 576 | "type": "text", 577 | "primaryKey": false, 578 | "notNull": true, 579 | "autoincrement": false 580 | }, 581 | "region": { 582 | "name": "region", 583 | "type": "text", 584 | "primaryKey": false, 585 | "notNull": false, 586 | "autoincrement": false 587 | }, 588 | "postal_code": { 589 | "name": "postal_code", 590 | "type": "text", 591 | "primaryKey": false, 592 | "notNull": true, 593 | "autoincrement": false 594 | }, 595 | "country": { 596 | "name": "country", 597 | "type": "text", 598 | "primaryKey": false, 599 | "notNull": true, 600 | "autoincrement": false 601 | }, 602 | "phone": { 603 | "name": "phone", 604 | "type": "text", 605 | "primaryKey": false, 606 | "notNull": true, 607 | "autoincrement": false 608 | } 609 | }, 610 | "indexes": {}, 611 | "foreignKeys": {}, 612 | "compositePrimaryKeys": {}, 613 | "uniqueConstraints": {} 614 | } 615 | }, 616 | "enums": {}, 617 | "_meta": { 618 | "schemas": {}, 619 | "tables": {}, 620 | "columns": {} 621 | }, 622 | "internal": { 623 | "indexes": {} 624 | } 625 | } -------------------------------------------------------------------------------- /src/sqlite/drizzle/meta/_journal.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "7", 3 | "dialect": "sqlite", 4 | "entries": [ 5 | { 6 | "idx": 0, 7 | "version": "6", 8 | "when": 1724332789799, 9 | "tag": "0000_glamorous_skaar", 10 | "breakpoints": true 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /src/sqlite/prisma-server-bun.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Hono } from "hono"; 3 | import { PrismaClient } from "@prisma/client"; 4 | import cpuUsage from "../cpu-usage"; 5 | import os from "os" 6 | 7 | const prisma = new PrismaClient(); 8 | 9 | const app = new Hono(); 10 | app.route('', cpuUsage); 11 | app.get("/customers", async (c) => { 12 | const limit = Number(c.req.query("limit")); 13 | const offset = Number(c.req.query("offset")); 14 | 15 | const result = await prisma.customer.findMany({ 16 | take: limit, 17 | skip: offset, 18 | }); 19 | 20 | return c.json(result); 21 | }); 22 | 23 | app.get("/customer-by-id", async (c) => { 24 | const result = await prisma.customer.findFirst({ 25 | where: { 26 | id: Number(c.req.query("id")!), 27 | }, 28 | }); 29 | return c.json(result); 30 | }); 31 | 32 | // app.get("/search-customer", async (c) => { 33 | // const result = await prisma.customer.findMany({ 34 | // where: { 35 | // companyName: { 36 | // search: `${c.req.query("term")}:*`, 37 | // }, 38 | // }, 39 | // }); 40 | 41 | // return c.json(result); 42 | // }); 43 | 44 | app.get("/employees", async (c) => { 45 | const limit = Number(c.req.query("limit")); 46 | const offset = Number(c.req.query("offset")); 47 | 48 | const result = await prisma.employee.findMany({ 49 | take: limit, 50 | skip: offset, 51 | }); 52 | return c.json(result); 53 | }); 54 | 55 | app.get("/employee-with-recipient", async (c) => { 56 | const result = await prisma.employee.findUnique({ 57 | where: { 58 | id: Number(c.req.query("id")!), 59 | }, 60 | include: { 61 | recipient: true, 62 | }, 63 | }); 64 | return c.json([result]); 65 | }); 66 | 67 | app.get("/suppliers", async (c) => { 68 | const limit = Number(c.req.query("limit")); 69 | const offset = Number(c.req.query("offset")); 70 | 71 | const result = await prisma.supplier.findMany({ 72 | take: limit, 73 | skip: offset, 74 | }); 75 | return c.json(result); 76 | }); 77 | 78 | app.get("/supplier-by-id", async (c) => { 79 | const result = await prisma.supplier.findUnique({ 80 | where: { 81 | id: Number(c.req.query("id")!), 82 | }, 83 | }); 84 | return c.json(result); 85 | }); 86 | 87 | app.get("/products", async (c) => { 88 | const limit = Number(c.req.query("limit")); 89 | const offset = Number(c.req.query("offset")); 90 | 91 | const result = await prisma.product.findMany({ 92 | take: limit, 93 | skip: offset, 94 | }); 95 | return c.json(result); 96 | }); 97 | 98 | app.get("/product-with-supplier", async (c) => { 99 | const result = await prisma.product.findUnique({ 100 | where: { 101 | id: Number(c.req.query("id")!), 102 | }, 103 | include: { 104 | supplier: true, 105 | }, 106 | }); 107 | return c.json([result]); 108 | }); 109 | 110 | // app.get("/search-product", async (c) => { 111 | // const result = await prisma.product.findMany({ 112 | // where: { 113 | // name: { 114 | // search: `${c.req.query("term")}:*`, 115 | // }, 116 | // }, 117 | // }); 118 | 119 | // return c.json(result); 120 | // }); 121 | 122 | app.get("/orders-with-details", async (c) => { 123 | const limit = Number(c.req.query("limit")); 124 | const offset = Number(c.req.query("offset")); 125 | 126 | const res = await prisma.order.findMany({ 127 | include: { 128 | details: true, 129 | }, 130 | take: limit, 131 | skip: offset, 132 | orderBy: { 133 | id: "asc", 134 | }, 135 | }); 136 | 137 | const result = res.map((item) => { 138 | return { 139 | id: item.id, 140 | shippedDate: item.shippedDate, 141 | shipName: item.shipName, 142 | shipCity: item.shipCity, 143 | shipCountry: item.shipCountry, 144 | productsCount: item.details.length, 145 | quantitySum: item.details.reduce( 146 | (sum, deteil) => (sum += +deteil.quantity), 147 | 0 148 | ), 149 | totalPrice: item.details.reduce( 150 | (sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 151 | 0 152 | ), 153 | }; 154 | }); 155 | return c.json(result); 156 | }); 157 | 158 | app.get("/order-with-details", async (c) => { 159 | const res = await prisma.order.findMany({ 160 | include: { 161 | details: true, 162 | }, 163 | where: { 164 | id: Number(c.req.query("id")!), 165 | }, 166 | }); 167 | 168 | const result = res.map((item) => { 169 | return { 170 | id: item.id, 171 | shippedDate: item.shippedDate, 172 | shipName: item.shipName, 173 | shipCity: item.shipCity, 174 | shipCountry: item.shipCountry, 175 | productsCount: item.details.length, 176 | quantitySum: item.details.reduce( 177 | (sum, detail) => (sum += detail.quantity), 178 | 0 179 | ), 180 | totalPrice: item.details.reduce( 181 | (sum, detail) => (sum += detail.quantity * detail.unitPrice.toNumber()), 182 | 0 183 | ), 184 | }; 185 | }); 186 | 187 | return c.json(result); 188 | }); 189 | 190 | app.get("/order-with-details-and-products", async (c) => { 191 | const result = await prisma.order.findMany({ 192 | where: { 193 | id: Number(c.req.query("id")!), 194 | }, 195 | include: { 196 | details: { 197 | include: { 198 | product: true, 199 | }, 200 | }, 201 | }, 202 | }); 203 | 204 | return c.json(result); 205 | }); 206 | 207 | export default { 208 | fetch: app.fetch, 209 | port: 3001, 210 | reusePort: true, 211 | } 212 | -------------------------------------------------------------------------------- /src/sqlite/prisma-server-node.ts: -------------------------------------------------------------------------------- 1 | import { serve } from '@hono/node-server'; 2 | import { Hono } from 'hono'; 3 | import { PrismaClient } from '@prisma/client'; 4 | import cpuUsage from '../cpu-usage'; 5 | 6 | import cluster from 'cluster'; 7 | 8 | const prisma = new PrismaClient(); 9 | 10 | const app = new Hono(); 11 | app.route('', cpuUsage); 12 | app.get('/customers', async (c) => { 13 | const limit = Number(c.req.query('limit')); 14 | const offset = Number(c.req.query('offset')); 15 | 16 | const result = await prisma.customer.findMany({ 17 | take: limit, 18 | skip: offset, 19 | }); 20 | 21 | return c.json(result); 22 | }); 23 | 24 | app.get('/customer-by-id', async (c) => { 25 | const result = await prisma.customer.findFirst({ 26 | where: { 27 | id: Number(c.req.query('id')!), 28 | }, 29 | }); 30 | return c.json(result); 31 | }); 32 | 33 | // app.get('/search-customer', async (c) => { 34 | // const result = await prisma.customer.findMany({ 35 | // where: { 36 | // companyName: { 37 | // search: `${c.req.query('term')}:*`, 38 | // }, 39 | // }, 40 | // }); 41 | 42 | // return c.json(result); 43 | // }); 44 | 45 | app.get('/employees', async (c) => { 46 | const limit = Number(c.req.query('limit')); 47 | const offset = Number(c.req.query('offset')); 48 | 49 | const result = await prisma.employee.findMany({ 50 | take: limit, 51 | skip: offset, 52 | }); 53 | return c.json(result); 54 | }); 55 | 56 | app.get('/employee-with-recipient', async (c) => { 57 | const result = await prisma.employee.findUnique({ 58 | where: { 59 | id: Number(c.req.query('id')!), 60 | }, 61 | include: { 62 | recipient: true, 63 | }, 64 | }); 65 | return c.json([result]); 66 | }); 67 | 68 | app.get('/suppliers', async (c) => { 69 | const limit = Number(c.req.query('limit')); 70 | const offset = Number(c.req.query('offset')); 71 | 72 | const result = await prisma.supplier.findMany({ 73 | take: limit, 74 | skip: offset, 75 | }); 76 | return c.json(result); 77 | }); 78 | 79 | app.get('/supplier-by-id', async (c) => { 80 | const result = await prisma.supplier.findUnique({ 81 | where: { 82 | id: Number(c.req.query('id')!), 83 | }, 84 | }); 85 | return c.json(result); 86 | }); 87 | 88 | app.get('/products', async (c) => { 89 | const limit = Number(c.req.query('limit')); 90 | const offset = Number(c.req.query('offset')); 91 | 92 | const result = await prisma.product.findMany({ 93 | take: limit, 94 | skip: offset, 95 | }); 96 | return c.json(result); 97 | }); 98 | 99 | app.get('/product-with-supplier', async (c) => { 100 | const result = await prisma.product.findUnique({ 101 | where: { 102 | id: Number(c.req.query('id')!), 103 | }, 104 | include: { 105 | supplier: true, 106 | }, 107 | }); 108 | return c.json([result]); 109 | }); 110 | 111 | // app.get('/search-product', async (c) => { 112 | // const result = await prisma.product.findMany({ 113 | // where: { 114 | // name: { 115 | // search: `${c.req.query('term')}:*`, 116 | // }, 117 | // }, 118 | // }); 119 | 120 | // return c.json(result); 121 | // }); 122 | 123 | app.get('/orders-with-details', async (c) => { 124 | const limit = Number(c.req.query('limit')); 125 | const offset = Number(c.req.query('offset')); 126 | 127 | const res = await prisma.order.findMany({ 128 | include: { 129 | details: true, 130 | }, 131 | take: limit, 132 | skip: offset, 133 | orderBy: { 134 | id: 'asc', 135 | }, 136 | }); 137 | 138 | const result = res.map((item) => { 139 | return { 140 | id: item.id, 141 | shippedDate: item.shippedDate, 142 | shipName: item.shipName, 143 | shipCity: item.shipCity, 144 | shipCountry: item.shipCountry, 145 | productsCount: item.details.length, 146 | quantitySum: item.details.reduce((sum, deteil) => (sum += +deteil.quantity), 0), 147 | totalPrice: item.details.reduce((sum, deteil) => (sum += +deteil.quantity * +deteil.unitPrice), 0), 148 | }; 149 | }); 150 | return c.json(result); 151 | }); 152 | 153 | app.get('/order-with-details', async (c) => { 154 | const res = await prisma.order.findMany({ 155 | include: { 156 | details: true, 157 | }, 158 | where: { 159 | id: Number(c.req.query('id')!), 160 | }, 161 | }); 162 | 163 | const result = res.map((item) => { 164 | return { 165 | id: item.id, 166 | shippedDate: item.shippedDate, 167 | shipName: item.shipName, 168 | shipCity: item.shipCity, 169 | shipCountry: item.shipCountry, 170 | productsCount: item.details.length, 171 | quantitySum: item.details.reduce((sum, detail) => (sum += detail.quantity), 0), 172 | totalPrice: item.details.reduce((sum, detail) => (sum += detail.quantity * detail.unitPrice.toNumber()), 0), 173 | }; 174 | }); 175 | 176 | return c.json(result); 177 | }); 178 | 179 | app.get('/order-with-details-and-products', async (c) => { 180 | const result = await prisma.order.findMany({ 181 | where: { 182 | id: Number(c.req.query('id')!), 183 | }, 184 | include: { 185 | details: { 186 | include: { 187 | product: true, 188 | }, 189 | }, 190 | }, 191 | }); 192 | 193 | return c.json(result); 194 | }); 195 | 196 | if (cluster.isPrimary) { 197 | console.log(`Primary ${process.pid} is running`); 198 | 199 | //Fork workers 200 | for (let i = 0; i < 2; i++) { 201 | cluster.fork(); 202 | } 203 | 204 | cluster.on('exit', (worker) => { 205 | console.log(`worker ${worker.process.pid} died`); 206 | }); 207 | } else { 208 | serve({ 209 | fetch: app.fetch, 210 | port: 3001, 211 | }); 212 | 213 | console.log(`Worker ${process.pid} started`); 214 | } 215 | -------------------------------------------------------------------------------- /src/sqlite/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | previewFeatures = ["fullTextIndex", "fullTextSearch", "referentialIntegrity", "relationJoins"] 4 | binaryTargets = ["native", "rhel-openssl-1.0.x"] 5 | } 6 | 7 | datasource db { 8 | provider = "sqlite" 9 | url = "file:./northwind.db" 10 | } 11 | 12 | model Customer { 13 | id Int @id @default(autoincrement()) 14 | companyName String @map("company_name") 15 | contactName String @map("contact_name") 16 | contactTitle String @map("contact_title") 17 | address String 18 | city String 19 | postalCode String? @map("postal_code") 20 | region String? 21 | country String 22 | phone String 23 | fax String? 24 | orders Order[] 25 | 26 | @@map("customers") 27 | } 28 | 29 | model Employee { 30 | id Int @id @default(autoincrement()) 31 | lastName String @map("last_name") 32 | firstName String? @map("first_name") 33 | title String 34 | titleOfCourtesy String @map("title_of_courtesy") 35 | birthDate DateTime @map("birth_date") 36 | hireDate DateTime @map("hire_date") 37 | address String 38 | city String 39 | postalCode String @map("postal_code") 40 | country String 41 | homePhone String @map("home_phone") 42 | extension Int 43 | notes String 44 | recipientId Int? @map("recipient_id") 45 | 46 | recipient Employee? @relation("reports", fields: [recipientId], references: [id], onDelete: NoAction, onUpdate: NoAction) 47 | reporters Employee[] @relation("reports") 48 | orders Order[] 49 | 50 | @@index([recipientId], map: "recepient_idx") 51 | @@map("employees") 52 | } 53 | 54 | model Detail { 55 | unitPrice Float @map("unit_price") 56 | quantity Int 57 | discount Float 58 | orderId Int @map("order_id") 59 | productId Int @map("product_id") 60 | product Product @relation(fields: [productId], references: [id], onDelete: Cascade, onUpdate: NoAction) 61 | order Order @relation(fields: [orderId], references: [id], onDelete: Cascade, onUpdate: NoAction) 62 | 63 | @@unique([orderId, productId]) 64 | @@index([productId], map: "product_id_idx") 65 | @@index([orderId], map: "order_id_idx") 66 | @@map("order_details") 67 | } 68 | 69 | model Order { 70 | id Int @id @default(autoincrement()) 71 | orderDate DateTime @map("order_date") 72 | requiredDate DateTime @map("required_date") 73 | shippedDate DateTime? @map("shipped_date") 74 | shipVia Int @map("ship_via") 75 | freight Float 76 | shipName String @map("ship_name") 77 | shipCity String @map("ship_city") 78 | shipRegion String? @map("ship_region") 79 | shipPostalCode String? @map("ship_postal_code") 80 | shipCountry String @map("ship_country") 81 | customerId Int @map("customer_id") 82 | employeeId Int @map("employee_id") 83 | details Detail[] 84 | employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade, onUpdate: NoAction) 85 | customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade, onUpdate: NoAction) 86 | 87 | @@map("orders") 88 | } 89 | 90 | model Product { 91 | id Int @id @default(autoincrement()) 92 | name String 93 | quantityPerUnit String @map("qt_per_unit") 94 | unitPrice Float @map("unit_price") 95 | unitsInStock Int @map("units_in_stock") 96 | unitsOnOrder Int @map("units_on_order") 97 | reorderLevel Int @map("reorder_level") 98 | discontinued Int 99 | supplierId Int @map("supplier_id") 100 | details Detail[] 101 | supplier Supplier @relation(fields: [supplierId], references: [id], onDelete: Cascade, onUpdate: NoAction) 102 | 103 | @@index([supplierId], map: "supplier_idx") 104 | @@map("products") 105 | } 106 | 107 | model Supplier { 108 | id Int @id @default(autoincrement()) 109 | companyName String @map("company_name") 110 | contactName String @map("contact_name") 111 | contactTitle String @map("contact_title") 112 | address String 113 | city String 114 | region String? 115 | postalCode String @map("postal_code") 116 | country String 117 | phone String 118 | products Product[] 119 | 120 | @@map("suppliers") 121 | } 122 | -------------------------------------------------------------------------------- /src/sqlite/schema.ts: -------------------------------------------------------------------------------- 1 | import { InferModel, relations } from 'drizzle-orm'; 2 | import { sqliteTable, foreignKey, integer, index, text, numeric } from 'drizzle-orm/sqlite-core'; 3 | 4 | export const customers = sqliteTable('customers', { 5 | id: integer('id').primaryKey({ 6 | autoIncrement: true, 7 | }), 8 | companyName: text('company_name').notNull(), 9 | contactName: text('contact_name').notNull(), 10 | contactTitle: text('contact_title').notNull(), 11 | address: text('address').notNull(), 12 | city: text('city').notNull(), 13 | postalCode: text('postal_code'), 14 | region: text('region'), 15 | country: text('country').notNull(), 16 | phone: text('phone').notNull(), 17 | fax: text('fax'), 18 | }); 19 | 20 | export const employees = sqliteTable( 21 | 'employees', 22 | { 23 | id: integer('id').primaryKey({ 24 | autoIncrement: true, 25 | }), 26 | lastName: text('last_name').notNull(), 27 | firstName: text('first_name'), 28 | title: text('title').notNull(), 29 | titleOfCourtesy: text('title_of_courtesy').notNull(), 30 | birthDate: integer('birth_date', { mode: 'timestamp_ms' }).notNull(), 31 | hireDate: integer('hire_date', { mode: 'timestamp_ms' }).notNull(), 32 | address: text('address').notNull(), 33 | city: text('city').notNull(), 34 | postalCode: text('postal_code').notNull(), 35 | country: text('country').notNull(), 36 | homePhone: text('home_phone').notNull(), 37 | extension: integer('extension').notNull(), 38 | notes: text('notes').notNull(), 39 | recipientId: integer('recipient_id'), 40 | }, 41 | (table) => ({ 42 | recipientFk: foreignKey({ 43 | columns: [table.recipientId], 44 | foreignColumns: [table.id], 45 | }), 46 | recepientIdx: index('recepient_idx').on(table.recipientId), 47 | }), 48 | ); 49 | 50 | export const orders = sqliteTable('orders', { 51 | id: integer('id').primaryKey({ 52 | autoIncrement: true, 53 | }), 54 | orderDate: integer('order_date', { mode: 'timestamp_ms' }).notNull(), 55 | requiredDate: integer('required_date', { mode: 'timestamp_ms' }).notNull(), 56 | shippedDate: integer('shipped_date', { mode: 'timestamp_ms' }), 57 | shipVia: integer('ship_via').notNull(), 58 | freight: numeric('freight').notNull(), 59 | shipName: text('ship_name').notNull(), 60 | shipCity: text('ship_city').notNull(), 61 | shipRegion: text('ship_region'), 62 | shipPostalCode: text('ship_postal_code'), 63 | shipCountry: text('ship_country').notNull(), 64 | 65 | customerId: integer('customer_id') 66 | .notNull() 67 | .references(() => customers.id, { onDelete: 'cascade' }), 68 | 69 | employeeId: integer('employee_id') 70 | .notNull() 71 | .references(() => employees.id, { onDelete: 'cascade' }), 72 | }); 73 | 74 | export const suppliers = sqliteTable('suppliers', { 75 | id: integer('id').primaryKey({ 76 | autoIncrement: true, 77 | }), 78 | companyName: text('company_name').notNull(), 79 | contactName: text('contact_name').notNull(), 80 | contactTitle: text('contact_title').notNull(), 81 | address: text('address').notNull(), 82 | city: text('city').notNull(), 83 | region: text('region'), 84 | postalCode: text('postal_code').notNull(), 85 | country: text('country').notNull(), 86 | phone: text('phone').notNull(), 87 | }); 88 | 89 | export const products = sqliteTable( 90 | 'products', 91 | { 92 | id: integer('id').primaryKey({ 93 | autoIncrement: true, 94 | }), 95 | name: text('name').notNull(), 96 | quantityPerUnit: text('qt_per_unit').notNull(), 97 | unitPrice: numeric('unit_price').notNull(), 98 | unitsInStock: integer('units_in_stock').notNull(), 99 | unitsOnOrder: integer('units_on_order').notNull(), 100 | reorderLevel: integer('reorder_level').notNull(), 101 | discontinued: integer('discontinued').notNull(), 102 | 103 | supplierId: integer('supplier_id') 104 | .notNull() 105 | .references(() => suppliers.id, { onDelete: 'cascade' }), 106 | }, 107 | (table) => { 108 | return { 109 | supplierIdx: index('supplier_idx').on(table.supplierId), 110 | }; 111 | }, 112 | ); 113 | 114 | export const details = sqliteTable( 115 | 'order_details', 116 | { 117 | unitPrice: numeric('unit_price').notNull(), 118 | quantity: integer('quantity').notNull(), 119 | discount: numeric('discount').notNull(), 120 | 121 | orderId: integer('order_id') 122 | .notNull() 123 | .references(() => orders.id, { onDelete: 'cascade' }), 124 | 125 | productId: integer('product_id') 126 | .notNull() 127 | .references(() => products.id, { onDelete: 'cascade' }), 128 | }, 129 | (t) => { 130 | return { 131 | orderIdIdx: index('order_id_idx').on(t.orderId), 132 | productIdIdx: index('product_id_idx').on(t.productId), 133 | }; 134 | }, 135 | ); 136 | 137 | export const ordersRelations = relations(orders, (r) => { 138 | return { 139 | details: r.many(details), 140 | // products: r.many(products) 141 | }; 142 | }); 143 | 144 | export const detailsRelations = relations(details, (r) => { 145 | return { 146 | order: r.one(orders, { 147 | fields: [details.orderId], 148 | references: [orders.id], 149 | }), 150 | product: r.one(products, { 151 | fields: [details.productId], 152 | references: [products.id], 153 | }), 154 | }; 155 | }); 156 | 157 | export const employeesRelations = relations(employees, (r) => { 158 | return { 159 | recipient: r.one(employees, { 160 | fields: [employees.recipientId], 161 | references: [employees.id], 162 | }), 163 | }; 164 | }); 165 | 166 | export const productsRelations = relations(products, (r) => { 167 | return { 168 | supplier: r.one(suppliers, { 169 | fields: [products.supplierId], 170 | references: [suppliers.id], 171 | }), 172 | }; 173 | }); 174 | 175 | export type Customer = InferModel; 176 | export type CustomerInsert = InferModel; 177 | export type Employee = InferModel; 178 | export type EmployeeInsert = InferModel; 179 | export type Order = InferModel; 180 | export type OrderInsert = InferModel; 181 | export type Supplier = InferModel; 182 | export type SupplierInsert = InferModel; 183 | export type Product = InferModel; 184 | export type ProductInsert = InferModel; 185 | export type Detail = InferModel; 186 | export type DetailInsert = InferModel; 187 | -------------------------------------------------------------------------------- /src/sqlite/seed.ts: -------------------------------------------------------------------------------- 1 | import Database from 'better-sqlite3'; 2 | import { migrate } from 'drizzle-orm/better-sqlite3/migrator'; 3 | import { drizzle } from 'drizzle-orm/better-sqlite3'; 4 | import { 5 | CustomerInsert, 6 | customers, 7 | DetailInsert, 8 | details, 9 | employees, 10 | OrderInsert, 11 | orders, 12 | ProductInsert, 13 | products, 14 | SupplierInsert, 15 | suppliers, 16 | } from '../schema'; 17 | import { faker } from '@faker-js/faker'; 18 | import 'dotenv/config'; 19 | 20 | const titlesOfCourtesy = ['Ms.', 'Mrs.', 'Dr.']; 21 | const unitsOnOrders = [0, 10, 20, 30, 50, 60, 70, 80, 100]; 22 | const reorderLevels = [0, 5, 10, 15, 20, 25, 30]; 23 | const quantityPerUnit = [ 24 | '100 - 100 g pieces', 25 | '100 - 250 g bags', 26 | '10 - 200 g glasses', 27 | '10 - 4 oz boxes', 28 | '10 - 500 g pkgs.', 29 | '10 - 500 g pkgs.', 30 | '10 boxes x 12 pieces', 31 | '10 boxes x 20 bags', 32 | '10 boxes x 8 pieces', 33 | '10 kg pkg.', 34 | '10 pkgs.', 35 | '12 - 100 g bars', 36 | '12 - 100 g pkgs', 37 | '12 - 12 oz cans', 38 | '12 - 1 lb pkgs.', 39 | '12 - 200 ml jars', 40 | '12 - 250 g pkgs.', 41 | '12 - 355 ml cans', 42 | '12 - 500 g pkgs.', 43 | '750 cc per bottle', 44 | '5 kg pkg.', 45 | '50 bags x 30 sausgs.', 46 | '500 ml', 47 | '500 g', 48 | '48 pieces', 49 | '48 - 6 oz jars', 50 | '4 - 450 g glasses', 51 | '36 boxes', 52 | '32 - 8 oz bottles', 53 | '32 - 500 g boxes', 54 | ]; 55 | const discounts = [0.05, 0.15, 0.2, 0.25]; 56 | 57 | const getRandomInt = (from: number, to: number): number => { 58 | return Math.round(Math.random() * (to - from) + from); 59 | }; 60 | 61 | const randFromArray = faker.helpers.arrayElement; 62 | 63 | const weightedRandom = (config: { weight: number; value: T | T[] }[]): (() => T) => { 64 | // probability mass function 65 | const pmf = config.map((it) => it.weight); 66 | 67 | const fn = (sum: number) => (value: number) => { 68 | return (sum += value); 69 | }; 70 | const mapFn = fn(0); 71 | 72 | // cumulative distribution function 73 | const cdf = pmf.map((it) => mapFn(it)); 74 | // [0.6, 0.7, 0.9...] 75 | return () => { 76 | const rand = Math.random(); 77 | for (let i = 0; i < cdf.length; i++) { 78 | if (cdf[i] >= rand) { 79 | const item = config[i]; 80 | if (typeof item.value === 'object') { 81 | return randFromArray(item.value as T[]); 82 | } 83 | return item.value; 84 | } 85 | } 86 | throw Error(`no rand for: ${rand} | ${cdf.join(',')}`); 87 | }; 88 | }; 89 | 90 | const sizes = { 91 | nano: { 92 | employees: 50, 93 | customers: 1000, 94 | orders: 5000, 95 | products: 500, 96 | suppliers: 100, 97 | }, 98 | micro: { 99 | employees: 200, 100 | customers: 10000, 101 | orders: 50000, 102 | products: 5000, 103 | suppliers: 1000, 104 | }, 105 | } as const; 106 | 107 | async function main(size: keyof typeof sizes) { 108 | const config = sizes[size]; 109 | 110 | try { 111 | const client = new Database('src/sqlite/northwind.db'); 112 | const db = drizzle(client, { logger: false }); 113 | 114 | migrate(db, { migrationsFolder: 'src/sqlite/drizzle' }); 115 | 116 | console.log('seeding customers...'); 117 | let customerModels: CustomerInsert[] = []; 118 | for (let customerId = 1; customerId <= config.customers; customerId++) { 119 | customerModels.push({ 120 | companyName: faker.company.name(), 121 | contactName: faker.person.fullName(), 122 | contactTitle: faker.person.jobTitle(), 123 | address: faker.location.streetAddress(), 124 | city: faker.location.city(), 125 | postalCode: getRandomInt(0, 1) ? faker.location.zipCode() : null, 126 | region: faker.location.state(), 127 | country: faker.location.country(), 128 | phone: faker.phone.number('(###) ###-####'), 129 | fax: faker.phone.number('(###) ###-####'), 130 | }); 131 | 132 | if (customerModels.length > 2_000) { 133 | await db.insert(customers).values(customerModels).execute(); 134 | customerModels = []; 135 | } 136 | } 137 | 138 | if (customerModels.length) { 139 | await db.insert(customers).values(customerModels).execute(); 140 | } 141 | 142 | console.log('seeding employees...'); 143 | for (let employeeId = 1; employeeId <= config.employees; employeeId++) { 144 | await db 145 | .insert(employees) 146 | .values({ 147 | firstName: faker.person.firstName(), 148 | lastName: faker.person.lastName(), 149 | title: faker.person.jobTitle(), 150 | titleOfCourtesy: faker.helpers.arrayElement(titlesOfCourtesy), 151 | birthDate: faker.date.birthdate(), 152 | hireDate: faker.date.past(), 153 | address: faker.location.streetAddress(), 154 | city: faker.location.city(), 155 | postalCode: faker.location.zipCode(), 156 | country: faker.location.country(), 157 | homePhone: faker.phone.number('(###) ###-####'), 158 | extension: getRandomInt(428, 5467), 159 | notes: faker.person.bio(), 160 | recipientId: employeeId > 1 ? getRandomInt(1, employeeId - 1) : null, 161 | }) 162 | .execute(); 163 | } 164 | 165 | console.log('locading orders...'); 166 | let startOrderDate = new Date('2016'); 167 | 168 | let orderModels: OrderInsert[] = []; 169 | for (let orderId = 1; orderId <= config.orders; orderId++) { 170 | const orderDate = startOrderDate; 171 | 172 | const requiredDate = new Date(orderDate); 173 | requiredDate.setHours(orderDate.getHours() + 30 * 24); 174 | 175 | const shippedDate = new Date(orderDate); 176 | shippedDate.setHours(orderDate.getHours() + 10 * 24); 177 | 178 | orderModels.push({ 179 | orderDate, 180 | requiredDate, 181 | shippedDate, 182 | shipVia: getRandomInt(1, 3), 183 | freight: Number(`${getRandomInt(0, 1000)}.${getRandomInt(10, 99)}`), 184 | shipName: faker.location.streetAddress(), 185 | shipCity: faker.location.city(), 186 | shipRegion: faker.location.state(), 187 | shipPostalCode: faker.location.zipCode(), 188 | shipCountry: faker.location.country(), 189 | customerId: getRandomInt(1, config.customers), 190 | employeeId: getRandomInt(1, config.employees), 191 | }); 192 | 193 | startOrderDate.setMinutes(startOrderDate.getMinutes() + 1); 194 | 195 | if (orderModels.length > 2_000) { 196 | await db.insert(orders).values(orderModels).execute(); 197 | orderModels = []; 198 | } 199 | } 200 | 201 | if (orderModels.length) { 202 | await db.insert(orders).values(orderModels).execute(); 203 | } 204 | 205 | console.log('seeding suppliers...'); 206 | let supplierModels: SupplierInsert[] = []; 207 | for (let supplierId = 1; supplierId <= config.suppliers; supplierId++) { 208 | supplierModels.push({ 209 | companyName: faker.company.name(), 210 | contactName: faker.person.fullName(), 211 | contactTitle: faker.person.jobTitle(), 212 | address: faker.location.streetAddress(), 213 | city: faker.location.city(), 214 | postalCode: faker.location.zipCode(), 215 | region: faker.location.state(), 216 | country: faker.location.country(), 217 | phone: faker.phone.number('(###) ###-####'), 218 | }); 219 | 220 | if (supplierModels.length > 2_000) { 221 | await db.insert(suppliers).values(supplierModels).execute(); 222 | supplierModels = []; 223 | } 224 | } 225 | 226 | if (supplierModels.length) { 227 | await db.insert(suppliers).values(supplierModels).execute(); 228 | } 229 | 230 | console.log('seeding products...'); 231 | let productModels: ProductInsert[] = []; 232 | const productPrices = new Map(); 233 | for (let productId = 1; productId <= config.products; productId++) { 234 | const unitPrice = getRandomInt(0, 1) 235 | ? getRandomInt(3, 300) 236 | : Number(`${getRandomInt(3, 300)}.${getRandomInt(5, 99)}`); 237 | 238 | productModels.push({ 239 | name: faker.company.name(), 240 | quantityPerUnit: faker.helpers.arrayElement(quantityPerUnit), 241 | unitPrice, 242 | unitsInStock: getRandomInt(0, 125), 243 | unitsOnOrder: faker.helpers.arrayElement(unitsOnOrders), 244 | reorderLevel: faker.helpers.arrayElement(reorderLevels), 245 | discontinued: getRandomInt(0, 1), 246 | supplierId: getRandomInt(1, config.suppliers), 247 | }); 248 | 249 | productPrices.set(productId, unitPrice); 250 | 251 | if (productModels.length > 2_000) { 252 | await db.insert(products).values(productModels).execute(); 253 | productModels = []; 254 | } 255 | } 256 | 257 | if (productModels.length) { 258 | await db.insert(products).values(productModels).execute(); 259 | } 260 | 261 | console.log('seeding order details...'); 262 | let detailModels: DetailInsert[] = []; 263 | const productCount = weightedRandom([ 264 | { weight: 0.6, value: [1, 2, 3, 4] }, 265 | { weight: 0.2, value: [5, 6, 7, 8, 9, 10] }, 266 | { weight: 0.15, value: [11, 12, 13, 14, 15, 16, 17] }, 267 | { weight: 0.05, value: [18, 19, 20, 21, 22, 23, 24, 25] }, 268 | ]); 269 | 270 | for (let orderId = 1; orderId <= config.orders; orderId++) { 271 | const count = productCount(); 272 | 273 | for (let i = 0; i < count; i++) { 274 | const productId = getRandomInt(1, config.products); 275 | const unitPrice = productPrices.get(productId); 276 | 277 | detailModels.push({ 278 | unitPrice: unitPrice!, 279 | quantity: getRandomInt(1, 130), 280 | discount: getRandomInt(0, 1) ? 0 : faker.helpers.arrayElement(discounts), 281 | orderId: orderId, 282 | productId, 283 | }); 284 | 285 | if (detailModels.length > 2_000) { 286 | await db.insert(details).values(detailModels).execute(); 287 | detailModels = []; 288 | } 289 | } 290 | } 291 | 292 | if (detailModels.length) { 293 | await db.insert(details).values(detailModels).execute(); 294 | } 295 | 296 | console.log('done!'); 297 | process.exit(0); 298 | } catch (err: any) { 299 | console.log('FATAL ERROR:', err); 300 | } 301 | } 302 | 303 | main('micro'); 304 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from "fs"; 2 | import axios from "axios"; 3 | import r from "ramda"; 4 | import diff from "deep-diff"; 5 | import "dotenv/config"; 6 | 7 | const reqs = JSON.parse( 8 | readFileSync("./data/requests.json", "utf-8") 9 | ) as string[]; 10 | 11 | const phost = `http://192.168.31.144:3001`; // prisma 12 | const dhost = `http://192.168.31.144:3000`; // drizzle 13 | 14 | const main = async () => { 15 | let i = 0; 16 | for (let i = 400000; i < reqs.length; i++) { 17 | const params = reqs[i]; 18 | const url1 = `${phost}${params}`; 19 | const url2 = `${dhost}${params}`; 20 | 21 | const [res1, res2] = ( 22 | await Promise.all([axios.get(url1), axios.get(url2)]) 23 | ).map((it) => it.data); 24 | 25 | i += 1; 26 | if (i % 1000 === 1) { 27 | console.log(i); 28 | } 29 | 30 | if (!r.equals(res1, res2)) { 31 | const diffed = diff(res1, res2)!!; 32 | if (diffed[0]!.path![1] === "totalPrice") { 33 | continue; 34 | } 35 | console.log(i, url1); 36 | console.log(diffed); 37 | return; 38 | } 39 | } 40 | }; 41 | 42 | main(); 43 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | "resolveJsonModule": true, 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 39 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 42 | // "resolveJsonModule": true, /* Enable importing .json files. */ 43 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 44 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 45 | 46 | /* JavaScript Support */ 47 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 48 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 49 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 50 | 51 | /* Emit */ 52 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 53 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 54 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 55 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 56 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 57 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 58 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 59 | // "removeComments": true, /* Disable emitting comments. */ 60 | // "noEmit": true, /* Disable emitting files from a compilation. */ 61 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 62 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 66 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 67 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 68 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 69 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 70 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 71 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 72 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 73 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 74 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 75 | 76 | /* Interop Constraints */ 77 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 78 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 79 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 80 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 82 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 83 | 84 | /* Type Checking */ 85 | "strict": true, /* Enable all strict type-checking options. */ 86 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 87 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 88 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 89 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 90 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 91 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 92 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 93 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 94 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 95 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 96 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 97 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 98 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 99 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 100 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 101 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 102 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 103 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 104 | 105 | /* Completeness */ 106 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 107 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 108 | } 109 | } 110 | --------------------------------------------------------------------------------