Skip to content

Commit 9669497

Browse files
committed
#220 - Refactor StatementMapper.
Use limit/offset instead of Page and accept Expression objects to declare a select list. Use SqlIdentifier in Update, Query, Criteria and fluent API.
1 parent 9b6c2f0 commit 9669497

21 files changed

+417
-209
lines changed

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

+6-6
Original file line numberDiff line numberDiff line change
@@ -818,8 +818,8 @@ private <R> FetchSpec<R> exchange(BiFunction<Row, RowMetadata, R> mappingFunctio
818818

819819
StatementMapper mapper = dataAccessStrategy.getStatementMapper();
820820

821-
StatementMapper.SelectSpec selectSpec = mapper.createSelect(this.table).withProjection(this.projectedFields)
822-
.withSort(this.sort).withPage(this.page);
821+
StatementMapper.SelectSpec selectSpec = mapper.createSelect(this.table)
822+
.withProjection(this.projectedFields.toArray(new SqlIdentifier[0])).withSort(this.sort).withPage(this.page);
823823

824824
if (this.criteria != null) {
825825
selectSpec = selectSpec.withCriteria(this.criteria);
@@ -931,8 +931,8 @@ private <R> FetchSpec<R> exchange(BiFunction<Row, RowMetadata, R> mappingFunctio
931931
columns = this.projectedFields;
932932
}
933933

934-
StatementMapper.SelectSpec selectSpec = mapper.createSelect(this.table).withProjection(columns)
935-
.withPage(this.page).withSort(this.sort);
934+
StatementMapper.SelectSpec selectSpec = mapper.createSelect(this.table)
935+
.withProjection(columns.toArray(new SqlIdentifier[0])).withPage(this.page).withSort(this.sort);
936936

937937
if (this.criteria != null) {
938938
selectSpec = selectSpec.withCriteria(this.criteria);
@@ -1038,7 +1038,7 @@ private <R> FetchSpec<R> exchange(BiFunction<Row, RowMetadata, R> mappingFunctio
10381038
StatementMapper.InsertSpec insert = mapper.createInsert(this.table);
10391039

10401040
for (SqlIdentifier column : this.byName.keySet()) {
1041-
insert = insert.withColumn(dataAccessStrategy.toSql(column), this.byName.get(column));
1041+
insert = insert.withColumn(column, this.byName.get(column));
10421042
}
10431043

10441044
PreparedOperation<?> operation = mapper.getMappedObject(insert);
@@ -1161,7 +1161,7 @@ private <MR> FetchSpec<MR> exchange(Object toInsert, BiFunction<Row, RowMetadata
11611161
for (SqlIdentifier column : outboundRow.keySet()) {
11621162
SettableValue settableValue = outboundRow.get(column);
11631163
if (settableValue.hasValue()) {
1164-
insert = insert.withColumn(dataAccessStrategy.toSql(column), settableValue);
1164+
insert = insert.withColumn(column, settableValue);
11651165
}
11661166
}
11671167

src/main/java/org/springframework/data/r2dbc/core/DefaultStatementMapper.java

+19-23
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,8 @@
1616
package org.springframework.data.r2dbc.core;
1717

1818
import java.util.ArrayList;
19-
import java.util.Collection;
2019
import java.util.List;
2120

22-
import org.springframework.data.domain.Pageable;
23-
import org.springframework.data.domain.Sort;
2421
import org.springframework.data.mapping.context.MappingContext;
2522
import org.springframework.data.r2dbc.dialect.BindMarkers;
2623
import org.springframework.data.r2dbc.dialect.BindTarget;
@@ -84,9 +81,9 @@ public PreparedOperation<?> getMappedObject(SelectSpec selectSpec) {
8481
private PreparedOperation<Select> getMappedObject(SelectSpec selectSpec,
8582
@Nullable RelationalPersistentEntity<?> entity) {
8683

87-
Table table = Table.create(toSql(selectSpec.getTable()));
88-
List<Column> columns = table.columns(toSql(selectSpec.getProjectedFields()));
89-
SelectBuilder.SelectFromAndJoin selectBuilder = StatementBuilder.select(columns).from(table);
84+
Table table = selectSpec.getTable();
85+
SelectBuilder.SelectFromAndJoin selectBuilder = StatementBuilder.select(getSelectList(selectSpec, entity))
86+
.from(table);
9087

9188
BindMarkers bindMarkers = this.dialect.getBindMarkersFactory().create();
9289
Bindings bindings = Bindings.empty();
@@ -102,37 +99,36 @@ private PreparedOperation<Select> getMappedObject(SelectSpec selectSpec,
10299

103100
if (selectSpec.getSort().isSorted()) {
104101

105-
Sort mappedSort = this.updateMapper.getMappedObject(selectSpec.getSort(), entity);
106-
selectBuilder.orderBy(createOrderByFields(table, mappedSort));
102+
List<OrderByField> sort = this.updateMapper.getMappedSort(table, selectSpec.getSort(), entity);
103+
selectBuilder.orderBy(sort);
107104
}
108105

109-
if (selectSpec.getPage().isPaged()) {
110-
111-
Pageable page = selectSpec.getPage();
106+
if (selectSpec.getLimit() > 0) {
107+
selectBuilder.limit(selectSpec.getLimit());
108+
}
112109

113-
selectBuilder.limitOffset(page.getPageSize(), page.getOffset());
110+
if (selectSpec.getOffset() > 0) {
111+
selectBuilder.offset(selectSpec.getOffset());
114112
}
115113

116114
Select select = selectBuilder.build();
117115
return new DefaultPreparedOperation<>(select, this.renderContext, bindings);
118116
}
119117

120-
private Collection<? extends OrderByField> createOrderByFields(Table table, Sort sortToUse) {
121-
122-
List<OrderByField> fields = new ArrayList<>();
118+
protected List<Expression> getSelectList(SelectSpec selectSpec, @Nullable RelationalPersistentEntity<?> entity) {
123119

124-
for (Sort.Order order : sortToUse) {
120+
if (entity == null) {
121+
return selectSpec.getSelectList();
122+
}
125123

126-
OrderByField orderByField = OrderByField.from(table.column(order.getProperty()));
124+
List<Expression> selectList = selectSpec.getSelectList();
125+
List<Expression> mapped = new ArrayList<>(selectList.size());
127126

128-
if (order.getDirection() != null) {
129-
fields.add(order.isAscending() ? orderByField.asc() : orderByField.desc());
130-
} else {
131-
fields.add(orderByField);
132-
}
127+
for (Expression expression : selectList) {
128+
mapped.add(updateMapper.getMappedObject(expression, entity));
133129
}
134130

135-
return fields;
131+
return mapped;
136132
}
137133

138134
/*

src/main/java/org/springframework/data/r2dbc/core/FluentR2dbcOperations.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2018-2020 the original author or authors.
2+
* Copyright 2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.

src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java

+17-14
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@
4343
import org.springframework.data.r2dbc.query.Update;
4444
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
4545
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
46+
import org.springframework.data.relational.core.sql.Expression;
4647
import org.springframework.data.relational.core.sql.Functions;
48+
import org.springframework.data.relational.core.sql.SqlIdentifier;
49+
import org.springframework.data.relational.core.sql.Table;
4750
import org.springframework.data.util.ProxyUtils;
4851
import org.springframework.util.Assert;
4952

@@ -174,7 +177,7 @@ public Mono<Long> count(Query query, Class<?> entityClass) throws DataAccessExce
174177
return doCount(query, entityClass, getTableName(entityClass));
175178
}
176179

177-
Mono<Long> doCount(Query query, Class<?> entityClass, String tableName) {
180+
Mono<Long> doCount(Query query, Class<?> entityClass, SqlIdentifier tableName) {
178181

179182
RelationalPersistentEntity<?> entity = getRequiredEntity(entityClass);
180183
StatementMapper statementMapper = dataAccessStrategy.getStatementMapper().forType(entityClass);
@@ -211,12 +214,13 @@ public Mono<Boolean> exists(Query query, Class<?> entityClass) throws DataAccess
211214
return doExists(query, entityClass, getTableName(entityClass));
212215
}
213216

214-
Mono<Boolean> doExists(Query query, Class<?> entityClass, String tableName) {
217+
Mono<Boolean> doExists(Query query, Class<?> entityClass, SqlIdentifier tableName) {
215218

216219
RelationalPersistentEntity<?> entity = getRequiredEntity(entityClass);
217220
StatementMapper statementMapper = dataAccessStrategy.getStatementMapper().forType(entityClass);
218221

219-
String columnName = entity.hasIdProperty() ? entity.getRequiredIdProperty().getColumnName() : "*";
222+
SqlIdentifier columnName = entity.hasIdProperty() ? entity.getRequiredIdProperty().getColumnName()
223+
: SqlIdentifier.unquoted("*");
220224

221225
StatementMapper.SelectSpec selectSpec = statementMapper //
222226
.createSelect(tableName) //
@@ -248,14 +252,13 @@ public <T> Flux<T> select(Query query, Class<T> entityClass) throws DataAccessEx
248252
return doSelect(query, entityClass, getTableName(entityClass), entityClass).all();
249253
}
250254

251-
<T> RowsFetchSpec<T> doSelect(Query query, Class<?> entityClass, String tableName, Class<T> returnType) {
255+
<T> RowsFetchSpec<T> doSelect(Query query, Class<?> entityClass, SqlIdentifier tableName, Class<T> returnType) {
252256

253-
RelationalPersistentEntity<?> entity = getRequiredEntity(entityClass);
254257
StatementMapper statementMapper = dataAccessStrategy.getStatementMapper().forType(entityClass);
255258

256259
StatementMapper.SelectSpec selectSpec = statementMapper //
257260
.createSelect(tableName) //
258-
.withProjection(getSelectProjection(query, returnType));
261+
.doWithTable((table, spec) -> spec.withProjection(getSelectProjection(table, query, returnType)));
259262

260263
if (query.getLimit() > 0) {
261264
selectSpec = selectSpec.limit(query.getLimit());
@@ -310,7 +313,7 @@ public Mono<Integer> update(Query query, Update update, Class<?> entityClass) th
310313
return doUpdate(query, update, entityClass, getTableName(entityClass));
311314
}
312315

313-
Mono<Integer> doUpdate(Query query, Update update, Class<?> entityClass, String tableName) {
316+
Mono<Integer> doUpdate(Query query, Update update, Class<?> entityClass, SqlIdentifier tableName) {
314317

315318
StatementMapper statementMapper = dataAccessStrategy.getStatementMapper().forType(entityClass);
316319

@@ -339,7 +342,7 @@ public Mono<Integer> delete(Query query, Class<?> entityClass) throws DataAccess
339342
return doDelete(query, entityClass, getTableName(entityClass));
340343
}
341344

342-
Mono<Integer> doDelete(Query query, Class<?> entityClass, String tableName) {
345+
Mono<Integer> doDelete(Query query, Class<?> entityClass, SqlIdentifier tableName) {
343346

344347
StatementMapper statementMapper = dataAccessStrategy.getStatementMapper().forType(entityClass);
345348

@@ -371,7 +374,7 @@ public <T> Mono<T> insert(T entity) throws DataAccessException {
371374
return doInsert(entity, getRequiredEntity(entity).getTableName());
372375
}
373376

374-
<T> Mono<T> doInsert(T entity, String tableName) {
377+
<T> Mono<T> doInsert(T entity, SqlIdentifier tableName) {
375378

376379
RelationalPersistentEntity<T> persistentEntity = getRequiredEntity(entity);
377380

@@ -434,7 +437,7 @@ private <T> Query getByIdQuery(T entity, RelationalPersistentEntity<?> persisten
434437
return Query.query(Criteria.where(persistentEntity.getRequiredIdProperty().getName()).is(id));
435438
}
436439

437-
String getTableName(Class<?> entityClass) {
440+
SqlIdentifier getTableName(Class<?> entityClass) {
438441
return getRequiredEntity(entityClass).getTableName();
439442
}
440443

@@ -447,7 +450,7 @@ private <T> RelationalPersistentEntity<T> getRequiredEntity(T entity) {
447450
return (RelationalPersistentEntity) getRequiredEntity(entityType);
448451
}
449452

450-
private <T> List<String> getSelectProjection(Query query, Class<T> returnType) {
453+
private <T> List<Expression> getSelectProjection(Table table, Query query, Class<T> returnType) {
451454

452455
if (query.getColumns().isEmpty()) {
453456

@@ -456,15 +459,15 @@ private <T> List<String> getSelectProjection(Query query, Class<T> returnType) {
456459
ProjectionInformation projectionInformation = projectionFactory.getProjectionInformation(returnType);
457460

458461
if (projectionInformation.isClosed()) {
459-
return projectionInformation.getInputProperties().stream().map(FeatureDescriptor::getName)
462+
return projectionInformation.getInputProperties().stream().map(FeatureDescriptor::getName).map(table::column)
460463
.collect(Collectors.toList());
461464
}
462465
}
463466

464-
return Collections.singletonList("*");
467+
return Collections.singletonList(table.asterisk());
465468
}
466469

467-
return query.getColumns();
470+
return query.getColumns().stream().map(table::column).collect(Collectors.toList());
468471
}
469472

470473
private static ReactiveDataAccessStrategy getDataAccessStrategy(DatabaseClient databaseClient) {

src/main/java/org/springframework/data/r2dbc/core/ReactiveDeleteOperation.java

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2018-2020 the original author or authors.
2+
* Copyright 2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
1818
import reactor.core.publisher.Mono;
1919

2020
import org.springframework.data.r2dbc.query.Query;
21+
import org.springframework.data.relational.core.sql.SqlIdentifier;
2122

2223
/**
2324
* The {@link ReactiveDeleteOperation} interface allows creation and execution of {@code DELETE} operations in a fluent
@@ -67,7 +68,21 @@ interface DeleteWithTable {
6768
* @throws IllegalArgumentException if {@link String table} is {@literal null} or empty.
6869
* @see DeleteWithQuery
6970
*/
70-
DeleteWithQuery from(String table);
71+
default DeleteWithQuery from(String table) {
72+
return from(SqlIdentifier.unquoted(table));
73+
}
74+
75+
/**
76+
* Explicitly set the {@link SqlIdentifier name} of the table on which to perform the delete.
77+
* <p>
78+
* Skip this step to use the default table derived from the {@link Class domain type}.
79+
*
80+
* @param table {@link SqlIdentifier name} of the table; must not be {@literal null}.
81+
* @return new instance of {@link DeleteWithQuery}.
82+
* @throws IllegalArgumentException if {@link SqlIdentifier table} is {@literal null}.
83+
* @see DeleteWithQuery
84+
*/
85+
DeleteWithQuery from(SqlIdentifier table);
7186
}
7287

7388
/**

src/main/java/org/springframework/data/r2dbc/core/ReactiveDeleteOperationSupport.java

+13-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2018-2020 the original author or authors.
2+
* Copyright 2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
1818
import reactor.core.publisher.Mono;
1919

2020
import org.springframework.data.r2dbc.query.Query;
21+
import org.springframework.data.relational.core.sql.SqlIdentifier;
2122
import org.springframework.lang.Nullable;
2223
import org.springframework.util.Assert;
2324

@@ -35,7 +36,7 @@ class ReactiveDeleteOperationSupport implements ReactiveDeleteOperation {
3536
this.template = template;
3637
}
3738

38-
/*
39+
/*
3940
* (non-Javadoc)
4041
* @see org.springframework.data.r2dbc.core.ReactiveDeleteOperation#delete(java.lang.Class)
4142
*/
@@ -55,28 +56,29 @@ static class ReactiveDeleteSupport implements ReactiveDelete, TerminatingDelete
5556

5657
private final Query query;
5758

58-
private final @Nullable String tableName;
59+
private final @Nullable SqlIdentifier tableName;
5960

60-
ReactiveDeleteSupport(R2dbcEntityTemplate template, Class<?> domainType, Query query, @Nullable String tableName) {
61+
ReactiveDeleteSupport(R2dbcEntityTemplate template, Class<?> domainType, Query query,
62+
@Nullable SqlIdentifier tableName) {
6163
this.template = template;
6264
this.domainType = domainType;
6365
this.query = query;
6466
this.tableName = tableName;
6567
}
6668

67-
/*
69+
/*
6870
* (non-Javadoc)
69-
* @see org.springframework.data.r2dbc.core.ReactiveDeleteOperation.DeleteWithTable#from(java.lang.String)
71+
* @see org.springframework.data.r2dbc.core.ReactiveDeleteOperation.DeleteWithTable#from(SqlIdentifier)
7072
*/
7173
@Override
72-
public DeleteWithQuery from(String tableName) {
74+
public DeleteWithQuery from(SqlIdentifier tableName) {
7375

74-
Assert.hasText(tableName, "Table name must not be null or empty");
76+
Assert.notNull(tableName, "Table name must not be null");
7577

7678
return new ReactiveDeleteSupport(this.template, this.domainType, this.query, tableName);
7779
}
7880

79-
/*
81+
/*
8082
* (non-Javadoc)
8183
* @see org.springframework.data.r2dbc.core.ReactiveDeleteOperation.DeleteWithQuery#matching(org.springframework.data.r2dbc.query.Query)
8284
*/
@@ -88,15 +90,15 @@ public TerminatingDelete matching(Query query) {
8890
return new ReactiveDeleteSupport(this.template, this.domainType, query, this.tableName);
8991
}
9092

91-
/*
93+
/*
9294
* (non-Javadoc)
9395
* @see org.springframework.data.r2dbc.core.ReactiveDeleteOperation.TerminatingDelete#all()
9496
*/
9597
public Mono<Integer> all() {
9698
return this.template.doDelete(this.query, this.domainType, getTableName());
9799
}
98100

99-
private String getTableName() {
101+
private SqlIdentifier getTableName() {
100102
return this.tableName != null ? this.tableName : this.template.getTableName(this.domainType);
101103
}
102104
}

src/main/java/org/springframework/data/r2dbc/core/ReactiveInsertOperation.java

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2018-2020 the original author or authors.
2+
* Copyright 2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,8 @@
1717

1818
import reactor.core.publisher.Mono;
1919

20+
import org.springframework.data.relational.core.sql.SqlIdentifier;
21+
2022
/**
2123
* The {@link ReactiveInsertOperation} interface allows creation and execution of {@code INSERT} operations in a fluent
2224
* API style.
@@ -63,7 +65,20 @@ interface InsertWithTable<T> extends TerminatingInsert<T> {
6365
* @return new instance of {@link TerminatingInsert}.
6466
* @throws IllegalArgumentException if {@link String table} is {@literal null} or empty.
6567
*/
66-
TerminatingInsert<T> into(String table);
68+
default TerminatingInsert<T> into(String table) {
69+
return into(SqlIdentifier.unquoted(table));
70+
}
71+
72+
/**
73+
* Explicitly set the {@link SqlIdentifier name} of the table.
74+
* <p>
75+
* Skip this step to use the default table derived from the {@link Class domain type}.
76+
*
77+
* @param table {@link SqlIdentifier name} of the table; must not be {@literal null}.
78+
* @return new instance of {@link TerminatingInsert}.
79+
* @throws IllegalArgumentException if {@link SqlIdentifier table} is {@literal null}.
80+
*/
81+
TerminatingInsert<T> into(SqlIdentifier table);
6782
}
6883

6984
/**

0 commit comments

Comments
 (0)