'use strict'; // reserved Postgres words var reservedMap = require(__dirname + '/reserved.js'); var fmtPattern = { ident: 'I', literal: 'L', string: 's', }; // convert to Postgres default ISO 8601 format function formatDate(date) { date = date.replace('T', ' '); date = date.replace('Z', '+00'); return date; } function isReserved(value) { if (reservedMap[value.toUpperCase()]) { return true; } return false; } function arrayToList(useSpace, array, formatter) { var sql = ''; var temp = []; sql += useSpace ? ' (' : '('; for (var i = 0; i < array.length; i++) { sql += (i === 0 ? '' : ', ') + formatter(array[i]); } sql += ')'; return sql; } // Ported from PostgreSQL 9.2.4 source code in src/interfaces/libpq/fe-exec.c function quoteIdent(value) { if (value === undefined || value === null) { throw new Error('SQL identifier cannot be null or undefined'); } else if (value === false) { return '"f"'; } else if (value === true) { return '"t"'; } else if (value instanceof Date) { return '"' + formatDate(value.toISOString()) + '"'; } else if (value instanceof Buffer) { throw new Error('SQL identifier cannot be a buffer'); } else if (Array.isArray(value) === true) { var temp = []; for (var i = 0; i < value.length; i++) { if (Array.isArray(value[i]) === true) { throw new Error('Nested array to grouped list conversion is not supported for SQL identifier'); } else { temp.push(quoteIdent(value[i])); } } return temp.toString(); } else if (value === Object(value)) { throw new Error('SQL identifier cannot be an object'); } var ident = value.toString().slice(0); // create copy // do not quote a valid, unquoted identifier if (/^[a-z_][a-z0-9_$]*$/.test(ident) === true && isReserved(ident) === false) { return ident; } var quoted = '"'; for (var i = 0; i < ident.length; i++) { var c = ident[i]; if (c === '"') { quoted += c + c; } else { quoted += c; } } quoted += '"'; return quoted; }; // Ported from PostgreSQL 9.2.4 source code in src/interfaces/libpq/fe-exec.c function quoteLiteral(value) { var literal = null; var explicitCast = null; if (value === undefined || value === null) { return 'NULL'; } else if (value === false) { return "'f'"; } else if (value === true) { return "'t'"; } else if (value instanceof Date) { return "'" + formatDate(value.toISOString()) + "'"; } else if (value instanceof Buffer) { return "E'\\\\x" + value.toString('hex') + "'"; } else if (Array.isArray(value) === true) { var temp = []; for (var i = 0; i < value.length; i++) { if (Array.isArray(value[i]) === true) { temp.push(arrayToList(i !== 0, value[i], quoteLiteral)) } else { temp.push(quoteLiteral(value[i])); } } return temp.toString(); } else if (value === Object(value)) { explicitCast = 'jsonb'; literal = JSON.stringify(value); } else { literal = value.toString().slice(0); // create copy } var hasBackslash = false; var quoted = '\''; for (var i = 0; i < literal.length; i++) { var c = literal[i]; if (c === '\'') { quoted += c + c; } else if (c === '\\') { quoted += c + c; hasBackslash = true; } else { quoted += c; } } quoted += '\''; if (hasBackslash === true) { quoted = 'E' + quoted; } if (explicitCast) { quoted += '::' + explicitCast; } return quoted; }; function quoteString(value) { if (value === undefined || value === null) { return ''; } else if (value === false) { return 'f'; } else if (value === true) { return 't'; } else if (value instanceof Date) { return formatDate(value.toISOString()); } else if (value instanceof Buffer) { return '\\x' + value.toString('hex'); } else if (Array.isArray(value) === true) { var temp = []; for (var i = 0; i < value.length; i++) { if (value[i] !== null && value[i] !== undefined) { if (Array.isArray(value[i]) === true) { temp.push(arrayToList(i !== 0, value[i], quoteString)); } else { temp.push(quoteString(value[i])); } } } return temp.toString(); } else if (value === Object(value)) { return JSON.stringify(value); } return value.toString().slice(0); // return copy } function config(cfg) { // default fmtPattern.ident = 'I'; fmtPattern.literal = 'L'; fmtPattern.string = 's'; if (cfg && cfg.pattern) { if (cfg.pattern.ident) { fmtPattern.ident = cfg.pattern.ident; } if (cfg.pattern.literal) { fmtPattern.literal = cfg.pattern.literal; } if (cfg.pattern.string) { fmtPattern.string = cfg.pattern.string; } } } function formatWithArray(fmt, parameters) { var index = 0; var params = parameters; var re = '%(%|(\\d+\\$)?['; re += fmtPattern.ident; re += fmtPattern.literal; re += fmtPattern.string; re += '])'; re = new RegExp(re, 'g'); return fmt.replace(re, function(_, type) { if (type === '%') { return '%'; } var position = index; var tokens = type.split('$'); if (tokens.length > 1) { position = parseInt(tokens[0]) - 1; type = tokens[1]; } if (position < 0) { throw new Error('specified argument 0 but arguments start at 1'); } else if (position > params.length - 1) { throw new Error('too few arguments'); } index = position + 1; if (type === fmtPattern.ident) { return quoteIdent(params[position]); } else if (type === fmtPattern.literal) { return quoteLiteral(params[position]); } else if (type === fmtPattern.string) { return quoteString(params[position]); } }); } function format(fmt) { var args = Array.prototype.slice.call(arguments); args = args.slice(1); // first argument is fmt return formatWithArray(fmt, args); } exports = module.exports = format; exports.config = config; exports.ident = quoteIdent; exports.literal = quoteLiteral; exports.string = quoteString; exports.withArray = formatWithArray;