Skip to content

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

Closed
wants to merge 5 commits into from
Closed

Conversation

mp911de
Copy link
Member

@mp911de mp911de commented Jan 4, 2019

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))


Related ticket: #23.

@mp911de mp911de requested review from schauder and odrotbohm January 9, 2019 10:33
Copy link
Contributor

@schauder schauder left a 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")
Copy link
Contributor

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.

Copy link
Member Author

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"by index" -> "by name"

Copy link
Member Author

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.
Copy link
Contributor

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);
Copy link
Contributor

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.

Copy link
Contributor

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) -> {
Copy link
Contributor

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);
Copy link
Contributor

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() {
Copy link
Contributor

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.

Copy link
Member Author

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";
Copy link
Contributor

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.

Copy link
Member Author

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";
Copy link
Contributor

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?

Copy link
Member Author

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 {
Copy link
Contributor

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.

Copy link
Member Author

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 😬)

mp911de and others added 5 commits January 11, 2019 10:54
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))
Minor doc fixes. Add Javadoc, align license headers to comply with inlined Spring Framework code.
Formatting.
Made Tests simpler and stricter by using `containsExactly`.
Added @test annotation to ignored database specific tests so they actually show up as ignored.
@mp911de mp911de force-pushed the issues/23-named-parameters branch from d4c258d to e7b0572 Compare January 11, 2019 09:54
mp911de added a commit that referenced this pull request Jan 18, 2019
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.
mp911de pushed a commit that referenced this pull request Jan 18, 2019
Formatting.
Made Tests simpler and stricter by using `containsExactly`.
Added @test annotation to ignored database specific tests so they actually show up as ignored.

Original pull request: #47.
mp911de added a commit that referenced this pull request Jan 18, 2019
Original pull request: #47.
@mp911de mp911de added the type: enhancement A general enhancement label Jan 18, 2019
@mp911de mp911de added this to the 1.0 M2 milestone Jan 18, 2019
@mp911de
Copy link
Member Author

mp911de commented Jan 18, 2019

That's merged now.

@mp911de mp911de closed this Jan 18, 2019
@mp911de mp911de deleted the issues/23-named-parameters branch January 18, 2019 13:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants