formatting.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931
  1. /*
  2. * Copyright (c) 2015-present, Vitaly Tomilov
  3. *
  4. * See the LICENSE file at the top-level directory of this distribution
  5. * for licensing information.
  6. *
  7. * Removal or modification of this copyright notice is prohibited.
  8. */
  9. const {assert} = require('./assert');
  10. const npm = {
  11. pgUtils: require('pg/lib/utils'),
  12. patterns: require('./patterns'),
  13. utils: require('./utils')
  14. };
  15. // Format Modification Flags;
  16. const fmFlags = {
  17. raw: 1, // Raw-Text variable
  18. alias: 2, // SQL Alias
  19. name: 4, // SQL Name/Identifier
  20. json: 8, // JSON modifier
  21. csv: 16, // CSV modifier
  22. value: 32 // escaped, but without ''
  23. };
  24. // Format Modification Map;
  25. const fmMap = {
  26. '^': fmFlags.raw,
  27. ':raw': fmFlags.raw,
  28. ':alias': fmFlags.alias,
  29. '~': fmFlags.name,
  30. ':name': fmFlags.name,
  31. ':json': fmFlags.json,
  32. ':csv': fmFlags.csv,
  33. ':list': fmFlags.csv,
  34. ':value': fmFlags.value,
  35. '#': fmFlags.value
  36. };
  37. // Global symbols for Custom Type Formatting:
  38. const ctfSymbols = {
  39. toPostgres: Symbol.for('ctf.toPostgres'),
  40. rawType: Symbol.for('ctf.rawType')
  41. };
  42. const maxVariable = 100000; // maximum supported variable is '$100000'
  43. ////////////////////////////////////////////////////
  44. // Converts a single value into its Postgres format.
  45. function formatValue({value, fm, cc, options}) {
  46. if (typeof value === 'function') {
  47. return formatValue({value: resolveFunc(value, cc), fm, cc});
  48. }
  49. const ctf = getCTF(value); // Custom Type Formatting
  50. if (ctf) {
  51. fm |= ctf.rawType ? fmFlags.raw : 0;
  52. return formatValue({value: resolveFunc(ctf.toPostgres, value), fm, cc});
  53. }
  54. const isRaw = !!(fm & fmFlags.raw);
  55. fm &= ~fmFlags.raw;
  56. switch (fm) {
  57. case fmFlags.alias:
  58. return $as.alias(value);
  59. case fmFlags.name:
  60. return $as.name(value);
  61. case fmFlags.json:
  62. return $as.json(value, isRaw);
  63. case fmFlags.csv:
  64. return $to.csv(value, options);
  65. case fmFlags.value:
  66. return $as.value(value);
  67. default:
  68. break;
  69. }
  70. if (isNull(value)) {
  71. throwIfRaw(isRaw);
  72. return 'null';
  73. }
  74. switch (typeof value) {
  75. case 'string':
  76. return $to.text(value, isRaw);
  77. case 'boolean':
  78. return $to.bool(value);
  79. case 'number':
  80. case 'bigint':
  81. return $to.number(value);
  82. case 'symbol':
  83. throw new TypeError(`Type Symbol has no meaning for PostgreSQL: ${value.toString()}`);
  84. default:
  85. if (value instanceof Date) {
  86. return $to.date(value, isRaw);
  87. }
  88. if (Array.isArray(value)) {
  89. return $to.array(value, options);
  90. }
  91. if (Buffer.isBuffer(value)) {
  92. return $to.buffer(value, isRaw);
  93. }
  94. return $to.json(value, isRaw);
  95. }
  96. }
  97. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  98. // Converts array of values into PostgreSQL Array Constructor: array[...], as per PostgreSQL documentation:
  99. // http://www.postgresql.org/docs/9.6/static/arrays.html
  100. //
  101. // Arrays of any depth/dimension are supported.
  102. //
  103. // Top-level empty arrays are formatted as literal '{}' to avoid the necessity of explicit type casting,
  104. // as the server cannot automatically infer the type of empty non-literal array.
  105. function formatArray(array, options) {
  106. const loop = a => '[' + a.map(value => Array.isArray(value) ? loop(value) : formatValue({
  107. value,
  108. options
  109. })).join() + ']';
  110. const prefix = options && options.capSQL ? 'ARRAY' : 'array';
  111. return array.length ? (prefix + loop(array)) : '\'{}\'';
  112. }
  113. ///////////////////////////////////////////////////////////////////
  114. // Formats array/object/value as a list of comma-separated values.
  115. function formatCSV(values, options) {
  116. if (Array.isArray(values)) {
  117. return values.map(value => formatValue({value, options})).join();
  118. }
  119. if (typeof values === 'object' && values !== null) {
  120. return Object.keys(values).map(v => formatValue({value: values[v], options})).join();
  121. }
  122. return values === undefined ? '' : formatValue({value: values, options});
  123. }
  124. ///////////////////////////////
  125. // Query formatting helpers;
  126. const formatAs = {
  127. object({query, obj, raw, options}) {
  128. options = options && typeof options === 'object' ? options : {};
  129. return query.replace(npm.patterns.namedParameters, name => {
  130. const v = formatAs.stripName(name.replace(/^\$[{(<[/]|[\s})>\]/]/g, ''), raw),
  131. c = npm.utils.getIfHas(obj, v.name);
  132. if (!c.valid) {
  133. throw new Error(`Invalid property name '${v.name}'.`);
  134. }
  135. if (c.has) {
  136. return formatValue({value: c.value, fm: v.fm, cc: c.target, options});
  137. }
  138. if (v.name === 'this') {
  139. return formatValue({value: obj, fm: v.fm, options});
  140. }
  141. if ('def' in options) {
  142. const d = options.def, value = typeof d === 'function' ? d.call(obj, v.name, obj) : d;
  143. return formatValue({value, fm: v.fm, cc: obj, options});
  144. }
  145. if (options.partial) {
  146. return name;
  147. }
  148. // property must exist as the object's own or inherited;
  149. throw new Error(`Property '${v.name}' doesn't exist.`);
  150. });
  151. },
  152. array({query, array, raw, options}) {
  153. options = options && typeof options === 'object' ? options : {};
  154. return query.replace(npm.patterns.multipleValues, name => {
  155. const v = formatAs.stripName(name.substr(1), raw);
  156. const idx = v.name - 1;
  157. if (idx >= maxVariable) {
  158. throw new RangeError(`Variable $${v.name} exceeds supported maximum of $${maxVariable}`);
  159. }
  160. if (idx < array.length) {
  161. return formatValue({value: array[idx], fm: v.fm, options});
  162. }
  163. if ('def' in options) {
  164. const d = options.def, value = typeof d === 'function' ? d.call(array, idx, array) : d;
  165. return formatValue({value, fm: v.fm, options});
  166. }
  167. if (options.partial) {
  168. return name;
  169. }
  170. throw new RangeError(`Variable $${v.name} out of range. Parameters array length: ${array.length}`);
  171. });
  172. },
  173. value({query, value, raw, options}) {
  174. return query.replace(npm.patterns.singleValue, name => {
  175. const v = formatAs.stripName(name, raw);
  176. return formatValue({value, fm: v.fm, options});
  177. });
  178. },
  179. stripName(name, raw) {
  180. const mod = name.match(npm.patterns.hasValidModifier);
  181. if (mod) {
  182. return {
  183. name: name.substr(0, mod.index),
  184. fm: fmMap[mod[0]] | (raw ? fmFlags.raw : 0)
  185. };
  186. }
  187. return {
  188. name,
  189. fm: raw ? fmFlags.raw : null
  190. };
  191. }
  192. };
  193. ////////////////////////////////////////////
  194. // Simpler check for null/undefined;
  195. function isNull(value) {
  196. return value === undefined || value === null;
  197. }
  198. //////////////////////////////////////////////////////////////////
  199. // Checks if the value supports Custom Type Formatting,
  200. // to return {toPostgres, rawType}, if it does, or null otherwise.
  201. function getCTF(value) {
  202. if (!isNull(value)) {
  203. let toPostgres = value[ctfSymbols.toPostgres], rawType = !!value[ctfSymbols.rawType];
  204. if (typeof toPostgres !== 'function') {
  205. toPostgres = value.toPostgres;
  206. rawType = !!value.rawType;
  207. }
  208. if (typeof toPostgres === 'function') {
  209. if (toPostgres.constructor.name !== 'Function') {
  210. throw new Error('CTF does not support asynchronous toPostgres functions.');
  211. }
  212. return {toPostgres, rawType};
  213. }
  214. }
  215. return null;
  216. }
  217. /////////////////////////////////////////
  218. // Wraps a text string in single quotes;
  219. function wrapText(text) {
  220. return `'${text}'`;
  221. }
  222. ////////////////////////////////////////////////
  223. // Replaces each single-quote symbol ' with two,
  224. // for compliance with PostgreSQL strings.
  225. function safeText(text) {
  226. return text.replace(/'/g, '\'\'');
  227. }
  228. /////////////////////////////////////////////
  229. // Throws an exception, if flag 'raw' is set.
  230. function throwIfRaw(raw) {
  231. if (raw) {
  232. throw new TypeError('Values null/undefined cannot be used as raw text.');
  233. }
  234. }
  235. /////////////////////////////////////////////////////////////////////////////
  236. // Recursively resolves parameter-function, with an optional Calling Context.
  237. function resolveFunc(value, cc) {
  238. while (typeof value === 'function') {
  239. if (value.constructor.name !== 'Function') {
  240. // Constructor name for asynchronous functions have different names:
  241. // - 'GeneratorFunction' for ES6 generators
  242. // - 'AsyncFunction' for ES7 async functions
  243. throw new Error('Cannot use asynchronous functions with query formatting.');
  244. }
  245. value = value.call(cc, cc);
  246. }
  247. return value;
  248. }
  249. ///////////////////////////////////////////////////////////////////////////////////
  250. // It implements two types of formatting, depending on the 'values' passed:
  251. //
  252. // 1. format '$1, $2, etc', when 'values' is of type string, boolean, number, date,
  253. // function or null (or an array of the same types, plus undefined values);
  254. // 2. format $*propName*, when 'values' is an object (not null and not Date),
  255. // and where * is any of the supported open-close pairs: {}, (), [], <>, //
  256. //
  257. function formatQuery(query, values, raw, options) {
  258. if (typeof query !== 'string') {
  259. throw new TypeError('Parameter \'query\' must be a text string.');
  260. }
  261. const ctf = getCTF(values);
  262. if (ctf) {
  263. // Custom Type Formatting
  264. return formatQuery(query, resolveFunc(ctf.toPostgres, values), raw || ctf.rawType, options);
  265. }
  266. if (typeof values === 'object' && values !== null) {
  267. if (Array.isArray(values)) {
  268. // $1, $2,... formatting to be applied;
  269. return formatAs.array({query, array: values, raw, options});
  270. }
  271. if (!(values instanceof Date || values instanceof Buffer)) {
  272. // $*propName* formatting to be applied;
  273. return formatAs.object({query, obj: values, raw, options});
  274. }
  275. }
  276. // $1 formatting to be applied, if values != undefined;
  277. return values === undefined ? query : formatAs.value({query, value: values, raw, options});
  278. }
  279. //////////////////////////////////////////////////////
  280. // Formats a function or stored procedure call query;
  281. function formatEntity(entity, values, {capSQL, type}) {
  282. let prefix = type === 'func' ? 'select * from' : 'call';
  283. if (capSQL) {
  284. prefix = prefix.toUpperCase();
  285. }
  286. return `${prefix} ${$as.alias(entity)}(${formatCSV(values, {capSQL})})`;
  287. }
  288. function formatSqlName(name) {
  289. return `"${name.replace(/"/g, '""')}"`;
  290. }
  291. /**
  292. * @namespace formatting
  293. * @description
  294. * Namespace for all query-formatting functions, available from `pgp.as` before and after initializing the library.
  295. *
  296. * @property {formatting.ctf} ctf
  297. * Namespace for symbols used by $[Custom Type Formatting].
  298. *
  299. * @property {function} alias
  300. * {@link formatting.alias alias} - formats an SQL alias.
  301. *
  302. * @property {function} name
  303. * {@link formatting.name name} - formats an SQL Name/Identifier.
  304. *
  305. * @property {function} text
  306. * {@link formatting.text text} - formats a text string.
  307. *
  308. * @property {function} number
  309. * {@link formatting.number number} - formats a number.
  310. *
  311. * @property {function} buffer
  312. * {@link formatting.buffer buffer} - formats a `Buffer` object.
  313. *
  314. * @property {function} value
  315. * {@link formatting.value value} - formats text as an open value.
  316. *
  317. * @property {function} json
  318. * {@link formatting.json json} - formats any value as JSON.
  319. *
  320. * @property {function} array
  321. * {@link formatting.array array} - formats an array of any depth.
  322. *
  323. * @property {function} csv
  324. * {@link formatting.csv csv} - formats an array as a list of comma-separated values.
  325. *
  326. * @property {function} func
  327. * {@link formatting.func func} - formats the value returned from a function.
  328. *
  329. * @property {function} format
  330. * {@link formatting.format format} - formats a query, according to parameters.
  331. *
  332. */
  333. const $as = {
  334. /**
  335. * @namespace formatting.ctf
  336. * @description
  337. * Namespace for ES6 symbols used by $[Custom Type Formatting], available from `pgp.as.ctf` before and after initializing the library.
  338. *
  339. * It was added to avoid explicit/enumerable extension of types that need to be used as formatting parameters, to keep their type signature intact.
  340. *
  341. * @property {external:Symbol} toPostgres
  342. * Property name for the $[Custom Type Formatting] callback function `toPostgres`.
  343. *
  344. * @property {external:Symbol} rawType
  345. * Property name for the $[Custom Type Formatting] flag `rawType`.
  346. *
  347. * @example
  348. * const ctf = pgp.as.ctf; // Custom Type Formatting symbols
  349. *
  350. * class MyType {
  351. * constructor() {
  352. * this[ctf.rawType] = true; // set it only when toPostgres returns a pre-formatted result
  353. * }
  354. *
  355. * [ctf.toPostgres](self) {
  356. * // self = this
  357. *
  358. * // return the custom/actual value here
  359. * }
  360. * }
  361. *
  362. * const a = new MyType();
  363. *
  364. * const s = pgp.as.format('$1', a); // will be custom-formatted
  365. */
  366. ctf: ctfSymbols,
  367. /**
  368. * @method formatting.text
  369. * @description
  370. * Converts a value into PostgreSQL text presentation, escaped as required.
  371. *
  372. * Escaping the result means:
  373. * 1. Every single-quote (apostrophe) is replaced with two
  374. * 2. The resulting text is wrapped in apostrophes
  375. *
  376. * @param {value|function} value
  377. * Value to be converted, or a function that returns the value.
  378. *
  379. * If the `value` resolves as `null` or `undefined`, while `raw`=`true`,
  380. * it will throw {@link external:TypeError TypeError} = `Values null/undefined cannot be used as raw text.`
  381. *
  382. * @param {boolean} [raw=false]
  383. * Indicates when not to escape the resulting text.
  384. *
  385. * @returns {string}
  386. *
  387. * - `null` string, if the `value` resolves as `null` or `undefined`
  388. * - escaped result of `value.toString()`, if the `value` isn't a string
  389. * - escaped string version, if `value` is a string.
  390. *
  391. * The result is not escaped, if `raw` was passed in as `true`.
  392. */
  393. text(value, raw) {
  394. value = resolveFunc(value);
  395. if (isNull(value)) {
  396. throwIfRaw(raw);
  397. return 'null';
  398. }
  399. if (typeof value !== 'string') {
  400. value = value.toString();
  401. }
  402. return $to.text(value, raw);
  403. },
  404. /**
  405. * @method formatting.name
  406. * @description
  407. * Properly escapes an sql name or identifier, fixing double-quote symbols and wrapping the result in double quotes.
  408. *
  409. * Implements a safe way to format $[SQL Names] that neutralizes SQL Injection.
  410. *
  411. * When formatting a query, a variable makes use of this method via modifier `:name` or `~`. See method {@link formatting.format format}.
  412. *
  413. * @param {string|function|array|object} name
  414. * SQL name or identifier, or a function that returns it.
  415. *
  416. * The name must be at least 1 character long.
  417. *
  418. * If `name` doesn't resolve into a non-empty string, it throws {@link external:TypeError TypeError} = `Invalid sql name: ...`
  419. *
  420. * If the `name` contains only a single `*` (trailing spaces are ignored), then `name` is returned exactly as is (unescaped).
  421. *
  422. * - If `name` is an Array, it is formatted as a comma-separated list of $[SQL Names]
  423. * - If `name` is a non-Array object, its keys are formatted as a comma-separated list of $[SQL Names]
  424. *
  425. * Passing in an empty array/object will throw {@link external:Error Error} = `Cannot retrieve sql names from an empty array/object.`
  426. *
  427. * @returns {string}
  428. * The SQL Name/Identifier, properly escaped for compliance with the PostgreSQL standard for $[SQL Names] and identifiers.
  429. *
  430. * @see
  431. * {@link formatting.alias alias},
  432. * {@link formatting.format format}
  433. *
  434. * @example
  435. *
  436. * // automatically list object properties as sql names:
  437. * format('INSERT INTO table(${this~}) VALUES(${one}, ${two})', {
  438. * one: 1,
  439. * two: 2
  440. * });
  441. * //=> INSERT INTO table("one","two") VALUES(1, 2)
  442. *
  443. */
  444. name(name) {
  445. name = resolveFunc(name);
  446. if (name) {
  447. if (typeof name === 'string') {
  448. return /^\s*\*(\s*)$/.test(name) ? name : formatSqlName(name);
  449. }
  450. if (typeof name === 'object') {
  451. const keys = Array.isArray(name) ? name : Object.keys(name);
  452. if (!keys.length) {
  453. throw new Error('Cannot retrieve sql names from an empty array/object.');
  454. }
  455. return keys.map(value => {
  456. if (!value || typeof value !== 'string') {
  457. throw new Error(`Invalid sql name: ${npm.utils.toJson(value)}`);
  458. }
  459. return formatSqlName(value);
  460. }).join();
  461. }
  462. }
  463. throw new TypeError(`Invalid sql name: ${npm.utils.toJson(name)}`);
  464. },
  465. /**
  466. * @method formatting.alias
  467. * @description
  468. * Simpler (non-verbose) version of method {@link formatting.name name}, to handle only a regular string-identifier
  469. * that's mostly used as an SQL alias, i.e. it doesn't support `*` or an array/object of names, which in the context of
  470. * an SQL alias would be incorrect. However, it supports `.` as name-separator, for simpler escaping of composite names.
  471. *
  472. * The surrounding double quotes are not added when the alias uses a simple syntax:
  473. * - it is a same-case single word, without spaces
  474. * - it can contain underscores, and can even start with them
  475. * - it can contain digits and `$`, but cannot start with those
  476. *
  477. * The method will automatically split the string with `.`, to support composite SQL names.
  478. *
  479. * When formatting a query, a variable makes use of this method via modifier `:alias`. See method {@link formatting.format format}.
  480. *
  481. * @param {string|function} name
  482. * SQL alias name, or a function that returns it.
  483. *
  484. * The name must be at least 1 character long. And it can contain `.`, to split into multiple SQL names.
  485. *
  486. * If `name` doesn't resolve into a non-empty string, it throws {@link external:TypeError TypeError} = `Invalid sql alias: ...`
  487. *
  488. * @returns {string}
  489. * The SQL alias, properly escaped for compliance with the PostgreSQL standard for $[SQL Names] and identifiers.
  490. *
  491. * @see
  492. * {@link formatting.name name},
  493. * {@link formatting.format format}
  494. *
  495. */
  496. alias(name) {
  497. name = resolveFunc(name);
  498. if (name && typeof name === 'string') {
  499. return name.split('.')
  500. .filter(f => f)
  501. .map(a => {
  502. const m = a.match(/^([a-z_][a-z0-9_$]*|[A-Z_][A-Z0-9_$]*)$/);
  503. if (m && m[0] === a) {
  504. return a;
  505. }
  506. return `"${a.replace(/"/g, '""')}"`;
  507. }).join('.');
  508. }
  509. throw new TypeError(`Invalid sql alias: ${npm.utils.toJson(name)}`);
  510. },
  511. /**
  512. * @method formatting.value
  513. * @description
  514. * Represents an open value, one to be formatted according to its type, properly escaped, but without surrounding quotes for text types.
  515. *
  516. * When formatting a query, a variable makes use of this method via modifier `:value` or `#`. See method {@link formatting.format format}.
  517. *
  518. * @param {value|function} value
  519. * Value to be converted, or a function that returns the value.
  520. *
  521. * If `value` resolves as `null` or `undefined`, it will throw {@link external:TypeError TypeError} = `Open values cannot be null or undefined.`
  522. *
  523. * @returns {string}
  524. * Formatted and properly escaped string, but without surrounding quotes for text types.
  525. *
  526. * @see {@link formatting.format format}
  527. *
  528. */
  529. value(value) {
  530. value = resolveFunc(value);
  531. if (isNull(value)) {
  532. throw new TypeError('Open values cannot be null or undefined.');
  533. }
  534. return safeText(formatValue({value, fm: fmFlags.raw}));
  535. },
  536. /**
  537. * @method formatting.buffer
  538. * @description
  539. * Converts an object of type `Buffer` into a hex string compatible with PostgreSQL type `bytea`.
  540. *
  541. * @param {Buffer|function} obj
  542. * Object to be converted, or a function that returns one.
  543. *
  544. * @param {boolean} [raw=false]
  545. * Indicates when not to wrap the resulting string in quotes.
  546. *
  547. * The generated hex string doesn't need to be escaped.
  548. *
  549. * @returns {string}
  550. */
  551. buffer(obj, raw) {
  552. obj = resolveFunc(obj);
  553. if (isNull(obj)) {
  554. throwIfRaw(raw);
  555. return 'null';
  556. }
  557. if (obj instanceof Buffer) {
  558. return $to.buffer(obj, raw);
  559. }
  560. throw new TypeError(`${wrapText(obj)} is not a Buffer object.`);
  561. },
  562. /**
  563. * @method formatting.bool
  564. * @description
  565. * Converts a truthy value into PostgreSQL boolean presentation.
  566. *
  567. * @param {boolean|function} value
  568. * Value to be converted, or a function that returns the value.
  569. *
  570. * @returns {string}
  571. */
  572. bool(value) {
  573. value = resolveFunc(value);
  574. if (isNull(value)) {
  575. return 'null';
  576. }
  577. return $to.bool(value);
  578. },
  579. /**
  580. * @method formatting.date
  581. * @description
  582. * Converts a `Date`-type value into PostgreSQL date/time presentation,
  583. * wrapped in quotes (unless flag `raw` is set).
  584. *
  585. * @param {Date|function} d
  586. * Date object to be converted, or a function that returns one.
  587. *
  588. * @param {boolean} [raw=false]
  589. * Indicates when not to escape the value.
  590. *
  591. * @returns {string}
  592. */
  593. date(d, raw) {
  594. d = resolveFunc(d);
  595. if (isNull(d)) {
  596. throwIfRaw(raw);
  597. return 'null';
  598. }
  599. if (d instanceof Date) {
  600. return $to.date(d, raw);
  601. }
  602. throw new TypeError(`${wrapText(d)} is not a Date object.`);
  603. },
  604. /**
  605. * @method formatting.number
  606. * @description
  607. * Converts a numeric value into its PostgreSQL number presentation, with support
  608. * for special values of `NaN`, `+Infinity` and `-Infinity`.
  609. *
  610. * @param {number|bigint|function} num
  611. * Number to be converted, or a function that returns one.
  612. *
  613. * @returns {string}
  614. */
  615. number(num) {
  616. num = resolveFunc(num);
  617. if (isNull(num)) {
  618. return 'null';
  619. }
  620. const t = typeof num;
  621. if (t !== 'number' && t !== 'bigint') {
  622. throw new TypeError(`${wrapText(num)} is not a number.`);
  623. }
  624. return $to.number(num);
  625. },
  626. /**
  627. * @method formatting.array
  628. * @description
  629. * Converts an array of values into its PostgreSQL presentation as an Array-Type constructor string: `array[]`.
  630. *
  631. * Top-level empty arrays are formatted as literal `{}`, to avoid the necessity of explicit type casting,
  632. * as the server cannot automatically infer type of empty non-literal array.
  633. *
  634. * @param {Array|function} arr
  635. * Array to be converted, or a function that returns one.
  636. *
  637. * @param {{}} [options]
  638. * Array-Formatting Options.
  639. *
  640. * @param {boolean} [options.capSQL=false]
  641. * When `true`, outputs `ARRAY` instead of `array`.
  642. *
  643. * @returns {string}
  644. */
  645. array(arr, options) {
  646. options = assert(options, ['capSQL']);
  647. arr = resolveFunc(arr);
  648. if (isNull(arr)) {
  649. return 'null';
  650. }
  651. if (Array.isArray(arr)) {
  652. return $to.array(arr, options);
  653. }
  654. throw new TypeError(`${wrapText(arr)} is not an Array object.`);
  655. },
  656. /**
  657. * @method formatting.csv
  658. * @description
  659. * Converts a single value or an array of values into a CSV (comma-separated values) string, with all values formatted
  660. * according to their JavaScript type.
  661. *
  662. * When formatting a query, a variable makes use of this method via modifier `:csv` or its alias `:list`.
  663. *
  664. * When `values` is an object that's not `null` or `Array`, its properties are enumerated for the actual values.
  665. *
  666. * @param {Array|Object|value|function} values
  667. * Value(s) to be converted, or a function that returns it.
  668. *
  669. * @returns {string}
  670. *
  671. * @see {@link formatting.format format}
  672. */
  673. csv(values) {
  674. return $to.csv(values);
  675. },
  676. /**
  677. * @method formatting.json
  678. * @description
  679. * Converts any value into JSON (includes `BigInt` support), and returns it as a valid string,
  680. * with single-quote symbols fixed, unless flag `raw` is set.
  681. *
  682. * When formatting a query, a variable makes use of this method via modifier `:json`. See method {@link formatting.format format}.
  683. *
  684. * @param {*} data
  685. * Object/value to be converted, or a function that returns it.
  686. *
  687. * @param {boolean} [raw=false]
  688. * Indicates when not to escape the result.
  689. *
  690. * @returns {string}
  691. *
  692. * @see {@link formatting.format format}
  693. */
  694. json(data, raw) {
  695. data = resolveFunc(data);
  696. if (isNull(data)) {
  697. throwIfRaw(raw);
  698. return 'null';
  699. }
  700. return $to.json(data, raw);
  701. },
  702. /**
  703. * @method formatting.func
  704. * @description
  705. * Calls the function to get the actual value, and then formats the result according to its type + `raw` flag.
  706. *
  707. * @param {function} func
  708. * Function to be called, with support for nesting.
  709. *
  710. * @param {boolean} [raw=false]
  711. * Indicates when not to escape the result.
  712. *
  713. * @param {*} [cc]
  714. * Calling Context: `this` + the only value to be passed into the function on all nested levels.
  715. *
  716. * @returns {string}
  717. */
  718. func(func, raw, cc) {
  719. if (isNull(func)) {
  720. throwIfRaw(raw);
  721. return 'null';
  722. }
  723. if (typeof func !== 'function') {
  724. throw new TypeError(`${wrapText(func)} is not a function.`);
  725. }
  726. const fm = raw ? fmFlags.raw : null;
  727. return formatValue({value: resolveFunc(func, cc), fm, cc});
  728. },
  729. /**
  730. * @method formatting.format
  731. * @description
  732. * Replaces variables in a string according to the type of `values`:
  733. *
  734. * - Replaces `$1` occurrences when `values` is of type `string`, `boolean`, `number`, `bigint`, `Date`, `Buffer` or when it is `null`.
  735. *
  736. * - Replaces variables `$1`, `$2`, ...`$100000` when `values` is an array of parameters. It throws a {@link external:RangeError RangeError}
  737. * when the values or variables are out of range.
  738. *
  739. * - Replaces `$*propName*`, where `*` is any of `{}`, `()`, `[]`, `<>`, `//`, when `values` is an object that's not a
  740. * `Date`, `Buffer`, {@link QueryFile} or `null`. Special property name `this` refers to the formatting object itself,
  741. * to be injected as a JSON string. When referencing a property that doesn't exist in the formatting object, it throws
  742. * {@link external:Error Error} = `Property 'PropName' doesn't exist`, unless option `partial` is used.
  743. *
  744. * - Supports $[Nested Named Parameters] of any depth.
  745. *
  746. * By default, each variable is automatically formatted according to its type, unless it is a special variable:
  747. *
  748. * - Raw-text variables end with `:raw` or symbol `^`, and prevent escaping the text. Such variables are not
  749. * allowed to be `null` or `undefined`, or the method will throw {@link external:TypeError TypeError} = `Values null/undefined cannot be used as raw text.`
  750. * - `$1:raw`, `$2:raw`,..., and `$*propName:raw*` (see `*` above)
  751. * - `$1^`, `$2^`,..., and `$*propName^*` (see `*` above)
  752. *
  753. * - Open-value variables end with `:value` or symbol `#`, to be escaped, but not wrapped in quotes. Such variables are
  754. * not allowed to be `null` or `undefined`, or the method will throw {@link external:TypeError TypeError} = `Open values cannot be null or undefined.`
  755. * - `$1:value`, `$2:value`,..., and `$*propName:value*` (see `*` above)
  756. * - `$1#`, `$2#`,..., and `$*propName#*` (see `*` above)
  757. *
  758. * - SQL name variables end with `:name` or symbol `~` (tilde), and provide proper escaping for SQL names/identifiers:
  759. * - `$1:name`, `$2:name`,..., and `$*propName:name*` (see `*` above)
  760. * - `$1~`, `$2~`,..., and `$*propName~*` (see `*` above)
  761. *
  762. * - Modifier `:alias` - non-verbose $[SQL Names] escaping.
  763. *
  764. * - JSON override ends with `:json` to format the value of any type as a JSON string
  765. *
  766. * - CSV override ends with `:csv` or `:list` to format an array as a properly escaped comma-separated list of values.
  767. *
  768. * @param {string|QueryFile|object} query
  769. * A query string, a {@link QueryFile} or any object that implements $[Custom Type Formatting], to be formatted according to `values`.
  770. *
  771. * @param {array|object|value} [values]
  772. * Formatting parameter(s) / variable value(s).
  773. *
  774. * @param {{}} [options]
  775. * Formatting Options.
  776. *
  777. * @param {boolean} [options.capSQL=false]
  778. * Formats reserved SQL words capitalized. Presently, this only concerns arrays, to output `ARRAY` when required.
  779. *
  780. * @param {boolean} [options.partial=false]
  781. * Indicates that we intend to do only a partial replacement, i.e. throw no error when encountering a variable or
  782. * property name that's missing within the formatting parameters.
  783. *
  784. * **NOTE:** This option has no meaning when option `def` is used.
  785. *
  786. * @param {*} [options.def]
  787. * Sets default value for every variable that's missing, consequently preventing errors when encountering a variable
  788. * or property name that's missing within the formatting parameters.
  789. *
  790. * It can also be set to a function, to be called with two parameters that depend on the type of formatting being used,
  791. * and to return the actual default value:
  792. *
  793. * - For $[Named Parameters] formatting:
  794. * - `name` - name of the property missing in the formatting object
  795. * - `obj` - the formatting object, and is the same as `this` context
  796. *
  797. * - For $[Index Variables] formatting:
  798. * - `index` - element's index (starts with 1) that's outside of the input array
  799. * - `arr` - the formatting/input array, and is the same as `this` context
  800. *
  801. * You can tell which type of call it is by checking the type of the first parameter.
  802. *
  803. * @returns {string}
  804. * Formatted query string.
  805. *
  806. * The function will throw an error, if any occurs during formatting.
  807. */
  808. format(query, values, options) {
  809. options = assert(options, ['capSQL', 'partial', 'def']);
  810. const ctf = getCTF(query);
  811. if (ctf) {
  812. query = ctf.toPostgres.call(query, query);
  813. }
  814. return formatQuery(query, values, false, options);
  815. }
  816. };
  817. /* Pre-parsed type formatting */
  818. const $to = {
  819. array(arr, options) {
  820. return formatArray(arr, options);
  821. },
  822. csv(values, options) {
  823. return formatCSV(resolveFunc(values), options);
  824. },
  825. bool(value) {
  826. return value ? 'true' : 'false';
  827. },
  828. buffer(obj, raw) {
  829. const s = `\\x${obj.toString('hex')}`;
  830. return raw ? s : wrapText(s);
  831. },
  832. date(d, raw) {
  833. const s = npm.pgUtils.prepareValue(d);
  834. return raw ? s : wrapText(s);
  835. },
  836. json(data, raw) {
  837. const s = npm.utils.toJson(data);
  838. return raw ? s : wrapText(safeText(s));
  839. },
  840. number(num) {
  841. if (typeof num === 'bigint' || Number.isFinite(num)) {
  842. return num.toString();
  843. }
  844. // Converting NaN/+Infinity/-Infinity according to Postgres documentation:
  845. // http://www.postgresql.org/docs/9.6/static/datatype-numeric.html#DATATYPE-FLOAT
  846. //
  847. // NOTE: strings for 'NaN'/'+Infinity'/'-Infinity' are not case-sensitive.
  848. if (num === Number.POSITIVE_INFINITY) {
  849. return wrapText('+Infinity');
  850. }
  851. if (num === Number.NEGATIVE_INFINITY) {
  852. return wrapText('-Infinity');
  853. }
  854. return wrapText('NaN');
  855. },
  856. text(value, raw) {
  857. return raw ? value : wrapText(safeText(value));
  858. }
  859. };
  860. module.exports = {
  861. formatQuery,
  862. formatEntity,
  863. resolveFunc,
  864. as: $as
  865. };
  866. /**
  867. * @external Error
  868. * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
  869. */
  870. /**
  871. * @external TypeError
  872. * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError
  873. */
  874. /**
  875. * @external RangeError
  876. * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError
  877. */
  878. /**
  879. * @external Symbol
  880. * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
  881. */