Skip to content

Introduce PreparedOperation #82

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 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
75b6ede
#73 - Prepare issue branch.
mp911de Mar 12, 2019
cd28c65
#73 - Introduce PreparedOperation.
mp911de Mar 18, 2019
8dce8ad
#73 - Polishing.
mp911de Mar 22, 2019
71fea91
#73 - hacking - introduced alternative bind method.
schauder Apr 30, 2019
f5d7791
#73 - hacking - removed call to old bind.
schauder Apr 30, 2019
3e558ba
#73 - hacking - code symmetry.
schauder Apr 30, 2019
7030d1c
#73 - hacking - extracted ParameterbindingPreparedOperation.
schauder Apr 30, 2019
605e52e
#73 - hacking - minor code improvements.
schauder Apr 30, 2019
58bd298
#73 - hacking - minor code improvements.
schauder Apr 30, 2019
a158c6b
#73 - hacking - disassembling.
schauder Apr 30, 2019
565faf5
#73 - hacking - removed old bind method from interface.
schauder Apr 30, 2019
e13fcd7
#73 - hacking - changed method name according to review.
schauder May 2, 2019
9b32943
#73 - hacking - extracted super class.
schauder May 2, 2019
2804726
#73 - hacking - separate Bindings class.
schauder May 3, 2019
dcdd968
#73 - hacking - prepared various single bindings.
schauder May 3, 2019
01d114d
#73 - hacking - proper expanded single binding.
schauder May 3, 2019
7b57ff8
#73 - hacking - always nice when code compiles.
schauder May 3, 2019
71b81a1
#73 - hacking - fixed an error.
schauder May 3, 2019
7f9c42c
#73 - hacking - fixed another error.
schauder May 3, 2019
13690ea
#73 - hacking - added backup.
schauder May 6, 2019
fa55f0e
#73 - hacking - moved bindings in constructor.
schauder May 6, 2019
2265c7f
#73 - hacking - ExpandedBindings work as well.
schauder May 6, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-r2dbc</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<version>1.0.0.gh-73-SNAPSHOT</version>

<name>Spring Data R2DBC</name>
<description>Spring Data module for R2DBC.</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
/**
* A single indexed bind marker.
*/
class IndexedBindMarker implements BindMarker {
public class IndexedBindMarker implements BindMarker {

private final String placeholder;

private int index;
private final int index;

IndexedBindMarker(String placeholder, int index) {
this.placeholder = placeholder;
Expand Down Expand Up @@ -57,4 +57,10 @@ public void bind(Statement statement, Object value) {
public void bindNull(Statement statement, Class<?> valueType) {
statement.bindNull(this.index, valueType);
}


public int getIndex() {
return index;
}

}

This file was deleted.

104 changes: 104 additions & 0 deletions src/main/java/org/springframework/data/r2dbc/function/Bindings.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.r2dbc.function;

import io.r2dbc.spi.Statement;
import lombok.RequiredArgsConstructor;

import java.util.List;

import org.springframework.data.r2dbc.domain.SettableValue;

/**
* @author Jens Schauder
*/
public class Bindings {

private final List<SingleBinding> bindings;

public Bindings(List<SingleBinding> bindings) {
this.bindings = bindings;
}

public void apply(Statement statement) {
bindings.forEach(sb -> sb.bindTo(statement));
}

@RequiredArgsConstructor
public static abstract class SingleBinding<T> {

final T identifier;
final SettableValue value;

public abstract void bindTo(Statement statement);

public abstract boolean isIndexed();

public final boolean isNamed() {
return !isIndexed();
}
}


public static class IndexedSingleBinding extends SingleBinding<Integer> {

public IndexedSingleBinding(Integer identifier, SettableValue value) {
super(identifier, value);
}

@Override
public void bindTo(Statement statement) {

if (value.isEmpty()) {
statement.bindNull((int) identifier, value.getType());
} else {
statement.bind((int) identifier, value.getValue());
}
}

@Override
public boolean isIndexed() {
return true;
}
}

public static class NamedExpandedSingleBinding extends SingleBinding<String> {

private final BindableOperation operation;

public NamedExpandedSingleBinding(String identifier, SettableValue value, BindableOperation operation) {

super(identifier, value);

this.operation = operation;
}

@Override
public void bindTo(Statement statement) {

if (value != null) {
operation.bind(statement, identifier, value);
} else {
operation.bindNull(statement, identifier, value.getType());
}
}

@Override
public boolean isIndexed() {
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.function.Supplier;

import org.reactivestreams.Publisher;

import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.r2dbc.support.R2dbcExceptionTranslator;
Expand Down Expand Up @@ -137,6 +138,9 @@ interface Builder {
* Contract for specifying a SQL call along with options leading to the exchange. The SQL string can contain either
* native parameter bind markers (e.g. {@literal $1, $2} for Postgres, {@literal @P0, @P1} for SQL Server) or named
* parameters (e.g. {@literal :foo, :bar}) when {@link NamedParameterExpander} is enabled.
* <p>
* Accepts {@link PreparedOperation} as SQL and binding {@link Supplier}.
* </p>
*
* @see NamedParameterExpander
* @see DatabaseClient.Builder#namedParameters(NamedParameterExpander)
Expand All @@ -156,6 +160,7 @@ interface SqlSpec {
*
* @param sqlSupplier must not be {@literal null}.
* @return a new {@link GenericExecuteSpec}.
* @see PreparedOperation
*/
GenericExecuteSpec sql(Supplier<String> sqlSupplier);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiFunction;
import java.util.function.Function;
Expand All @@ -49,6 +48,7 @@
import org.reactivestreams.Publisher;

import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.r2dbc.UncategorizedR2dbcException;
Expand All @@ -57,6 +57,7 @@
import org.springframework.data.r2dbc.function.connectionfactory.ConnectionProxy;
import org.springframework.data.r2dbc.function.convert.ColumnMapRowMapper;
import org.springframework.data.r2dbc.support.R2dbcExceptionTranslator;
import org.springframework.data.relational.core.sql.Insert;
import org.springframework.jdbc.core.SqlProvider;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
Expand Down Expand Up @@ -331,32 +332,15 @@ protected String getSql() {

<T> FetchSpec<T> exchange(String sql, BiFunction<Row, RowMetadata, T> mappingFunction) {

Function<Connection, Statement> executeFunction = it -> {
PreparedOperation pop;

if (logger.isDebugEnabled()) {
logger.debug("Executing SQL statement [" + sql + "]");
}

BindableOperation operation = namedParameters.expand(sql, dataAccessStrategy.getBindMarkersFactory(),
new MapBindParameterSource(byName));

Statement statement = it.createStatement(operation.toQuery());

byName.forEach((name, o) -> {

if (o.getValue() != null) {
operation.bind(statement, name, o.getValue());
} else {
operation.bindNull(statement, name, o.getType());
}
});

bindByIndex(statement, byIndex);

return statement;
};
if (sqlSupplier instanceof PreparedOperation<?>) {
pop = ((PreparedOperation<?>) sqlSupplier);
} else {
pop = new ExpandedPreparedOperation(sql, namedParameters, dataAccessStrategy, byName, byIndex);
}

Function<Connection, Flux<Result>> resultFunction = it -> Flux.from(executeFunction.apply(it).execute());
Function<Connection, Flux<Result>> resultFunction = it -> Flux.from(pop.createBoundStatement(it).execute());

return new DefaultSqlResult<>(DefaultDatabaseClient.this, //
sql, //
Expand All @@ -367,6 +351,7 @@ <T> FetchSpec<T> exchange(String sql, BiFunction<Row, RowMetadata, T> mappingFun

public ExecuteSpecSupport bind(int index, Object value) {

assertNotPreparedOperation();
Assert.notNull(value, () -> String.format("Value at index %d must not be null. Use bindNull(…) instead.", index));

Map<Integer, SettableValue> byIndex = new LinkedHashMap<>(this.byIndex);
Expand All @@ -377,6 +362,8 @@ public ExecuteSpecSupport bind(int index, Object value) {

public ExecuteSpecSupport bindNull(int index, Class<?> type) {

assertNotPreparedOperation();

Map<Integer, SettableValue> byIndex = new LinkedHashMap<>(this.byIndex);
byIndex.put(index, SettableValue.empty(type));

Expand All @@ -385,6 +372,8 @@ public ExecuteSpecSupport bindNull(int index, Class<?> type) {

public ExecuteSpecSupport bind(String name, Object value) {

assertNotPreparedOperation();

Assert.hasText(name, "Parameter name must not be null or empty!");
Assert.notNull(value,
() -> String.format("Value for parameter %s must not be null. Use bindNull(…) instead.", name));
Expand All @@ -397,6 +386,7 @@ public ExecuteSpecSupport bind(String name, Object value) {

public ExecuteSpecSupport bindNull(String name, Class<?> type) {

assertNotPreparedOperation();
Assert.hasText(name, "Parameter name must not be null or empty!");

Map<String, SettableValue> byName = new LinkedHashMap<>(this.byName);
Expand All @@ -405,6 +395,12 @@ public ExecuteSpecSupport bindNull(String name, Class<?> type) {
return createInstance(this.byIndex, byName, this.sqlSupplier);
}

private void assertNotPreparedOperation() {
if (sqlSupplier instanceof PreparedOperation<?>) {
throw new InvalidDataAccessApiUsageException("Cannot add bindings to a PreparedOperation");
}
}

protected ExecuteSpecSupport createInstance(Map<Integer, SettableValue> byIndex, Map<String, SettableValue> byName,
Supplier<String> sqlSupplier) {
return new ExecuteSpecSupport(byIndex, byName, sqlSupplier);
Expand Down Expand Up @@ -882,26 +878,15 @@ private <R> FetchSpec<R> exchange(BiFunction<Row, RowMetadata, R> mappingFunctio
throw new IllegalStateException("Insert fields is empty!");
}

BindableOperation bindableInsert = dataAccessStrategy.insertAndReturnGeneratedKeys(table, byName.keySet());

String sql = bindableInsert.toQuery();
Function<Connection, Statement> insertFunction = it -> {

if (logger.isDebugEnabled()) {
logger.debug("Executing SQL statement [" + sql + "]");
}

Statement statement = it.createStatement(sql).returnGeneratedValues();

byName.forEach((k, v) -> bindableInsert.bind(statement, k, v));
PreparedOperation<Insert> operation = dataAccessStrategy.getStatements().insert(table, Collections.emptyList(),
it -> {
byName.forEach(it::bind);
});

return statement;
};

Function<Connection, Flux<Result>> resultFunction = it -> Flux.from(insertFunction.apply(it).execute());
Function<Connection, Flux<Result>> resultFunction = it -> Flux.from(operation.createBoundStatement(it).execute());

return new DefaultSqlResult<>(DefaultDatabaseClient.this, //
sql, //
operation.toQuery(), //
resultFunction, //
it -> resultFunction.apply(it).flatMap(Result::getRowsUpdated).next(), //
mappingFunction);
Expand Down Expand Up @@ -999,40 +984,20 @@ private <MR> FetchSpec<MR> exchange(Object toInsert, BiFunction<Row, RowMetadata

OutboundRow outboundRow = dataAccessStrategy.getOutboundRow(toInsert);

Set<String> columns = new LinkedHashSet<>();

outboundRow.forEach((k, v) -> {

if (v.hasValue()) {
columns.add(k);
}
});

BindableOperation bindableInsert = dataAccessStrategy.insertAndReturnGeneratedKeys(table, columns);
PreparedOperation<Insert> operation = dataAccessStrategy.getStatements().insert(table, Collections.emptyList(),
it -> {
outboundRow.forEach((k, v) -> {

String sql = bindableInsert.toQuery();
if (v.hasValue()) {
it.bind(k, v);
}
});
});

Function<Connection, Statement> insertFunction = it -> {

if (logger.isDebugEnabled()) {
logger.debug("Executing SQL statement [" + sql + "]");
}

Statement statement = it.createStatement(sql).returnGeneratedValues();

outboundRow.forEach((k, v) -> {
if (v.hasValue()) {
bindableInsert.bind(statement, k, v);
}
});

return statement;
};

Function<Connection, Flux<Result>> resultFunction = it -> Flux.from(insertFunction.apply(it).execute());
Function<Connection, Flux<Result>> resultFunction = it -> Flux.from(operation.createBoundStatement(it).execute());

return new DefaultSqlResult<>(DefaultDatabaseClient.this, //
sql, //
operation.toQuery(), //
resultFunction, //
it -> resultFunction //
.apply(it) //
Expand Down
Loading