Repository navigation
Render every value of an In list through a custom specification - #240
Open
simon-mundy wants to merge 1 commit into
Open
simon-mundy wants to merge 1 commit into
simon-mundy wants to merge 1 commit into
Conversation
A custom In/NotIn specification refers to the value set with one placeholder, but the value set renders one value per list item. With more than one value the extra values were dropped from the SQL while still being bound, leaving orphan parameters in prepared statements. Custom specifications are now rewritten so each placeholder covers every value its argument renders (positional placeholders, an empty list as NULL), and a specification that repeats or omits an argument that binds values, or names a missing argument, throws a Predicate InvalidArgumentException.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A custom specification on
In/NotInloses list values and leaves orphan parameters:SELECT "t".* FROM "t" WHERE "id" = ANY (ARRAY['1'])(the2is gone)... ARRAY[:where1], but the container holdswhere1andwhere2, so PDO rejects the parameter countCause
In::getExpressionData()returned the custom specification unchanged with values[identifier, valueSet]. The default specification embeds the value set's own(%s, %s), which lines up withAbstractSql::flattenExpressionValues()expanding theValuesargument into one value per item. A custom specification refers to the whole list with a single%2$s, so the expanded values shift: later ones are dropped from the SQL but still bound.Fix
Predicate\CustomSpecification::expand()rewrites a custom specification so each placeholder covers every value its argument renders. Placeholders can be plain (%s) or positional (%2$s). Output is all positional, so%2$sbecomes%2$s, %3$sfor a two-value list. An empty list becomesNULL. A sub-select stays a single placeholder.%%is left alone.Values,Value,Select) must be referenced exactly once. If a specification repeats or omits one, or names a missing argument, it throws aPredicate\Exception\InvalidArgumentException(SPECIFICATION_ARGUMENT_NOT_REFERENCED_ONCE,SPECIFICATION_ARGUMENT_OUT_OF_RANGE) instead of producing mismatched parameters. Identifiers and literals can be repeated or omitted.In::getExpressionData()applies this only when a custom specification is set. Default output is unchanged.NotIninherits the fix.docs/book/sql/where-having.mddocuments customInspecifications.Other multi-value predicates were checked.
Between/NotBetweentake scalarValuearguments, which are never expanded, so they are not affected.Tests
All new tests are
#[Group('unit')]and use data providers.CustomSpecificationTest: rewriting for 1, 2 and 3 values; plain and positional placeholders; list before the identifier; identifier repeated; escaped%%; empty list; sub-select; aValuesidentifier shifting the list. Also the rejection cases: list repeated, list omitted, bound identifier repeated, out-of-range positional argument, too many plain placeholders.InTest: unprepared SQL and prepared SQL plusgetNamedArray()for custom specifications, including a sub-select whose own parameter binds once.NotInTest: unprepared SQL for 1, 2 and 3 values.InvalidArgumentExceptionTest: the two new named constructors.phpunit --testsuite "unit test",mago format --check,mago lintandmago analyzeall pass.In,CustomSpecificationand the predicateInvalidArgumentExceptionhave 100% line coverage.