-
Notifications
You must be signed in to change notification settings - Fork 695
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: EXPOSED-547 idParam() registers composite id value with a single placeholder #2242
Conversation
override fun toQueryBuilder(queryBuilder: QueryBuilder): Unit = queryBuilder { registerArgument(sqlType, value) } | ||
internal val compositeValue: CompositeID? = (value as? EntityID<*>)?.value as? CompositeID | ||
|
||
override fun toQueryBuilder(queryBuilder: QueryBuilder) { | ||
queryBuilder { | ||
compositeValue?.let { | ||
it.values.entries.appendTo { (column, value) -> | ||
registerArgument(column.columnType, value) | ||
} | ||
} ?: registerArgument(sqlType, value) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively we could add a new class like CompositeQueryParameter
that would be very similar but might make some branches cleaner. But I couldn't think of any other applications for it so I went this way to keep the logic together.
I also didn't want to open QueryParameter
just for an override.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I understand that, I tried to write something like CompositeQueryParameter also (during review of PR), and it does not look significantly better. I also tried to extend it not from QueryParameter
but from Expression
, what it also does not make it much better.
fun <T : String?> Expression<T>.isNullOrEmpty() = if (this is Column<*> && isEntityIdentifier()) { | ||
table.mapIdOperator(::IsNullOp) | ||
} else { | ||
IsNullOp(this) | ||
}.or { [email protected]() eq 0 } | ||
fun <T : String?> Expression<T>.isNullOrEmpty() = IsNullOp(this).or { [email protected]() eq 0 } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure why this logic was added, as the function signature means it's not possible to call isNullOrEmpty()
on an IdTable.id
, so the first branch will always be false. charLength()
also cannot be invoked on IdTable.id
.
assertEquals("(${fullIdentity(Towns.zipCode)} = ?) AND (${fullIdentity(Towns.name)} = ?)", whereClause1) | ||
assertEquals(4, query1.single()[Towns.population]) | ||
|
||
val query2 = Towns.selectAll().where { idParam(townAId, Towns.id).isNotNull() } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be honest, I don't finally understand what this statement means.
It looks like we want to query all the Towns
that have non null id (that can not be null, because it's primary key). But even if we want to get all the entries with non null id (composite id), why do we need actual townAId
value.
As I can see, the condition where { idParam(townAId, Towns.id).isNotNull() }
generates the following sql SELECT towns.zip_code, towns."name", towns.population FROM towns WHERE (towns.zip_code IS NOT NULL) AND (towns."name" IS NOT NULL)
If I replace it with where { Towns.id.isNotNull() }
I get the same SQL.
I understand that it's the test for idParam
, but this part may confuse in terms of how idParam
is used.
It's not the problem of this PR, but I miss the reason fot At the first moment I had a thought that For example in The first one: The second one is more interesting: So I'd say that the main question is do we really need this |
Yup, I agree that tests should be reflective of usage so I'll drop this and change the existing one to actually use You're correct in that statements are parameterized by default, so using tester.selectAll().where { tester.id eq idParam(id1, tester.id) }
// above is equivalent to below
tester.selectAll().where { tester.id eq id1 }
// the example below is a bit trivial, but this won't compile
tester.select(coalesce(tester.id, id1)
// so we'd have to do this
tester.select(coalesce(tester.id, idParam(id1, tester.id)) This is the only example instance that I can think of when I'd ever recommend using it to a user. |
Current idParam registers the value from an entity id column with a single placeholder marker. This fix ensures that a placeholder is registered for every component value stored by CompositeID and that this argument registeration is overriden and handled by the mapping operator as needed.
… placeholder Replace creation of new QueryParameters with direct calls to registerArgument()
… placeholder - Refactor test usage of idParam()
f2c2cff
to
2565626
Compare
… placeholder - Fix type mismatch in tests due to alias.
Description
Summary of the change:
idParam()
now registers each value stored in a composite key value.Detailed description:
Using
idParam()
with aColumn<EntityID<CompositeID>>
only invokesregisterArgument()
once for the entire composite id value, leading to only 1 placeholder '?' being appended to the statement.QueryParameter.toQueryBuilder()
now checks whether the value to parameterize is aCompositeID
and, if so, callsregisterArgument()
for every component value.Using
idParam()
with equality or null comparison operators is also now possible.Internal property
compositeValue
has been added to theQueryParameter
class, both as a flag (to differentiate between otherQueryParameter
) and to store theCompositeID
instead of always casting first.Type of Change
Please mark the relevant options with an "X":
Affected databases:
Checklist
Related Issues
EXPOSED-547