#138 - Allow multiple usages of the same named parameter.

We now allow multiple usages of the same named parameter if the underlying database supports identifiable placeholders.

SELECT * FROM person where name = :id or lastname = :id

gets translated to

SELECT * FROM person where name = $1 or lastname = $1
This commit is contained in:
Mark Paluch
2019-07-15 16:39:27 +02:00
parent 7403651ba3
commit 929c4dfab4
6 changed files with 276 additions and 34 deletions

View File

@@ -152,6 +152,18 @@ public abstract class AbstractDatabaseClientIntegrationTests extends R2dbcIntegr
}).verifyComplete();
}
@Test // gh-2
public void executeSelectNamedParameters() {
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
databaseClient.execute("SELECT id, name, manual FROM legoset WHERE name = :name or manual = :name") //
.bind("name", "unknown").as(LegoSet.class) //
.fetch().all() //
.as(StepVerifier::create) //
.verifyComplete();
}
@Test // gh-2
public void insert() {

View File

@@ -19,7 +19,10 @@ import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.Test;
@@ -27,6 +30,7 @@ import org.springframework.data.r2dbc.dialect.BindMarkersFactory;
import org.springframework.data.r2dbc.dialect.BindTarget;
import org.springframework.data.r2dbc.dialect.PostgresDialect;
import org.springframework.data.r2dbc.dialect.SqlServerDialect;
import org.springframework.data.r2dbc.mapping.SettableValue;
/**
* Unit tests for {@link NamedParameterUtils}.
@@ -111,7 +115,7 @@ public class NamedParameterUtilsUnitTests {
String sql1 = "/*+ HINT */ xxx /* comment ? */ :a yyyy :b :c :a zzzzz -- :xx XX\n";
ParsedSql psql1 = NamedParameterUtils.parseSqlStatement(sql1);
assertThat(expand(psql1)).isEqualTo("/*+ HINT */ xxx /* comment ? */ $1 yyyy $2 $3 $4 zzzzz -- :xx XX\n");
assertThat(expand(psql1)).isEqualTo("/*+ HINT */ xxx /* comment ? */ $1 yyyy $2 $3 $1 zzzzz -- :xx XX\n");
MapBindParameterSource paramMap = new MapBindParameterSource(new HashMap<>());
paramMap.addValue("a", "a");
@@ -120,7 +124,7 @@ public class NamedParameterUtilsUnitTests {
String sql2 = "/*+ HINT */ xxx /* comment ? */ :a yyyy :b :c :a zzzzz -- :xx XX";
ParsedSql psql2 = NamedParameterUtils.parseSqlStatement(sql2);
assertThat(expand(psql2)).isEqualTo("/*+ HINT */ xxx /* comment ? */ $1 yyyy $2 $3 $4 zzzzz -- :xx XX");
assertThat(expand(psql2)).isEqualTo("/*+ HINT */ xxx /* comment ? */ $1 yyyy $2 $3 $1 zzzzz -- :xx XX");
}
@Test // gh-23
@@ -283,6 +287,129 @@ public class NamedParameterUtilsUnitTests {
assertThat(psql2.getParameterNames()).containsExactly("xxx");
}
@Test // gh-138
public void shouldAllowParsingMultipleUseOfParameter() {
String sql = "SELECT * FROM person where name = :id or lastname = :id";
ParsedSql parsed = NamedParameterUtils.parseSqlStatement(sql);
assertThat(parsed.getTotalParameterCount()).isEqualTo(2);
assertThat(parsed.getNamedParameterCount()).isEqualTo(1);
assertThat(parsed.getParameterNames()).containsExactly("id", "id");
}
@Test // gh-138
public void multipleEqualParameterReferencesBindsValueOnce() {
String sql = "SELECT * FROM person where name = :id or lastname = :id";
BindMarkersFactory factory = BindMarkersFactory.indexed("$", 0);
PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(sql, factory,
new MapBindParameterSource(Collections.singletonMap("id", SettableValue.from("foo"))));
assertThat(operation.toQuery()).isEqualTo("SELECT * FROM person where name = $0 or lastname = $0");
operation.bindTo(new BindTarget() {
@Override
public void bind(Object identifier, Object value) {
throw new UnsupportedOperationException();
}
@Override
public void bind(int index, Object value) {
assertThat(index).isEqualTo(0);
assertThat(value).isEqualTo("foo");
}
@Override
public void bindNull(Object identifier, Class<?> type) {
throw new UnsupportedOperationException();
}
@Override
public void bindNull(int index, Class<?> type) {
throw new UnsupportedOperationException();
}
});
}
@Test // gh-138
public void multipleEqualParameterReferencesForAnonymousMarkersBindsValueMultipleTimes() {
String sql = "SELECT * FROM person where name = :id or lastname = :id";
BindMarkersFactory factory = BindMarkersFactory.anonymous("?");
PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(sql, factory,
new MapBindParameterSource(Collections.singletonMap("id", SettableValue.from("foo"))));
assertThat(operation.toQuery()).isEqualTo("SELECT * FROM person where name = ? or lastname = ?");
Map<Integer, Object> bindValues = new LinkedHashMap<>();
operation.bindTo(new BindTarget() {
@Override
public void bind(Object identifier, Object value) {
throw new UnsupportedOperationException();
}
@Override
public void bind(int index, Object value) {
bindValues.put(index, value);
}
@Override
public void bindNull(Object identifier, Class<?> type) {
throw new UnsupportedOperationException();
}
@Override
public void bindNull(int index, Class<?> type) {
throw new UnsupportedOperationException();
}
});
assertThat(bindValues).hasSize(2).containsEntry(0, "foo").containsEntry(1, "foo");
}
@Test // gh-138
public void multipleEqualParameterReferencesBindsNullOnce() {
String sql = "SELECT * FROM person where name = :id or lastname = :id";
BindMarkersFactory factory = BindMarkersFactory.indexed("$", 0);
PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(sql, factory,
new MapBindParameterSource(Collections.singletonMap("id", SettableValue.empty(String.class))));
assertThat(operation.toQuery()).isEqualTo("SELECT * FROM person where name = $0 or lastname = $0");
operation.bindTo(new BindTarget() {
@Override
public void bind(Object identifier, Object value) {
throw new UnsupportedOperationException();
}
@Override
public void bind(int index, Object value) {
throw new UnsupportedOperationException();
}
@Override
public void bindNull(Object identifier, Class<?> type) {
throw new UnsupportedOperationException();
}
@Override
public void bindNull(int index, Class<?> type) {
assertThat(index).isEqualTo(0);
assertThat(type).isEqualTo(String.class);
}
});
}
private String expand(ParsedSql sql) {
return NamedParameterUtils.substituteNamedParameters(sql, BIND_MARKERS, new MapBindParameterSource()).toQuery();
}

View File

@@ -55,6 +55,11 @@ public class PostgresDatabaseClientIntegrationTests extends AbstractDatabaseClie
@Override
public void insert() {}
@Ignore("Postgres considers multiple references to the same parameter as multiple bindings (where name = $1 OR lastname = $1)")
@Test
@Override
public void executeSelectNamedParameters() {}
@Ignore("Adding RETURNING * lets Postgres report 0 affected rows.")
@Test
@Override