[jOOQ/jOOQ#11512] ParsingConnection should infer bind variable type from

PreparedStatement calls

This includes:
- [jOOQ/jOOQ#11623] Refactor a few internal checked exception throwing
functional interfaces
This commit is contained in:
Lukas Eder 2021-03-12 10:50:55 +01:00
parent e1082e9e1c
commit 984d308b07
10 changed files with 1304 additions and 139 deletions

View File

@ -330,11 +330,6 @@ abstract class AbstractContext<C extends Context<C>> extends AbstractScope imple
return (C) this;
}
@FunctionalInterface
private interface BooleanConsumer {
void accept(boolean b);
}
@Override
public final C data(Object key, Object value, Consumer<? super C> consumer) {
return toggle(

View File

@ -83,6 +83,7 @@ import org.jooq.conf.StatementType;
import org.jooq.exception.ControlFlowSignal;
import org.jooq.exception.DataAccessException;
import org.jooq.exception.DetachedException;
import org.jooq.impl.DefaultRenderContext.Rendered;
import org.jooq.tools.Ints;
import org.jooq.tools.JooqLogger;
@ -488,23 +489,6 @@ abstract class AbstractQuery<R extends Record> extends AbstractFetchable<R> impl
return true;
}
static class Rendered {
String sql;
QueryPartList<Param<?>> bindValues;
int skipUpdateCounts;
Rendered(String sql, QueryPartList<Param<?>> bindValues, int skipUpdateCounts) {
this.sql = sql;
this.bindValues = bindValues;
this.skipUpdateCounts = skipUpdateCounts;
}
@Override
public String toString() {
return sql;
}
}
private final Rendered getSQL0(ExecuteContext ctx) {
Rendered result;
DefaultRenderContext render;

View File

@ -336,7 +336,7 @@ abstract class AbstractRecord extends AbstractStore implements Record {
else if (Tools.nonReplacingEmbeddable(field))
return (T) Tools
.newRecord(fetched, ((EmbeddableTableField<?, ?>) field).recordType)
.operate(new TransferRecordState<Record>(embeddedFields(field)));
.operate(new TransferRecordState<>(embeddedFields(field)));
else
throw Tools.indexFail(fields, field);
}
@ -850,7 +850,7 @@ abstract class AbstractRecord extends AbstractStore implements Record {
return Tools.newRecord(fetched, type, fields, configuration()).operate(new TransferRecordState<>(null));
}
private class TransferRecordState<R extends Record> implements RecordOperation<R, MappingException> {
private class TransferRecordState<R extends Record> implements ThrowingFunction<R, R, MappingException> {
private final Field<?>[] targetFields;
@ -859,7 +859,7 @@ abstract class AbstractRecord extends AbstractStore implements Record {
}
@Override
public R operate(R target) throws MappingException {
public R apply(R target) throws MappingException {
AbstractRecord source = AbstractRecord.this;
try {

View File

@ -1453,7 +1453,7 @@ final class CursorImpl<R extends Record> extends AbstractCursor<R> {
throw new UnsupportedOperationException();
}
private class CursorRecordInitialiser implements RecordOperation<AbstractRecord, SQLException> {
private class CursorRecordInitialiser implements ThrowingFunction<AbstractRecord, AbstractRecord, SQLException> {
private final AbstractRow initialiserFields;
private int offset;
@ -1469,7 +1469,7 @@ final class CursorImpl<R extends Record> extends AbstractCursor<R> {
}
@Override
public AbstractRecord operate(AbstractRecord record) throws SQLException {
public AbstractRecord apply(AbstractRecord record) throws SQLException {
ctx.record(record);
listener.recordStart(ctx);
int size = initialiserFields.size();
@ -1528,7 +1528,7 @@ final class CursorImpl<R extends Record> extends AbstractCursor<R> {
}
if (nested != null) {
value = (T) Tools.newRecord(true, recordType, nested, ((DefaultExecuteContext) ctx).originalConfiguration())
value = (T) Tools.newRecord(true, (Class<AbstractRecord>) recordType, nested, ((DefaultExecuteContext) ctx).originalConfiguration())
.operate(new CursorRecordInitialiser(nested, offset + index));
offset += nested.size() - 1;

View File

@ -962,4 +962,21 @@ class DefaultRenderContext extends AbstractContext<RenderContext> implements Ren
log.debug("Re-render query", "Forcing bind variable inlining as " + configuration().dialect() + " does not support " + peekIndex() + " bind variables (or more) in a single query");
}
}
static class Rendered {
String sql;
QueryPartList<Param<?>> bindValues;
int skipUpdateCounts;
Rendered(String sql, QueryPartList<Param<?>> bindValues, int skipUpdateCounts) {
this.sql = sql;
this.bindValues = bindValues;
this.skipUpdateCounts = skipUpdateCounts;
}
@Override
public String toString() {
return sql;
}
}
}

View File

@ -157,7 +157,7 @@ final class MetaImpl extends AbstractMeta {
this.inverseSchemaCatalog = INVERSE_SCHEMA_CATALOG.contains(dialect());
}
final <R> R catalogSchema(Catalog catalog, Schema schema, ThrowingBiFunction<String, String, R> function) throws SQLException {
final <R> R catalogSchema(Catalog catalog, Schema schema, ThrowingBiFunction<String, String, R, SQLException> function) throws SQLException {
return catalogSchema(
catalog != null ? catalog.getName() : null,
schema != null ? schema.getName() : null,
@ -165,7 +165,7 @@ final class MetaImpl extends AbstractMeta {
);
}
final <R> R catalogSchema(String catalog, String schema, ThrowingBiFunction<String, String, R> function) throws SQLException {
final <R> R catalogSchema(String catalog, String schema, ThrowingBiFunction<String, String, R, SQLException> function) throws SQLException {
String c = defaultIfEmpty(catalog, null);
String s = defaultIfEmpty(schema, null);
@ -176,22 +176,12 @@ final class MetaImpl extends AbstractMeta {
return function.apply(c, s);
}
@FunctionalInterface
private interface ThrowingBiFunction<T1, T2, R> {
R apply(T1 t1, T2 t2) throws SQLException;
}
@FunctionalInterface
private interface MetaFunction {
Result<Record> run(DatabaseMetaData meta) throws SQLException;
}
private final Result<Record> meta(MetaFunction consumer) {
private final Result<Record> meta(ThrowingFunction<DatabaseMetaData, Result<Record>, SQLException> function) {
if (databaseMetaData == null)
return dsl().connectionResult(connection -> consumer.run(connection.getMetaData()));
return dsl().connectionResult(connection -> function.apply(connection.getMetaData()));
try {
return consumer.run(databaseMetaData);
return function.apply(databaseMetaData);
}
catch (SQLException e) {
throw new DataAccessException("Error while running MetaFunction", e);

View File

@ -44,7 +44,9 @@ import java.sql.Statement;
import org.jooq.Configuration;
import org.jooq.DSLContext;
import org.jooq.Param;
import org.jooq.Parser;
import org.jooq.impl.DefaultRenderContext.Rendered;
import org.jooq.tools.JooqLogger;
import org.jooq.tools.jdbc.DefaultConnection;
@ -55,9 +57,9 @@ final class ParsingConnection extends DefaultConnection {
private static final JooqLogger log = JooqLogger.getLogger(ParsingConnection.class);
private final Configuration configuration;
private final DSLContext ctx;
private final Parser parser;
final Configuration configuration;
final DSLContext ctx;
final Parser parser;
ParsingConnection(Configuration configuration) {
super(configuration.connectionProvider().acquire());
@ -67,10 +69,15 @@ final class ParsingConnection extends DefaultConnection {
this.parser = ctx.parser();
}
final String translate(String sql) {
final Rendered translate(String sql, Param<?>... bindValues) {
log.debug("Translating from", sql);
String result = ctx.render(parser.parseQuery(sql));
log.debug("Translating to", result);
DefaultRenderContext render = (DefaultRenderContext) ctx.renderContext();
Rendered result = new Rendered(
render.visit(parser.parseQuery(sql, (Object[]) bindValues)).render(),
render.bindValues(),
render.skipUpdateCounts()
);
log.debug("Translating to", result.sql);
return result;
}
@ -89,49 +96,61 @@ final class ParsingConnection extends DefaultConnection {
return new ParsingStatement(this, getDelegate().createStatement(resultSetType, resultSetConcurrency, resultSetHoldability));
}
private final ThrowingFunction<Param<?>[], PreparedStatement, SQLException> prepareAndBind(
String sql,
ThrowingFunction<String, PreparedStatement, SQLException> f
) {
return p -> {
Rendered rendered = translate(sql, p);
PreparedStatement s = f.apply(rendered.sql);
new DefaultBindContext(configuration, s).visit(rendered.bindValues);
return s;
};
}
@Override
public final PreparedStatement prepareStatement(String sql) throws SQLException {
return new ParsingStatement(this, getDelegate().prepareStatement(translate(sql)));
return new ParsingStatement(this, prepareAndBind(sql, s -> getDelegate().prepareStatement(s)));
}
@Override
public final PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
return new ParsingStatement(this, getDelegate().prepareStatement(translate(sql), resultSetType, resultSetConcurrency));
return new ParsingStatement(this, prepareAndBind(sql, s -> getDelegate().prepareStatement(s, resultSetType, resultSetConcurrency)));
}
@Override
public final PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
return new ParsingStatement(this, getDelegate().prepareStatement(translate(sql), resultSetType, resultSetConcurrency, resultSetHoldability));
return new ParsingStatement(this, prepareAndBind(sql, s -> getDelegate().prepareStatement(s, resultSetType, resultSetConcurrency, resultSetHoldability)));
}
@Override
public final PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
return new ParsingStatement(this, getDelegate().prepareStatement(translate(sql), autoGeneratedKeys));
return new ParsingStatement(this, prepareAndBind(sql, s -> getDelegate().prepareStatement(s, autoGeneratedKeys)));
}
@Override
public final PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
return new ParsingStatement(this, getDelegate().prepareStatement(translate(sql), columnIndexes));
return new ParsingStatement(this, prepareAndBind(sql, s -> getDelegate().prepareStatement(s, columnIndexes)));
}
@Override
public final PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
return new ParsingStatement(this, getDelegate().prepareStatement(translate(sql), columnNames));
return new ParsingStatement(this, prepareAndBind(sql, s -> getDelegate().prepareStatement(s, columnNames)));
}
@Override
public final CallableStatement prepareCall(String sql) throws SQLException {
return new ParsingStatement(this, getDelegate().prepareCall(translate(sql)));
return new ParsingStatement(this, prepareAndBind(sql, s -> getDelegate().prepareCall(s)));
}
@Override
public final CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
return new ParsingStatement(this, getDelegate().prepareCall(sql, resultSetType, resultSetConcurrency));
return new ParsingStatement(this, prepareAndBind(sql, s -> getDelegate().prepareCall(s, resultSetType, resultSetConcurrency)));
}
@Override
public final CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
return new ParsingStatement(this, getDelegate().prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability));
return new ParsingStatement(this, prepareAndBind(sql, s -> getDelegate().prepareCall(s, resultSetType, resultSetConcurrency, resultSetHoldability)));
}
@Override

File diff suppressed because it is too large Load Diff

View File

@ -92,7 +92,7 @@ final class RecordDelegate<R extends Record> {
}
@SuppressWarnings("unchecked")
final <E extends Exception> R operate(RecordOperation<? super R, E> operation) throws E {
final <E extends Exception> R operate(ThrowingFunction<R, R, E> operation) throws E {
R record = recordSupplier.get();
// [#3300] Records that were fetched from the database
@ -142,7 +142,7 @@ final class RecordDelegate<R extends Record> {
if (operation != null) {
try {
operation.operate(record);
operation.apply(record);
}
// [#2770][#3036] Exceptions must not propagate before listeners receive "end" events

View File

@ -1,56 +0,0 @@
/*
* 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
*
* http://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.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.impl;
import org.jooq.Record;
import org.jooq.RecordListener;
/**
* An operation callback for {@link Record} objects, abstracting
* {@link RecordListener} lifecycle handling.
*
* @author Lukas Eder
*/
@FunctionalInterface
interface RecordOperation<R extends Record, E extends Exception> {
/**
* Callback method to initialise a record.
*/
R operate(R record) throws E;
}