-
Notifications
You must be signed in to change notification settings - Fork 132
Add support for named parameters #47
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
Conversation
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.
I added one more polishing commit.
Please consider my comments.
@@ -104,7 +104,7 @@ Defining such a query is a matter of declaring a method on the repository interf | |||
---- | |||
public interface PersonRepository extends ReactiveCrudRepository<Person, Long> { | |||
|
|||
@Query("SELECT * FROM person WHERE lastname = $1") | |||
@Query("SELECT * FROM person WHERE lastname = :lastname") | |||
Flux<Person> findByLastname(String lastname); <1> | |||
|
|||
@Query("SELECT firstname, lastname FROM person WHERE lastname = $1") |
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.
Is the $1
syntax left in there an oversight or an attempt to demonstrate database dependent options?
If the latter I think we need an explanatory note to that extent.
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.
It's intentionally there, to occasionally use native bind markers. See the callout description:
The annotated query uses native bind markers, which are Postgres bind markers in this example.
==== | ||
|
||
NOTE: R2DBC repositories do not support query derivation. | ||
|
||
NOTE: R2DBC repositories require native parameter bind markers that are bound by index. | ||
NOTE: R2DBC repositories bind parameters to placeholders by index. |
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.
"by index" -> "by name"
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.
That's perhaps misleading in this context. It refers to R2DBC SPI Statement.bind(index).
.R2DBC Native Bind Markers | ||
**** | ||
R2DBC uses database-native bind markers that depend on the actual database. | ||
If you are familiar with JDBC, then you're also familiar with `?` (question mark) bind markers. |
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.
While technically correct I think this is really easy to misunderstand it to mean that we support ? markers as well.
I'd structure it like this:
R2DBC supports native bind markers.
An example are postgres bind markers ... Another example are SQL Server markers ...
This is different from JDBC which requires ? as bind markers.
Spring Data R2DBC allows you to use native bind markers or named bind markers with the :name syntax.
* @see NamedParameterExpander#enabled() | ||
* @see NamedParameterExpander#disabled() | ||
*/ | ||
Builder namedParameters(NamedParameterExpander namedParameters); |
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.
I don't like the shortening of NamedParameterExpander
to namedParameters
. The last name part is what it actually is so removing that seems to drastically change the intuitive meaning from something expanding SQL statements to something containing parameters.
If we really want to shorten I'd vote for dropping the named
part.
Also applies to several other places in the code.
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.
I just saw what you did with enabled
/ disabled
.
For this to work nicely I'd be willing to keep the method name here, or change it to namedParameterExpansion
.
I'd still like to change the parameter name and field names from namedParameters
to namedParametersExpander
.
|
||
Statement<?> statement = it.createStatement(operation.toQuery()); | ||
|
||
byName.forEach((name, o) -> { |
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.
doing the binding by name inline but calling a method for binding by index looks unpleasant unsymmetrically.
} | ||
}); | ||
|
||
doBind(statement, Collections.emptyMap(), byIndex); |
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.
At the very least we should lose the second parameter of doBind
since the only usage is passing an empty map and introduce a second doBind
for the named parameters.
@@ -91,7 +92,9 @@ public void before() { | |||
/** | |||
* Get a parameterized {@code INSERT INTO legoset} statement setting id, name, and manual values. | |||
*/ | |||
protected abstract String getInsertIntoLegosetStatement(); | |||
protected String getInsertIntoLegosetStatement() { |
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.
I find it super weird that binding by index can be used for named bind parameters (executeInsertInManagedTransaction
).
I wonder if this causes problems when native and named parameters are used.
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.
As of now, drivers support both variants. Indexed by the order of appearance and named by using the binding marker itself.
public void parseSqlStatementWithBracketDelimitedParameterNames() { | ||
|
||
String expectedSql = "select foo from bar where baz = b$1$2z"; | ||
String sql = "select foo from bar where baz = b:{p1}:{p2}z"; |
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.
What is the purpose of this feature?
And we should document it. I guess it is comming from NamedParameterJdbcTemplate
but I couldn't find any documentation either.
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.
It's originating in Spring's named parameter support to allow parameter declaration within a string.
assertThat(psql.getTotalParameterCount()).isEqualTo(4); | ||
assertThat(psql.getNamedParameterCount()).isEqualTo(3); | ||
|
||
String sql2 = "xxx &a yyyy ? zzzzz"; |
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.
Does this mean we also support ?
as parameter marker? May I veto that?
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.
That's a left-over. We should get rid of it as this is likely to conflict with MySQL.
* @author Juergen Hoeller | ||
* @author Mark Paluch | ||
*/ | ||
abstract class NamedParameterUtils { |
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.
I guess this works, is well tested in its incarnation from Spring JDBC and probably fast and efficient.
It makes me nervous though that we one day might want to support SpELs or similar.
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.
We included all the test cases and fixed bugs 🙃 (and not discovered bugs as well 😬)
DatabaseClient now supports named parameters prefixed with a colon such as :name in addition to database-native bind markers. Named parameters thus are supported in annotated repository query methods which also increases portability of queries across database vendors. Named parameter support unrolls collection arguments to reduce the need for argument-specific SQL statements: db.execute() .sql("SELECT id, name, state FROM table WHERE age IN (:ages)") .bind("ages", Arrays.asList(35, 50)); Results in a query: SELECT id, name, state FROM table WHERE age IN (35, 50) Collection arguments containing nested object arrays can be used to use select lists: List<Object[]> tuples = new ArrayList<>(); tuples.add(new Object[] {"John", 35}); tuples.add(new Object[] {"Ann", 50}); db.execute() .sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)") .bind("tuples", tuples); translates to: SELECT id, name, state FROM table WHERE (name, age) IN (('John', 35), ('Ann', 50))
Formatting. Made Tests simpler and stricter by using `containsExactly`. Added @test annotation to ignored database specific tests so they actually show up as ignored.
d4c258d
to
e7b0572
Compare
DatabaseClient now supports named parameters prefixed with a colon such as :name in addition to database-native bind markers. Named parameters thus are supported in annotated repository query methods which also increases portability of queries across database vendors. Named parameter support unrolls collection arguments to reduce the need for argument-specific SQL statements: db.execute() .sql("SELECT id, name, state FROM table WHERE age IN (:ages)") .bind("ages", Arrays.asList(35, 50)); Results in a query: SELECT id, name, state FROM table WHERE age IN (35, 50) Collection arguments containing nested object arrays can be used to use select lists: List<Object[]> tuples = new ArrayList<>(); tuples.add(new Object[] {"John", 35}); tuples.add(new Object[] {"Ann", 50}); db.execute() .sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)") .bind("tuples", tuples); translates to: SELECT id, name, state FROM table WHERE (name, age) IN (('John', 35), ('Ann', 50)) Original pull request: #47.
That's merged now. |
DatabaseClient
now supports named parameters prefixed with a colon such as:name
in addition to database-native bind markers. Named parameters thus are supported in annotated repository query methods which also increases portability of queries across database vendors.Named parameter support unrolls collection arguments to reduce the need for argument-specific SQL statements:
Results in a query:
SELECT id, name, state FROM table WHERE age IN (35, 50)
Collection arguments containing nested object arrays can be used to use select lists:
translates to:
SELECT id, name, state FROM table WHERE (name, age) IN (('John', 35), ('Ann', 50))
Related ticket: #23.