Add more postgres utility wrappers

This commit is contained in:
luaneko 2025-01-12 03:28:07 +11:00
parent a4c0055c79
commit c2ff6b4359
Signed by: luaneko
GPG Key ID: 406809B8763FF07A
2 changed files with 223 additions and 21 deletions

View File

@ -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;

157
wire.ts
View File

@ -545,18 +545,159 @@ export class Wire<V extends WireEvents = WireEvents>
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<ParameterSerializer>`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<RowConstructor>`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}]));`;
})}
}`;