diff --git a/query.ts b/query.ts index 15e47a6..b35fccf 100644 --- a/query.ts +++ b/query.ts @@ -168,6 +168,23 @@ export const text: SqlType = { }, }; +export const char: SqlType = { + input(c) { + const n = c.charCodeAt(0); + if (c.length === 1 && 0 <= n && n <= 255) return c; + throw new SqlTypeError(`invalid char input '${c}'`); + }, + output(x) { + let c: string; + if (typeof x === "undefined" || x === null) return null; + else if (typeof x === "number") c = String.fromCharCode(x); + else c = String(x); + const n = c.charCodeAt(0); + if (c.length === 1 && 0 <= n && n <= 255) return c; + else throw new SqlTypeError(`invalid char output '${x}'`); + }, +}; + export const int2: SqlType = { input(s) { const n = Number(s); @@ -201,6 +218,22 @@ export const int4: SqlType = { }, }; +export const uint4: SqlType = { + input(s) { + const n = Number(s); + if (Number.isInteger(n) && 0 <= n && n <= 4294967295) return n; + else throw new SqlTypeError(`invalid uint4 input '${s}'`); + }, + output(x) { + let n: number; + if (typeof x === "undefined" || x === null) return null; + else if (typeof x === "number") n = x; + else n = Number(x); + if (Number.isInteger(n) && 0 <= n && n <= 4294967295) return n.toString(); + else throw new SqlTypeError(`invalid uint4 output '${x}'`); + }, +}; + export const int8: SqlType = { input(s) { const n = BigInt(s); @@ -214,14 +247,36 @@ export const int8: SqlType = { else if (typeof x === "number" || typeof x === "bigint") n = x; else if (typeof x === "string") n = BigInt(x); else n = Number(x); - if (Number.isInteger(n)) { - if (-9007199254740991 <= n && n <= 9007199254740991) return n.toString(); - else throw new SqlTypeError(`unsafe int8 output '${x}'`); - } else if (typeof n === "bigint") { - if (-9223372036854775808n <= n && n <= 9223372036854775807n) - return n.toString(); - } - throw new SqlTypeError(`invalid int8 output '${x}'`); + if ( + (typeof n === "number" && Number.isSafeInteger(n)) || + (typeof n === "bigint" && + -9223372036854775808n <= n && + n <= 9223372036854775807n) + ) { + return n.toString(); + } else throw new SqlTypeError(`invalid int8 output '${x}'`); + }, +}; + +export const uint8: SqlType = { + input(s) { + const n = BigInt(s); + if (0n <= n && n <= 9007199254740991n) return Number(n); + else if (0n <= n && n <= 18446744073709551615n) return n; + else throw new SqlTypeError(`invalid uint8 input '${s}'`); + }, + output(x) { + let n: number | bigint; + if (typeof x === "undefined" || x === null) return null; + else if (typeof x === "number" || typeof x === "bigint") n = x; + else if (typeof x === "string") n = BigInt(x); + else n = Number(x); + if ( + (typeof n === "number" && Number.isSafeInteger(n) && 0 <= n) || + (typeof n === "bigint" && 0n <= n && n <= 18446744073709551615n) + ) { + return n.toString(); + } else throw new SqlTypeError(`invalid uint8 output '${x}'`); }, }; @@ -305,20 +360,26 @@ export const json: SqlType = { }; export const sql_types: SqlTypeMap = { + 0: text, 16: bool, // bool - 25: text, // text + 17: bytea, // bytea + 18: char, // char + 19: text, // name + 20: int8, // int8 21: int2, // int2 23: int4, // int4 - 20: int8, // int8 - 26: int8, // oid + 25: text, // text + 26: uint4, // oid + 28: uint4, // xid + 29: uint4, // cid + 114: json, // json 700: float4, // float4 701: float8, // float8 1082: timestamptz, // date 1114: timestamptz, // timestamp 1184: timestamptz, // timestamptz - 17: bytea, // bytea - 114: json, // json 3802: json, // jsonb + 5069: uint8, // xid8 }; sql.types = sql_types; diff --git a/wire.ts b/wire.ts index 9cd34c9..9939563 100644 --- a/wire.ts +++ b/wire.ts @@ -545,18 +545,159 @@ export class Wire return this.#notify(channel, payload); } - async get(param: string) { - return await this.query`select current_setting(${param}, true)` - .map(([s]) => String(s)) + async current_setting(name: string) { + return await this.query<[string]>`select current_setting(${name}, true)` + .map(([x]) => x) .first_or(null); } - async set(param: string, value: string, local = false) { - return await this.query`select set_config(${param}, ${value}, ${local})` - .map(([s]) => String(s)) + async set_config(name: string, value: string, local = false) { + return await this.query< + [string] + >`select set_config(${name}, ${value}, ${local})` + .map(([x]) => x) .first(); } + async cancel_backend(pid: number) { + return await this.query<[boolean]>`select pg_cancel_backend(${pid})` + .map(([x]) => x) + .first(); + } + + async terminate_backend(pid: number, timeout = 0) { + return await this.query< + [boolean] + >`select pg_terminate_backend(${pid}, ${timeout})` + .map(([x]) => x) + .first(); + } + + async inet() { + return await this.query<{ + client_addr: string; + client_port: number; + server_addr: string; + server_port: number; + }>` + select + inet_client_addr() as client_addr, + inet_client_port() as client_port, + inet_server_addr() as server_addr, + inet_server_port() as server_por + `.first(); + } + + async listening_channels() { + return await this.query<[string]>`select pg_listening_channels()` + .map(([x]) => x) + .collect(); + } + + async notification_queue_usage() { + return await this.query<[number]>`select pg_notification_queue_usage()` + .map(([x]) => x) + .first(); + } + + async postmaster_start_time() { + return await this.query<[Date]>`select pg_postmaster_start_time()` + .map(([x]) => x) + .first(); + } + + async current_wal() { + return await this.query<{ + lsn: string; + insert_lsn: string; + flush_lsn: string; + }>` + select + pg_current_wal_lsn() as lsn, + pg_current_wal_insert_lsn() as insert_lsn, + pg_current_wal_flush_lsn() as flush_lsn + `.first(); + } + + async switch_wal() { + return await this.query<[string]>`select pg_switch_wal()` + .map(([x]) => x) + .first(); + } + + async nextval(seq: string) { + return await this.query<[number | bigint]>`select nextval(${seq})` + .map(([x]) => x) + .first(); + } + + async setval(seq: string, value: number | bigint, is_called = true) { + return await this.query< + [number | bigint] + >`select setval(${seq}, ${value}, ${is_called})` + .map(([x]) => x) + .first(); + } + + async currval(seq: string) { + return await this.query<[number]>`select currval(${seq})` + .map(([x]) => x) + .first(); + } + + async lastval() { + return await this.query<[number]>`select lastval()`.map(([x]) => x).first(); + } + + async validate_input(s: string, type: string) { + return await this.query<{ + message: string | null; + detail: string | null; + hint: string | null; + sql_error_code: string | null; + }>`select * from pg_input_error_info(${s}, ${type})`.first(); + } + + async current_xid() { + return await this.query<[number | bigint]>`select pg_current_xact_id()` + .map(([x]) => x) + .first(); + } + + async current_xid_if_assigned() { + return await this.query< + [number | bigint | null] + >`select pg_current_xact_id_if_assigned()` + .map(([x]) => x) + .first(); + } + + async xact_info(xid: number | bigint) { + return await this.query<{ + status: "progress" | "committed" | "aborted"; + age: number; + mxid_age: number; + }>` + select + pg_xact_status(${xid}) as status, + age(${xid}) as age, + mxid_age(${xid}) as mxid_age + `; + } + + async version() { + return await this.query<{ + postgres: string; + unicode: string; + icu_unicode: string | null; + }>` + select + version() as postgres, + unicode_version() as unicode, + icu_unicode_version() as icu_unicode + `.first(); + } + close(reason?: unknown) { this.#close(reason); } @@ -1017,7 +1158,7 @@ function wire_impl( return jit.compiled`function ser_params(xs) { return [ ${jit.map(", ", param_types, (type_oid, i) => { - const type = types[type_oid] ?? text; + const type = types[type_oid] ?? types[0] ?? text; return jit`${type}.output(xs[${i}])`; })} ]; @@ -1034,7 +1175,7 @@ function wire_impl( function make_row_ctor({ columns }: RowDescription) { const Row = jit.compiled`function Row(xs) { ${jit.map(" ", columns, ({ name, type_oid }, i) => { - const type = types[type_oid] ?? text; + const type = types[type_oid] ?? types[0] ?? text; return jit`this[${name}] = xs[${i}] === null ? null : ${type}.input(${from_utf8}(xs[${i}]));`; })} }`;