#73 - Introduce BindTarget.
We now apply bindings to a BindTarget that can be overridden without the need to implement all Statement methods. PreparedOperation no longer has a direct dependency on R2DBC Statement. Original pull request: #82.
This commit is contained in:
@@ -2,6 +2,8 @@ package org.springframework.data.r2dbc.dialect;
|
||||
|
||||
import io.r2dbc.spi.Statement;
|
||||
|
||||
import org.springframework.data.r2dbc.domain.BindTarget;
|
||||
|
||||
/**
|
||||
* A bind marker represents a single bindable parameter within a query. Bind markers are dialect-specific and provide a
|
||||
* {@link #getPlaceholder() placeholder} that is used in the actual query.
|
||||
@@ -23,19 +25,19 @@ public interface BindMarker {
|
||||
/**
|
||||
* Bind the given {@code value} to the {@link Statement} using the underlying binding strategy.
|
||||
*
|
||||
* @param statement the statement to bind the value to.
|
||||
* @param bindTarget the target to bind the value to.
|
||||
* @param value the actual value. Must not be {@literal null}. Use {@link #bindNull(Statement, Class)} for
|
||||
* {@literal null} values.
|
||||
* @see Statement#bind
|
||||
*/
|
||||
void bind(Statement statement, Object value);
|
||||
void bind(BindTarget bindTarget, Object value);
|
||||
|
||||
/**
|
||||
* Bind a {@literal null} value to the {@link Statement} using the underlying binding strategy.
|
||||
*
|
||||
* @param statement the statement to bind the value to.
|
||||
* @param bindTarget the target to bind the value to.
|
||||
* @param valueType value type, must not be {@literal null}.
|
||||
* @see Statement#bindNull
|
||||
*/
|
||||
void bindNull(Statement statement, Class<?> valueType);
|
||||
void bindNull(BindTarget bindTarget, Class<?> valueType);
|
||||
}
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
*/
|
||||
package org.springframework.data.r2dbc.dialect;
|
||||
|
||||
import io.r2dbc.spi.Statement;
|
||||
import org.springframework.data.r2dbc.domain.BindTarget;
|
||||
|
||||
/**
|
||||
* A single indexed bind marker.
|
||||
*/
|
||||
public class IndexedBindMarker implements BindMarker {
|
||||
class IndexedBindMarker implements BindMarker {
|
||||
|
||||
private final String placeholder;
|
||||
|
||||
@@ -42,20 +42,20 @@ public class IndexedBindMarker implements BindMarker {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.dialect.BindMarker#bindValue(io.r2dbc.spi.Statement, java.lang.Object)
|
||||
* @see org.springframework.data.r2dbc.dialect.BindMarker#bindValue(org.springframework.data.r2dbc.dialect.BindTarget, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public void bind(Statement statement, Object value) {
|
||||
statement.bind(this.index, value);
|
||||
public void bind(BindTarget target, Object value) {
|
||||
target.bind(this.index, value);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.dialect.BindMarker#bindNull(io.r2dbc.spi.Statement, java.lang.Class)
|
||||
* @see org.springframework.data.r2dbc.dialect.BindMarker#bindNull(org.springframework.data.r2dbc.dialect.BindTarget, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public void bindNull(Statement statement, Class<?> valueType) {
|
||||
statement.bindNull(this.index, valueType);
|
||||
public void bindNull(BindTarget target, Class<?> valueType) {
|
||||
target.bindNull(this.index, valueType);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package org.springframework.data.r2dbc.dialect;
|
||||
|
||||
import io.r2dbc.spi.Statement;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.data.r2dbc.domain.BindTarget;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -98,20 +97,20 @@ class NamedBindMarkers implements BindMarkers {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.dialect.BindMarker#bindValue(io.r2dbc.spi.Statement, java.lang.Object)
|
||||
* @see org.springframework.data.r2dbc.dialect.BindMarker#bindValue(org.springframework.data.r2dbc.dialect.BindTarget, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public void bind(Statement statement, Object value) {
|
||||
statement.bind(this.identifier, value);
|
||||
public void bind(BindTarget target, Object value) {
|
||||
target.bind(this.identifier, value);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.dialect.BindMarker#bindNull(io.r2dbc.spi.Statement, java.lang.Class)
|
||||
* @see org.springframework.data.r2dbc.dialect.BindMarker#bindNull(org.springframework.data.r2dbc.dialect.BindTarget, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public void bindNull(Statement statement, Class<?> valueType) {
|
||||
statement.bindNull(this.identifier, valueType);
|
||||
public void bindNull(BindTarget target, Class<?> valueType) {
|
||||
target.bindNull(this.identifier, valueType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.domain;
|
||||
|
||||
/**
|
||||
* Target to apply bindings to.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @see PreparedOperation
|
||||
* @see io.r2dbc.spi.Statement#bind
|
||||
* @see io.r2dbc.spi.Statement#bindNull
|
||||
*/
|
||||
public interface BindTarget {
|
||||
|
||||
/**
|
||||
* Bind a value.
|
||||
*
|
||||
* @param identifier the identifier to bind to.
|
||||
* @param value the value to bind.
|
||||
*/
|
||||
void bind(Object identifier, Object value);
|
||||
|
||||
/**
|
||||
* Bind a value to an index. Indexes are zero-based.
|
||||
*
|
||||
* @param index the index to bind to.
|
||||
* @param value the value to bind.
|
||||
*/
|
||||
void bind(int index, Object value);
|
||||
|
||||
/**
|
||||
* Bind a {@code null} value.
|
||||
*
|
||||
* @param identifier the identifier to bind to.
|
||||
* @param type the type of {@literal null} value.
|
||||
*/
|
||||
void bindNull(Object identifier, Class<?> type);
|
||||
|
||||
/**
|
||||
* Bind a {@code null} value.
|
||||
*
|
||||
* @param index the index to bind to.
|
||||
* @param type the type of {@literal null} value.
|
||||
*/
|
||||
void bindNull(int index, Class<?> type);
|
||||
}
|
||||
@@ -39,7 +39,7 @@ public class OutboundRow implements Map<String, SettableValue> {
|
||||
* Creates an empty {@link OutboundRow} instance.
|
||||
*/
|
||||
public OutboundRow() {
|
||||
rowAsMap = new LinkedHashMap<>();
|
||||
this.rowAsMap = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,7 +51,7 @@ public class OutboundRow implements Map<String, SettableValue> {
|
||||
|
||||
Assert.notNull(map, "Map must not be null");
|
||||
|
||||
rowAsMap = new LinkedHashMap<>(map);
|
||||
this.rowAsMap = new LinkedHashMap<>(map);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,8 +61,8 @@ public class OutboundRow implements Map<String, SettableValue> {
|
||||
* @param value value.
|
||||
*/
|
||||
public OutboundRow(String key, SettableValue value) {
|
||||
rowAsMap = new LinkedHashMap<>();
|
||||
rowAsMap.put(key, value);
|
||||
this.rowAsMap = new LinkedHashMap<>();
|
||||
this.rowAsMap.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,7 +78,7 @@ public class OutboundRow implements Map<String, SettableValue> {
|
||||
* @return this
|
||||
*/
|
||||
public OutboundRow append(String key, SettableValue value) {
|
||||
rowAsMap.put(key, value);
|
||||
this.rowAsMap.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ public class OutboundRow implements Map<String, SettableValue> {
|
||||
*/
|
||||
@Override
|
||||
public int size() {
|
||||
return rowAsMap.size();
|
||||
return this.rowAsMap.size();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -97,7 +97,7 @@ public class OutboundRow implements Map<String, SettableValue> {
|
||||
*/
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return rowAsMap.isEmpty();
|
||||
return this.rowAsMap.isEmpty();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -106,7 +106,7 @@ public class OutboundRow implements Map<String, SettableValue> {
|
||||
*/
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return rowAsMap.containsKey(key);
|
||||
return this.rowAsMap.containsKey(key);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -115,7 +115,7 @@ public class OutboundRow implements Map<String, SettableValue> {
|
||||
*/
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return rowAsMap.containsValue(value);
|
||||
return this.rowAsMap.containsValue(value);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -124,7 +124,7 @@ public class OutboundRow implements Map<String, SettableValue> {
|
||||
*/
|
||||
@Override
|
||||
public SettableValue get(Object key) {
|
||||
return rowAsMap.get(key);
|
||||
return this.rowAsMap.get(key);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -133,7 +133,7 @@ public class OutboundRow implements Map<String, SettableValue> {
|
||||
*/
|
||||
@Override
|
||||
public SettableValue put(String key, SettableValue value) {
|
||||
return rowAsMap.put(key, value);
|
||||
return this.rowAsMap.put(key, value);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -142,7 +142,7 @@ public class OutboundRow implements Map<String, SettableValue> {
|
||||
*/
|
||||
@Override
|
||||
public SettableValue remove(Object key) {
|
||||
return rowAsMap.remove(key);
|
||||
return this.rowAsMap.remove(key);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -151,7 +151,7 @@ public class OutboundRow implements Map<String, SettableValue> {
|
||||
*/
|
||||
@Override
|
||||
public void putAll(Map<? extends String, ? extends SettableValue> m) {
|
||||
rowAsMap.putAll(m);
|
||||
this.rowAsMap.putAll(m);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -160,7 +160,7 @@ public class OutboundRow implements Map<String, SettableValue> {
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
rowAsMap.clear();
|
||||
this.rowAsMap.clear();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -169,7 +169,7 @@ public class OutboundRow implements Map<String, SettableValue> {
|
||||
*/
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return rowAsMap.keySet();
|
||||
return this.rowAsMap.keySet();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -178,7 +178,7 @@ public class OutboundRow implements Map<String, SettableValue> {
|
||||
*/
|
||||
@Override
|
||||
public Collection<SettableValue> values() {
|
||||
return rowAsMap.values();
|
||||
return this.rowAsMap.values();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -187,7 +187,7 @@ public class OutboundRow implements Map<String, SettableValue> {
|
||||
*/
|
||||
@Override
|
||||
public Set<Entry<String, SettableValue>> entrySet() {
|
||||
return rowAsMap.entrySet();
|
||||
return this.rowAsMap.entrySet();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -207,7 +207,7 @@ public class OutboundRow implements Map<String, SettableValue> {
|
||||
|
||||
OutboundRow row = (OutboundRow) o;
|
||||
|
||||
return rowAsMap.equals(row.rowAsMap);
|
||||
return this.rowAsMap.equals(row.rowAsMap);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -216,7 +216,7 @@ public class OutboundRow implements Map<String, SettableValue> {
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return rowAsMap.hashCode();
|
||||
return this.rowAsMap.hashCode();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -225,11 +225,11 @@ public class OutboundRow implements Map<String, SettableValue> {
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OutboundRow[" + rowAsMap + "]";
|
||||
return "OutboundRow[" + this.rowAsMap + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(BiConsumer<? super String, ? super SettableValue> action) {
|
||||
rowAsMap.forEach(action);
|
||||
this.rowAsMap.forEach(action);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,25 +13,21 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.r2dbc.function;
|
||||
package org.springframework.data.r2dbc.domain;
|
||||
|
||||
import io.r2dbc.spi.Connection;
|
||||
import io.r2dbc.spi.Statement;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Extension to {@link QueryOperation} for a prepared SQL query {@link Supplier} with bound parameters. Contains
|
||||
* parameter bindings that can be {@link #bind(Statement)} bound to a {@link Statement}.
|
||||
* parameter bindings that can be {@link #bindTo bound} bound to a {@link BindTarget}.
|
||||
* <p>
|
||||
* Can be executed with {@link DatabaseClient}.
|
||||
* Can be executed with {@link org.springframework.data.r2dbc.function.DatabaseClient}.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> underlying operation source.
|
||||
* @author Mark Paluch
|
||||
* @see DatabaseClient
|
||||
* @see DatabaseClient.SqlSpec#sql(Supplier)
|
||||
* @see org.springframework.data.r2dbc.function.DatabaseClient
|
||||
* @see org.springframework.data.r2dbc.function.DatabaseClient.SqlSpec#sql(Supplier)
|
||||
*/
|
||||
public interface PreparedOperation<T> extends QueryOperation {
|
||||
|
||||
@@ -41,15 +37,10 @@ public interface PreparedOperation<T> extends QueryOperation {
|
||||
T getSource();
|
||||
|
||||
/**
|
||||
* create a {@link Statement} from the generated SQL after applying the SQL filter and then applying the
|
||||
* {@link org.springframework.data.r2dbc.function.DefaultStatementFactory.Binding} after filtering those as well.
|
||||
* Apply bindings to {@link BindTarget}.
|
||||
*
|
||||
* @param connection the {@link Connection} used for constructing a statement
|
||||
* @return the bound statement.
|
||||
* @param target the target to apply bindings to.
|
||||
*/
|
||||
Statement createBoundStatement(Connection connection);
|
||||
void bindTo(BindTarget target);
|
||||
|
||||
void addSqlFilter(Function<String, String> filter);
|
||||
|
||||
void addBindingFilter(Function<Bindings, Bindings> filter);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.domain;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Interface declaring a query operation that can be represented with a query string. This interface is typically
|
||||
* implemented by classes representing a SQL operation such as {@code SELECT}, {@code INSERT}, and such.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @see PreparedOperation
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface QueryOperation extends Supplier<String> {
|
||||
|
||||
/**
|
||||
* Returns the string-representation of this operation to be used with {@link io.r2dbc.spi.Statement} creation.
|
||||
*
|
||||
* @return the operation as SQL string.
|
||||
* @see io.r2dbc.spi.Connection#createStatement(String)
|
||||
*/
|
||||
String toQuery();
|
||||
|
||||
@Override
|
||||
default String get() {
|
||||
return toQuery();
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,7 @@ public class SettableValue {
|
||||
*/
|
||||
@Nullable
|
||||
public Object getValue() {
|
||||
return value;
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,7 +93,7 @@ public class SettableValue {
|
||||
* @return the column value type
|
||||
*/
|
||||
public Class<?> getType() {
|
||||
return type;
|
||||
return this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,7 +102,7 @@ public class SettableValue {
|
||||
* @return whether this {@link SettableValue} has a value. {@literal false} if {@link #getValue()} is {@literal null}.
|
||||
*/
|
||||
public boolean hasValue() {
|
||||
return value != null;
|
||||
return this.value != null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,7 +111,7 @@ public class SettableValue {
|
||||
* @return whether this {@link SettableValue} is empty. {@literal true} if {@link #getValue()} is {@literal null}.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return value == null;
|
||||
return this.value == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -121,20 +121,20 @@ public class SettableValue {
|
||||
if (!(o instanceof SettableValue))
|
||||
return false;
|
||||
SettableValue value1 = (SettableValue) o;
|
||||
return Objects.equals(value, value1.value) && Objects.equals(type, value1.type);
|
||||
return Objects.equals(this.value, value1.value) && Objects.equals(this.type, value1.type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(value, type);
|
||||
return Objects.hash(this.value, this.type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
sb.append("SettableValue");
|
||||
sb.append("[value=").append(value);
|
||||
sb.append(", type=").append(type);
|
||||
sb.append("[value=").append(this.value);
|
||||
sb.append(", type=").append(this.type);
|
||||
sb.append(']');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ package org.springframework.data.r2dbc.function;
|
||||
|
||||
import io.r2dbc.spi.Statement;
|
||||
|
||||
import org.springframework.data.r2dbc.domain.BindTarget;
|
||||
import org.springframework.data.r2dbc.domain.PreparedOperation;
|
||||
import org.springframework.data.r2dbc.domain.QueryOperation;
|
||||
import org.springframework.data.r2dbc.domain.SettableValue;
|
||||
|
||||
/**
|
||||
@@ -11,46 +14,46 @@ import org.springframework.data.r2dbc.domain.SettableValue;
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @see Statement#bind
|
||||
* @see Statement#bindNull
|
||||
* @see Statement#bindNull TODO: Refactor to {@link PreparedOperation}.
|
||||
*/
|
||||
public interface BindableOperation extends QueryOperation {
|
||||
|
||||
/**
|
||||
* Bind the given {@code value} to the {@link Statement} using the underlying binding strategy.
|
||||
*
|
||||
* @param statement the statement to bind the value to.
|
||||
* @param bindTarget the bindTarget to bind the value to.
|
||||
* @param identifier named identifier that is considered by the underlying binding strategy.
|
||||
* @param value the actual value. Must not be {@literal null}. Use {@link #bindNull(Statement, Class)} for
|
||||
* {@literal null} values.
|
||||
* @see Statement#bind
|
||||
*/
|
||||
void bind(Statement statement, String identifier, Object value);
|
||||
void bind(BindTarget bindTarget, String identifier, Object value);
|
||||
|
||||
/**
|
||||
* Bind a {@literal null} value to the {@link Statement} using the underlying binding strategy.
|
||||
*
|
||||
* @param statement the statement to bind the value to.
|
||||
* @param bindTarget the bindTarget to bind the value to.
|
||||
* @param identifier named identifier that is considered by the underlying binding strategy.
|
||||
* @param valueType value type, must not be {@literal null}.
|
||||
* @see Statement#bindNull
|
||||
*/
|
||||
void bindNull(Statement statement, String identifier, Class<?> valueType);
|
||||
void bindNull(BindTarget bindTarget, String identifier, Class<?> valueType);
|
||||
|
||||
/**
|
||||
* Bind a {@link SettableValue} to the {@link Statement} using the underlying binding strategy. Binds either the
|
||||
* {@link SettableValue#getValue()} or {@literal null}, depending on whether the value is {@literal null}.
|
||||
*
|
||||
* @param statement the statement to bind the value to.
|
||||
* @param bindTarget the bindTarget to bind the value to.
|
||||
* @param value the settable value
|
||||
* @see Statement#bind
|
||||
* @see Statement#bindNull
|
||||
*/
|
||||
default void bind(Statement statement, String identifier, SettableValue value) {
|
||||
default void bind(BindTarget bindTarget, String identifier, SettableValue value) {
|
||||
|
||||
if (value.getValue() == null) {
|
||||
bindNull(statement, identifier, value.getType());
|
||||
bindNull(bindTarget, identifier, value.getType());
|
||||
} else {
|
||||
bind(statement, identifier, value.getValue());
|
||||
bind(bindTarget, identifier, value.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.r2dbc.domain.PreparedOperation;
|
||||
import org.springframework.data.r2dbc.support.R2dbcExceptionTranslator;
|
||||
|
||||
/**
|
||||
|
||||
@@ -52,7 +52,9 @@ import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.r2dbc.UncategorizedR2dbcException;
|
||||
import org.springframework.data.r2dbc.domain.BindTarget;
|
||||
import org.springframework.data.r2dbc.domain.OutboundRow;
|
||||
import org.springframework.data.r2dbc.domain.PreparedOperation;
|
||||
import org.springframework.data.r2dbc.domain.SettableValue;
|
||||
import org.springframework.data.r2dbc.function.connectionfactory.ConnectionProxy;
|
||||
import org.springframework.data.r2dbc.function.convert.ColumnMapRowMapper;
|
||||
@@ -60,6 +62,7 @@ import org.springframework.data.r2dbc.support.R2dbcExceptionTranslator;
|
||||
import org.springframework.data.relational.core.sql.Insert;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link DatabaseClient}.
|
||||
@@ -322,24 +325,51 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor {
|
||||
this.sqlSupplier = sqlSupplier;
|
||||
}
|
||||
|
||||
protected String getSql() {
|
||||
<T> FetchSpec<T> exchange(Supplier<String> sqlSupplier, BiFunction<Row, RowMetadata, T> mappingFunction) {
|
||||
|
||||
String sql = sqlSupplier.get();
|
||||
Assert.state(sql != null, "SQL supplier returned null!");
|
||||
return sql;
|
||||
}
|
||||
String sql = getRequiredSql(sqlSupplier);
|
||||
|
||||
<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 + "]");
|
||||
}
|
||||
|
||||
if (sqlSupplier instanceof PreparedOperation<?>) {
|
||||
pop = ((PreparedOperation<?>) sqlSupplier);
|
||||
} else {
|
||||
pop = new ExpandedPreparedOperation(sql, namedParameters, dataAccessStrategy, byName, byIndex);
|
||||
}
|
||||
if (sqlSupplier instanceof PreparedOperation<?>) {
|
||||
|
||||
Function<Connection, Flux<Result>> resultFunction = it -> Flux.from(pop.createBoundStatement(it).execute());
|
||||
Statement statement = it.createStatement(sql);
|
||||
BindTarget bindTarget = new StatementWrapper(statement);
|
||||
((PreparedOperation<?>) sqlSupplier).bindTo(bindTarget);
|
||||
|
||||
return statement;
|
||||
}
|
||||
|
||||
BindableOperation operation = namedParameters.expand(sql, dataAccessStrategy.getBindMarkersFactory(),
|
||||
new MapBindParameterSource(byName));
|
||||
|
||||
String expanded = operation.toQuery();
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Expanded SQL [" + expanded + "]");
|
||||
}
|
||||
|
||||
Statement statement = it.createStatement(expanded);
|
||||
BindTarget bindTarget = new StatementWrapper(statement);
|
||||
|
||||
byName.forEach((name, o) -> {
|
||||
|
||||
if (o.getValue() != null) {
|
||||
operation.bind(bindTarget, name, o.getValue());
|
||||
} else {
|
||||
operation.bindNull(bindTarget, name, o.getType());
|
||||
}
|
||||
});
|
||||
|
||||
bindByIndex(statement, byIndex);
|
||||
|
||||
return statement;
|
||||
};
|
||||
|
||||
Function<Connection, Flux<Result>> resultFunction = it -> Flux.from(executeFunction.apply(it).execute());
|
||||
|
||||
return new DefaultSqlResult<>(DefaultDatabaseClient.this, //
|
||||
sql, //
|
||||
@@ -395,7 +425,7 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor {
|
||||
}
|
||||
|
||||
private void assertNotPreparedOperation() {
|
||||
if (sqlSupplier instanceof PreparedOperation<?>) {
|
||||
if (this.sqlSupplier instanceof PreparedOperation<?>) {
|
||||
throw new InvalidDataAccessApiUsageException("Cannot add bindings to a PreparedOperation");
|
||||
}
|
||||
}
|
||||
@@ -440,12 +470,12 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor {
|
||||
|
||||
Assert.notNull(mappingFunction, "Mapping function must not be null!");
|
||||
|
||||
return exchange(getSql(), mappingFunction);
|
||||
return exchange(this.sqlSupplier, mappingFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FetchSpec<Map<String, Object>> fetch() {
|
||||
return exchange(getSql(), ColumnMapRowMapper.INSTANCE);
|
||||
return exchange(this.sqlSupplier, ColumnMapRowMapper.INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -525,12 +555,12 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor {
|
||||
|
||||
Assert.notNull(mappingFunction, "Mapping function must not be null!");
|
||||
|
||||
return exchange(getSql(), mappingFunction);
|
||||
return exchange(this.sqlSupplier, mappingFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FetchSpec<T> fetch() {
|
||||
return exchange(getSql(), mappingFunction);
|
||||
return exchange(this.sqlSupplier, mappingFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -882,10 +912,18 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor {
|
||||
byName.forEach(it::bind);
|
||||
});
|
||||
|
||||
Function<Connection, Flux<Result>> resultFunction = it -> Flux.from(operation.createBoundStatement(it).execute());
|
||||
String sql = getRequiredSql(operation);
|
||||
|
||||
Function<Connection, Flux<Result>> resultFunction = it -> {
|
||||
|
||||
Statement statement = it.createStatement(sql);
|
||||
operation.bindTo(new StatementWrapper(statement));
|
||||
|
||||
return Flux.from(statement.execute());
|
||||
};
|
||||
|
||||
return new DefaultSqlResult<>(DefaultDatabaseClient.this, //
|
||||
operation.toQuery(), //
|
||||
sql, //
|
||||
resultFunction, //
|
||||
it -> resultFunction.apply(it).flatMap(Result::getRowsUpdated).next(), //
|
||||
mappingFunction);
|
||||
@@ -993,10 +1031,18 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor {
|
||||
});
|
||||
});
|
||||
|
||||
Function<Connection, Flux<Result>> resultFunction = it -> Flux.from(operation.createBoundStatement(it).execute());
|
||||
String sql = getRequiredSql(operation);
|
||||
Function<Connection, Flux<Result>> resultFunction = it -> {
|
||||
|
||||
Statement statement = it.createStatement(sql);
|
||||
operation.bindTo(new StatementWrapper(statement));
|
||||
statement.returnGeneratedValues();
|
||||
|
||||
return Flux.from(statement.execute());
|
||||
};
|
||||
|
||||
return new DefaultSqlResult<>(DefaultDatabaseClient.this, //
|
||||
operation.toQuery(), //
|
||||
sql, //
|
||||
resultFunction, //
|
||||
it -> resultFunction //
|
||||
.apply(it) //
|
||||
@@ -1045,6 +1091,13 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor {
|
||||
}
|
||||
}
|
||||
|
||||
private static String getRequiredSql(Supplier<String> sqlSupplier) {
|
||||
|
||||
String sql = sqlSupplier.get();
|
||||
Assert.state(StringUtils.hasText(sql), "SQL returned by SQL supplier must not be empty!");
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invocation handler that suppresses close calls on R2DBC Connections. Also prepares returned Statement
|
||||
* (Prepared/CallbackStatement) objects.
|
||||
@@ -1118,4 +1171,31 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
static class StatementWrapper implements BindTarget {
|
||||
|
||||
final Statement statement;
|
||||
|
||||
@Override
|
||||
public void bind(Object identifier, Object value) {
|
||||
this.statement.bind(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(int index, Object value) {
|
||||
this.statement.bind(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindNull(Object identifier, Class<?> type) {
|
||||
this.statement.bindNull(identifier, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindNull(int index, Class<?> type) {
|
||||
this.statement.bindNull(index, type);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.r2dbc.function;
|
||||
|
||||
import io.r2dbc.spi.Connection;
|
||||
import io.r2dbc.spi.Statement;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -29,16 +28,29 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.r2dbc.dialect.BindMarker;
|
||||
import org.springframework.data.r2dbc.dialect.BindMarkers;
|
||||
import org.springframework.data.r2dbc.dialect.Dialect;
|
||||
import org.springframework.data.r2dbc.dialect.IndexedBindMarker;
|
||||
import org.springframework.data.r2dbc.domain.BindTarget;
|
||||
import org.springframework.data.r2dbc.domain.PreparedOperation;
|
||||
import org.springframework.data.r2dbc.domain.SettableValue;
|
||||
import org.springframework.data.relational.core.sql.*;
|
||||
import org.springframework.data.relational.core.sql.AssignValue;
|
||||
import org.springframework.data.relational.core.sql.Assignment;
|
||||
import org.springframework.data.relational.core.sql.Column;
|
||||
import org.springframework.data.relational.core.sql.Condition;
|
||||
import org.springframework.data.relational.core.sql.Delete;
|
||||
import org.springframework.data.relational.core.sql.DeleteBuilder;
|
||||
import org.springframework.data.relational.core.sql.Expression;
|
||||
import org.springframework.data.relational.core.sql.Insert;
|
||||
import org.springframework.data.relational.core.sql.SQL;
|
||||
import org.springframework.data.relational.core.sql.Select;
|
||||
import org.springframework.data.relational.core.sql.SelectBuilder;
|
||||
import org.springframework.data.relational.core.sql.StatementBuilder;
|
||||
import org.springframework.data.relational.core.sql.Table;
|
||||
import org.springframework.data.relational.core.sql.Update;
|
||||
import org.springframework.data.relational.core.sql.UpdateBuilder;
|
||||
import org.springframework.data.relational.core.sql.render.RenderContext;
|
||||
import org.springframework.data.relational.core.sql.render.SqlRenderer;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -94,42 +106,11 @@ class DefaultStatementFactory implements StatementFactory {
|
||||
return new DefaultPreparedOperation<>( //
|
||||
select, //
|
||||
renderContext, //
|
||||
createBindings(binding) //
|
||||
binding //
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Bindings createBindings(Binding binding) {
|
||||
|
||||
List<Bindings.SingleBinding> singleBindings = new ArrayList<>();
|
||||
|
||||
binding.getNulls().forEach( //
|
||||
(bindMarker, settableValue) -> {
|
||||
|
||||
if (bindMarker instanceof IndexedBindMarker) {
|
||||
singleBindings //
|
||||
.add(new Bindings.IndexedSingleBinding( //
|
||||
((IndexedBindMarker) bindMarker).getIndex(), //
|
||||
settableValue) //
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
binding.getValues().forEach( //
|
||||
(bindMarker, value) -> {
|
||||
if (bindMarker instanceof IndexedBindMarker) {
|
||||
singleBindings //
|
||||
.add(new Bindings.IndexedSingleBinding( //
|
||||
((IndexedBindMarker) bindMarker).getIndex(), //
|
||||
value instanceof SettableValue ? (SettableValue) value : SettableValue.from(value)) //
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return new Bindings(singleBindings);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.StatementFactory#insert(java.lang.String, java.util.Collection, java.util.function.Consumer)
|
||||
@@ -174,12 +155,7 @@ class DefaultStatementFactory implements StatementFactory {
|
||||
Insert insert = StatementBuilder.insert().into(table).columns(table.columns(binderBuilder.bindings.keySet()))
|
||||
.values(expressions).build();
|
||||
|
||||
return new DefaultPreparedOperation<Insert>(insert, renderContext, createBindings(binding)) {
|
||||
@Override
|
||||
public Statement bind(Statement to) {
|
||||
return super.bind(to).returnGeneratedValues(generatedKeysNames.toArray(new String[0]));
|
||||
}
|
||||
};
|
||||
return new DefaultPreparedOperation<Insert>(insert, renderContext, binding);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -228,7 +204,7 @@ class DefaultStatementFactory implements StatementFactory {
|
||||
update = updateBuilder.build();
|
||||
}
|
||||
|
||||
return new DefaultPreparedOperation<>(update, renderContext, createBindings(binding));
|
||||
return new DefaultPreparedOperation<>(update, renderContext, binding);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -266,7 +242,7 @@ class DefaultStatementFactory implements StatementFactory {
|
||||
delete = deleteBuilder.build();
|
||||
}
|
||||
|
||||
return new DefaultPreparedOperation<>(delete, renderContext, createBindings(binding));
|
||||
return new DefaultPreparedOperation<>(delete, renderContext, binding);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -429,97 +405,24 @@ class DefaultStatementFactory implements StatementFactory {
|
||||
*
|
||||
* @param to
|
||||
*/
|
||||
void apply(Statement to) {
|
||||
void apply(BindTarget to) {
|
||||
|
||||
values.forEach((marker, value) -> marker.bind(to, value));
|
||||
nulls.forEach((marker, value) -> marker.bindNull(to, value.getType()));
|
||||
}
|
||||
}
|
||||
|
||||
static abstract class PreparedOperationSupport<T> implements PreparedOperation<T> {
|
||||
|
||||
private Function<String, String> sqlFilter = s -> s;
|
||||
private Function<Bindings, Bindings> bindingFilter = b -> b;
|
||||
private final Bindings bindings;
|
||||
|
||||
protected PreparedOperationSupport(Bindings bindings) {
|
||||
|
||||
this.bindings = bindings;
|
||||
}
|
||||
|
||||
abstract protected String createBaseSql();
|
||||
|
||||
Bindings getBaseBinding() {
|
||||
return bindings;
|
||||
};
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.QueryOperation#toQuery()
|
||||
*/
|
||||
@Override
|
||||
public String toQuery() {
|
||||
|
||||
return sqlFilter.apply(createBaseSql());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.PreparedOperation#bind(io.r2dbc.spi.Statement)
|
||||
*/
|
||||
protected Statement bind(Statement to) {
|
||||
|
||||
bindingFilter.apply(getBaseBinding()).apply(to);
|
||||
return to;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement createBoundStatement(Connection connection) {
|
||||
|
||||
// TODO add back logging
|
||||
// if (logger.isDebugEnabled()) {
|
||||
// logger.debug("Executing SQL statement [" + sql + "]");
|
||||
// }
|
||||
|
||||
return bind(connection.createStatement(toQuery()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSqlFilter(Function<String, String> filter) {
|
||||
|
||||
Assert.notNull(filter, "Filter must not be null.");
|
||||
|
||||
sqlFilter = filter;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBindingFilter(Function<Bindings, Bindings> filter) {
|
||||
|
||||
Assert.notNull(filter, "Filter must not be null.");
|
||||
|
||||
bindingFilter = filter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of {@link PreparedOperation}.
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
static class DefaultPreparedOperation<T> extends PreparedOperationSupport<T> {
|
||||
@RequiredArgsConstructor
|
||||
static class DefaultPreparedOperation<T> implements PreparedOperation<T> {
|
||||
|
||||
private final T source;
|
||||
private final RenderContext renderContext;
|
||||
|
||||
DefaultPreparedOperation(T source, RenderContext renderContext, Bindings bindings) {
|
||||
|
||||
super(bindings);
|
||||
|
||||
this.source = source;
|
||||
this.renderContext = renderContext;
|
||||
}
|
||||
private final Binding binding;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -530,8 +433,12 @@ class DefaultStatementFactory implements StatementFactory {
|
||||
return this.source;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.QueryOperation#toQuery()
|
||||
*/
|
||||
@Override
|
||||
protected String createBaseSql() {
|
||||
public String toQuery() {
|
||||
|
||||
SqlRenderer sqlRenderer = SqlRenderer.create(renderContext);
|
||||
|
||||
@@ -554,5 +461,9 @@ class DefaultStatementFactory implements StatementFactory {
|
||||
throw new IllegalStateException("Cannot render " + this.getSource());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindTo(BindTarget target) {
|
||||
binding.apply(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* 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.Connection;
|
||||
import io.r2dbc.spi.Statement;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.r2dbc.domain.SettableValue;
|
||||
|
||||
/**
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public class ExpandedPreparedOperation
|
||||
extends DefaultStatementFactory.PreparedOperationSupport<BindableOperation> {
|
||||
|
||||
private final BindableOperation operation;
|
||||
private final Map<String, SettableValue> byName;
|
||||
private final Map<Integer, SettableValue> byIndex;
|
||||
|
||||
private ExpandedPreparedOperation(BindableOperation operation, Map<String, SettableValue> byName,
|
||||
Map<Integer, SettableValue> byIndex) {
|
||||
|
||||
super(createBindings(operation, byName, byIndex));
|
||||
|
||||
this.operation = operation;
|
||||
this.byName = byName;
|
||||
this.byIndex = byIndex;
|
||||
}
|
||||
|
||||
private static Bindings createBindings(BindableOperation operation, Map<String, SettableValue> byName,
|
||||
Map<Integer, SettableValue> byIndex) {
|
||||
|
||||
List<Bindings.SingleBinding> bindings = new ArrayList<>();
|
||||
|
||||
byName.forEach(
|
||||
(identifier, settableValue) -> bindings.add(new Bindings.NamedExpandedSingleBinding(identifier, settableValue, operation)));
|
||||
|
||||
byIndex.forEach((identifier, settableValue) -> bindings.add(new Bindings.IndexedSingleBinding(identifier, settableValue)));
|
||||
|
||||
return new Bindings(bindings);
|
||||
}
|
||||
|
||||
ExpandedPreparedOperation(String sql, NamedParameterExpander namedParameters,
|
||||
ReactiveDataAccessStrategy dataAccessStrategy, Map<String, SettableValue> byName,
|
||||
Map<Integer, SettableValue> byIndex) {
|
||||
|
||||
this( //
|
||||
namedParameters.expand(sql, dataAccessStrategy.getBindMarkersFactory(), new MapBindParameterSource(byName)), //
|
||||
byName, //
|
||||
byIndex //
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String createBaseSql() {
|
||||
return toQuery();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BindableOperation getSource() {
|
||||
return operation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement createBoundStatement(Connection connection) {
|
||||
|
||||
Statement statement = connection.createStatement(operation.toQuery());
|
||||
|
||||
bindByName(statement, byName);
|
||||
bindByIndex(statement, byIndex);
|
||||
|
||||
return statement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toQuery() {
|
||||
return operation.toQuery();
|
||||
}
|
||||
|
||||
// todo that is a weird assymmetry between bindByName and bindByIndex
|
||||
private void bindByName(Statement statement, Map<String, SettableValue> byName) {
|
||||
|
||||
byName.forEach((name, o) -> {
|
||||
|
||||
if (o.getValue() != null) {
|
||||
operation.bind(statement, name, o.getValue());
|
||||
} else {
|
||||
operation.bindNull(statement, name, o.getType());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void bindByIndex(Statement statement, Map<Integer, SettableValue> byIndex) {
|
||||
|
||||
byIndex.forEach((i, o) -> {
|
||||
|
||||
if (o.getValue() != null) {
|
||||
statement.bind(i.intValue(), o.getValue());
|
||||
} else {
|
||||
statement.bindNull(i.intValue(), o.getType());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -15,14 +15,13 @@
|
||||
*/
|
||||
package org.springframework.data.r2dbc.function;
|
||||
|
||||
import io.r2dbc.spi.Statement;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.data.r2dbc.dialect.BindMarkersFactory;
|
||||
import org.springframework.data.r2dbc.domain.BindTarget;
|
||||
|
||||
/**
|
||||
* SQL translation support allowing the use of named parameters rather than native placeholders.
|
||||
@@ -147,13 +146,13 @@ public class NamedParameterExpander {
|
||||
return new BindableOperation() {
|
||||
|
||||
@Override
|
||||
public void bind(Statement statement, String identifier, Object value) {
|
||||
statement.bind(identifier, value);
|
||||
public void bind(BindTarget target, String identifier, Object value) {
|
||||
target.bind(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindNull(Statement statement, String identifier, Class<?> valueType) {
|
||||
statement.bindNull(identifier, valueType);
|
||||
public void bindNull(BindTarget target, String identifier, Class<?> valueType) {
|
||||
target.bindNull(identifier, valueType);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.r2dbc.function;
|
||||
|
||||
import io.r2dbc.spi.Statement;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -31,6 +30,7 @@ import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.r2dbc.dialect.BindMarker;
|
||||
import org.springframework.data.r2dbc.dialect.BindMarkers;
|
||||
import org.springframework.data.r2dbc.dialect.BindMarkersFactory;
|
||||
import org.springframework.data.r2dbc.domain.BindTarget;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -391,22 +391,22 @@ abstract class NamedParameterUtils {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.BindableOperation#bind(io.r2dbc.spi.Statement, java.lang.String, java.lang.Object)
|
||||
* @see org.springframework.data.r2dbc.function.BindableOperation#bind(BindTarget, java.lang.String, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void bind(Statement statement, String identifier, Object value) {
|
||||
public void bind(BindTarget target, String identifier, Object value) {
|
||||
|
||||
List<BindMarker> bindMarkers = getBindMarkers(identifier);
|
||||
|
||||
if (bindMarkers == null) {
|
||||
|
||||
statement.bind(identifier, value);
|
||||
target.bind(identifier, value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bindMarkers.size() == 1) {
|
||||
bindMarkers.get(0).bind(statement, value);
|
||||
bindMarkers.get(0).bind(target, value);
|
||||
} else {
|
||||
|
||||
Assert.isInstanceOf(Collection.class, value,
|
||||
@@ -424,36 +424,36 @@ abstract class NamedParameterUtils {
|
||||
if (valueToBind instanceof Object[]) {
|
||||
Object[] objects = (Object[]) valueToBind;
|
||||
for (Object object : objects) {
|
||||
bind(statement, markers, object);
|
||||
bind(target, markers, object);
|
||||
}
|
||||
} else {
|
||||
bind(statement, markers, valueToBind);
|
||||
bind(target, markers, valueToBind);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void bind(Statement statement, Iterator<BindMarker> markers, Object valueToBind) {
|
||||
private void bind(BindTarget target, Iterator<BindMarker> markers, Object valueToBind) {
|
||||
|
||||
Assert.isTrue(markers.hasNext(),
|
||||
() -> String.format(
|
||||
"No bind marker for value [%s] in SQL [%s]. Check that the query was expanded using the same arguments.",
|
||||
valueToBind, toQuery()));
|
||||
|
||||
markers.next().bind(statement, valueToBind);
|
||||
markers.next().bind(target, valueToBind);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.BindableOperation#bindNull(io.r2dbc.spi.Statement, java.lang.String, java.lang.Class)
|
||||
* @see org.springframework.data.r2dbc.function.BindableOperation#bindNull(BindTarget, java.lang.String, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public void bindNull(Statement statement, String identifier, Class<?> valueType) {
|
||||
public void bindNull(BindTarget target, String identifier, Class<?> valueType) {
|
||||
|
||||
List<BindMarker> bindMarkers = getBindMarkers(identifier);
|
||||
|
||||
if (bindMarkers.size() == 1) {
|
||||
bindMarkers.get(0).bindNull(statement, valueType);
|
||||
bindMarkers.get(0).bindNull(target, valueType);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* 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.Connection;
|
||||
import io.r2dbc.spi.Statement;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.data.r2dbc.domain.SettableValue;
|
||||
|
||||
/**
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public class OldParameterbindingPreparedOperation implements PreparedOperation<BindableOperation> {
|
||||
|
||||
private final BindableOperation operation;
|
||||
private final Map<String, SettableValue> byName;
|
||||
private final Map<Integer, SettableValue> byIndex;
|
||||
|
||||
private OldParameterbindingPreparedOperation(BindableOperation operation, Map<String, SettableValue> byName,
|
||||
Map<Integer, SettableValue> byIndex) {
|
||||
|
||||
this.operation = operation;
|
||||
this.byName = byName;
|
||||
this.byIndex = byIndex;
|
||||
}
|
||||
|
||||
OldParameterbindingPreparedOperation(String sql, NamedParameterExpander namedParameters,
|
||||
ReactiveDataAccessStrategy dataAccessStrategy, Map<String, SettableValue> byName,
|
||||
Map<Integer, SettableValue> byIndex) {
|
||||
|
||||
this( //
|
||||
namedParameters.expand(sql, dataAccessStrategy.getBindMarkersFactory(), new MapBindParameterSource(byName)), //
|
||||
byName, //
|
||||
byIndex //
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BindableOperation getSource() {
|
||||
return operation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement createBoundStatement(Connection connection) {
|
||||
|
||||
Statement statement = connection.createStatement(operation.toQuery());
|
||||
|
||||
bindByName(statement, byName);
|
||||
bindByIndex(statement, byIndex);
|
||||
|
||||
return statement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSqlFilter(Function<String, String> filter) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBindingFilter(Function<Bindings, Bindings> filter) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toQuery() {
|
||||
return operation.toQuery();
|
||||
}
|
||||
|
||||
// todo that is a weird assymmetry between bindByName and bindByIndex
|
||||
private void bindByName(Statement statement, Map<String, SettableValue> byName) {
|
||||
|
||||
byName.forEach((name, o) -> {
|
||||
|
||||
if (o.getValue() != null) {
|
||||
operation.bind(statement,name, o.getValue());
|
||||
} else {
|
||||
operation.bindNull(statement, name, o.getType());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void bindByIndex(Statement statement, Map<Integer, SettableValue> byIndex) {
|
||||
|
||||
byIndex.forEach((i, o) -> {
|
||||
|
||||
if (o.getValue() != null) {
|
||||
statement.bind(i.intValue(), o.getValue());
|
||||
} else {
|
||||
statement.bindNull(i.intValue(), o.getType());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package org.springframework.data.r2dbc.function;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Interface declaring a query operation that can be represented with a query string. This interface is typically
|
||||
* implemented by classes representing a SQL operation such as {@code SELECT}, {@code INSERT}, and such.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface QueryOperation extends Supplier<String> {
|
||||
|
||||
/**
|
||||
* Returns the string-representation of this operation to be used with {@link io.r2dbc.spi.Statement} creation.
|
||||
*
|
||||
* @return the operation as SQL string.
|
||||
* @see io.r2dbc.spi.Connection#createStatement(String)
|
||||
*/
|
||||
String toQuery();
|
||||
|
||||
@Override
|
||||
default String get() {
|
||||
return toQuery();
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import java.util.Collection;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.data.r2dbc.dialect.Dialect;
|
||||
import org.springframework.data.r2dbc.domain.PreparedOperation;
|
||||
import org.springframework.data.r2dbc.domain.SettableValue;
|
||||
import org.springframework.data.relational.core.sql.Delete;
|
||||
import org.springframework.data.relational.core.sql.Insert;
|
||||
|
||||
@@ -27,9 +27,9 @@ import java.util.Set;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.data.r2dbc.domain.PreparedOperation;
|
||||
import org.springframework.data.r2dbc.domain.SettableValue;
|
||||
import org.springframework.data.r2dbc.function.DatabaseClient;
|
||||
import org.springframework.data.r2dbc.function.PreparedOperation;
|
||||
import org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy;
|
||||
import org.springframework.data.r2dbc.function.StatementFactory;
|
||||
import org.springframework.data.r2dbc.function.convert.R2dbcConverter;
|
||||
|
||||
Reference in New Issue
Block a user