diff --git a/experiments/raqb-concepts.js b/experiments/raqb-concepts.js index b58c2aa..20df375 100644 --- a/experiments/raqb-concepts.js +++ b/experiments/raqb-concepts.js @@ -43,29 +43,42 @@ try { // left: "foo", // condition: equals("bar") // }); - - + + let niceNumbers = anyOf([ 1, 2, 3 ]); + query = select("projects", [ where({ - // foo: anyOf([ "bar", not(not("baz")), anyOf([ "bar2", "baz2" ]), unsafeSQL("TRUE") ]), - // qux: anyOf([ 13, moreThan(42) ]), - complex: anyOf([ - 30, - 40, - allOf([ - moreThan(100), - lessThan(200), - lessThan(parameter("max")) - ]) - ]) + number_one: niceNumbers, + number_two: niceNumbers }), - // where({ second: 2 }), - // where(not({ - // thirdA: 3, - // thirdB: 3 - // })), - // where(anyOf([ { foo: "bar" } ])) + where({ + number_three: anyOf([ 42, column("number_one") ]), + number_four: 1337 + }) ]); + + + // query = select("projects", [ + // where({ + // // foo: anyOf([ "bar", not(not("baz")), anyOf([ "bar2", "baz2" ]), unsafeSQL("TRUE") ]), + // // qux: anyOf([ 13, moreThan(42) ]), + // complex: anyOf([ + // 30, + // 40, + // allOf([ + // moreThan(100), + // lessThan(200), + // lessThan(parameter("max")) + // ]) + // ]) + // }), + // // where({ second: 2 }), + // // where(not({ + // // thirdA: 3, + // // thirdB: 3 + // // })), + // // where(anyOf([ { foo: "bar" } ])) + // ]); // query = select("projects", [ // onlyColumns([ diff --git a/src/ast-to-query.js b/src/ast-to-query.js index 8f2e094..2eeba8e 100644 --- a/src/ast-to-query.js +++ b/src/ast-to-query.js @@ -163,9 +163,12 @@ function $parenthesize(query) { return $combine`(${query})`; } +// FIXME: This is a bit hacky; we should probably have a `$parenthesizedHandle` or something instead... +let simpleColumnNameRegex = /^[a-z_]+(?:\.[a-z_]+)?$/; + function $maybeParenthesize($query) { - if ($query.query === "?") { - // We don't want to bloat the generated query with unnecessary parentheses, so we leave them out for things that only evaluated to placeholders anyway. Other simple cases may be added here in the future, though we're limited in what we can *safely and reliably* analyze from already-generated SQL output. + if ($query.query === "?" || simpleColumnNameRegex.test($query.query)) { + // We don't want to bloat the generated query with unnecessary parentheses, so we leave them out for things that only evaluated to placeholders or column names anyway. Other simple cases may be added here in the future, though we're limited in what we can *safely and reliably* analyze from already-generated SQL output. return $query; } else { return $parenthesize($query); @@ -180,12 +183,10 @@ function $maybeParenthesizeAll(nodes) { return nodes.map((node) => $maybeParenthesize(node)); } -function $arrayFrom(items) { - let containsSQLExpressions = Array.isArray(items) && items.some((item) => typeOf(item) === "sqlExpression"); - +function $arrayFrom(items, canBeParameterized) { if (typeOf(items) === "placeholder") { return $handle(items); - } else if (containsSQLExpressions) { + } else if (!canBeParameterized) { // If the list contains SQL expressions, we cannot pass it in as a param at query time; we need to explicitly compile the expressions into the query let $items = items.map((item) => $maybeParenthesize($handle(item))); @@ -288,7 +289,7 @@ let process = { notExpression: function ({ expression }) { return $combine`NOT (${$handle(expression)})`; }, - _arrayOf: function ({ listType, items }) { + _arrayOf: function ({ listType, items, canBeParameterized }) { let $keyword = $object({ query: matchValue(listType, { anyOf: "ANY", @@ -297,7 +298,7 @@ let process = { }); // return $combine`${keyword}(${$maybeParenthesize($arrayFrom(items))})`; - return $combine`${$keyword}(${$arrayFrom(items)})`; + return $combine`${$keyword}(${$arrayFrom(items, canBeParameterized)})`; }, anyOfExpressions: function ({ items }) { if (items.length === 1) { diff --git a/src/internal-operations/array-of.js b/src/internal-operations/array-of.js index fd63011..138e63f 100644 --- a/src/internal-operations/array-of.js +++ b/src/internal-operations/array-of.js @@ -4,6 +4,7 @@ const { validateOptions } = require("@validatem/core"); const required = require("@validatem/required"); const oneOf = require("@validatem/one-of"); const arrayOf = require("@validatem/array-of"); +const isBoolean = require("@validatem/is-boolean"); const node = require("../ast-node"); @@ -11,13 +12,15 @@ module.exports = function (operations) { const isValueExpression = require("../validators/operations/is-value-expression")(operations); return function _arrayOf(_options) { - let { type, items } = validateOptions(arguments, { + let { type, items, canBeParameterized } = validateOptions(arguments, { type: [ required, oneOf([ "anyOf", "allOf" ]) ], + canBeParameterized: [ required, isBoolean ], items: [ required, arrayOf(isValueExpression) ] }); return node({ type: "_arrayOf", + canBeParameterized: canBeParameterized, listType: type, items: items }); diff --git a/src/optimizers/arrayify-predicate-lists.js b/src/optimizers/arrayify-predicate-lists.js index 1c8c7a3..bd63724 100644 --- a/src/optimizers/arrayify-predicate-lists.js +++ b/src/optimizers/arrayify-predicate-lists.js @@ -7,8 +7,11 @@ const syncpipe = require("syncpipe"); const operations = require("../operations"); const internalOperations = require("../internal-operations"); const concat = require("../concat"); +const typeOf = require("../type-of"); const NoChange = require("./util/no-change"); +let parameterizableTypes = [ "literalValue", "placeholder" ]; + // FIXME: Have some sort of internally-cacheable way to find nodes of a certain type? So that different optimizer visitors don't need to filter the list of clauses over and over again... function leftIdentity(left) { @@ -94,6 +97,7 @@ function createHandler(type) { return expressions[0]; } else { let allValues = expressions.map((expression) => expression.condition.expression); + let canBeParameterized = allValues.every((value) => parameterizableTypes.includes(typeOf(value))); return operations.expression({ left: expressions[0].left, @@ -101,6 +105,7 @@ function createHandler(type) { type: conditionType, expression: internalOperations._arrayOf({ type: internalArrayType, + canBeParameterized: canBeParameterized, items: allValues }) })