diff --git a/cf/src/connection.js b/cf/src/connection.js index 8e79170a..1b768cfc 100644 --- a/cf/src/connection.js +++ b/cf/src/connection.js @@ -958,6 +958,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose return b.i32(0xFFFFFFFF) type = types[i] + x = options.transform.value.to ? options.transform.value.to(x, { type }) : x parameters[i] = x = type in options.serializers ? options.serializers[type](x) : '' + x diff --git a/cf/src/types.js b/cf/src/types.js index aa2ead29..712ee1b4 100644 --- a/cf/src/types.js +++ b/cf/src/types.js @@ -341,7 +341,9 @@ function createJsonTransform(fn) { return typeof x === 'object' && x !== null && (column.type === 114 || column.type === 3802) ? Array.isArray(x) ? x.map(x => jsonTransform(x, column)) - : Object.entries(x).reduce((acc, [k, v]) => Object.assign(acc, { [fn(k)]: jsonTransform(v, column) }), {}) + : Object.getPrototypeOf(x) === Object.prototype || Object.getPrototypeOf(x) === null + ? Object.entries(x).reduce((acc, [k, v]) => Object.assign(acc, { [fn(k)]: jsonTransform(v, column) }), {}) + : x : x } } @@ -349,20 +351,26 @@ function createJsonTransform(fn) { toCamel.column = { from: toCamel } toCamel.value = { from: createJsonTransform(toCamel) } fromCamel.column = { to: fromCamel } +fromCamel.value = { to: createJsonTransform(fromCamel) } export const camel = { ...toCamel } camel.column.to = fromCamel +camel.value.to = fromCamel.value.to toPascal.column = { from: toPascal } toPascal.value = { from: createJsonTransform(toPascal) } fromPascal.column = { to: fromPascal } +fromPascal.value = { to: createJsonTransform(fromPascal) } export const pascal = { ...toPascal } pascal.column.to = fromPascal +pascal.value.to = fromPascal.value.to toKebab.column = { from: toKebab } toKebab.value = { from: createJsonTransform(toKebab) } fromKebab.column = { to: fromKebab } +fromKebab.value = { to: createJsonTransform(fromKebab) } export const kebab = { ...toKebab } kebab.column.to = fromKebab +kebab.value.to = fromKebab.value.to diff --git a/cjs/src/connection.js b/cjs/src/connection.js index 07f67167..4ce3f9b3 100644 --- a/cjs/src/connection.js +++ b/cjs/src/connection.js @@ -956,6 +956,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose return b.i32(0xFFFFFFFF) type = types[i] + x = options.transform.value.to ? options.transform.value.to(x, { type }) : x parameters[i] = x = type in options.serializers ? options.serializers[type](x) : '' + x diff --git a/cjs/src/types.js b/cjs/src/types.js index 0578284c..44a35068 100644 --- a/cjs/src/types.js +++ b/cjs/src/types.js @@ -340,7 +340,9 @@ function createJsonTransform(fn) { return typeof x === 'object' && x !== null && (column.type === 114 || column.type === 3802) ? Array.isArray(x) ? x.map(x => jsonTransform(x, column)) - : Object.entries(x).reduce((acc, [k, v]) => Object.assign(acc, { [fn(k)]: jsonTransform(v, column) }), {}) + : Object.getPrototypeOf(x) === Object.prototype || Object.getPrototypeOf(x) === null + ? Object.entries(x).reduce((acc, [k, v]) => Object.assign(acc, { [fn(k)]: jsonTransform(v, column) }), {}) + : x : x } } @@ -348,20 +350,26 @@ function createJsonTransform(fn) { toCamel.column = { from: toCamel } toCamel.value = { from: createJsonTransform(toCamel) } fromCamel.column = { to: fromCamel } +fromCamel.value = { to: createJsonTransform(fromCamel) } const camel = module.exports.camel = { ...toCamel } camel.column.to = fromCamel +camel.value.to = fromCamel.value.to toPascal.column = { from: toPascal } toPascal.value = { from: createJsonTransform(toPascal) } fromPascal.column = { to: fromPascal } +fromPascal.value = { to: createJsonTransform(fromPascal) } const pascal = module.exports.pascal = { ...toPascal } pascal.column.to = fromPascal +pascal.value.to = fromPascal.value.to toKebab.column = { from: toKebab } toKebab.value = { from: createJsonTransform(toKebab) } fromKebab.column = { to: fromKebab } +fromKebab.value = { to: createJsonTransform(fromKebab) } const kebab = module.exports.kebab = { ...toKebab } kebab.column.to = fromKebab +kebab.value.to = fromKebab.value.to diff --git a/cjs/tests/index.js b/cjs/tests/index.js index 85d1aa46..83970b5d 100644 --- a/cjs/tests/index.js +++ b/cjs/tests/index.js @@ -106,6 +106,77 @@ t('Json', async() => { return ['hello,42', [x.a, x.b].join()] }) +t('Json transform parameter keys', async() => { + const sql = postgres({ + ...options, + transform: postgres.camel + }) + const x = (await sql`select ${ sql.json({ aTest: 1 }) }->>'a_test' as x`)[0].x + return ['1', x] +}) + +t('Json without transform keeps parameter keys', async() => { + const x = (await sql`select ${ sql.json({ aTest: 1 }) }->>'aTest' as x`)[0].x + return ['1', x] +}) + +t('Json transform nested parameter keys', async() => { + const sql = postgres({ + ...options, + transform: postgres.camel + }) + const x = (await sql` + select ${ sql.json({ aTest: [{ bTest: 1 }, { bTest: 2 }] }) }#>>'{a_test,1,b_test}' as x + `)[0].x + return ['2', x] +}) + +t('Json transform typed json parameters', async() => { + const sql = postgres({ + ...options, + transform: postgres.camel + }) + const x = (await sql`select ${ sql.typed({ aTest: 1 }, 114) }->>'a_test' as x`)[0].x + return ['1', x] +}) + +t('Json transform implicit jsonb parameters', async() => { + const sql = postgres({ + ...options, + transform: postgres.camel + }) + const x = (await sql`select ${ { aTest: 1 } }::jsonb->>'a_test' as x`)[0].x + return ['1', x] +}) + +t('Json transform implicit json parameters', async() => { + const sql = postgres({ + ...options, + transform: postgres.camel + }) + const x = (await sql`select ${ { aTest: 1 } }::json->>'a_test' as x`)[0].x + return ['1', x] +}) + +t('Json transform result keys', async() => { + const sql = postgres({ + ...options, + transform: postgres.camel + }) + const x = (await sql`select '{"a_test":1}'::jsonb as x`)[0].x + return [1, x.aTest] +}) + +t('Json transform does not transform parameter values with .toJSON()', async() => { + const now = new Date() + const sql = postgres({ + ...options, + transform: postgres.camel + }) + const x = (await sql`select ${ sql.json({ aTest: now }) }->>'a_test' as x`)[0].x + return [now.toJSON(), x] +}) + t('implicit json', async() => { const x = (await sql`select ${ { a: 'hello', b: 42 } }::json as x`)[0].x return ['hello,42', [x.a, x.b].join()] diff --git a/deno/src/connection.js b/deno/src/connection.js index 796725de..bce667e3 100644 --- a/deno/src/connection.js +++ b/deno/src/connection.js @@ -959,6 +959,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose return b.i32(0xFFFFFFFF) type = types[i] + x = options.transform.value.to ? options.transform.value.to(x, { type }) : x parameters[i] = x = type in options.serializers ? options.serializers[type](x) : '' + x diff --git a/deno/src/types.js b/deno/src/types.js index ea0da6a2..6e57930b 100644 --- a/deno/src/types.js +++ b/deno/src/types.js @@ -341,7 +341,9 @@ function createJsonTransform(fn) { return typeof x === 'object' && x !== null && (column.type === 114 || column.type === 3802) ? Array.isArray(x) ? x.map(x => jsonTransform(x, column)) - : Object.entries(x).reduce((acc, [k, v]) => Object.assign(acc, { [fn(k)]: jsonTransform(v, column) }), {}) + : Object.getPrototypeOf(x) === Object.prototype || Object.getPrototypeOf(x) === null + ? Object.entries(x).reduce((acc, [k, v]) => Object.assign(acc, { [fn(k)]: jsonTransform(v, column) }), {}) + : x : x } } @@ -349,20 +351,26 @@ function createJsonTransform(fn) { toCamel.column = { from: toCamel } toCamel.value = { from: createJsonTransform(toCamel) } fromCamel.column = { to: fromCamel } +fromCamel.value = { to: createJsonTransform(fromCamel) } export const camel = { ...toCamel } camel.column.to = fromCamel +camel.value.to = fromCamel.value.to toPascal.column = { from: toPascal } toPascal.value = { from: createJsonTransform(toPascal) } fromPascal.column = { to: fromPascal } +fromPascal.value = { to: createJsonTransform(fromPascal) } export const pascal = { ...toPascal } pascal.column.to = fromPascal +pascal.value.to = fromPascal.value.to toKebab.column = { from: toKebab } toKebab.value = { from: createJsonTransform(toKebab) } fromKebab.column = { to: fromKebab } +fromKebab.value = { to: createJsonTransform(fromKebab) } export const kebab = { ...toKebab } kebab.column.to = fromKebab +kebab.value.to = fromKebab.value.to diff --git a/deno/tests/index.js b/deno/tests/index.js index cc2a2518..4542db42 100644 --- a/deno/tests/index.js +++ b/deno/tests/index.js @@ -108,6 +108,77 @@ t('Json', async() => { return ['hello,42', [x.a, x.b].join()] }) +t('Json transform parameter keys', async() => { + const sql = postgres({ + ...options, + transform: postgres.camel + }) + const x = (await sql`select ${ sql.json({ aTest: 1 }) }->>'a_test' as x`)[0].x + return ['1', x] +}) + +t('Json without transform keeps parameter keys', async() => { + const x = (await sql`select ${ sql.json({ aTest: 1 }) }->>'aTest' as x`)[0].x + return ['1', x] +}) + +t('Json transform nested parameter keys', async() => { + const sql = postgres({ + ...options, + transform: postgres.camel + }) + const x = (await sql` + select ${ sql.json({ aTest: [{ bTest: 1 }, { bTest: 2 }] }) }#>>'{a_test,1,b_test}' as x + `)[0].x + return ['2', x] +}) + +t('Json transform typed json parameters', async() => { + const sql = postgres({ + ...options, + transform: postgres.camel + }) + const x = (await sql`select ${ sql.typed({ aTest: 1 }, 114) }->>'a_test' as x`)[0].x + return ['1', x] +}) + +t('Json transform implicit jsonb parameters', async() => { + const sql = postgres({ + ...options, + transform: postgres.camel + }) + const x = (await sql`select ${ { aTest: 1 } }::jsonb->>'a_test' as x`)[0].x + return ['1', x] +}) + +t('Json transform implicit json parameters', async() => { + const sql = postgres({ + ...options, + transform: postgres.camel + }) + const x = (await sql`select ${ { aTest: 1 } }::json->>'a_test' as x`)[0].x + return ['1', x] +}) + +t('Json transform result keys', async() => { + const sql = postgres({ + ...options, + transform: postgres.camel + }) + const x = (await sql`select '{"a_test":1}'::jsonb as x`)[0].x + return [1, x.aTest] +}) + +t('Json transform does not transform parameter values with .toJSON()', async() => { + const now = new Date() + const sql = postgres({ + ...options, + transform: postgres.camel + }) + const x = (await sql`select ${ sql.json({ aTest: now }) }->>'a_test' as x`)[0].x + return [now.toJSON(), x] +}) + t('implicit json', async() => { const x = (await sql`select ${ { a: 'hello', b: 42 } }::json as x`)[0].x return ['hello,42', [x.a, x.b].join()] diff --git a/deno/types/index.d.ts b/deno/types/index.d.ts index 85112eda..36613696 100644 --- a/deno/types/index.d.ts +++ b/deno/types/index.d.ts @@ -236,7 +236,7 @@ declare namespace postgres { function toPascal(str: string): string; namespace toPascal { namespace column { function from(str: string): string; } - namespace value { function from(str: unknown, column: Column): string } + namespace value { function from(str: unknown, column: Column): unknown } } /** * Convert a PascalCase string to snake_case. @@ -246,6 +246,7 @@ declare namespace postgres { function fromPascal(str: string): string; namespace fromPascal { namespace column { function to(str: string): string } + namespace value { function to(str: unknown, column: Column): unknown } } /** * Convert snake_case to and from PascalCase. @@ -255,7 +256,10 @@ declare namespace postgres { function from(str: string): string; function to(str: string): string; } - namespace value { function from(str: unknown, column: Column): string } + namespace value { + function from(str: unknown, column: Column): unknown; + function to(str: unknown, column: Column): unknown; + } } /** * Convert a snake_case string to camelCase. @@ -265,7 +269,7 @@ declare namespace postgres { function toCamel(str: string): string; namespace toCamel { namespace column { function from(str: string): string; } - namespace value { function from(str: unknown, column: Column): string } + namespace value { function from(str: unknown, column: Column): unknown } } /** * Convert a camelCase string to snake_case. @@ -275,6 +279,7 @@ declare namespace postgres { function fromCamel(str: string): string; namespace fromCamel { namespace column { function to(str: string): string } + namespace value { function to(str: unknown, column: Column): unknown } } /** * Convert snake_case to and from camelCase. @@ -284,7 +289,10 @@ declare namespace postgres { function from(str: string): string; function to(str: string): string; } - namespace value { function from(str: unknown, column: Column): string } + namespace value { + function from(str: unknown, column: Column): unknown; + function to(str: unknown, column: Column): unknown; + } } /** * Convert a snake_case string to kebab-case. @@ -294,7 +302,7 @@ declare namespace postgres { function toKebab(str: string): string; namespace toKebab { namespace column { function from(str: string): string; } - namespace value { function from(str: unknown, column: Column): string } + namespace value { function from(str: unknown, column: Column): unknown } } /** * Convert a kebab-case string to snake_case. @@ -304,6 +312,7 @@ declare namespace postgres { function fromKebab(str: string): string; namespace fromKebab { namespace column { function to(str: string): string } + namespace value { function to(str: unknown, column: Column): unknown } } /** * Convert snake_case to and from kebab-case. @@ -313,7 +322,10 @@ declare namespace postgres { function from(str: string): string; function to(str: string): string; } - namespace value { function from(str: unknown, column: Column): string } + namespace value { + function from(str: unknown, column: Column): unknown; + function to(str: unknown, column: Column): unknown; + } } const BigInt: PostgresType; @@ -404,7 +416,7 @@ declare namespace postgres { /** Transform function for values in result rows */ from: ((value: any, column?: Column) => any) | undefined; /** Transform function for interpolated values passed to tagged template literal */ - to: undefined; // (value: any) => any + to: ((value: any, column?: Column) => any) | undefined; }; row: { /** Transform function for entire result rows */ diff --git a/src/connection.js b/src/connection.js index 1b1cccde..0513ce4e 100644 --- a/src/connection.js +++ b/src/connection.js @@ -956,6 +956,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose return b.i32(0xFFFFFFFF) type = types[i] + x = options.transform.value.to ? options.transform.value.to(x, { type }) : x parameters[i] = x = type in options.serializers ? options.serializers[type](x) : '' + x diff --git a/src/types.js b/src/types.js index 7c7c2b93..58ce74cb 100644 --- a/src/types.js +++ b/src/types.js @@ -340,7 +340,9 @@ function createJsonTransform(fn) { return typeof x === 'object' && x !== null && (column.type === 114 || column.type === 3802) ? Array.isArray(x) ? x.map(x => jsonTransform(x, column)) - : Object.entries(x).reduce((acc, [k, v]) => Object.assign(acc, { [fn(k)]: jsonTransform(v, column) }), {}) + : Object.getPrototypeOf(x) === Object.prototype || Object.getPrototypeOf(x) === null + ? Object.entries(x).reduce((acc, [k, v]) => Object.assign(acc, { [fn(k)]: jsonTransform(v, column) }), {}) + : x : x } } @@ -348,20 +350,26 @@ function createJsonTransform(fn) { toCamel.column = { from: toCamel } toCamel.value = { from: createJsonTransform(toCamel) } fromCamel.column = { to: fromCamel } +fromCamel.value = { to: createJsonTransform(fromCamel) } export const camel = { ...toCamel } camel.column.to = fromCamel +camel.value.to = fromCamel.value.to toPascal.column = { from: toPascal } toPascal.value = { from: createJsonTransform(toPascal) } fromPascal.column = { to: fromPascal } +fromPascal.value = { to: createJsonTransform(fromPascal) } export const pascal = { ...toPascal } pascal.column.to = fromPascal +pascal.value.to = fromPascal.value.to toKebab.column = { from: toKebab } toKebab.value = { from: createJsonTransform(toKebab) } fromKebab.column = { to: fromKebab } +fromKebab.value = { to: createJsonTransform(fromKebab) } export const kebab = { ...toKebab } kebab.column.to = fromKebab +kebab.value.to = fromKebab.value.to diff --git a/tests/index.js b/tests/index.js index 23e6c4d4..55b54ad7 100644 --- a/tests/index.js +++ b/tests/index.js @@ -106,6 +106,77 @@ t('Json', async() => { return ['hello,42', [x.a, x.b].join()] }) +t('Json transform parameter keys', async() => { + const sql = postgres({ + ...options, + transform: postgres.camel + }) + const x = (await sql`select ${ sql.json({ aTest: 1 }) }->>'a_test' as x`)[0].x + return ['1', x] +}) + +t('Json without transform keeps parameter keys', async() => { + const x = (await sql`select ${ sql.json({ aTest: 1 }) }->>'aTest' as x`)[0].x + return ['1', x] +}) + +t('Json transform nested parameter keys', async() => { + const sql = postgres({ + ...options, + transform: postgres.camel + }) + const x = (await sql` + select ${ sql.json({ aTest: [{ bTest: 1 }, { bTest: 2 }] }) }#>>'{a_test,1,b_test}' as x + `)[0].x + return ['2', x] +}) + +t('Json transform typed json parameters', async() => { + const sql = postgres({ + ...options, + transform: postgres.camel + }) + const x = (await sql`select ${ sql.typed({ aTest: 1 }, 114) }->>'a_test' as x`)[0].x + return ['1', x] +}) + +t('Json transform implicit jsonb parameters', async() => { + const sql = postgres({ + ...options, + transform: postgres.camel + }) + const x = (await sql`select ${ { aTest: 1 } }::jsonb->>'a_test' as x`)[0].x + return ['1', x] +}) + +t('Json transform implicit json parameters', async() => { + const sql = postgres({ + ...options, + transform: postgres.camel + }) + const x = (await sql`select ${ { aTest: 1 } }::json->>'a_test' as x`)[0].x + return ['1', x] +}) + +t('Json transform result keys', async() => { + const sql = postgres({ + ...options, + transform: postgres.camel + }) + const x = (await sql`select '{"a_test":1}'::jsonb as x`)[0].x + return [1, x.aTest] +}) + +t('Json transform does not transform parameter values with .toJSON()', async() => { + const now = new Date() + const sql = postgres({ + ...options, + transform: postgres.camel + }) + const x = (await sql`select ${ sql.json({ aTest: now }) }->>'a_test' as x`)[0].x + return [now.toJSON(), x] +}) + t('implicit json', async() => { const x = (await sql`select ${ { a: 'hello', b: 42 } }::json as x`)[0].x return ['hello,42', [x.a, x.b].join()] diff --git a/types/index.d.ts b/types/index.d.ts index 13c3432f..c165c452 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -234,7 +234,7 @@ declare namespace postgres { function toPascal(str: string): string; namespace toPascal { namespace column { function from(str: string): string; } - namespace value { function from(str: unknown, column: Column): string } + namespace value { function from(str: unknown, column: Column): unknown } } /** * Convert a PascalCase string to snake_case. @@ -244,6 +244,7 @@ declare namespace postgres { function fromPascal(str: string): string; namespace fromPascal { namespace column { function to(str: string): string } + namespace value { function to(str: unknown, column: Column): unknown } } /** * Convert snake_case to and from PascalCase. @@ -253,7 +254,10 @@ declare namespace postgres { function from(str: string): string; function to(str: string): string; } - namespace value { function from(str: unknown, column: Column): string } + namespace value { + function from(str: unknown, column: Column): unknown; + function to(str: unknown, column: Column): unknown; + } } /** * Convert a snake_case string to camelCase. @@ -263,7 +267,7 @@ declare namespace postgres { function toCamel(str: string): string; namespace toCamel { namespace column { function from(str: string): string; } - namespace value { function from(str: unknown, column: Column): string } + namespace value { function from(str: unknown, column: Column): unknown } } /** * Convert a camelCase string to snake_case. @@ -273,6 +277,7 @@ declare namespace postgres { function fromCamel(str: string): string; namespace fromCamel { namespace column { function to(str: string): string } + namespace value { function to(str: unknown, column: Column): unknown } } /** * Convert snake_case to and from camelCase. @@ -282,7 +287,10 @@ declare namespace postgres { function from(str: string): string; function to(str: string): string; } - namespace value { function from(str: unknown, column: Column): string } + namespace value { + function from(str: unknown, column: Column): unknown; + function to(str: unknown, column: Column): unknown; + } } /** * Convert a snake_case string to kebab-case. @@ -292,7 +300,7 @@ declare namespace postgres { function toKebab(str: string): string; namespace toKebab { namespace column { function from(str: string): string; } - namespace value { function from(str: unknown, column: Column): string } + namespace value { function from(str: unknown, column: Column): unknown } } /** * Convert a kebab-case string to snake_case. @@ -302,6 +310,7 @@ declare namespace postgres { function fromKebab(str: string): string; namespace fromKebab { namespace column { function to(str: string): string } + namespace value { function to(str: unknown, column: Column): unknown } } /** * Convert snake_case to and from kebab-case. @@ -311,7 +320,10 @@ declare namespace postgres { function from(str: string): string; function to(str: string): string; } - namespace value { function from(str: unknown, column: Column): string } + namespace value { + function from(str: unknown, column: Column): unknown; + function to(str: unknown, column: Column): unknown; + } } const BigInt: PostgresType; @@ -402,7 +414,7 @@ declare namespace postgres { /** Transform function for values in result rows */ from: ((value: any, column?: Column) => any) | undefined; /** Transform function for interpolated values passed to tagged template literal */ - to: undefined; // (value: any) => any + to: ((value: any, column?: Column) => any) | undefined; }; row: { /** Transform function for entire result rows */