Skip to content

Commit

Permalink
A bit of pre-release sanding
Browse files Browse the repository at this point in the history
  • Loading branch information
vampirical committed Mar 7, 2024
1 parent 59d9f5c commit 821a8dc
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 100 deletions.
4 changes: 4 additions & 0 deletions src/RecordQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,12 +278,16 @@ class RecordQuery extends Object {
* Set options for the query, chainable.
*
* @param {Object} options
* @param {boolean} [options.debug] - Like debug=.
* @param {outputType} [options.output] - Like output().
* @param {string|Array|Set} [options.returns] - Like returns().
* @param {boolean} options [options.stream]
* @returns {RecordQuery}
*/
options(options) {
if (options.debug !== undefined) {
this.debug = options.debug;
}
if (options.stream) {
const oldStream = this._options.stream;
this._options.stream = options.stream;
Expand Down
2 changes: 1 addition & 1 deletion src/SqlValue.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class SqlValue extends Object {
quote;

/**
* Create a SqlValue that species how a value should be used within queries.
* Create a SqlValue that specifies how a value is bound/quoted and compared to within generated SQL.
*
* @param {*} value
* @param bind
Expand Down
4 changes: 4 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const codeStatementTimeout = '57014'; // Technically 57014 is all "query_cancele
* @property {string} less
* @property {string} lessEqual
* @property {string} like
* @property {string} not
* @property {string} notAll
* @property {string} notAny
* @property {string} notDistinctFrom
Expand Down Expand Up @@ -49,6 +50,7 @@ const comparison = {
less: '<',
lessEqual: '<=',
like: 'LIKE',
not: 'NOT',
notAll: '!= ALL',
notAny: '!= ANY',
notDistinctFrom: 'IS NOT DISTINCT FROM',
Expand All @@ -72,9 +74,11 @@ const comparison = {
*
* @property {string} and
* @property {string} or
* @property {string} not
*/
const connective = {
and: 'and',
not: 'not',
or: 'or',
};

Expand Down
16 changes: 14 additions & 2 deletions src/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,20 @@ class InvalidOptionCombinationError extends Error {
}
}

/**
* @typedef {Error} WhereParserError
* @memberOf SQL
*/
class WhereParserError extends Error {
constructor(message = 'Where parsing failed.') {
super(message);
this.name = this.constructor.name;
}
}

module.exports = {
AutoPrunedUnusablePoolConnectionError,
AsyncIterationUnavailableError,
AutoPrunedUnusablePoolConnectionError,
FailedToFindUsablePoolConnectionError,
FieldNotFoundError,
ImplicitNestedTransactionError,
Expand All @@ -200,9 +211,10 @@ module.exports = {
MissingRequiredArgError,
NoPoolSetError,
PrimaryKeyValueMissingError,
QueryNotLoadedIterationError,
RecordMissingPrimaryKeyError,
RecordTypeRequiredError,
QueryNotLoadedIterationError,
StatementTimeoutError,
UnavailableInStreamModeError,
WhereParserError,
};
6 changes: 3 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const Record = require('./Record');
const RecordTransform = require('./RecordTransform');
const RecordQuery = require('./RecordQuery');
const Value = require('./SqlValue');
const {ConnectedWheres, And, Or} = require('./wheres');
const {ConnectedWheres, And, Not, Or} = require('./wheres');

Check failure on line 10 in src/index.js

View workflow job for this annotation

GitHub Actions / validate (16)

'Not' is assigned a value but never used

Check failure on line 10 in src/index.js

View workflow job for this annotation

GitHub Actions / validate (18)

'Not' is assigned a value but never used

Check failure on line 10 in src/index.js

View workflow job for this annotation

GitHub Actions / validate (20)

'Not' is assigned a value but never used
const {quoteIdentifier, quoteLiteral} = require('./utils/sql');
const {DatabaseError} = require('pg-protocol');

Expand Down Expand Up @@ -227,7 +227,7 @@ const SQL = {
},

/**
* Shortcut for where AND.
* Where AND.
*
* @param {...*} wheres
* @returns {And}
Expand All @@ -237,7 +237,7 @@ const SQL = {
},

/**
* Shortcut for where OR.
* Where OR.
*
* @param {...*} wheres
* @returns {Or}
Expand Down
33 changes: 26 additions & 7 deletions src/wheres.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';
const {comparison: comparisonDefs, connective: connectiveDefs, type: typeDefs, valueNotNull} = require('./constants');
const {FieldNotFoundError} = require('./errors');
const {FieldNotFoundError, WhereParserError} = require('./errors');
const {getFieldDbName} = require('./utils/misc');
const {quoteIdentifier} = require('./utils/sql');
const SqlValue = require('./SqlValue');
Expand All @@ -10,12 +10,19 @@ const PARENS_COMPARISONS = new Set([
comparisonDefs.any,
comparisonDefs.exists,
comparisonDefs.in,
comparisonDefs.not,
comparisonDefs.notAll,
comparisonDefs.notAny,
comparisonDefs.notExists,
comparisonDefs.notIn,
]);

const RHS_ONLY_COMPARISONS = new Set([
comparisonDefs.exists,
comparisonDefs.not,
comparisonDefs.notExists,
]);

const TEXT_COMPARISONS = new Set([
comparisonDefs.ilike,
comparisonDefs.iregex,
Expand Down Expand Up @@ -114,7 +121,14 @@ function getWhereSql(

let fields;
if (lhs) {
const wheresProto = Object.getPrototypeOf(wheres);
const isPojoLike = wheres !== null && typeof wheres === 'object' && (wheresProto === null || wheresProto === Object.prototype);
if (isPojoLike) {
throw new WhereParserError(`Where parsing failed for key "${lhs}". Where object literals are not allowed within values.`);
}
fields = [[lhs, wheres]];
} else if (wheres instanceof SqlValue) {
fields = [[undefined, wheres]];
} else {
// We now know that these represent simple fields.
fields = typeof wheres.entries === 'function' ? wheres.entries() : Object.entries(wheres);
Expand All @@ -124,7 +138,7 @@ function getWhereSql(
const values = [];
for (const [key, value] of fields) {
const fieldDefinition = fieldDefinitions[key];
if (!fieldDefinition) {
if (key !== undefined && !fieldDefinition) {
throw new FieldNotFoundError(key, recordName);
}

Expand Down Expand Up @@ -175,7 +189,11 @@ function getWhereSql(
let sqlLhs = columnSql.lhs;
const sqlComparison = columnSql.comparison || comparisonDefs.equal;

if (TEXT_COMPARISONS.has(sqlComparison) && fieldDefinition.type !== typeDefs.text) {
if (key && RHS_ONLY_COMPARISONS.has(sqlComparison)) {
throw new WhereParserError(`Where parsing failed for key "${key}". Comparison requested (${sqlComparison}) does not support a left hand side.`);
}

if (TEXT_COMPARISONS.has(sqlComparison) && fieldDefinition && fieldDefinition.type !== typeDefs.text) {
sqlLhs += '::text';
}

Expand All @@ -190,7 +208,7 @@ function getWhereSql(
}
}

const queryPart = `${sqlLhs} ${sqlComparison} ${sqlRhs}`;
const queryPart = [sqlLhs, sqlComparison, sqlRhs].filter(v => v !== undefined).join(' ');

queryParts.push(queryPart);
Array.prototype.push.apply(values, columnSql.values);
Expand All @@ -203,7 +221,7 @@ function getWhereSql(
}

function getColumnWhereSql(conn, recordName, fieldDefinitions, key, value, {comparison = null} = {}) {
let lhs = quoteIdentifier(getFieldDbName(fieldDefinitions, key));
let lhs = key ? quoteIdentifier(getFieldDbName(fieldDefinitions, key)) : undefined;
let rhs = null;
let values = [];

Expand Down Expand Up @@ -236,8 +254,9 @@ function getColumnWhereSql(conn, recordName, fieldDefinitions, key, value, {comp
outputComparison = value.comparison || comparisonDefs.equal;

const actualValue = value.getValue();
const isParensComparison = PARENS_COMPARISONS.has(outputComparison);
if (value.bind) {
if (PARENS_COMPARISONS.has(outputComparison) && (Array.isArray(actualValue) || actualValue instanceof Set)) {
if (isParensComparison && (Array.isArray(actualValue) || actualValue instanceof Set)) {
if (actualValue instanceof Set) {
for (const subValue of actualValue) {
values.push(subValue);
Expand All @@ -255,7 +274,7 @@ function getColumnWhereSql(conn, recordName, fieldDefinitions, key, value, {comp
values.push(actualValue);
}
} else {
rhs = actualValue;
rhs = isParensComparison ? `(${actualValue})` : actualValue;
}
} else if (value && typeof value.getSql === 'function') { // RecordQuery or custom implementor.
const sqlPack = value.getSql(conn, {isSubquery: true});
Expand Down

0 comments on commit 821a8dc

Please sign in to comment.