Skip to content

Commit c241cd9

Browse files
mp911deschauder
authored andcommitted
#56 - Use Statement Builder API for SELECT statements.
Original pull request: #66.
1 parent 2cefdc8 commit c241cd9

File tree

6 files changed

+154
-183
lines changed

6 files changed

+154
-183
lines changed

src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java

+4-13
Original file line numberDiff line numberDiff line change
@@ -703,17 +703,9 @@ public FetchSpec<Map<String, Object>> fetch() {
703703

704704
private <R> FetchSpec<R> exchange(BiFunction<Row, RowMetadata, R> mappingFunction) {
705705

706-
Set<String> columns;
706+
String select = dataAccessStrategy.select(table, new LinkedHashSet<>(this.projectedFields), sort, page);
707707

708-
if (this.projectedFields.isEmpty()) {
709-
columns = Collections.singleton("*");
710-
} else {
711-
columns = new LinkedHashSet<>(this.projectedFields);
712-
}
713-
714-
QueryOperation select = dataAccessStrategy.select(table, columns, sort, page);
715-
716-
return execute(select.toQuery(), mappingFunction);
708+
return execute(select, mappingFunction);
717709
}
718710

719711
@Override
@@ -797,11 +789,10 @@ private <R> FetchSpec<R> exchange(BiFunction<Row, RowMetadata, R> mappingFunctio
797789
} else {
798790
columns = this.projectedFields;
799791
}
800-
Sort sortToUse = sort.isSorted() ? dataAccessStrategy.getMappedSort(typeToRead, sort) : Sort.unsorted();
801792

802-
QueryOperation select = dataAccessStrategy.select(table, new LinkedHashSet<>(columns), sortToUse, page);
793+
String select = dataAccessStrategy.select(table, new LinkedHashSet<>(columns), sort, page);
803794

804-
return execute(select.get(), mappingFunction);
795+
return execute(select, mappingFunction);
805796
}
806797

807798
@Override

src/main/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategy.java

+32-74
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.LinkedHashMap;
2727
import java.util.List;
2828
import java.util.Map;
29+
import java.util.OptionalLong;
2930
import java.util.Set;
3031
import java.util.function.BiFunction;
3132
import java.util.function.Function;
@@ -43,16 +44,20 @@
4344
import org.springframework.data.r2dbc.dialect.BindMarkers;
4445
import org.springframework.data.r2dbc.dialect.BindMarkersFactory;
4546
import org.springframework.data.r2dbc.dialect.Dialect;
46-
import org.springframework.data.r2dbc.dialect.LimitClause;
47-
import org.springframework.data.r2dbc.dialect.LimitClause.Position;
4847
import org.springframework.data.r2dbc.function.convert.EntityRowMapper;
4948
import org.springframework.data.r2dbc.function.convert.R2dbcCustomConversions;
5049
import org.springframework.data.r2dbc.function.convert.SettableValue;
50+
import org.springframework.data.r2dbc.support.StatementRenderUtil;
5151
import org.springframework.data.relational.core.conversion.BasicRelationalConverter;
5252
import org.springframework.data.relational.core.conversion.RelationalConverter;
5353
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
5454
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
5555
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
56+
import org.springframework.data.relational.core.sql.Expression;
57+
import org.springframework.data.relational.core.sql.OrderByField;
58+
import org.springframework.data.relational.core.sql.SelectBuilder.SelectFromAndOrderBy;
59+
import org.springframework.data.relational.core.sql.StatementBuilder;
60+
import org.springframework.data.relational.core.sql.Table;
5661
import org.springframework.data.util.TypeInformation;
5762
import org.springframework.lang.Nullable;
5863
import org.springframework.util.Assert;
@@ -312,94 +317,47 @@ public BindableOperation insertAndReturnGeneratedKeys(String table, Set<String>
312317
* @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#select(java.lang.String, java.util.Set, org.springframework.data.domain.Sort, org.springframework.data.domain.Pageable)
313318
*/
314319
@Override
315-
public QueryOperation select(String table, Set<String> columns, Sort sort, Pageable page) {
320+
public String select(String table, Set<String> columns, Sort sort, Pageable page) {
316321

317-
StringBuilder selectBuilder = new StringBuilder();
322+
Table tableToUse = Table.create(table);
318323

319-
selectBuilder.append("SELECT").append(' ') //
320-
.append(StringUtils.collectionToDelimitedString(columns, ", ")).append(' ') //
321-
.append("FROM").append(' ').append(table);
324+
Collection<? extends Expression> selectList;
322325

323-
if (sort.isSorted()) {
324-
selectBuilder.append(' ').append("ORDER BY").append(' ').append(getSortClause(sort));
326+
if (columns.isEmpty()) {
327+
selectList = Collections.singletonList(tableToUse.asterisk());
328+
} else {
329+
selectList = tableToUse.columns(columns);
325330
}
326331

327-
if (page.isPaged()) {
328-
329-
LimitClause limitClause = dialect.limit();
330-
331-
if (limitClause.getClausePosition() == Position.END) {
332-
333-
selectBuilder.append(' ').append(limitClause.getClause(page.getPageSize(), page.getOffset()));
334-
}
335-
}
336-
337-
return selectBuilder::toString;
338-
}
339-
340-
private StringBuilder getSortClause(Sort sort) {
341-
342-
StringBuilder sortClause = new StringBuilder();
332+
SelectFromAndOrderBy selectBuilder = StatementBuilder.select(selectList).from(table)
333+
.orderBy(createOrderByFields(tableToUse, sort));
334+
OptionalLong limit = OptionalLong.empty();
335+
OptionalLong offset = OptionalLong.empty();
343336

344-
for (Order order : sort) {
345-
346-
if (sortClause.length() != 0) {
347-
sortClause.append(',').append(' ');
348-
}
349-
350-
sortClause.append(order.getProperty()).append(' ').append(order.getDirection().isAscending() ? "ASC" : "DESC");
337+
if (page.isPaged()) {
338+
limit = OptionalLong.of(page.getPageSize());
339+
offset = OptionalLong.of(page.getOffset());
351340
}
352-
return sortClause;
353-
}
354-
355-
/*
356-
* (non-Javadoc)
357-
* @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#selectById(java.lang.String, java.util.Set, java.lang.String)
358-
*/
359-
@Override
360-
public BindIdOperation selectById(String table, Set<String> columns, String idColumn) {
361-
362-
return new DefaultBindIdOperation(dialect.getBindMarkersFactory().create(), marker -> {
363341

364-
String columnClause = StringUtils.collectionToDelimitedString(columns, ", ");
365-
366-
return String.format("SELECT %s FROM %s WHERE %s = %s", columnClause, table, idColumn, marker.getPlaceholder());
367-
});
342+
return StatementRenderUtil.render(selectBuilder.build(), limit, offset, this.dialect);
368343
}
369344

370-
/*
371-
* (non-Javadoc)
372-
* @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#selectById(java.lang.String, java.util.Set, java.lang.String, int)
373-
*/
374-
@Override
375-
public BindIdOperation selectById(String table, Set<String> columns, String idColumn, int limit) {
376-
377-
LimitClause limitClause = dialect.limit();
345+
private Collection<? extends OrderByField> createOrderByFields(Table table, Sort sortToUse) {
378346

379-
return new DefaultBindIdOperation(dialect.getBindMarkersFactory().create(), marker -> {
347+
List<OrderByField> fields = new ArrayList<>();
380348

381-
String columnClause = StringUtils.collectionToDelimitedString(columns, ", ");
349+
for (Order order : sortToUse) {
382350

383-
if (limitClause.getClausePosition() == Position.END) {
351+
OrderByField orderByField = OrderByField.from(table.column(order.getProperty()));
384352

385-
return String.format("SELECT %s FROM %s WHERE %s = %s ORDER BY %s %s", columnClause, table, idColumn,
386-
marker.getPlaceholder(), idColumn, limitClause.getClause(limit));
353+
if (order.getDirection() != null) {
354+
fields.add(order.isAscending() ? orderByField.asc() : orderByField.desc());
355+
} else {
356+
fields.add(orderByField);
387357
}
358+
}
388359

389-
throw new UnsupportedOperationException(
390-
String.format("Limit clause position %s not supported!", limitClause.getClausePosition()));
391-
});
392-
}
393-
394-
/*
395-
* (non-Javadoc)
396-
* @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#selectByIdIn(java.lang.String, java.util.Set, java.lang.String)
397-
*/
398-
@Override
399-
public BindIdOperation selectByIdIn(String table, Set<String> columns, String idColumn) {
400-
401-
String query = String.format("SELECT %s FROM %s", StringUtils.collectionToDelimitedString(columns, ", "), table);
402-
return new DefaultBindIdIn(dialect.getBindMarkersFactory().create(), query, idColumn);
360+
return fields;
403361
}
404362

405363
/*

src/main/java/org/springframework/data/r2dbc/function/ReactiveDataAccessStrategy.java

+1-36
Original file line numberDiff line numberDiff line change
@@ -108,42 +108,7 @@ public interface ReactiveDataAccessStrategy {
108108
* @param page
109109
* @return
110110
*/
111-
QueryOperation select(String table, Set<String> columns, Sort sort, Pageable page);
112-
113-
/**
114-
* Create a {@code SELECT … WHERE id = ?} operation for the given {@code table} using {@code columns} to project and
115-
* {@code idColumn}.
116-
*
117-
* @param table the table to insert data to.
118-
* @param columns columns to return.
119-
* @param idColumn name of the primary key.
120-
* @return
121-
*/
122-
BindIdOperation selectById(String table, Set<String> columns, String idColumn);
123-
124-
/**
125-
* Create a {@code SELECT … WHERE id = ?} operation for the given {@code table} using {@code columns} to project and
126-
* {@code idColumn} applying a limit (TOP, LIMIT, …).
127-
*
128-
* @param table the table to insert data to.
129-
* @param columns columns to return.
130-
* @param idColumn name of the primary key.
131-
* @param limit number of rows to return.
132-
* @return
133-
*/
134-
BindIdOperation selectById(String table, Set<String> columns, String idColumn, int limit);
135-
136-
/**
137-
* Create a {@code SELECT … WHERE id IN (?)} operation for the given {@code table} using {@code columns} to project
138-
* and {@code idColumn}. The actual {@link BindableOperation#toQuery() query} string depends on
139-
* {@link BindIdOperation#bindIds(Statement, Iterable) bound parameters}.
140-
*
141-
* @param table the table to insert data to.
142-
* @param columns columns to return.
143-
* @param idColumn name of the primary key.
144-
* @return
145-
*/
146-
BindIdOperation selectByIdIn(String table, Set<String> columns, String idColumn);
111+
String select(String table, Set<String> columns, Sort sort, Pageable page);
147112

148113
/**
149114
* Create a {@code UPDATE … SET … WHERE id = ?} operation for the given {@code table} updating {@code columns} and

src/main/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepository.java

+52-18
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,32 @@
2121
import reactor.core.publisher.Flux;
2222
import reactor.core.publisher.Mono;
2323

24-
import java.util.Collections;
24+
import java.util.ArrayList;
2525
import java.util.LinkedHashSet;
26+
import java.util.List;
2627
import java.util.Map;
2728
import java.util.Set;
2829
import java.util.function.BiConsumer;
2930

3031
import org.reactivestreams.Publisher;
32+
33+
import org.springframework.data.r2dbc.dialect.BindMarker;
34+
import org.springframework.data.r2dbc.dialect.BindMarkers;
3135
import org.springframework.data.r2dbc.function.BindIdOperation;
3236
import org.springframework.data.r2dbc.function.BindableOperation;
3337
import org.springframework.data.r2dbc.function.DatabaseClient;
3438
import org.springframework.data.r2dbc.function.DatabaseClient.GenericExecuteSpec;
3539
import org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy;
3640
import org.springframework.data.r2dbc.function.convert.MappingR2dbcConverter;
3741
import org.springframework.data.r2dbc.function.convert.SettableValue;
42+
import org.springframework.data.relational.core.sql.Conditions;
43+
import org.springframework.data.relational.core.sql.Expression;
44+
import org.springframework.data.relational.core.sql.Functions;
45+
import org.springframework.data.relational.core.sql.SQL;
46+
import org.springframework.data.relational.core.sql.Select;
47+
import org.springframework.data.relational.core.sql.StatementBuilder;
48+
import org.springframework.data.relational.core.sql.Table;
49+
import org.springframework.data.relational.core.sql.render.SqlRenderer;
3850
import org.springframework.data.relational.repository.query.RelationalEntityInformation;
3951
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
4052
import org.springframework.util.Assert;
@@ -118,13 +130,17 @@ public Mono<T> findById(ID id) {
118130

119131
Set<String> columns = new LinkedHashSet<>(accessStrategy.getAllColumns(entity.getJavaType()));
120132
String idColumnName = getIdColumnName();
121-
BindIdOperation select = accessStrategy.selectById(entity.getTableName(), columns, idColumnName);
122133

123-
GenericExecuteSpec sql = databaseClient.execute().sql(select);
124-
BindSpecAdapter<GenericExecuteSpec> wrapper = BindSpecAdapter.create(sql);
125-
select.bindId(wrapper, id);
134+
BindMarkers bindMarkers = accessStrategy.getBindMarkersFactory().create();
135+
BindMarker bindMarker = bindMarkers.next("id");
126136

127-
return wrapper.getBoundOperation().as(entity.getJavaType()) //
137+
Table table = Table.create(entity.getTableName());
138+
Select select = StatementBuilder.select(table.columns(columns)).from(table)
139+
.where(Conditions.isEqual(table.column(idColumnName), SQL.bindMarker(bindMarker.getPlaceholder()))).build();
140+
141+
return databaseClient.execute().sql(SqlRenderer.render(select)) //
142+
.bind(0, id) //
143+
.as(entity.getJavaType()) //
128144
.fetch() //
129145
.one();
130146
}
@@ -146,14 +162,16 @@ public Mono<Boolean> existsById(ID id) {
146162
Assert.notNull(id, "Id must not be null!");
147163

148164
String idColumnName = getIdColumnName();
149-
BindIdOperation select = accessStrategy.selectById(entity.getTableName(), Collections.singleton(idColumnName),
150-
idColumnName, 10);
151165

152-
GenericExecuteSpec sql = databaseClient.execute().sql(select);
153-
BindSpecAdapter<GenericExecuteSpec> wrapper = BindSpecAdapter.create(sql);
154-
select.bindId(wrapper, id);
166+
BindMarkers bindMarkers = accessStrategy.getBindMarkersFactory().create();
167+
BindMarker bindMarker = bindMarkers.next("id");
155168

156-
return wrapper.getBoundOperation().as(entity.getJavaType()) //
169+
Table table = Table.create(entity.getTableName());
170+
Select select = StatementBuilder.select(table.column(idColumnName)).from(table)
171+
.where(Conditions.isEqual(table.column(idColumnName), SQL.bindMarker(bindMarker.getPlaceholder()))).build();
172+
173+
return databaseClient.execute().sql(SqlRenderer.render(select)) //
174+
.bind(0, id) //
157175
.map((r, md) -> r) //
158176
.first() //
159177
.hasElement();
@@ -202,12 +220,26 @@ public Flux<T> findAllById(Publisher<ID> idPublisher) {
202220

203221
Set<String> columns = new LinkedHashSet<>(accessStrategy.getAllColumns(entity.getJavaType()));
204222
String idColumnName = getIdColumnName();
205-
BindIdOperation select = accessStrategy.selectByIdIn(entity.getTableName(), columns, idColumnName);
206223

207-
BindSpecAdapter<GenericExecuteSpec> wrapper = BindSpecAdapter.create(databaseClient.execute().sql(select));
208-
select.bindIds(wrapper, ids);
224+
BindMarkers bindMarkers = accessStrategy.getBindMarkersFactory().create();
225+
226+
List<Expression> markers = new ArrayList<>();
209227

210-
return wrapper.getBoundOperation().as(entity.getJavaType()).fetch().all();
228+
for (int i = 0; i < ids.size(); i++) {
229+
markers.add(SQL.bindMarker(bindMarkers.next("id").getPlaceholder()));
230+
}
231+
232+
Table table = Table.create(entity.getTableName());
233+
Select select = StatementBuilder.select(table.columns(columns)).from(table)
234+
.where(Conditions.in(table.column(idColumnName), markers)).build();
235+
236+
GenericExecuteSpec executeSpec = databaseClient.execute().sql(SqlRenderer.render(select));
237+
238+
for (int i = 0; i < ids.size(); i++) {
239+
executeSpec = executeSpec.bind(i, ids.get(i));
240+
}
241+
242+
return executeSpec.as(entity.getJavaType()).fetch().all();
211243
});
212244
}
213245

@@ -217,8 +249,10 @@ public Flux<T> findAllById(Publisher<ID> idPublisher) {
217249
@Override
218250
public Mono<Long> count() {
219251

220-
return databaseClient.execute()
221-
.sql(String.format("SELECT COUNT(%s) FROM %s", getIdColumnName(), entity.getTableName())) //
252+
Table table = Table.create(entity.getTableName());
253+
Select select = StatementBuilder.select(Functions.count(table.column(getIdColumnName()))).from(table).build();
254+
255+
return databaseClient.execute().sql(SqlRenderer.render(select)) //
222256
.map((r, md) -> r.get(0, Long.class)) //
223257
.first() //
224258
.defaultIfEmpty(0L);

0 commit comments

Comments
 (0)