[jOOQ/jOOQ#8821] Add Settings.parseNamedParamPrefix to support dialect

specific named parameter placeholders
This commit is contained in:
Lukas Eder 2021-03-24 15:34:25 +01:00
parent a49950b831
commit c6d049d9d7
12 changed files with 274 additions and 187 deletions

View File

@ -265,6 +265,8 @@ public class Settings
protected String parseDateFormat = "YYYY-MM-DD";
@XmlElement(defaultValue = "YYYY-MM-DD HH24:MI:SS.FF")
protected String parseTimestampFormat = "YYYY-MM-DD HH24:MI:SS.FF";
@XmlElement(defaultValue = ":")
protected String parseNamedParamPrefix = ":";
@XmlElement(defaultValue = "DEFAULT")
@XmlSchemaType(name = "string")
protected ParseNameCase parseNameCase = ParseNameCase.DEFAULT;
@ -450,12 +452,12 @@ public class Settings
}
/**
* The prefix to use for named parameters.
* The prefix to use for named parameters in generated SQL.
* <p>
* Named parameter syntax defaults to <code>:name</code> (such as supported by Oracle, JPA, Spring), but
* vendor specific parameters may look differently. This flag can be used to determine the prefix to be
* used by named parameters, such as <code>@</code> for SQL Server's <code>@name</code> or <code>$</code>
* for PostgreSQL's <code>$name</code>.
* for PostgreSQL's <code>$name</code>, when generating SQL.
* <p>
* "Named indexed" parameters can be obtained in the same way by specifingy {@code ParamType#NAMED} and not
* providing a name to parameters, resulting in <code>:1</code> or <code>@1</code> or <code>$1</code>, etc.
@ -466,12 +468,12 @@ public class Settings
}
/**
* The prefix to use for named parameters.
* The prefix to use for named parameters in generated SQL.
* <p>
* Named parameter syntax defaults to <code>:name</code> (such as supported by Oracle, JPA, Spring), but
* vendor specific parameters may look differently. This flag can be used to determine the prefix to be
* used by named parameters, such as <code>@</code> for SQL Server's <code>@name</code> or <code>$</code>
* for PostgreSQL's <code>$name</code>.
* for PostgreSQL's <code>$name</code>, when generating SQL.
* <p>
* "Named indexed" parameters can be obtained in the same way by specifingy {@code ParamType#NAMED} and not
* providing a name to parameters, resulting in <code>:1</code> or <code>@1</code> or <code>$1</code>, etc.
@ -2389,6 +2391,38 @@ public class Settings
this.parseTimestampFormat = value;
}
/**
* The prefix to use for named parameters in parsed SQL.
* <p>
* Named parameter syntax defaults to <code>:name</code> (such as supported by Oracle, JPA, Spring), but
* vendor specific parameters may look differently. This flag can be used to determine the prefix to be
* used by named parameters, such as <code>@</code> for SQL Server's <code>@name</code> or <code>$</code>
* for PostgreSQL's <code>$name</code> when parsing SQL.
* <p>
* "Named indexed" parameters can be obtained in the same way by specifingy {@code ParamType#NAMED} and not
* providing a name to parameters, resulting in <code>:1</code> or <code>@1</code> or <code>$1</code>, etc.
*
*/
public String getParseNamedParamPrefix() {
return parseNamedParamPrefix;
}
/**
* The prefix to use for named parameters in parsed SQL.
* <p>
* Named parameter syntax defaults to <code>:name</code> (such as supported by Oracle, JPA, Spring), but
* vendor specific parameters may look differently. This flag can be used to determine the prefix to be
* used by named parameters, such as <code>@</code> for SQL Server's <code>@name</code> or <code>$</code>
* for PostgreSQL's <code>$name</code> when parsing SQL.
* <p>
* "Named indexed" parameters can be obtained in the same way by specifingy {@code ParamType#NAMED} and not
* providing a name to parameters, resulting in <code>:1</code> or <code>@1</code> or <code>$1</code>, etc.
*
*/
public void setParseNamedParamPrefix(String value) {
this.parseNamedParamPrefix = value;
}
/**
* [#7337] The default name case for parsed identifiers.
*
@ -2676,12 +2710,12 @@ public class Settings
}
/**
* The prefix to use for named parameters.
* The prefix to use for named parameters in generated SQL.
* <p>
* Named parameter syntax defaults to <code>:name</code> (such as supported by Oracle, JPA, Spring), but
* vendor specific parameters may look differently. This flag can be used to determine the prefix to be
* used by named parameters, such as <code>@</code> for SQL Server's <code>@name</code> or <code>$</code>
* for PostgreSQL's <code>$name</code>.
* for PostgreSQL's <code>$name</code>, when generating SQL.
* <p>
* "Named indexed" parameters can be obtained in the same way by specifingy {@code ParamType#NAMED} and not
* providing a name to parameters, resulting in <code>:1</code> or <code>@1</code> or <code>$1</code>, etc.
@ -3355,6 +3389,23 @@ public class Settings
return this;
}
/**
* The prefix to use for named parameters in parsed SQL.
* <p>
* Named parameter syntax defaults to <code>:name</code> (such as supported by Oracle, JPA, Spring), but
* vendor specific parameters may look differently. This flag can be used to determine the prefix to be
* used by named parameters, such as <code>@</code> for SQL Server's <code>@name</code> or <code>$</code>
* for PostgreSQL's <code>$name</code> when parsing SQL.
* <p>
* "Named indexed" parameters can be obtained in the same way by specifingy {@code ParamType#NAMED} and not
* providing a name to parameters, resulting in <code>:1</code> or <code>@1</code> or <code>$1</code>, etc.
*
*/
public Settings withParseNamedParamPrefix(String value) {
setParseNamedParamPrefix(value);
return this;
}
/**
* [#7337] The default name case for parsed identifiers.
*
@ -3590,6 +3641,7 @@ public class Settings
builder.append("parseLocale", parseLocale);
builder.append("parseDateFormat", parseDateFormat);
builder.append("parseTimestampFormat", parseTimestampFormat);
builder.append("parseNamedParamPrefix", parseNamedParamPrefix);
builder.append("parseNameCase", parseNameCase);
builder.append("parseWithMetaLookups", parseWithMetaLookups);
builder.append("parseSetCommands", parseSetCommands);
@ -4488,6 +4540,15 @@ public class Settings
return false;
}
}
if (parseNamedParamPrefix == null) {
if (other.parseNamedParamPrefix!= null) {
return false;
}
} else {
if (!parseNamedParamPrefix.equals(other.parseNamedParamPrefix)) {
return false;
}
}
if (parseNameCase == null) {
if (other.parseNameCase!= null) {
return false;
@ -4708,6 +4769,7 @@ public class Settings
result = ((prime*result)+((parseLocale == null)? 0 :parseLocale.hashCode()));
result = ((prime*result)+((parseDateFormat == null)? 0 :parseDateFormat.hashCode()));
result = ((prime*result)+((parseTimestampFormat == null)? 0 :parseTimestampFormat.hashCode()));
result = ((prime*result)+((parseNamedParamPrefix == null)? 0 :parseNamedParamPrefix.hashCode()));
result = ((prime*result)+((parseNameCase == null)? 0 :parseNameCase.hashCode()));
result = ((prime*result)+((parseWithMetaLookups == null)? 0 :parseWithMetaLookups.hashCode()));
result = ((prime*result)+((parseSetCommands == null)? 0 :parseSetCommands.hashCode()));

View File

@ -596,7 +596,7 @@ final class DiagnosticsResultSet extends DefaultResultSet {
// XXX Utilities
// ------------------------------------------------------------------------
private final void wasPrimitive(int columnIndex) {
private final void wasPrimitive(int columnIndex) throws SQLException {
checkPrimitive();
wasColumnIndex = columnIndex;
@ -608,7 +608,7 @@ final class DiagnosticsResultSet extends DefaultResultSet {
wasPrimitive(super.findColumn(columnLabel));
}
private final void checkPrimitive() {
private final void checkPrimitive() throws SQLException {
if (wasPrimitive && wasNullable) {
DefaultDiagnosticsContext ctx = ctx();
ctx.resultSetMissingWasNullCall = true;
@ -628,7 +628,7 @@ final class DiagnosticsResultSet extends DefaultResultSet {
read(super.findColumn(columnLabel));
}
private final DefaultDiagnosticsContext ctx() {
private final DefaultDiagnosticsContext ctx() throws SQLException {
DefaultDiagnosticsContext ctx = new DefaultDiagnosticsContext(sql);
ctx.resultSet = super.getDelegate();

View File

@ -748,7 +748,7 @@ final class LoaderImpl<R extends Record> implements
}
@Override
public void close() {
public void close() throws SQLException {
for (CachedPS ps : map.values())
safeClose(ps.getDelegate());
}

View File

@ -83,6 +83,7 @@ import static org.jooq.impl.Tools.aliased;
import static org.jooq.impl.Tools.normaliseNameCase;
import static org.jooq.impl.XMLPassingMechanism.BY_REF;
import static org.jooq.impl.XMLPassingMechanism.BY_VALUE;
import static org.jooq.tools.StringUtils.defaultIfNull;
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
@ -7049,9 +7050,15 @@ final class DefaultParseContext extends AbstractScope implements ParseContext {
switch (characterUpper()) {
// [#8821] Known prefixes so far:
case ':':
case '@':
case '?':
return parseBindVariable();
if ((field = parseBindVariableIf()) != null)
return field;
break;
@ -7065,7 +7072,9 @@ final class DefaultParseContext extends AbstractScope implements ParseContext {
return inline(parseStringLiteral());
case '$':
if ((value = parseDollarQuotedStringLiteralIf()) != null)
if ((field = parseBindVariableIf()) != null)
return field;
else if ((value = parseDollarQuotedStringLiteralIf()) != null)
return inline((String) value);
break;
@ -11501,10 +11510,8 @@ final class DefaultParseContext extends AbstractScope implements ParseContext {
return c;
}
private final Field<?> parseBindVariable() {
// [#11074] Bindings can be Param or even Field types
Object binding = nextBinding();
private final Field<?> parseBindVariableIf() {
int p = position();
String paramName;
switch (character()) {
@ -11513,16 +11520,28 @@ final class DefaultParseContext extends AbstractScope implements ParseContext {
paramName = "" + bindIndex;
break;
case ':':
parse(':', false);
Name identifier = parseIdentifier();
paramName = identifier.last();
break;
default:
throw exception("Illegal bind variable character");
String prefix = defaultIfNull(settings().getParseNamedParamPrefix(), ":");
if (parseIf(prefix, false)) {
Name identifier = parseIdentifier();
paramName = identifier.last();
// [#8821] Avoid conflicts with dollar quoted string literals
if ("$".equals(prefix) && paramName.endsWith("$")) {
position(p);
return null;
}
break;
}
else
return null;
}
// [#11074] Bindings can be Param or even Field types
Object binding = nextBinding();
if (binding instanceof Field)
return (Field<?>) binding;
@ -11684,8 +11703,10 @@ final class DefaultParseContext extends AbstractScope implements ParseContext {
private final String parseDollarQuotedStringLiteralIf() {
int previous = position();
if (!parseIf('$'))
if (!peek('$'))
return null;
else
parse('$');
int openTokenStart = previous;
int openTokenEnd = previous;
@ -12044,7 +12065,15 @@ final class DefaultParseContext extends AbstractScope implements ParseContext {
private final Field<Long> parseUnsignedIntegerOrBindVariable() {
Long i = parseUnsignedIntegerLiteralIf();
return i != null ? DSL.inline(i) : (Field<Long>) parseBindVariable();
if (i != null)
return DSL.inline(i);
Field<?> f = parseBindVariableIf();
if (f != null)
return (Field<Long>) f;
throw expected("Unsigned integer or bind variable");
}
@Override

View File

@ -76,6 +76,7 @@ final class ParsingConnectionFactory implements ConnectionFactory {
ParsingConnectionFactory(Configuration configuration) {
this.configuration = configuration.derive();
this.configuration.set(SettingsTools.clone(configuration.settings())
.withParseNamedParamPrefix("$")
.withRenderNamedParamPrefix("$")
.withParamType(ParamType.NAMED));
}

View File

@ -41,35 +41,24 @@ import static org.jooq.conf.ParamType.NAMED;
import static org.jooq.impl.Tools.recordFactory;
import static org.jooq.tools.StringUtils.defaultIfNull;
import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.NClob;
import java.sql.Ref;
import java.sql.ResultSetMetaData;
import java.sql.RowId;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLType;
import java.sql.SQLXML;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.sql.Wrapper;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayDeque;
import java.util.Calendar;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
import java.util.function.Supplier;
import org.jooq.BindingGetResultSetContext;
import org.jooq.Configuration;
import org.jooq.Cursor;
import org.jooq.DataType;
@ -78,6 +67,7 @@ import org.jooq.Record;
import org.jooq.conf.SettingsTools;
import org.jooq.impl.DefaultRenderContext.Rendered;
import org.jooq.tools.jdbc.DefaultPreparedStatement;
import org.jooq.tools.jdbc.DefaultResultSet;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
@ -85,6 +75,7 @@ import org.reactivestreams.Subscription;
import io.r2dbc.spi.ColumnMetadata;
import io.r2dbc.spi.Connection;
import io.r2dbc.spi.Row;
import io.r2dbc.spi.RowMetadata;
import io.r2dbc.spi.Statement;
@ -159,13 +150,21 @@ final class R2DBC {
RecordDelegate<AbstractRecord> delegate = Tools.newRecord(true, (Supplier<AbstractRecord>) recordFactory(query.getRecordType(), Tools.row0(fields)), query.configuration());
return (R) delegate.operate(record -> {
// TODO: Go through Field.getBinding()
// TODO: What data to pass here?
DefaultBindingGetResultSetContext<?> ctx = new DefaultBindingGetResultSetContext<>(
query.configuration(),
query.configuration().data(),
new R2DBCResultSet(query.configuration(), row),
0
);
// TODO: Make sure all the embeddable records, and other types of nested records are supported
for (int i = 0; i < fields.length; i++) {
Field<?> f = fields[i];
Object value = row.get(i, f.getType());
record.values[i] = value;
record.originals[i] = value;
ctx.index(i + 1);
fields[i].getBinding().get((BindingGetResultSetContext) ctx);
record.values[i] = ctx.value();
record.originals[i] = ctx.value();
}
return record;
@ -208,20 +207,15 @@ final class R2DBC {
public final void onNext(Connection c) {
try {
DefaultRenderContext render = new DefaultRenderContext(query.configuration().derive(
SettingsTools.clone(query.configuration().settings()).withParamType(NAMED).withRenderNamedParamPrefix("$")
SettingsTools.clone(query.configuration().settings())
.withParseNamedParamPrefix("$")
.withRenderNamedParamPrefix("$")
.withParamType(NAMED)
));
Rendered r = new Rendered(render.paramType(NAMED).visit(query).render(), render.bindValues(), render.skipUpdateCounts());
Statement stmt = c.createStatement(r.sql);
new DefaultBindContext(query.configuration(), new R2DBCPreparedStatement(query.configuration(), stmt)).visit(r.bindValues);
// ;
// int i = 0;
// for (Param<?> p : r.bindValues)
// if (p.getValue() == null)
// stmt.bindNull(i++, p.getType());
// else
// stmt.bind(i++, p.getValue());
stmt.execute().subscribe(new ResultSubscriber<>(query, this));
}
@ -328,28 +322,13 @@ final class R2DBC {
// JDBC to R2DBC bridges for better interop, where it doesn't matter
// -------------------------------------------------------------------------
static abstract class R2DBCWrapper implements Wrapper {
@Override
public final <T> T unwrap(Class<T> iface) throws SQLException {
throw new SQLFeatureNotSupportedException("R2DBC can't unwrap JDBC types");
}
@Override
public final boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
}
static final class R2DBCPreparedStatement extends DefaultPreparedStatement {
final Configuration c;
final Statement s;
R2DBCPreparedStatement(Configuration c, Statement s) {
// TODO: Refactor super class to throw a custom exception if trying to dereference this null pointer.
super(null);
super(null, null, () -> new SQLFeatureNotSupportedException("Unsupported operation of the JDBC to R2DBC bridge."));
this.c = c;
this.s = s;
@ -367,7 +346,7 @@ final class R2DBC {
if (x == null)
s.bindNull(parameterIndex - 1, type);
else
bindNonNull(parameterIndex - 1, conversion.apply(x));
bindNonNull(parameterIndex, conversion.apply(x));
}
private final void bindNonNull(int parameterIndex, Object x) {
@ -483,154 +462,122 @@ final class R2DBC {
public final void setObject(int parameterIndex, Object x, SQLType targetSqlType) throws SQLException {
setObject(parameterIndex, x, defaultIfNull(targetSqlType.getVendorTypeNumber(), Types.OTHER));
}
}
@Override
public final void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
static final class R2DBCResultSet extends DefaultResultSet {
final Configuration c;
final Row r;
boolean wasNull;
R2DBCResultSet(Configuration c, Row r) {
super(null, null, () -> new SQLFeatureNotSupportedException("Unsupported operation of the JDBC to R2DBC bridge."));
this.c = c;
this.r = r;
}
private final <T> T wasNull(T nullable) {
wasNull = nullable == null;
return nullable;
}
private final <T> T nullable(int columnIndex, Class<T> type) {
return nullable(columnIndex, type, t -> t);
}
private final <T, U> U nullable(int columnIndex, Class<T> type, Function<? super T, ? extends U> conversion) {
T t = wasNull(r.get(columnIndex - 1, type));
return wasNull ? null : conversion.apply(t);
}
private final <T> T nonNull(int columnIndex, Class<T> type, T nullValue) {
T t = wasNull(r.get(columnIndex - 1, type));
return wasNull ? nullValue : t;
}
@Override
public final void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
public final boolean wasNull() throws SQLException {
return wasNull;
}
@Override
public final void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
public final boolean getBoolean(int columnIndex) throws SQLException {
return nonNull(columnIndex, Boolean.class, false);
}
@Override
public final void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
public final byte getByte(int columnIndex) throws SQLException {
return nonNull(columnIndex, Byte.class, (byte) 0);
}
@Override
public final void setRef(int parameterIndex, Ref x) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
public final short getShort(int columnIndex) throws SQLException {
return nonNull(columnIndex, Short.class, (short) 0);
}
@Override
public final void setBlob(int parameterIndex, Blob x) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
public final int getInt(int columnIndex) throws SQLException {
return nonNull(columnIndex, Integer.class, 0);
}
@Override
public final void setClob(int parameterIndex, Clob x) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
public final long getLong(int columnIndex) throws SQLException {
return nonNull(columnIndex, Long.class, 0L);
}
@Override
public final void setArray(int parameterIndex, Array x) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
public final float getFloat(int columnIndex) throws SQLException {
return nonNull(columnIndex, Float.class, 0.0f);
}
@Override
public final void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
public final double getDouble(int columnIndex) throws SQLException {
return nonNull(columnIndex, Double.class, 0.0);
}
@Override
public final void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
public final BigDecimal getBigDecimal(int columnIndex) throws SQLException {
return nullable(columnIndex, BigDecimal.class);
}
@Override
public final void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
public final String getString(int columnIndex) throws SQLException {
return nullable(columnIndex, String.class);
}
@Override
public final void setURL(int parameterIndex, URL x) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
public final byte[] getBytes(int columnIndex) throws SQLException {
return nullable(columnIndex, byte[].class);
}
@Override
public final void setRowId(int parameterIndex, RowId x) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
public final Date getDate(int columnIndex) throws SQLException {
return nullable(columnIndex, LocalDate.class, Date::valueOf);
}
@Override
public final void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
public final Time getTime(int columnIndex) throws SQLException {
return nullable(columnIndex, LocalTime.class, Time::valueOf);
}
@Override
public final void setNClob(int parameterIndex, NClob value) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
public final Timestamp getTimestamp(int columnIndex) throws SQLException {
return nullable(columnIndex, LocalDateTime.class, Timestamp::valueOf);
}
@Override
public final void setClob(int parameterIndex, Reader reader, long length) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
public final Object getObject(int columnIndex) throws SQLException {
return getObject(columnIndex, Object.class);
}
@Override
public final void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
}
@Override
public final void setNClob(int parameterIndex, Reader reader, long length) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
}
@Override
public final void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
}
@Override
public final void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
}
@Override
public final void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
}
@Override
public final void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
}
@Override
public final void setAsciiStream(int parameterIndex, InputStream x) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
}
@Override
public final void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
}
@Override
public final void setCharacterStream(int parameterIndex, Reader reader) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
}
@Override
public final void setNCharacterStream(int parameterIndex, Reader value) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
}
@Override
public final void setClob(int parameterIndex, Reader reader) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
}
@Override
public final void setBlob(int parameterIndex, InputStream inputStream) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
}
@Override
public final void setNClob(int parameterIndex, Reader reader) throws SQLException {
throw new SQLFeatureNotSupportedException("The JDBC to R2DBC bridge doesn't support this data type");
public final <T> T getObject(int columnIndex, Class<T> type) throws SQLException {
return nullable(columnIndex, type);
}
}
static final class R2DBCResultSetMetaData extends R2DBCWrapper implements ResultSetMetaData {
static final class R2DBCResultSetMetaData implements ResultSetMetaData {
final Configuration c;
final RowMetadata m;
@ -644,6 +591,16 @@ final class R2DBC {
return m.getColumnMetadata(column - 1);
}
@Override
public final <T> T unwrap(Class<T> iface) throws SQLException {
throw new SQLFeatureNotSupportedException("R2DBC can't unwrap JDBC types");
}
@Override
public final boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public final int getColumnCount() throws SQLException {
return m.getColumnNames().size();

View File

@ -43,6 +43,7 @@ import static org.jooq.impl.QueryPartListView.wrap;
import static org.jooq.impl.SQLDataType.OTHER;
import static org.jooq.impl.Tools.embeddedFields;
import static org.jooq.impl.Tools.BooleanDataKey.DATA_LIST_ALREADY_INDENTED;
import static org.jooq.tools.StringUtils.defaultIfNull;
import java.sql.SQLException;
import java.sql.SQLWarning;
@ -163,7 +164,7 @@ final class Val<T> extends AbstractParam<T> {
final String getBindVariable(Context<?> ctx) {
if (ctx.paramType() == NAMED || ctx.paramType() == NAMED_OR_INLINED) {
int index = ctx.nextIndex();
String prefix = StringUtils.defaultIfNull(ctx.settings().getRenderNamedParamPrefix(), ":");
String prefix = defaultIfNull(ctx.settings().getRenderNamedParamPrefix(), ":");
if (StringUtils.isBlank(getParamName()))
return prefix + index;

View File

@ -75,11 +75,11 @@ public class DefaultCallableStatement extends DefaultPreparedStatement implement
}
@Override
public CallableStatement getDelegate() {
public CallableStatement getDelegate() throws SQLException {
return getDelegateCallableStatement();
}
public CallableStatement getDelegateCallableStatement() {
public CallableStatement getDelegateCallableStatement() throws SQLException {
return (CallableStatement) getDelegateStatement();
}

View File

@ -60,6 +60,7 @@ import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.function.Supplier;
/**
* A default JDBC PreparedStatement implementation delegating all JDBC 4.0 calls
@ -81,12 +82,16 @@ public class DefaultPreparedStatement extends DefaultStatement implements Prepar
super(delegate, creator);
}
protected DefaultPreparedStatement(Statement delegate, Connection creator, Supplier<? extends SQLException> errorIfUnsupported) {
super(delegate, creator, errorIfUnsupported);
}
@Override
public PreparedStatement getDelegate() {
public PreparedStatement getDelegate() throws SQLException {
return getDelegatePreparedStatement();
}
public final PreparedStatement getDelegatePreparedStatement() {
public final PreparedStatement getDelegatePreparedStatement() throws SQLException {
return (PreparedStatement) getDelegateStatement();
}

View File

@ -59,6 +59,7 @@ import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Map;
import java.util.function.Supplier;
/**
* A default JDBC ResultSet implementation delegating all JDBC 4.0 calls to an
@ -68,20 +69,29 @@ import java.util.Map;
*/
public class DefaultResultSet extends JDBC41ResultSet implements ResultSet {
private final ResultSet delegate;
private final Statement creator;
private final ResultSet delegate;
private final Statement creator;
private final Supplier<? extends SQLException> errorIfUnsupported;
public DefaultResultSet(ResultSet delegate) {
this(delegate, null);
this(delegate, null, null);
}
public DefaultResultSet(ResultSet delegate, Statement creator) {
this.delegate = delegate;
this.creator = creator;
this(delegate, creator, null);
}
public ResultSet getDelegate() {
return delegate;
public DefaultResultSet(ResultSet delegate, Statement creator, Supplier<? extends SQLException> errorIfUnsupported) {
this.delegate = delegate;
this.creator = creator;
this.errorIfUnsupported = errorIfUnsupported;
}
public ResultSet getDelegate() throws SQLException {
if (delegate != null || errorIfUnsupported == null)
return delegate;
else
throw errorIfUnsupported.get();
}
@Override

View File

@ -42,6 +42,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.function.Supplier;
/**
* A default JDBC Statement implementation delegating all JDBC 4.0 calls to an
@ -51,24 +52,33 @@ import java.sql.Statement;
*/
public class DefaultStatement extends JDBC41Statement implements Statement {
private final Statement delegate;
private final Connection creator;
private final Statement delegate;
private final Connection creator;
private final Supplier<? extends SQLException> errorIfUnsupported;
public DefaultStatement(Statement delegate) {
this(delegate, null);
this(delegate, null, null);
}
public DefaultStatement(Statement delegate, Connection creator) {
this.delegate = delegate;
this.creator = creator;
this(delegate, creator, null);
}
public Statement getDelegate() {
public DefaultStatement(Statement delegate, Connection creator, Supplier<? extends SQLException> errorIfUnsupported) {
this.delegate = delegate;
this.creator = creator;
this.errorIfUnsupported = errorIfUnsupported;
}
public Statement getDelegate() throws SQLException {
return getDelegateStatement();
}
public Statement getDelegateStatement() {
return delegate;
public Statement getDelegateStatement() throws SQLException {
if (delegate != null || errorIfUnsupported == null)
return delegate;
else
throw errorIfUnsupported.get();
}
// ------------------------------------------------------------------------

View File

@ -69,12 +69,12 @@ This is set to "QUOTED" by default for backwards-compatibility.
</element>
<element name="renderNamedParamPrefix" type="string" minOccurs="0" maxOccurs="1" default=":">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[The prefix to use for named parameters.
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[The prefix to use for named parameters in generated SQL.
<p>
Named parameter syntax defaults to <code>:name</code> (such as supported by Oracle, JPA, Spring), but
vendor specific parameters may look differently. This flag can be used to determine the prefix to be
used by named parameters, such as <code>@</code> for SQL Server's <code>@name</code> or <code>$</code>
for PostgreSQL's <code>$name</code>.
for PostgreSQL's <code>$name</code>, when generating SQL.
<p>
"Named indexed" parameters can be obtained in the same way by specifingy {@code ParamType#NAMED} and not
providing a name to parameters, resulting in <code>:1</code> or <code>@1</code> or <code>$1</code>, etc.]]></jxb:javadoc></jxb:property></appinfo></annotation>
@ -568,7 +568,19 @@ jOOQ queries, for which no specific fetchSize value was specified.]]></jxb:javad
<element name="parseTimestampFormat" type="string" minOccurs="0" maxOccurs="1" default="YYYY-MM-DD HH24:MI:SS.FF">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[The timestamp format to use when parsing functions whose behaviour depends on some session date format, such as NLS_TIMESTAMP_FORMAT in Oracle]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="parseNamedParamPrefix" type="string" minOccurs="0" maxOccurs="1" default=":">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[The prefix to use for named parameters in parsed SQL.
<p>
Named parameter syntax defaults to <code>:name</code> (such as supported by Oracle, JPA, Spring), but
vendor specific parameters may look differently. This flag can be used to determine the prefix to be
used by named parameters, such as <code>@</code> for SQL Server's <code>@name</code> or <code>$</code>
for PostgreSQL's <code>$name</code> when parsing SQL.
<p>
"Named indexed" parameters can be obtained in the same way by specifingy {@code ParamType#NAMED} and not
providing a name to parameters, resulting in <code>:1</code> or <code>@1</code> or <code>$1</code>, etc.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="parseNameCase" type="jooq-runtime:ParseNameCase" minOccurs="0" maxOccurs="1" default="DEFAULT">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[[#7337] The default name case for parsed identifiers.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>