From 4039addbe6c8aad081bfa870902b85af09f45831 Mon Sep 17 00:00:00 2001 From: Bobbie Soedirgo Date: Tue, 25 Jan 2022 18:25:33 +0800 Subject: [PATCH 1/3] chore: ctrl-c cleans up `npm run dev` --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2f702431..6fac472a 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "build:server": "tsc -p tsconfig.server.json && cpy 'src/lib/sql/*.sql' bin/src/lib/sql", "docs:export": "PG_META_EXPORT_DOCS=true ts-node-dev src/server/app.ts", "start": "NODE_ENV=production node bin/src/server/app.js", - "dev": "run-s db:clean db:run && NODE_ENV=development ts-node-dev src/server/app.ts | pino-pretty --colorize", + "dev": "trap 'npm run db:clean' INT && run-s db:clean db:run && NODE_ENV=development ts-node-dev src/server/app.ts | pino-pretty --colorize", "pkg": "run-s clean build:server && pkg --out-path bin .pkg.config.json", "test": "run-s db:clean db:run test:run db:clean", "db:clean": "cd test/db && docker-compose down", From a1fe9003afbece62e25f0948a31b8f79f3b6fde5 Mon Sep 17 00:00:00 2001 From: Bobbie Soedirgo Date: Tue, 25 Jan 2022 18:26:18 +0800 Subject: [PATCH 2/3] fix: return `date`, `timestamp`, `timestamptz` as string --- package-lock.json | 30 +++++++++++++++++++++++------- package.json | 4 +++- src/lib/db.ts | 9 ++++++++- test/lib/roles.ts | 8 ++++---- 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index b438f20b..ddddd980 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "pg-format": "^1.0.4", "pgsql-parser": "^13.1.11", "pino": "^7.6.3", + "postgres-array": "^3.0.1", "prettier": "^2.4.1", "prettier-plugin-sql": "^0.4.0", "sql-formatter": "^4.0.2" @@ -7688,6 +7689,14 @@ "node": ">=4" } }, + "node_modules/pg-types/node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, "node_modules/pgpass": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.4.tgz", @@ -8170,11 +8179,11 @@ } }, "node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.1.tgz", + "integrity": "sha512-h7i53Dw2Yq3a1uuZ6lbVFAkvMMwssJ8jkzeAg0XaZm1XIFF/t/s+tockdqbWTymyEm07dVenOQbFisEi+kj8uA==", "engines": { - "node": ">=4" + "node": ">=12" } }, "node_modules/postgres-bytea": { @@ -17227,6 +17236,13 @@ "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" + }, + "dependencies": { + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + } } }, "pgpass": { @@ -17595,9 +17611,9 @@ "dev": true }, "postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.1.tgz", + "integrity": "sha512-h7i53Dw2Yq3a1uuZ6lbVFAkvMMwssJ8jkzeAg0XaZm1XIFF/t/s+tockdqbWTymyEm07dVenOQbFisEi+kj8uA==" }, "postgres-bytea": { "version": "1.0.0", diff --git a/package.json b/package.json index 6fac472a..ad0267d8 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "test": "run-s db:clean db:run test:run db:clean", "db:clean": "cd test/db && docker-compose down", "db:run": "cd test/db && docker-compose up --detach && sleep 5", - "test:run": "jest --runInBand" + "test:run": "jest --runInBand", + "test:update": "run-s db:clean db:run && jest --runInBand --updateSnapshot && run-s db:clean" }, "engines": { "node": ">=14 <15", @@ -38,6 +39,7 @@ "pg-format": "^1.0.4", "pgsql-parser": "^13.1.11", "pino": "^7.6.3", + "postgres-array": "^3.0.1", "prettier": "^2.4.1", "prettier-plugin-sql": "^0.4.0", "sql-formatter": "^4.0.2" diff --git a/src/lib/db.ts b/src/lib/db.ts index 6924df90..a2e46551 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -1,7 +1,14 @@ import { types, Pool, PoolConfig } from 'pg' +import { parse as parseArray } from 'postgres-array' import { PostgresMetaResult } from './types' -types.setTypeParser(20, parseInt) +types.setTypeParser(types.builtins.INT8, parseInt) +types.setTypeParser(types.builtins.DATE, (x) => x) +types.setTypeParser(types.builtins.TIMESTAMP, (x) => x) +types.setTypeParser(types.builtins.TIMESTAMPTZ, (x) => x) +types.setTypeParser(1115, parseArray) // _timestamp +types.setTypeParser(1182, parseArray) // _date +types.setTypeParser(1185, parseArray) // _timestamptz export const init: (config: PoolConfig) => { query: (sql: string) => Promise> diff --git a/test/lib/roles.ts b/test/lib/roles.ts index b0727b6a..c7a57de1 100644 --- a/test/lib/roles.ts +++ b/test/lib/roles.ts @@ -442,7 +442,7 @@ test('retrieve, create, update, delete', async () => { "is_superuser": true, "name": "r", "password": "********", - "valid_until": 2020-01-01T00:00:00.000Z, + "valid_until": "2020-01-01 00:00:00+00", }, "error": null, } @@ -467,7 +467,7 @@ test('retrieve, create, update, delete', async () => { "is_superuser": true, "name": "r", "password": "********", - "valid_until": 2020-01-01T00:00:00.000Z, + "valid_until": "2020-01-01 00:00:00+00", }, "error": null, } @@ -507,7 +507,7 @@ test('retrieve, create, update, delete', async () => { "is_superuser": true, "name": "rr", "password": "********", - "valid_until": 2020-01-01T00:00:00.000Z, + "valid_until": "2020-01-01 00:00:00+00", }, "error": null, } @@ -532,7 +532,7 @@ test('retrieve, create, update, delete', async () => { "is_superuser": true, "name": "rr", "password": "********", - "valid_until": 2020-01-01T00:00:00.000Z, + "valid_until": "2020-01-01 00:00:00+00", }, "error": null, } From f397839ee5506cb7ee4501e7c6435496a66c2d49 Mon Sep 17 00:00:00 2001 From: Bobbie Soedirgo Date: Wed, 26 Jan 2022 16:06:47 +0800 Subject: [PATCH 3/3] feat: multi-statement queries & arrayMode --- src/lib/PostgresMeta.ts | 33 ++++++++++++++++----------------- src/lib/db.ts | 40 ++++++++++++++++++++++++++++++++++++++++ test/lib/columns.ts | 27 ++++++++++++++++++++++++--- test/lib/query.ts | 41 +++++++++++++++++++++++++---------------- 4 files changed, 105 insertions(+), 36 deletions(-) diff --git a/src/lib/PostgresMeta.ts b/src/lib/PostgresMeta.ts index 069cf21d..5084a820 100644 --- a/src/lib/PostgresMeta.ts +++ b/src/lib/PostgresMeta.ts @@ -14,9 +14,8 @@ import PostgresMetaTypes from './PostgresMetaTypes' import PostgresMetaVersion from './PostgresMetaVersion' import PostgresMetaViews from './PostgresMetaViews' import { init } from './db' -import { PostgresMetaResult } from './types' export default class PostgresMeta { - query: (sql: string) => Promise> + query: (sql: string) => Promise end: () => Promise columns: PostgresMetaColumns config: PostgresMetaConfig @@ -37,21 +36,21 @@ export default class PostgresMeta { format = Parser.Format constructor(config: PoolConfig) { - const { query, end } = init(config) - this.query = query + const { query, queryArrayMode, end } = init(config) + this.query = queryArrayMode this.end = end - this.columns = new PostgresMetaColumns(this.query) - this.config = new PostgresMetaConfig(this.query) - this.extensions = new PostgresMetaExtensions(this.query) - this.functions = new PostgresMetaFunctions(this.query) - this.policies = new PostgresMetaPolicies(this.query) - this.publications = new PostgresMetaPublications(this.query) - this.roles = new PostgresMetaRoles(this.query) - this.schemas = new PostgresMetaSchemas(this.query) - this.tables = new PostgresMetaTables(this.query) - this.triggers = new PostgresMetaTriggers(this.query) - this.types = new PostgresMetaTypes(this.query) - this.version = new PostgresMetaVersion(this.query) - this.views = new PostgresMetaViews(this.query) + this.columns = new PostgresMetaColumns(query) + this.config = new PostgresMetaConfig(query) + this.extensions = new PostgresMetaExtensions(query) + this.functions = new PostgresMetaFunctions(query) + this.policies = new PostgresMetaPolicies(query) + this.publications = new PostgresMetaPublications(query) + this.roles = new PostgresMetaRoles(query) + this.schemas = new PostgresMetaSchemas(query) + this.tables = new PostgresMetaTables(query) + this.triggers = new PostgresMetaTriggers(query) + this.types = new PostgresMetaTypes(query) + this.version = new PostgresMetaVersion(query) + this.views = new PostgresMetaViews(query) } } diff --git a/src/lib/db.ts b/src/lib/db.ts index a2e46551..dd6718ca 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -12,6 +12,7 @@ types.setTypeParser(1185, parseArray) // _timestamptz export const init: (config: PoolConfig) => { query: (sql: string) => Promise> + queryArrayMode: (sql: string) => Promise end: () => Promise } = (config) => { // NOTE: Race condition could happen here: one async task may be doing @@ -38,6 +39,45 @@ export const init: (config: PoolConfig) => { } }, + async queryArrayMode(sql) { + try { + if (!pool) { + const pool = new Pool(config) + let res: any = await pool.query({ + rowMode: 'array', + text: sql, + }) + if (!Array.isArray(res)) { + res = [res] + } + return { + data: res.map(({ fields, rows }: any) => ({ + columns: fields.map((x: any) => x.name), + rows, + })), + error: null, + } + } + + let res: any = await pool.query({ + rowMode: 'array', + text: sql, + }) + if (!Array.isArray(res)) { + res = [res] + } + return { + data: res.map(({ fields, rows }: any) => ({ + columns: fields.map((x: any) => x.name), + rows, + })), + error: null, + } + } catch (e: any) { + return { data: null, error: { message: e.message } } + } + }, + async end() { const _pool = pool pool = null diff --git a/test/lib/columns.ts b/test/lib/columns.ts index 31549093..35f2ac9c 100644 --- a/test/lib/columns.ts +++ b/test/lib/columns.ts @@ -235,7 +235,14 @@ AND i.indisprimary; Object { "data": Array [ Object { - "attname": "c", + "columns": Array [ + "attname", + ], + "rows": Array [ + Array [ + "c", + ], + ], }, ], "error": null, @@ -267,7 +274,14 @@ AND i.indisunique; Object { "data": Array [ Object { - "attname": "c", + "columns": Array [ + "attname", + ], + "rows": Array [ + Array [ + "c", + ], + ], }, ], "error": null, @@ -387,7 +401,14 @@ SELECT pg_get_constraintdef(( Object { "data": Array [ Object { - "pg_get_constraintdef": null, + "columns": Array [ + "pg_get_constraintdef", + ], + "rows": Array [ + Array [ + null, + ], + ], }, ], "error": null, diff --git a/test/lib/query.ts b/test/lib/query.ts index 7715c376..5f467929 100644 --- a/test/lib/query.ts +++ b/test/lib/query.ts @@ -6,14 +6,23 @@ test('query', async () => { Object { "data": Array [ Object { - "id": 1, - "name": "Joe Bloggs", - "status": "ACTIVE", - }, - Object { - "id": 2, - "name": "Jane Doe", - "status": "ACTIVE", + "columns": Array [ + "id", + "name", + "status", + ], + "rows": Array [ + Array [ + 1, + "Joe Bloggs", + "ACTIVE", + ], + Array [ + 2, + "Jane Doe", + "ACTIVE", + ], + ], }, ], "error": null, @@ -463,14 +472,14 @@ CREATE TABLE table_name ( const deparse = pgMeta.deparse(res.data!) expect(deparse.data).toMatchInlineSnapshot(` -"CREATE TABLE table_name ( - id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - inserted_at pg_catalog.timestamptz DEFAULT ( timezone('utc'::text, now()) ) NOT NULL, - updated_at pg_catalog.timestamptz DEFAULT ( timezone('utc'::text, now()) ) NOT NULL, - data jsonb, - name text -);" -`) + "CREATE TABLE table_name ( + id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + inserted_at pg_catalog.timestamptz DEFAULT ( timezone('utc'::text, now()) ) NOT NULL, + updated_at pg_catalog.timestamptz DEFAULT ( timezone('utc'::text, now()) ) NOT NULL, + data jsonb, + name text + );" + `) }) test('formatter', async () => { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy