From 096f5cddce017395ea090676acb2cd86fe1262b8 Mon Sep 17 00:00:00 2001 From: Olivier Guimbal Date: Tue, 6 Aug 2024 15:53:19 +0200 Subject: [PATCH] fix things --- src/adapters/adapters.ts | 38 ++- src/adapters/pg-socket-adapter.ts | 288 +++++++++--------- src/interfaces.ts | 19 ++ src/schema/pg-catalog/index.ts | 4 + src/schema/pg-catalog/pg-proc.ts | 36 +++ src/schema/pg-catalog/pg-range.ts | 39 ++- .../pg-catalog/pg_statio_user_tables.ts | 58 ++++ src/schema/prepared-intercepted.ts | 58 +++- src/schema/prepared.ts | 2 +- src/tests/postgres.js-real.spec.ts | 2 +- src/tests/union.queries.spec.ts | 2 +- src/transforms/union.ts | 2 +- testbind.ts | 73 +++++ 13 files changed, 466 insertions(+), 155 deletions(-) create mode 100644 src/schema/pg-catalog/pg-proc.ts create mode 100644 src/schema/pg-catalog/pg_statio_user_tables.ts create mode 100644 testbind.ts diff --git a/src/adapters/adapters.ts b/src/adapters/adapters.ts index 5a8a0568..a1de0375 100644 --- a/src/adapters/adapters.ts +++ b/src/adapters/adapters.ts @@ -1,4 +1,4 @@ -import { LibAdapters, IMemoryDb, NotSupported, QueryResult, SlonikAdapterOptions } from '../interfaces'; +import { LibAdapters, IMemoryDb, NotSupported, QueryResult, SlonikAdapterOptions, BindServerOptions, BindServerResult } from '../interfaces'; import lru from 'lru-cache'; import { compareVersions, delay, doRequire, timeoutOrImmediate } from '../utils'; import { toLiteral } from '../misc/pg-utils'; @@ -6,7 +6,7 @@ import { _IDb, _IType } from '../interfaces-private'; import { TYPE_SYMBOL } from '../execution/select'; import { ArrayType } from '../datatypes'; import { CustomEnumType } from '../datatypes/t-custom-enum'; -import { socketAdapter } from './pg-socket-adapter'; +import { bindPgServer, socketAdapter } from './pg-socket-adapter'; export function replaceQueryArgs$(this: void, sql: string, values: any[]) { @@ -429,4 +429,38 @@ export class Adapters implements LibAdapters { }); return sql; } + + + async bindServer(opts?: BindServerOptions): Promise { + const { createServer } = doRequire('net') as typeof import('net'); + const srv = createServer(); + return new Promise((res, rej) => { + srv.listen(opts?.port ?? 0, opts?.host ?? '127.0.0.1', () => { + const a = srv.address(); + if (!a) { + srv.close(); + return rej('cannot find a port'); + } + if (typeof a === 'string') { + srv.close(); + return rej('cannot find a port'); + } + + srv.on('connection', (socket) => { + bindPgServer(socket, this.db.public); + }); + + res({ + postgresConnectionString: `postgresql://${a.address}:${a.port}/postgres?sslmode=disable`, + connectionSettings: { + port: a.port, + host: a.address, + }, + close: () => { + srv.close(); + }, + }) + }); + }); + } } diff --git a/src/adapters/pg-socket-adapter.ts b/src/adapters/pg-socket-adapter.ts index 41190daa..1553438f 100644 --- a/src/adapters/pg-socket-adapter.ts +++ b/src/adapters/pg-socket-adapter.ts @@ -8,13 +8,7 @@ import { AsyncQueue, delay, doRequire, lazySync, nullIsh } from '../utils'; // https://www.postgresql.org/docs/current/protocol-message-formats.html export const socketAdapter = lazySync(() => { - const { CommandCode, bindSocket } = doRequire('pg-server') as typeof import('pg-server'); const EventEmitter = doRequire('events') as typeof import('events'); - - const byCode = Object.fromEntries( - Object.entries(CommandCode).map(([k, v]) => [v, k]) - ); - class InMemorySocket extends EventEmitter { readonly peer: InMemorySocket; isTop: boolean; @@ -60,147 +54,165 @@ export const socketAdapter = lazySync(() => { class Connection { socket = new InMemorySocket(); constructor(private mem: _ISchema, private queryLatency?: number) { - const log = typeof process !== 'undefined' && process.env.DEBUG_PG_SERVER === 'true' - ? console.log.bind(console) - : (...args: any[]) => { }; - let prepared: _IPreparedQuery | undefined; - let preparedQuery: string | undefined; - let bound: _IBoundQuery | undefined; - let runningTx: _Transaction | undefined; - const queue = new AsyncQueue(); - bindSocket(this.socket.peer as any, ({ command: cmd }, writer) => queue.enqueue(async () => { - function sendDescribe(prepared: _IPreparedQuery) { - const p = prepared.describe(); - writer.parameterDescription(p.parameters.map(x => x.typeId)); - - // see RowDescription() in connection.js of postgres.js - // => we just need typeId - const descs = p.result.map(x => ({ - name: x.name, - tableID: 0, - columnID: 0, - dataTypeID: x.typeId, - dataTypeSize: 0, - dataTypeModifier: -1, - format: 0, - mode: 'text', - // mode: textMode ? 'text' : 'binary', - })) - writer.rowDescription(descs); - } + bindPgServer(this.socket.peer, mem, queryLatency); + } + } + + return (mem: _ISchema, queryLatency?: number) => new Connection(mem, queryLatency).socket; +}); - function sendResults(bound: _IBoundQuery, qname: string) { - const results = bound.executeAll(runningTx); - if (runningTx && results.state) { - runningTx = results.state; + +export function bindPgServer(this: void, peer: any, mem: _ISchema, queryLatency?: number) { + const { CommandCode, bindSocket } = doRequire('pg-server') as typeof import('pg-server'); + const byCode = Object.fromEntries( + Object.entries(CommandCode).map(([k, v]) => [v, k]) + ); + + const log = typeof process !== 'undefined' && process.env.DEBUG_PG_SERVER === 'true' + ? console.log.bind(console) + : (...args: any[]) => { }; + let prepared: _IPreparedQuery | undefined; + let preparedQuery: string | undefined; + let bound: _IBoundQuery | undefined; + let runningTx: _Transaction | undefined; + const queue = new AsyncQueue(); + bindSocket(peer, ({ command: cmd }, writer) => queue.enqueue(async () => { + function sendDescribe(prepared: _IPreparedQuery) { + const p = prepared.describe(); + writer.parameterDescription(p.parameters.map(x => x.typeId)); + + // see RowDescription() in connection.js of postgres.js + // => we just need typeId + const descs = p.result.map(x => ({ + name: x.name, + tableID: 0, + columnID: 0, + dataTypeID: x.typeId, + dataTypeSize: 0, + dataTypeModifier: -1, + format: 0, + mode: 'text', + // mode: textMode ? 'text' : 'binary', + })) + writer.rowDescription(descs); + } + + function sendResults(bound: _IBoundQuery, qname: string) { + const results = bound.executeAll(runningTx); + if (runningTx && results.state) { + runningTx = results.state; + } + for (const row of results.rows) { + writer.dataRow(results.fields.map((x) => row[x.name])); + } + log('...complete', qname); + writer.commandComplete(qname); + writer.readyForQuery(); + } + try { + await delay(queryLatency ?? 0); + const t = cmd.type; + const cmdName = byCode[t]; + + switch (t) { + case CommandCode.init: + writer.authenticationOk(); + writer.parameterStatus('client_encoding', 'UTF8'); + writer.parameterStatus('DateStyle', 'ISO, MDY'); + writer.parameterStatus('integer_datetimes', 'on'); + writer.parameterStatus('server_encoding', 'UTF8'); + writer.parameterStatus('server_version', '12.5'); + writer.parameterStatus('TimeZone', 'UTC'); + + return writer.readyForQuery(); + + case CommandCode.parse: + try { + prepared = mem.prepare(cmd.query); + if (!prepared) { + return writer.emptyQuery(); + } + preparedQuery = cmd.queryName || cmd.query; + } catch (e: any) { + return writer.error(e); } - for (const row of results.rows) { - writer.dataRow(results.fields.map((x) => row[x.name])); + return writer.parseComplete(); + case CommandCode.describe: { + if (!prepared) { + return writer.error("no prepared query"); } - log('...complete', qname); - writer.commandComplete(qname); - writer.readyForQuery(); + + sendDescribe(prepared); + return; } - try { - await delay(this.queryLatency ?? 0); - const t = cmd.type; - const cmdName = byCode[t]; - - switch (t) { - case CommandCode.init: - return writer.readyForQuery(); - - case CommandCode.parse: - try { - prepared = mem.prepare(cmd.query); - if (!prepared) { - return writer.emptyQuery(); - } - preparedQuery = cmd.queryName; - } catch (e: any) { - return writer.error(e); - } - return writer.parseComplete(); - case CommandCode.describe: { - if (!prepared) { - return writer.error("no prepared query"); - } + case CommandCode.bind: { + if (!prepared) { + return writer.error("no prepared query"); + } + try { + bound = prepared.bind(cmd.values); + } catch (e: any) { + return writer.error(e); + } + return writer.bindComplete(); + } + case CommandCode.execute: { + if (!bound || !preparedQuery) { + return writer.error("no bound query"); + } + sendResults(bound, preparedQuery); + return; + } + case CommandCode.sync: + prepared = undefined; + preparedQuery = undefined; + bound = undefined; + // writer.readyForQuery(); + return; + case CommandCode.flush: + return; + case CommandCode.query: { + if (!cmd.query) { + return writer.emptyQuery(); + } - sendDescribe(prepared); + // handle transactions + const qlow = cmd.query.trim().toLowerCase(); + switch (qlow) { + case 'begin': + runningTx = mem.db.data.fork(); + writer.commandComplete(qlow.toUpperCase()); + writer.readyForQuery(); return; - } - case CommandCode.bind: { - if (!prepared) { - return writer.error("no prepared query"); - } - try { - bound = prepared.bind(cmd.values); - } catch (e: any) { - return writer.error(e); + case 'commit': + if (!runningTx) { + return writer.error("no transaction to commit"); } - return writer.bindComplete(); - } - case CommandCode.execute: { - if (!bound || !preparedQuery) { - return writer.error("no bound query"); - } - sendResults(bound, preparedQuery); - return; - } - case CommandCode.sync: - prepared = undefined; - preparedQuery = undefined; - bound = undefined; - // writer.readyForQuery(); - return; - case CommandCode.flush: + runningTx.fullCommit(); + runningTx = undefined; + writer.commandComplete(qlow.toUpperCase()); + writer.readyForQuery(); return; - case CommandCode.query: { - if (!cmd.query) { - return writer.emptyQuery(); - } - - // handle transactions - const qlow = cmd.query.trim().toLowerCase(); - switch (qlow) { - case 'begin': - runningTx = mem.db.data.fork(); - writer.commandComplete(qlow.toUpperCase()); - writer.readyForQuery(); - return; - case 'commit': - if (!runningTx) { - return writer.error("no transaction to commit"); - } - runningTx.fullCommit(); - runningTx = undefined; - writer.commandComplete(qlow.toUpperCase()); - writer.readyForQuery(); - return; - case 'rollback': - runningTx = undefined; - writer.commandComplete(qlow.toUpperCase()); - writer.readyForQuery(); - return; - } - - // simple query flow - const prep = mem.prepare(cmd.query); - sendDescribe(prep); - const bound = prep.bind(); - sendResults(bound, cmd.query); + case 'rollback': + runningTx = undefined; + writer.commandComplete(qlow.toUpperCase()); + writer.readyForQuery(); return; - } - default: - return writer.error(`pg-mem does not implement PG command ${cmdName}`); } - } catch (e: any) { - log("🔥 ", e); - writer.error(e); + + // simple query flow + const prep = mem.prepare(cmd.query); + sendDescribe(prep); + const bound = prep.bind(); + sendResults(bound, cmd.query); + return; } - })); + default: + return writer.error(`pg-mem does not implement PG command ${cmdName}`); + } + } catch (e: any) { + log("🔥 ", e); + writer.error(e); } - } - - return (mem: _ISchema, queryLatency?: number) => new Connection(mem, queryLatency).socket; -}); + })); +} \ No newline at end of file diff --git a/src/interfaces.ts b/src/interfaces.ts index b532f5be..bad80e1d 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -228,6 +228,25 @@ export interface LibAdapters { /** Creates a Postres.js `sql` tag bound to this db */ createPostgresJsTag(queryLatency?: number): any; + + /** Binds a server to this instance */ + bindServer(opts?: BindServerOptions): Promise; +} + +export interface BindServerResult { + postgresConnectionString: string; + connectionSettings: { + host: string; + port: number; + }; + close(): void; +} + +export interface BindServerOptions { + /** defaults to a random port */ + port?: number; + /** defaults to 'localhost' */ + host?: string; } export interface SlonikAdapterOptions { diff --git a/src/schema/pg-catalog/index.ts b/src/schema/pg-catalog/index.ts index 0ec735bf..c996f9a6 100644 --- a/src/schema/pg-catalog/index.ts +++ b/src/schema/pg-catalog/index.ts @@ -12,6 +12,8 @@ import { sqlSubstring } from '../../parser/expression-builder'; import { PgDatabaseTable } from './pg-database'; import { registerCommonOperators } from './binary-operators'; import { registerSqlFunctionLanguage } from './sql-function-language'; +import { PgProc } from './pg-proc'; +import { PgStatioUserTables } from './pg_statio_user_tables'; export function setupPgCatalog(db: _IDb) { @@ -53,7 +55,9 @@ export function setupPgCatalog(db: _IDb) { new PgIndexTable(catalog).register(); new PgTypeTable(catalog).register(); new PgRange(catalog).register(); + new PgProc(catalog).register(); new PgDatabaseTable(catalog).register(); + new PgStatioUserTables(catalog).register(); // this is an ugly hack... diff --git a/src/schema/pg-catalog/pg-proc.ts b/src/schema/pg-catalog/pg-proc.ts new file mode 100644 index 00000000..ca584984 --- /dev/null +++ b/src/schema/pg-catalog/pg-proc.ts @@ -0,0 +1,36 @@ +import { _ITable, _ISelection, IValue, _IIndex, _IDb, IndexKey, setId, _ISchema } from '../../interfaces-private'; +import { Schema } from '../../interfaces'; +import { Types } from '../../datatypes'; +import { ReadOnlyTable } from '../readonly-table'; + +// https://www.postgresql.org/docs/13/catalog-pg-range.html +export class PgProc extends ReadOnlyTable implements _ITable { + + + _schema: Schema = { + name: 'pg_range', + fields: [ + { name: 'rngtypid', type: Types.integer } // oid + , { name: 'rngsubtype', type: Types.integer } // oid + , { name: 'rngcollation', type: Types.integer } // oid + , { name: 'rngsubopc', type: Types.integer } // oid + , { name: 'rngcanonical', type: Types.integer } // oid + , { name: 'rngsubdiff', type: Types.integer } // oid + ] + }; + + + entropy(): number { + return 0; + } + + *enumerate() { + } + + + + hasItem(value: any): boolean { + return false; + } + +} diff --git a/src/schema/pg-catalog/pg-range.ts b/src/schema/pg-catalog/pg-range.ts index 5aa4cd06..8bd95f9c 100644 --- a/src/schema/pg-catalog/pg-range.ts +++ b/src/schema/pg-catalog/pg-range.ts @@ -8,14 +8,39 @@ export class PgRange extends ReadOnlyTable implements _ITable { _schema: Schema = { - name: 'pg_range', + name: 'pg_proc', fields: [ - { name: 'rngtypid', type: Types.integer } // oid - , { name: 'rngsubtype', type: Types.integer } // oid - , { name: 'rngcollation', type: Types.integer } // oid - , { name: 'rngsubopc', type: Types.integer } // oid - , { name: 'rngcanonical', type: Types.integer } // oid - , { name: 'rngsubdiff', type: Types.integer } // oid + // types might be wrong + { name: 'oid', type: Types.integer } + , { name: 'proname', type: Types.text() } + , { name: 'pronamespace', type: Types.text() } + , { name: 'pronamespace', type: Types.integer } + , { name: 'proowner', type: Types.integer } + , { name: 'prolang', type: Types.integer } + , { name: 'procost', type: Types.integer } + , { name: 'prorows', type: Types.integer } + , { name: 'provariadic', type: Types.integer } + , { name: 'prosupport', type: Types.text() } + , { name: 'prokind', type: Types.text(1) } + , { name: 'prosecdef', type: Types.bool } + , { name: 'proleakproof', type: Types.bool } + , { name: 'proisstrict', type: Types.bool } + , { name: 'proretset', type: Types.bool } + , { name: 'provolatile', type: Types.text(1) } + , { name: 'pronargs', type: Types.integer } + , { name: 'pronargdefaults', type: Types.integer } + , { name: 'prorettype', type: Types.integer } + , { name: 'proargtypes', type: Types.integer } + , { name: 'proallargtypes', type: Types.integer.asArray() } + , { name: 'proargmodes', type: Types.text().asArray() } + , { name: 'proargnames', type: Types.text().asArray() } + , { name: 'proargdefaults', type: Types.text() } + , { name: 'protrftypes', type: Types.text() } + , { name: 'prosrc', type: Types.text() } + , { name: 'probin', type: Types.text() } + , { name: 'prosqlbody', type: Types.text() } + , { name: 'proconfig', type: Types.text().asArray() } + , { name: 'proacl', type: Types.text().asArray() } ] }; diff --git a/src/schema/pg-catalog/pg_statio_user_tables.ts b/src/schema/pg-catalog/pg_statio_user_tables.ts new file mode 100644 index 00000000..f70cc9b9 --- /dev/null +++ b/src/schema/pg-catalog/pg_statio_user_tables.ts @@ -0,0 +1,58 @@ +import { _ITable, _ISelection, IValue, _IIndex, _IDb, IndexKey, setId, _ISchema } from '../../interfaces-private'; +import { Schema } from '../../interfaces'; +import { Types } from '../../datatypes'; +import { ReadOnlyTable } from '../readonly-table'; + +// https://www.postgresql.org/docs/13/catalog-pg-range.html +const IS_SCHEMA = Symbol('_is_pg_statio_user_tables'); +export class PgStatioUserTables extends ReadOnlyTable implements _ITable { + + + _schema: Schema = { + name: 'pg_statio_user_tables', + fields: [ + { name: 'relid', type: Types.integer } // oid + , { name: 'schemaname', type: Types.text() } + , { name: 'relname', type: Types.text() } + , { name: 'heap_blks_read', type: Types.integer } + , { name: 'heap_blks_hit', type: Types.integer } + , { name: 'idx_blks_read', type: Types.integer } + , { name: 'idx_blks_hit', type: Types.integer } + , { name: 'toast_blks_read', type: Types.integer } + , { name: 'toast_blks_hit', type: Types.integer } + , { name: 'tidx_blks_read', type: Types.integer } + , { name: 'tidx_blks_hit', type: Types.integer } + + ] + }; + + + entropy(): number { + return 0; + } + + *enumerate() { + for (const t of this.db.public.listTables()) { + yield { + relid: t.reg.typeId, + schemaname: 'public', + relname: t.name, + heap_blks_read: 0, + heap_blks_hit: 0, + idx_blks_read: 0, + idx_blks_hit: 0, + toast_blks_read: 0, + toast_blks_hit: 0, + tidx_blks_read: 0, + tidx_blks_hit: 0, + [IS_SCHEMA]: true, + }; + } + } + + + + hasItem(value: any): boolean { + return value[IS_SCHEMA]; + } +} diff --git a/src/schema/prepared-intercepted.ts b/src/schema/prepared-intercepted.ts index 6c8881a2..79e3ac6f 100644 --- a/src/schema/prepared-intercepted.ts +++ b/src/schema/prepared-intercepted.ts @@ -1,13 +1,39 @@ -import { IBoundQuery, IPreparedQuery, QueryDescription, QueryResult } from '../interfaces'; +import { Types } from '../datatypes'; +import { DataType, FieldInfo, IBoundQuery, IPreparedQuery, QueryDescription, QueryResult } from '../interfaces'; +import { _IType } from '../interfaces-private'; +import { nullIsh } from '../utils'; export class InterceptedPreparedQuery implements IPreparedQuery, IBoundQuery { constructor(private command: string, private result: any[]) { } describe(): QueryDescription { + const template = this.result[0]; + const keys = Object.keys(template); + const fields = keys.map((name, index) => ({ + name, + type: DataType.text, + typeId: Types.text().reg.typeId, + index, + })); + const fieldsByKey = Object.fromEntries(fields.map(x => [x.name, x])); + for (const k of keys) { + for (const v of this.result) { + if (nullIsh(v[k])) { + continue; + } + const t = inferType(v[k]); + if (t) { + fieldsByKey[k].type = t.primary; + fieldsByKey[k].typeId = t.reg.typeId; + break; + } + } + } + return { - parameters: [], - result: [], + parameters: [{ type: DataType.text, typeId: Types.text().reg.typeId }], + result: fields, }; } @@ -32,4 +58,28 @@ export class InterceptedPreparedQuery implements IPreparedQuery, IBoundQuery { -} \ No newline at end of file +} + + +function inferType(v: any): _IType | null { + switch (typeof v) { + case 'bigint': + return Types.bigint; + case 'number': + if (Number.isInteger(v)) { + return Types.integer; + } + return Types.float; + case 'string': + return Types.text(); + case 'boolean': + return Types.bool; + case 'object': + if (v instanceof Date) { + return Types.timestamp(); + } + return Types.jsonb; + default: + return null; + } +} diff --git a/src/schema/prepared.ts b/src/schema/prepared.ts index c94ba967..689dddcd 100644 --- a/src/schema/prepared.ts +++ b/src/schema/prepared.ts @@ -58,7 +58,7 @@ const paramsVisitor = astVisitor(() => ({ } })) -function collectParams(stmt: Statement[]): Parameter[] | null { +export function collectParams(stmt: Statement[]): Parameter[] | null { for (const s of stmt) { paramsVisitor.statement(s); } diff --git a/src/tests/postgres.js-real.spec.ts b/src/tests/postgres.js-real.spec.ts index 73e08429..9ed25d01 100644 --- a/src/tests/postgres.js-real.spec.ts +++ b/src/tests/postgres.js-real.spec.ts @@ -92,7 +92,7 @@ describe('Postgres.js', () => { }); - it.only('can have a string argument casted to json', async () => { + it('can have a string argument casted to json', async () => { const nm = 'Charlie'; // argument is a string, but that will be cased to JSON by the query const dat = '{"gender":"unknown"}'; diff --git a/src/tests/union.queries.spec.ts b/src/tests/union.queries.spec.ts index b9444e2b..063f1f5e 100644 --- a/src/tests/union.queries.spec.ts +++ b/src/tests/union.queries.spec.ts @@ -42,7 +42,7 @@ describe('Union', () => { it('cannot cast', () => { expectQueryError(() => many(`select * from (values ('1')) as ta union select * from (values (2)) as tb`) - , /UNION types text and integer cannot be matched/); + , /UNION types text.* and integer.* cannot be matched/); }); it('respects simple union', () => { diff --git a/src/transforms/union.ts b/src/transforms/union.ts index b3749149..e48a2c38 100644 --- a/src/transforms/union.ts +++ b/src/transforms/union.ts @@ -18,7 +18,7 @@ export function buildUnion(left: _ISelection, right: _ISelection) { const type = reconciliateTypes([l, r], true); if (!type) { - throw new QueryError(`UNION types ${l.type.name} and ${r.type.name} cannot be matched`); + throw new QueryError(`UNION types ${l.type.name} (${l.id ?? ''}) and ${r.type.name} (${r.id ?? ''}) cannot be matched`); } cols[i] = { name: l.id ?? ('column' + i), diff --git a/testbind.ts b/testbind.ts new file mode 100644 index 00000000..02d2fda4 --- /dev/null +++ b/testbind.ts @@ -0,0 +1,73 @@ +import { DataType, newDb } from './src'; + +// attempt to make tableplus or dbeaver working on pg-mem +// ...still things failing + +const db = newDb(); + +db.public.query(` + create table users (id serial primary key, name text, is_ok boolean, data jsonb); + insert into users (name, is_ok, data) values + ('Alice', true, '{"gender":"female"}'), + ('Bob', false, null), + ('Anon', null, null); + `); + + +db.public.registerFunction({ + name: 'version', + returns: DataType.text, + implementation: () => `PostgreSQL 16.3 (Debian 16.3-1.pgdg120+1) on aarch64-unknown-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit` +}) + + +db.getSchema('pg_catalog').registerFunction({ + name: 'pg_get_userbyid', + args: [DataType.integer], + returns: DataType.text, + implementation: () => 'pgmem' +}); +db.getSchema('pg_catalog').registerFunction({ + name: 'obj_description', + args: [DataType.integer, DataType.text], + returns: DataType.text, + implementation: () => null, +}); +db.getSchema('pg_catalog').registerFunction({ + name: 'pg_total_relation_size', + args: [DataType.integer], + returns: DataType.integer, + implementation: () => 0, +}); +db.getSchema('pg_catalog').registerFunction({ + name: 'pg_table_size', + args: [DataType.integer], + returns: DataType.integer, + implementation: () => 0, +}); +db.getSchema('pg_catalog').registerFunction({ + name: 'pg_indexes_size', + args: [DataType.integer], + returns: DataType.integer, + implementation: () => 0, +}); +db.getSchema('pg_catalog').registerFunction({ + name: 'pg_get_function_identity_arguments', + args: [DataType.integer], + returns: DataType.text, + implementation: () => 'pgmem' +}); + +db.public.interceptQueries(sql => { + + // see https://github.com/pgjdbc/pgjdbc/blob/fc60537c8e2c40b7da6a952ca2ba4a12f2d5ae86/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java#L820 + // and https://github.com/pgjdbc/pgjdbc/blob/fc60537c8e2c40b7da6a952ca2ba4a12f2d5ae86/pgjdbc/src/main/java/org/postgresql/jdbc/TypeInfoCache.java#L252 + if (sql.includes('current_schemas(')) { + return [{ oid: 603, typname: 'box' }]; + } + return undefined; +}) + +db.adapters.bindServer({ port: 52932 }) + .then(v => console.log('Server at', v)) + .catch(e => console.error('Error', e));