diff --git a/cf/src/connection.js b/cf/src/connection.js index 8e79170a..fcf7423c 100644 --- a/cf/src/connection.js +++ b/cf/src/connection.js @@ -563,7 +563,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose if (needsTypes) { initial.reserve && (initial = null) - return fetchArrayTypes() + return fetchTypes() } initial && !initial.reserve && execute(initial) @@ -659,7 +659,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose name: transform.column.from ? transform.column.from(x.toString('utf8', start, index - 1)) : x.toString('utf8', start, index - 1), - parser: parsers[type], + parser: parsers[type] || parsers[options.shared.typeOidToName[type]], table, number, type @@ -767,26 +767,29 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose backend.secret = x.readUInt32BE(9) } - async function fetchArrayTypes() { + async function fetchTypes() { needsTypes = false const types = await new Query([` - select b.oid, b.typarray - from pg_catalog.pg_type a - left join pg_catalog.pg_type b on b.oid = a.typelem - where a.typcategory = 'A' - group by b.oid, b.typarray - order by b.oid + select oid, typname, typarray + from pg_catalog.pg_type + order by oid `], [], execute) - types.forEach(({ oid, typarray }) => addArrayType(oid, typarray)) + types.forEach(({ oid, typname, typarray }) => { + options.shared.typeNameToOid[typname] = oid + options.shared.typeOidToName[oid] = typname + + if (typarray) addArrayType(oid, typarray) + }) } function addArrayType(oid, typarray) { if (!!options.parsers[typarray] && !!options.serializers[typarray]) return - const parser = options.parsers[oid] + const name = options.shared.typeOidToName[oid] + const parser = options.parsers[oid] || options.parsers[name] options.shared.typeArrayMap[oid] = typarray options.parsers[typarray] = (xs) => arrayParser(xs, parser, typarray) options.parsers[typarray].array = true - options.serializers[typarray] = (xs) => arraySerializer(xs, options.serializers[oid], options, typarray) + options.serializers[typarray] = (xs) => arraySerializer(xs, options.serializers[oid] || options.serializers[name], options, typarray) } function tryNext(x, xs) { @@ -973,7 +976,10 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose function Parse(str, parameters, types, name = '') { b().P().str(name + b.N).str(str + b.N).i16(parameters.length) - parameters.forEach((x, i) => b.i32(types[i] || 0)) + parameters.forEach((x, i) => { + const type = types[i] + b.i32(options.shared.typeNameToOid[type] || type || 0) + }) return b.end() } diff --git a/cf/src/index.js b/cf/src/index.js index ffbe7aef..2f0f75a9 100644 --- a/cf/src/index.js +++ b/cf/src/index.js @@ -496,7 +496,7 @@ function parseOptions(a, b) { socket : o.socket, transform : parseTransform(o.transform || { undefined: undefined }), parameters : {}, - shared : { retries: 0, typeArrayMap: {} }, + shared : { retries: 0, typeArrayMap: {}, typeNameToOid: {}, typeOidToName: {} }, ...mergeUserTypes(o.types) } } diff --git a/cf/src/types.js b/cf/src/types.js index aa2ead29..f2913159 100644 --- a/cf/src/types.js +++ b/cf/src/types.js @@ -87,7 +87,7 @@ export function handleValue(x, parameters, types, options) { return '$' + (types.push( x instanceof Parameter ? (parameters.push(x.value), x.array - ? x.array[x.type || inferType(x.value)] || x.type || firstIsString(x.value) + ? x.array[options.shared.typeNameToOid[x.type] || x.type || inferType(x.value)] || x.type || firstIsString(x.value) : x.type ) : (parameters.push(x), inferType(x)) diff --git a/cjs/src/connection.js b/cjs/src/connection.js index 07f67167..801e3b3f 100644 --- a/cjs/src/connection.js +++ b/cjs/src/connection.js @@ -561,7 +561,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose if (needsTypes) { initial.reserve && (initial = null) - return fetchArrayTypes() + return fetchTypes() } initial && !initial.reserve && execute(initial) @@ -657,7 +657,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose name: transform.column.from ? transform.column.from(x.toString('utf8', start, index - 1)) : x.toString('utf8', start, index - 1), - parser: parsers[type], + parser: parsers[type] || parsers[options.shared.typeOidToName[type]], table, number, type @@ -765,26 +765,29 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose backend.secret = x.readUInt32BE(9) } - async function fetchArrayTypes() { + async function fetchTypes() { needsTypes = false const types = await new Query([` - select b.oid, b.typarray - from pg_catalog.pg_type a - left join pg_catalog.pg_type b on b.oid = a.typelem - where a.typcategory = 'A' - group by b.oid, b.typarray - order by b.oid + select oid, typname, typarray + from pg_catalog.pg_type + order by oid `], [], execute) - types.forEach(({ oid, typarray }) => addArrayType(oid, typarray)) + types.forEach(({ oid, typname, typarray }) => { + options.shared.typeNameToOid[typname] = oid + options.shared.typeOidToName[oid] = typname + + if (typarray) addArrayType(oid, typarray) + }) } function addArrayType(oid, typarray) { if (!!options.parsers[typarray] && !!options.serializers[typarray]) return - const parser = options.parsers[oid] + const name = options.shared.typeOidToName[oid] + const parser = options.parsers[oid] || options.parsers[name] options.shared.typeArrayMap[oid] = typarray options.parsers[typarray] = (xs) => arrayParser(xs, parser, typarray) options.parsers[typarray].array = true - options.serializers[typarray] = (xs) => arraySerializer(xs, options.serializers[oid], options, typarray) + options.serializers[typarray] = (xs) => arraySerializer(xs, options.serializers[oid] || options.serializers[name], options, typarray) } function tryNext(x, xs) { @@ -971,7 +974,10 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose function Parse(str, parameters, types, name = '') { b().P().str(name + b.N).str(str + b.N).i16(parameters.length) - parameters.forEach((x, i) => b.i32(types[i] || 0)) + parameters.forEach((x, i) => { + const type = types[i] + b.i32(options.shared.typeNameToOid[type] || type || 0) + }) return b.end() } diff --git a/cjs/src/index.js b/cjs/src/index.js index f09c61c7..23e9effe 100644 --- a/cjs/src/index.js +++ b/cjs/src/index.js @@ -495,7 +495,7 @@ function parseOptions(a, b) { socket : o.socket, transform : parseTransform(o.transform || { undefined: undefined }), parameters : {}, - shared : { retries: 0, typeArrayMap: {} }, + shared : { retries: 0, typeArrayMap: {}, typeNameToOid: {}, typeOidToName: {} }, ...mergeUserTypes(o.types) } } diff --git a/cjs/src/types.js b/cjs/src/types.js index 0578284c..7209cf1d 100644 --- a/cjs/src/types.js +++ b/cjs/src/types.js @@ -86,7 +86,7 @@ module.exports.handleValue = handleValue;function handleValue(x, parameters, typ return '$' + (types.push( x instanceof Parameter ? (parameters.push(x.value), x.array - ? x.array[x.type || inferType(x.value)] || x.type || firstIsString(x.value) + ? x.array[options.shared.typeNameToOid[x.type] || x.type || inferType(x.value)] || x.type || firstIsString(x.value) : x.type ) : (parameters.push(x), inferType(x)) diff --git a/cjs/tests/index.js b/cjs/tests/index.js index 85d1aa46..c4ac0e41 100644 --- a/cjs/tests/index.js +++ b/cjs/tests/index.js @@ -539,6 +539,24 @@ t('Point type array', async() => { return [30, (await sql`select x from test`)[0].x[1][1], await sql`drop table test`] }) +t('Point type with named OIDs', async() => { + const sql = postgres({ + ...options, + types: { + point: { + to: 'point', + from: ['point'], + serialize: ([x, y]) => '(' + x + ',' + y + ')', + parse: (x) => x.slice(1, -1).split(',').map(x => +x) + } + } + }) + + await sql`create table test (x point)` + await sql`insert into test (x) values (${ sql.types.point([10, 20]) })` + return [20, (await sql`select x from test`)[0].x[1], await sql`drop table test`] +}) + t('sql file', async() => [1, (await sql.file(rel('select.sql')))[0].x] ) diff --git a/deno/src/connection.js b/deno/src/connection.js index 796725de..21112fbe 100644 --- a/deno/src/connection.js +++ b/deno/src/connection.js @@ -564,7 +564,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose if (needsTypes) { initial.reserve && (initial = null) - return fetchArrayTypes() + return fetchTypes() } initial && !initial.reserve && execute(initial) @@ -660,7 +660,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose name: transform.column.from ? transform.column.from(x.toString('utf8', start, index - 1)) : x.toString('utf8', start, index - 1), - parser: parsers[type], + parser: parsers[type] || parsers[options.shared.typeOidToName[type]], table, number, type @@ -768,26 +768,29 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose backend.secret = x.readUInt32BE(9) } - async function fetchArrayTypes() { + async function fetchTypes() { needsTypes = false const types = await new Query([` - select b.oid, b.typarray - from pg_catalog.pg_type a - left join pg_catalog.pg_type b on b.oid = a.typelem - where a.typcategory = 'A' - group by b.oid, b.typarray - order by b.oid + select oid, typname, typarray + from pg_catalog.pg_type + order by oid `], [], execute) - types.forEach(({ oid, typarray }) => addArrayType(oid, typarray)) + types.forEach(({ oid, typname, typarray }) => { + options.shared.typeNameToOid[typname] = oid + options.shared.typeOidToName[oid] = typname + + if (typarray) addArrayType(oid, typarray) + }) } function addArrayType(oid, typarray) { if (!!options.parsers[typarray] && !!options.serializers[typarray]) return - const parser = options.parsers[oid] + const name = options.shared.typeOidToName[oid] + const parser = options.parsers[oid] || options.parsers[name] options.shared.typeArrayMap[oid] = typarray options.parsers[typarray] = (xs) => arrayParser(xs, parser, typarray) options.parsers[typarray].array = true - options.serializers[typarray] = (xs) => arraySerializer(xs, options.serializers[oid], options, typarray) + options.serializers[typarray] = (xs) => arraySerializer(xs, options.serializers[oid] || options.serializers[name], options, typarray) } function tryNext(x, xs) { @@ -974,7 +977,10 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose function Parse(str, parameters, types, name = '') { b().P().str(name + b.N).str(str + b.N).i16(parameters.length) - parameters.forEach((x, i) => b.i32(types[i] || 0)) + parameters.forEach((x, i) => { + const type = types[i] + b.i32(options.shared.typeNameToOid[type] || type || 0) + }) return b.end() } diff --git a/deno/src/index.js b/deno/src/index.js index b6d23db1..e9a7e436 100644 --- a/deno/src/index.js +++ b/deno/src/index.js @@ -496,7 +496,7 @@ function parseOptions(a, b) { socket : o.socket, transform : parseTransform(o.transform || { undefined: undefined }), parameters : {}, - shared : { retries: 0, typeArrayMap: {} }, + shared : { retries: 0, typeArrayMap: {}, typeNameToOid: {}, typeOidToName: {} }, ...mergeUserTypes(o.types) } } diff --git a/deno/src/types.js b/deno/src/types.js index ea0da6a2..c40cca05 100644 --- a/deno/src/types.js +++ b/deno/src/types.js @@ -87,7 +87,7 @@ export function handleValue(x, parameters, types, options) { return '$' + (types.push( x instanceof Parameter ? (parameters.push(x.value), x.array - ? x.array[x.type || inferType(x.value)] || x.type || firstIsString(x.value) + ? x.array[options.shared.typeNameToOid[x.type] || x.type || inferType(x.value)] || x.type || firstIsString(x.value) : x.type ) : (parameters.push(x), inferType(x)) diff --git a/deno/tests/index.js b/deno/tests/index.js index cc2a2518..2b972ce2 100644 --- a/deno/tests/index.js +++ b/deno/tests/index.js @@ -541,6 +541,24 @@ t('Point type array', async() => { return [30, (await sql`select x from test`)[0].x[1][1], await sql`drop table test`] }) +t('Point type with named OIDs', async() => { + const sql = postgres({ + ...options, + types: { + point: { + to: 'point', + from: ['point'], + serialize: ([x, y]) => '(' + x + ',' + y + ')', + parse: (x) => x.slice(1, -1).split(',').map(x => +x) + } + } + }) + + await sql`create table test (x point)` + await sql`insert into test (x) values (${ sql.types.point([10, 20]) })` + return [20, (await sql`select x from test`)[0].x[1], await sql`drop table test`] +}) + t('sql file', async() => [1, (await sql.file(rel('select.sql')))[0].x] ) diff --git a/deno/types/index.d.ts b/deno/types/index.d.ts index c141cfd9..3f739f34 100644 --- a/deno/types/index.d.ts +++ b/deno/types/index.d.ts @@ -319,8 +319,8 @@ declare namespace postgres { const BigInt: PostgresType; interface PostgresType { - to: number; - from: number[]; + to: (number | string); + from: (number | string)[]; serialize: (value: T) => unknown; parse: (raw: any) => T; } @@ -386,8 +386,8 @@ declare namespace postgres { pass: null; /** @inheritdoc */ transform: Transform; - serializers: Record unknown>; - parsers: Record unknown>; + serializers: Record unknown>; + parsers: Record unknown>; } interface Transform { @@ -419,9 +419,9 @@ declare namespace postgres { interface Parameter extends NotAPromise { /** - * PostgreSQL OID of the type + * PostgreSQL OID or type name of the type */ - type: number; + type: number | string; /** * Serialized value */ @@ -684,7 +684,7 @@ declare namespace postgres { options: ParsedOptions; parameters: ConnectionParameters; types: this['typed']; - typed: ((value: T, oid: number) => Parameter) & { + typed: ((value: T, type: number | string) => Parameter) & { [name in keyof TTypes]: (value: TTypes[name]) => postgres.Parameter }; @@ -701,7 +701,7 @@ declare namespace postgres { begin(cb: (sql: TransactionSql) => T | Promise): Promise>; begin(options: string, cb: (sql: TransactionSql) => T | Promise): Promise>; - array[] = SerializableParameter[]>(value: T, type?: number | undefined): ArrayParameter; + array[] = SerializableParameter[]>(value: T, type?: number | string | undefined): ArrayParameter; file(path: string | Buffer | URL | number, options?: { cache?: boolean | undefined } | undefined): PendingQuery; file(path: string | Buffer | URL | number, args: (ParameterOrJSON)[], options?: { cache?: boolean | undefined } | undefined): PendingQuery; json(value: JSONValue): Parameter; diff --git a/src/connection.js b/src/connection.js index 1b1cccde..86d18af9 100644 --- a/src/connection.js +++ b/src/connection.js @@ -561,7 +561,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose if (needsTypes) { initial.reserve && (initial = null) - return fetchArrayTypes() + return fetchTypes() } initial && !initial.reserve && execute(initial) @@ -657,7 +657,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose name: transform.column.from ? transform.column.from(x.toString('utf8', start, index - 1)) : x.toString('utf8', start, index - 1), - parser: parsers[type], + parser: parsers[type] || parsers[options.shared.typeOidToName[type]], table, number, type @@ -765,26 +765,29 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose backend.secret = x.readUInt32BE(9) } - async function fetchArrayTypes() { + async function fetchTypes() { needsTypes = false const types = await new Query([` - select b.oid, b.typarray - from pg_catalog.pg_type a - left join pg_catalog.pg_type b on b.oid = a.typelem - where a.typcategory = 'A' - group by b.oid, b.typarray - order by b.oid + select oid, typname, typarray + from pg_catalog.pg_type + order by oid `], [], execute) - types.forEach(({ oid, typarray }) => addArrayType(oid, typarray)) + types.forEach(({ oid, typname, typarray }) => { + options.shared.typeNameToOid[typname] = oid + options.shared.typeOidToName[oid] = typname + + if (typarray) addArrayType(oid, typarray) + }) } function addArrayType(oid, typarray) { if (!!options.parsers[typarray] && !!options.serializers[typarray]) return - const parser = options.parsers[oid] + const name = options.shared.typeOidToName[oid] + const parser = options.parsers[oid] || options.parsers[name] options.shared.typeArrayMap[oid] = typarray options.parsers[typarray] = (xs) => arrayParser(xs, parser, typarray) options.parsers[typarray].array = true - options.serializers[typarray] = (xs) => arraySerializer(xs, options.serializers[oid], options, typarray) + options.serializers[typarray] = (xs) => arraySerializer(xs, options.serializers[oid] || options.serializers[name], options, typarray) } function tryNext(x, xs) { @@ -971,7 +974,10 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose function Parse(str, parameters, types, name = '') { b().P().str(name + b.N).str(str + b.N).i16(parameters.length) - parameters.forEach((x, i) => b.i32(types[i] || 0)) + parameters.forEach((x, i) => { + const type = types[i] + b.i32(options.shared.typeNameToOid[type] || type || 0) + }) return b.end() } diff --git a/src/index.js b/src/index.js index c7fba3da..bcef3e2f 100644 --- a/src/index.js +++ b/src/index.js @@ -495,7 +495,7 @@ function parseOptions(a, b) { socket : o.socket, transform : parseTransform(o.transform || { undefined: undefined }), parameters : {}, - shared : { retries: 0, typeArrayMap: {} }, + shared : { retries: 0, typeArrayMap: {}, typeNameToOid: {}, typeOidToName: {} }, ...mergeUserTypes(o.types) } } diff --git a/src/types.js b/src/types.js index 7c7c2b93..a26f3166 100644 --- a/src/types.js +++ b/src/types.js @@ -86,7 +86,7 @@ export function handleValue(x, parameters, types, options) { return '$' + (types.push( x instanceof Parameter ? (parameters.push(x.value), x.array - ? x.array[x.type || inferType(x.value)] || x.type || firstIsString(x.value) + ? x.array[options.shared.typeNameToOid[x.type] || x.type || inferType(x.value)] || x.type || firstIsString(x.value) : x.type ) : (parameters.push(x), inferType(x)) diff --git a/tests/index.js b/tests/index.js index 23e6c4d4..ef9a4c49 100644 --- a/tests/index.js +++ b/tests/index.js @@ -539,6 +539,24 @@ t('Point type array', async() => { return [30, (await sql`select x from test`)[0].x[1][1], await sql`drop table test`] }) +t('Point type with named OIDs', async() => { + const sql = postgres({ + ...options, + types: { + point: { + to: 'point', + from: ['point'], + serialize: ([x, y]) => '(' + x + ',' + y + ')', + parse: (x) => x.slice(1, -1).split(',').map(x => +x) + } + } + }) + + await sql`create table test (x point)` + await sql`insert into test (x) values (${ sql.types.point([10, 20]) })` + return [20, (await sql`select x from test`)[0].x[1], await sql`drop table test`] +}) + t('sql file', async() => [1, (await sql.file(rel('select.sql')))[0].x] ) diff --git a/types/index.d.ts b/types/index.d.ts index 4b796799..7f79e28d 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -317,8 +317,8 @@ declare namespace postgres { const BigInt: PostgresType; interface PostgresType { - to: number; - from: number[]; + to: (number | string); + from: (number | string)[]; serialize: (value: T) => unknown; parse: (raw: any) => T; } @@ -384,8 +384,8 @@ declare namespace postgres { pass: null; /** @inheritdoc */ transform: Transform; - serializers: Record unknown>; - parsers: Record unknown>; + serializers: Record unknown>; + parsers: Record unknown>; } interface Transform { @@ -417,9 +417,9 @@ declare namespace postgres { interface Parameter extends NotAPromise { /** - * PostgreSQL OID of the type + * PostgreSQL OID or type name of the type */ - type: number; + type: number | string; /** * Serialized value */ @@ -682,7 +682,7 @@ declare namespace postgres { options: ParsedOptions; parameters: ConnectionParameters; types: this['typed']; - typed: ((value: T, oid: number) => Parameter) & { + typed: ((value: T, type: number | string) => Parameter) & { [name in keyof TTypes]: (value: TTypes[name]) => postgres.Parameter }; @@ -699,7 +699,7 @@ declare namespace postgres { begin(cb: (sql: TransactionSql) => T | Promise): Promise>; begin(options: string, cb: (sql: TransactionSql) => T | Promise): Promise>; - array[] = SerializableParameter[]>(value: T, type?: number | undefined): ArrayParameter; + array[] = SerializableParameter[]>(value: T, type?: number | string | undefined): ArrayParameter; file(path: string | Buffer | URL | number, options?: { cache?: boolean | undefined } | undefined): PendingQuery; file(path: string | Buffer | URL | number, args: (ParameterOrJSON)[], options?: { cache?: boolean | undefined } | undefined): PendingQuery; json(value: JSONValue): Parameter;