diff --git a/src/migration-builder.ts b/src/migration-builder.ts index 0ff8115a..c39bec0f 100644 --- a/src/migration-builder.ts +++ b/src/migration-builder.ts @@ -341,8 +341,8 @@ export default class MigrationBuilderImpl implements MigrationBuilder { const options: MigrationOptions = { typeShorthands, - schemalize: createSchemalize(shouldDecamelize, false), - literal: createSchemalize(shouldDecamelize, true), + schemalize: createSchemalize({ shouldDecamelize, shouldQuote: false }), + literal: createSchemalize({ shouldDecamelize, shouldQuote: true }), logger, }; diff --git a/src/runner.ts b/src/runner.ts index 35e8e882..d17cf7a1 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -97,10 +97,10 @@ const ensureMigrationsTable = async ( try { const schema = getMigrationTableSchema(options); const { migrationsTable } = options; - const fullTableName = createSchemalize( - Boolean(options.decamelize), - true - )({ + const fullTableName = createSchemalize({ + shouldDecamelize: Boolean(options.decamelize), + shouldQuote: true, + })({ schema, name: migrationsTable, }); @@ -133,10 +133,10 @@ const ensureMigrationsTable = async ( const getRunMigrations = async (db: DBConnection, options: RunnerOption) => { const schema = getMigrationTableSchema(options); const { migrationsTable } = options; - const fullTableName = createSchemalize( - Boolean(options.decamelize), - true - )({ + const fullTableName = createSchemalize({ + shouldDecamelize: Boolean(options.decamelize), + shouldQuote: true, + })({ schema, name: migrationsTable, }); diff --git a/src/utils/createSchemalize.ts b/src/utils/createSchemalize.ts index cc23793a..3aa725aa 100644 --- a/src/utils/createSchemalize.ts +++ b/src/utils/createSchemalize.ts @@ -2,21 +2,44 @@ import decamelize from 'decamelize'; import { identity, quote } from '.'; import type { Name } from '../operations/generalTypes'; +/** @deprecated Use createSchemalize(options) instead. */ export function createSchemalize( shouldDecamelize: boolean, shouldQuote: boolean -): (v: Name) => string { +): (value: Name) => string; +export function createSchemalize(options: { + shouldDecamelize: boolean; + shouldQuote: boolean; +}): (value: Name) => string; +export function createSchemalize( + options: boolean | { shouldDecamelize: boolean; shouldQuote: boolean }, + _legacyShouldQuote?: boolean +): (value: Name) => string { + const { shouldDecamelize, shouldQuote } = + typeof options === 'boolean' + ? { + shouldDecamelize: options, + shouldQuote: _legacyShouldQuote, + } + : options; + + if (typeof options === 'boolean') { + console.warn( + 'createSchemalize(shouldDecamelize, shouldQuote) is deprecated. Use createSchemalize({ shouldDecamelize, shouldQuote }) instead.' + ); + } + const transform = [ shouldDecamelize ? decamelize : identity, shouldQuote ? quote : identity, - ].reduce((acc, fn) => (fn === identity ? acc : (x: string) => acc(fn(x)))); + ].reduce((acc, fn) => (fn === identity ? acc : (str) => acc(fn(str)))); - return (v) => { - if (typeof v === 'object') { - const { schema, name } = v; + return (value) => { + if (typeof value === 'object') { + const { schema, name } = value; return (schema ? `${transform(schema)}.` : '') + transform(name); } - return transform(v); + return transform(value); }; } diff --git a/test/presetMigrationOptions.ts b/test/presetMigrationOptions.ts index edf72c03..12b43f1d 100644 --- a/test/presetMigrationOptions.ts +++ b/test/presetMigrationOptions.ts @@ -3,14 +3,14 @@ import { createSchemalize } from '../src/utils'; export const options1: MigrationOptions = { typeShorthands: {}, - schemalize: createSchemalize(false, false), - literal: createSchemalize(false, true), + schemalize: createSchemalize({ shouldDecamelize: false, shouldQuote: false }), + literal: createSchemalize({ shouldDecamelize: false, shouldQuote: true }), logger: console, }; export const options2: MigrationOptions = { typeShorthands: {}, - schemalize: createSchemalize(true, false), - literal: createSchemalize(true, true), + schemalize: createSchemalize({ shouldDecamelize: true, shouldQuote: false }), + literal: createSchemalize({ shouldDecamelize: true, shouldQuote: true }), logger: console, }; diff --git a/test/utils/createSchemalize.spec.ts b/test/utils/createSchemalize.spec.ts new file mode 100644 index 00000000..50b2f386 --- /dev/null +++ b/test/utils/createSchemalize.spec.ts @@ -0,0 +1,271 @@ +import { describe, expect, it } from 'vitest'; +import { createSchemalize } from '../../src/utils'; + +describe('utils', () => { + describe('createSchemalize', () => { + it.each([ + [true, true], + [true, false], + [false, true], + [false, false], + ])('should return a function', (shouldDecamelize, shouldQuote) => { + const actual = createSchemalize({ + shouldDecamelize, + shouldQuote, + }); + + expect(actual).toBeTypeOf('function'); + }); + + it('should decamelize and quote for string', () => { + const schemalize = createSchemalize({ + shouldDecamelize: true, + shouldQuote: true, + }); + + const actual = schemalize('myTable'); + + expect(actual).toBeTypeOf('string'); + expect(actual).toBe('"my_table"'); + }); + + it('should decamelize and quote for schema', () => { + const schemalize = createSchemalize({ + shouldDecamelize: true, + shouldQuote: true, + }); + + const actual = schemalize({ schema: 'mySchema', name: 'myTable' }); + + expect(actual).toBeTypeOf('string'); + expect(actual).toBe('"my_schema"."my_table"'); + }); + + it('should only decamelize for string', () => { + const schemalize = createSchemalize({ + shouldDecamelize: true, + shouldQuote: false, + }); + + const actual = schemalize('myTable'); + + expect(actual).toBeTypeOf('string'); + expect(actual).toBe('my_table'); + }); + + it('should only decamelize for schema', () => { + const schemalize = createSchemalize({ + shouldDecamelize: true, + shouldQuote: false, + }); + + const actual = schemalize({ schema: 'mySchema', name: 'myTable' }); + + expect(actual).toBeTypeOf('string'); + expect(actual).toBe('my_schema.my_table'); + }); + + it('should only quote for string', () => { + const schemalize = createSchemalize({ + shouldDecamelize: false, + shouldQuote: true, + }); + + const actual = schemalize('myTable'); + + expect(actual).toBeTypeOf('string'); + expect(actual).toBe('"myTable"'); + }); + + it('should only quote for schema', () => { + const schemalize = createSchemalize({ + shouldDecamelize: false, + shouldQuote: true, + }); + + const actual = schemalize({ schema: 'mySchema', name: 'myTable' }); + + expect(actual).toBeTypeOf('string'); + expect(actual).toBe('"mySchema"."myTable"'); + }); + + it('should not decamelize and quote for string', () => { + const schemalize = createSchemalize({ + shouldDecamelize: false, + shouldQuote: false, + }); + + const actual = schemalize('myTable'); + + expect(actual).toBeTypeOf('string'); + expect(actual).toBe('myTable'); + }); + + it('should not decamelize and quote for schema', () => { + const schemalize = createSchemalize({ + shouldDecamelize: false, + shouldQuote: false, + }); + + const actual = schemalize({ schema: 'mySchema', name: 'myTable' }); + + expect(actual).toBeTypeOf('string'); + expect(actual).toBe('mySchema.myTable'); + }); + + // TODO @Shinigami92 2024-04-03: Should this throw an error? + it.each([ + [true, true, '""'], + [true, false, ''], + [false, true, '""'], + [false, false, ''], + ])( + 'should return empty string', + (shouldDecamelize, shouldQuote, expected) => { + const schemalize = createSchemalize({ + shouldDecamelize, + shouldQuote, + }); + + const actual = schemalize(''); + + expect(actual).toBeTypeOf('string'); + expect(actual).toBe(expected); + } + ); + + it.each([ + [true, true, '"my_table"'], + [true, false, 'my_table'], + [false, true, '"myTable"'], + [false, false, 'myTable'], + ])('should accept only name', (shouldDecamelize, shouldQuote, expected) => { + const schemalize = createSchemalize({ + shouldDecamelize, + shouldQuote, + }); + + const actual = schemalize({ name: 'myTable' }); + + expect(actual).toBeTypeOf('string'); + expect(actual).toBe(expected); + }); + }); + + describe('createSchemalize (deprecated)', () => { + it.each([ + [true, true], + [true, false], + [false, true], + [false, false], + ])('should return a function', (shouldDecamelize, shouldQuote) => { + const actual = createSchemalize(shouldDecamelize, shouldQuote); + + expect(actual).toBeTypeOf('function'); + }); + + it('should decamelize and quote for string', () => { + const schemalize = createSchemalize(true, true); + + const actual = schemalize('myTable'); + + expect(actual).toBeTypeOf('string'); + expect(actual).toBe('"my_table"'); + }); + + it('should decamelize and quote for schema', () => { + const schemalize = createSchemalize(true, true); + + const actual = schemalize({ schema: 'mySchema', name: 'myTable' }); + + expect(actual).toBeTypeOf('string'); + expect(actual).toBe('"my_schema"."my_table"'); + }); + + it('should only decamelize for string', () => { + const schemalize = createSchemalize(true, false); + + const actual = schemalize('myTable'); + + expect(actual).toBeTypeOf('string'); + expect(actual).toBe('my_table'); + }); + + it('should only decamelize for schema', () => { + const schemalize = createSchemalize(true, false); + + const actual = schemalize({ schema: 'mySchema', name: 'myTable' }); + + expect(actual).toBeTypeOf('string'); + expect(actual).toBe('my_schema.my_table'); + }); + + it('should only quote for string', () => { + const schemalize = createSchemalize(false, true); + + const actual = schemalize('myTable'); + + expect(actual).toBeTypeOf('string'); + expect(actual).toBe('"myTable"'); + }); + + it('should only quote for schema', () => { + const schemalize = createSchemalize(false, true); + + const actual = schemalize({ schema: 'mySchema', name: 'myTable' }); + + expect(actual).toBeTypeOf('string'); + expect(actual).toBe('"mySchema"."myTable"'); + }); + + it('should not decamelize and quote for string', () => { + const schemalize = createSchemalize(false, false); + + const actual = schemalize('myTable'); + + expect(actual).toBeTypeOf('string'); + expect(actual).toBe('myTable'); + }); + + it('should not decamelize and quote for schema', () => { + const schemalize = createSchemalize(false, false); + + const actual = schemalize({ schema: 'mySchema', name: 'myTable' }); + + expect(actual).toBeTypeOf('string'); + expect(actual).toBe('mySchema.myTable'); + }); + + // TODO @Shinigami92 2024-04-03: Should this throw an error? + it.each([ + [true, true, '""'], + [true, false, ''], + [false, true, '""'], + [false, false, ''], + ])( + 'should return empty string', + (shouldDecamelize, shouldQuote, expected) => { + const schemalize = createSchemalize(shouldDecamelize, shouldQuote); + + const actual = schemalize(''); + + expect(actual).toBeTypeOf('string'); + expect(actual).toBe(expected); + } + ); + + it.each([ + [true, true, '"my_table"'], + [true, false, 'my_table'], + [false, true, '"myTable"'], + [false, false, 'myTable'], + ])('should accept only name', (shouldDecamelize, shouldQuote, expected) => { + const schemalize = createSchemalize(shouldDecamelize, shouldQuote); + + const actual = schemalize({ name: 'myTable' }); + + expect(actual).toBeTypeOf('string'); + expect(actual).toBe(expected); + }); + }); +}); diff --git a/test/utils/createTransformer.spec.ts b/test/utils/createTransformer.spec.ts index cd24dc5a..7ec4e245 100644 --- a/test/utils/createTransformer.spec.ts +++ b/test/utils/createTransformer.spec.ts @@ -5,7 +5,12 @@ import { createSchemalize, createTransformer } from '../../src/utils'; describe('utils', () => { describe('createTransformer', () => { it('should handle string and Name', () => { - const t = createTransformer(createSchemalize(true, true)); + const t = createTransformer( + createSchemalize({ + shouldDecamelize: true, + shouldQuote: true, + }) + ); const actual = t('CREATE INDEX {string} ON {name} (id);', { string: 'string', @@ -16,7 +21,12 @@ describe('utils', () => { }); it('should not escape PgLiteral', () => { - const t = createTransformer(createSchemalize(true, true)); + const t = createTransformer( + createSchemalize({ + shouldDecamelize: true, + shouldQuote: true, + }) + ); const actual = t('INSERT INTO s (id) VALUES {values};', { values: new PgLiteral(['s1', 's2'].map((e) => `('${e}')`).join(', ')), @@ -26,7 +36,12 @@ describe('utils', () => { }); it('should use number', () => { - const t = createTransformer(createSchemalize(true, true)); + const t = createTransformer( + createSchemalize({ + shouldDecamelize: true, + shouldQuote: true, + }) + ); const actual = t('INSERT INTO s (id) VALUES ({values});', { values: 1, @@ -36,7 +51,12 @@ describe('utils', () => { }); it('should fallback to empty mapping', () => { - const t = createTransformer(createSchemalize(true, true)); + const t = createTransformer( + createSchemalize({ + shouldDecamelize: true, + shouldQuote: true, + }) + ); const actual = t('INSERT INTO s (id) VALUES ({values});'); @@ -44,7 +64,12 @@ describe('utils', () => { }); it('should fallback to empty string for undefined value', () => { - const t = createTransformer(createSchemalize(true, true)); + const t = createTransformer( + createSchemalize({ + shouldDecamelize: true, + shouldQuote: true, + }) + ); const actual = t('INSERT INTO s (id) VALUES ({values});', { // @ts-expect-error: JS-only test