Repository: salsita/node-pg-migrate (ts)

Issue:
regression pgm.sql no longer works with arrays  
This used to work on the v3 branch, currently, it always quotes the variable which obviously breaks this.
And using `pgm.func` results in an `undefined`;

```js
exports.up = (pgm) => {
    pgm.sql('INSERT INTO states (id) VALUES {values};', {
        values: ['queued', 'busy', 'completed', 'failed'].map(e => `('${e}')`).join(', \n')
    });
};
```

Suggestion: make `pgm.sql` aware of `PgLiteral`, its not the most sugar-coated but it is fairly simple and allows for special cases, which is exactly what this is.

Pull request description:
`pgm.sql` can also accept `PgLiteral`, `number`, `boolean`, `null`, `array`
fixes #694

Patch:
diff --git a/src/operations/othersTypes.ts b/src/operations/othersTypes.ts
index 2438909..a67ac49 100644
--- a/src/operations/othersTypes.ts
+++ b/src/operations/othersTypes.ts
@@ -1,3 +1,4 @@
+import { Value } from '..'
 import { Name } from './generalTypes'
 
-export type Sql = (sqlStr: string, args?: { [key: string]: Name }) => string | string[]
+export type Sql = (sqlStr: string, args?: { [key: string]: Name | Value }) => string | string[]
diff --git a/src/utils.ts b/src/utils.ts
index 094a6d9..dc81e20 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -3,6 +3,7 @@ import { ColumnDefinitions, ColumnDefinition } from './operations/tablesTypes'
 import { Name, Type, Value } from './operations/generalTypes'
 import { MigrationOptions, Literal, RunnerOption } from './types'
 import { FunctionParam, FunctionParamType } from './operations/functionsTypes'
+import { PgLiteral } from '.'
 
 const identity = <T>(v: T) => v
 const quote = (str: string) => `"${str}"`
@@ -20,12 +21,8 @@ export const createSchemalize = (shouldDecamelize: boolean, shouldQuote: boolean
   }
 }
 
-export const createTransformer = (literal: Literal) => (s: string, d?: { [key: string]: Name }) =>
-  Object.keys(d || {}).reduce(
-    (str: string, p) =>
-      str.replace(new RegExp(`{${p}}`, 'g'), d === undefined || d[p] === undefined ? '' : literal(d[p])), // eslint-disable-line security/detect-non-literal-regexp
-    s,
-  )
+const isPgLiteral = (val: unknown): val is PgLiteral =>
+  typeof val === 'object' && val !== null && 'literal' in val && (val as { literal: unknown }).literal === true
 
 export const escapeValue = (val: Value): string | number => {
   if (val === null) {
@@ -50,12 +47,23 @@ export const escapeValue = (val: Value): string | number => {
     const arrayStr = val.map(escapeValue).join(',').replace(/ARRAY/g, '')
     return `ARRAY[${arrayStr}]`
   }
-  if (typeof val === 'object' && val.literal) {
+  if (isPgLiteral(val)) {
     return val.value
   }
   return ''
 }
 
+export const createTransformer = (literal: Literal) => (s: string, d?: { [key: string]: Name | Value }) =>
+  Object.keys(d || {}).reduce((str: string, p) => {
+    const v = d?.[p]
+    return str.replace(
+      // eslint-disable-next-line security/detect-non-literal-regexp
+      new RegExp(`{${p}}`, 'g'),
+      // eslint-disable-next-line no-nested-ternary
+      v === undefined ? '' : typeof v === 'object' && v !== null && 'name' in v ? literal(v) : String(escapeValue(v)),
+    )
+  }, s)
+
 export const getSchemas = (schema?: string | string[]): string[] => {
   const schemas = (Array.isArray(schema) ? schema : [schema]).filter(
     (s): s is string => typeof s === 'string' && s.length > 0,


Test patch:
diff --git a/test/utils-test.ts b/test/utils-test.ts
index 618bd54..efbd2d8 100644
--- a/test/utils-test.ts
+++ b/test/utils-test.ts
@@ -1,5 +1,5 @@
 import { expect } from 'chai'
-import { escapeValue, applyType } from '../src/utils'
+import { escapeValue, applyType, createSchemalize, createTransformer } from '../src/utils'
 import { ColumnDefinitions } from '../src/operations/tablesTypes'
 import PgLiteral from '../src/operations/PgLiteral'
 import { PgLiteralValue } from '../src/operations/generalTypes'
@@ -101,4 +101,26 @@ describe('lib/utils', () => {
       expect(() => applyType('user', shorthands)).to.throw()
     })
   })
+
+  describe('.createTransformer', () => {
+    it('Do not escape PgLiteral', () => {
+      const t = createTransformer(createSchemalize(true, true))
+
+      expect(
+        t('INSERT INTO s (id) VALUES {values};', {
+          values: new PgLiteral(['s1', 's2'].map((e) => `('${e}')`).join(', ')),
+        }),
+      ).to.equal("INSERT INTO s (id) VALUES ('s1'), ('s2');")
+    })
+
+    it('Can use number', () => {
+      const t = createTransformer(createSchemalize(true, true))
+
+      expect(
+        t('INSERT INTO s (id) VALUES ({values});', {
+          values: 1,
+        }),
+      ).to.equal('INSERT INTO s (id) VALUES (1);')
+    })
+  })
 })