diff --git a/src/java.base/share/classes/java/lang/StringTemplate.java b/src/java.base/share/classes/java/lang/StringTemplate.java index 93aab08ca6da3..42a2d55e3a140 100644 --- a/src/java.base/share/classes/java/lang/StringTemplate.java +++ b/src/java.base/share/classes/java/lang/StringTemplate.java @@ -437,6 +437,19 @@ public StringTemplate mapValues(Function mapper) { return Factory.createStringTemplate(fragments(), values); } + /** + * TBD + * @param TBD + * @param owner TBD + * @param supplier TBD + * @return TBD + */ + public T getMetaData(Object owner, Supplier supplier) { + Objects.requireNonNull(owner, "owner must not be null"); + Objects.requireNonNull(supplier, "supplier must not be null"); + return sharedData.getMetaData(owner, supplier); + } + /** * Test this {@link StringTemplate} against another {@link StringTemplate} for equality. * @@ -944,7 +957,7 @@ public List> getTypes(StringTemplate st) { public T getMetaData(StringTemplate st, Object owner, Supplier supplier) { Objects.requireNonNull(st, "st must not be null"); Objects.requireNonNull(owner, "owner must not be null"); - Objects.requireNonNull(st, "supplier must not be null"); + Objects.requireNonNull(supplier, "supplier must not be null"); return st.sharedData.getMetaData(owner, supplier); } diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java index 17a66b856a1e4..84c98ece207c0 100644 --- a/src/java.base/share/classes/module-info.java +++ b/src/java.base/share/classes/module-info.java @@ -154,6 +154,7 @@ exports jdk.internal.javac to java.compiler, java.desktop, // for ScopedValue + java.sql, jdk.compiler, jdk.incubator.vector, // participates in preview features jdk.jartool, // participates in preview features diff --git a/src/java.sql/share/classes/java/sql/Connection.java b/src/java.sql/share/classes/java/sql/Connection.java index f2d1d48f6d3cf..c202c49644dfb 100644 --- a/src/java.sql/share/classes/java/sql/Connection.java +++ b/src/java.sql/share/classes/java/sql/Connection.java @@ -28,6 +28,8 @@ import java.util.Properties; import java.util.concurrent.Executor; +import jdk.internal.javac.PreviewFeature; + /** *

A connection (session) with a specific * database. SQL statements are executed and results are returned @@ -277,6 +279,7 @@ PreparedStatement prepareStatement(String sql) * * @throws SQLException if a database access error occurs */ + @Override void close() throws SQLException; /** @@ -1712,4 +1715,243 @@ default void setShardingKey(ShardingKey shardingKey) throws SQLException { throw new SQLFeatureNotSupportedException("setShardingKey not implemented"); } + + ///// + /** + * TBD + * + * @param autoGeneratedKeys a flag indicating whether auto-generated keys + * should be returned; one of + * {@code Statement.RETURN_GENERATED_KEYS} or + * {@code Statement.NO_GENERATED_KEYS} + * @param sql TBD + * @return TBD + * @throws SQLException TBD + * @throws SQLFeatureNotSupportedException TBD + */ + @PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES) + default PreparedStatement prepareStatement(int autoGeneratedKeys, StringTemplate sql) + throws SQLException { + return SQLTemplateSupport.prepareStatement(sql, this, + query -> prepareStatement(query, autoGeneratedKeys)); + } + + + /** + * TBD + * + * @param resultSetType one of the following {@code ResultSet} + * constants: + * {@code ResultSet.TYPE_FORWARD_ONLY}, + * {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or + * {@code ResultSet.TYPE_SCROLL_SENSITIVE} + * @param resultSetConcurrency one of the following {@code ResultSet} + * constants: + * {@code ResultSet.CONCUR_READ_ONLY} or + * {@code ResultSet.CONCUR_UPDATABLE} + * @param resultSetHoldability one of the following {@code ResultSet} + * constants: + * {@code ResultSet.HOLD_CURSORS_OVER_COMMIT} or + * {@code ResultSet.CLOSE_CURSORS_AT_COMMIT} + * @param sql TBD + * @return TBD + * @throws SQLException TBD + * @throws SQLFeatureNotSupportedException TBD + */ + @PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES) + default PreparedStatement prepareStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability, + StringTemplate sql) throws SQLException { + return SQLTemplateSupport.prepareStatement(sql, this, + query -> prepareStatement(query, resultSetType, resultSetConcurrency, resultSetHoldability)); + } + + /** + * TBD + * + * @param resultSetType a result set type; one of + * {@code ResultSet.TYPE_FORWARD_ONLY}, + * {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or + * {@code ResultSet.TYPE_SCROLL_SENSITIVE} + * @param resultSetConcurrency a concurrency type; one of + * {@code ResultSet.CONCUR_READ_ONLY} or + * {@code ResultSet.CONCUR_UPDATABLE} + * @param sql TBD + * @return TBD + * @throws SQLException TBD + * @throws SQLFeatureNotSupportedException TBD + */ + @PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES) + default PreparedStatement prepareStatement(int resultSetType, int resultSetConcurrency, StringTemplate sql) + throws SQLException { + return SQLTemplateSupport.prepareStatement(sql, this, + query -> prepareStatement(query, resultSetType, resultSetConcurrency)); + } + /** + * TBD + * Result sets created using the returned {@code PreparedStatement} + * object will by default be type {@code TYPE_FORWARD_ONLY} + * and have a concurrency level of {@code CONCUR_READ_ONLY}. + * The holdability of the created result sets can be determined by + * calling {@link #getHoldability}. + * + * @param sql TBD + * @return TBD + * @throws SQLException TBD + */ + @PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES) + default PreparedStatement prepareStatement(StringTemplate sql) throws SQLException { + return SQLTemplateSupport.prepareStatement(sql, this, query -> prepareStatement(query)); + } + + /** + * Creates a default {@code PreparedStatement} object capable + * of returning the auto-generated keys designated by the given array. + * This array contains the indexes of the columns in the target + * table that contain the auto-generated keys that should be made + * available. The driver will ignore the array if the SQL statement + * is not an {@code INSERT} statement, or an SQL statement able to return + * auto-generated keys (the list of such statements is vendor-specific). + *

+ * An SQL statement with or without IN parameters can be + * pre-compiled and stored in a {@code PreparedStatement} object. This + * object can then be used to efficiently execute this statement + * multiple times. + *

+ * Note: This method is optimized for handling + * parametric SQL statements that benefit from precompilation. If + * the driver supports precompilation, + * the method {@code prepareStatement} will send + * the statement to the database for precompilation. Some drivers + * may not support precompilation. In this case, the statement may + * not be sent to the database until the {@code PreparedStatement} + * object is executed. This has no direct effect on users; however, it does + * affect which methods throw certain SQLExceptions. + *

+ * Result sets created using the returned {@code PreparedStatement} + * object will by default be type {@code TYPE_FORWARD_ONLY} + * and have a concurrency level of {@code CONCUR_READ_ONLY}. + * The holdability of the created result sets can be determined by + * calling {@link #getHoldability}. + * + * @param columnIndexes an array of column indexes indicating the columns + * that should be returned from the inserted row or rows + * @param sql TBD + * @return TBD + * @throws SQLException TBD + * @throws SQLFeatureNotSupportedException TBD + */ + @PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES) + default PreparedStatement prepareStatement(int columnIndexes[], StringTemplate sql) + throws SQLException { + return SQLTemplateSupport.prepareStatement(sql, this, + query -> prepareStatement(query, columnIndexes)); + } + + /** + * Creates a default {@code PreparedStatement} object capable + * of returning the auto-generated keys designated by the given array. + * This array contains the names of the columns in the target + * table that contain the auto-generated keys that should be returned. + * The driver will ignore the array if the SQL statement + * is not an {@code INSERT} statement, or an SQL statement able to return + * auto-generated keys (the list of such statements is vendor-specific). + *

+ * An SQL statement with or without IN parameters can be + * pre-compiled and stored in a {@code PreparedStatement} object. This + * object can then be used to efficiently execute this statement + * multiple times. + *

+ * Note: This method is optimized for handling + * parametric SQL statements that benefit from precompilation. If + * the driver supports precompilation, + * the method {@code prepareStatement} will send + * the statement to the database for precompilation. Some drivers + * may not support precompilation. In this case, the statement may + * not be sent to the database until the {@code PreparedStatement} + * object is executed. This has no direct effect on users; however, it does + * affect which methods throw certain SQLExceptions. + *

+ * Result sets created using the returned {@code PreparedStatement} + * object will by default be type {@code TYPE_FORWARD_ONLY} + * and have a concurrency level of {@code CONCUR_READ_ONLY}. + * The holdability of the created result sets can be determined by + * calling {@link #getHoldability}. + * + * @param columnNames an array of column names indicating the columns + * that should be returned from the inserted row or rows + * @param sql TBD + * @return TBD + * @throws SQLException TBD + * @throws SQLFeatureNotSupportedException TBD + */ + @PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES) + default PreparedStatement prepareStatement(String columnNames[], StringTemplate sql) + throws SQLException { + return SQLTemplateSupport.prepareStatement(sql, this, + query -> prepareStatement(query, columnNames)); + } + + /** + * TBD + * + * @param sql TBD + * @param resultSetType one of the following {@code ResultSet} + * constants: + * {@code ResultSet.TYPE_FORWARD_ONLY}, + * {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or + * {@code ResultSet.TYPE_SCROLL_SENSITIVE} + * @param resultSetConcurrency one of the following {@code ResultSet} + * constants: + * {@code ResultSet.CONCUR_READ_ONLY} or + * {@code ResultSet.CONCUR_UPDATABLE} + * @param resultSetHoldability one of the following {@code ResultSet} + * constants: + * {@code ResultSet.HOLD_CURSORS_OVER_COMMIT} or + * {@code ResultSet.CLOSE_CURSORS_AT_COMMIT} + * @return TBD + * @throws SQLException TBS + * @throws SQLFeatureNotSupportedException TBD + */ + @PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES) + default boolean call(int resultSetType, + int resultSetConcurrency, + int resultSetHoldability, + StringTemplate sql) throws SQLException { + + return SQLTemplateSupport.call(sql, this, + query -> prepareCall(query, resultSetType, resultSetConcurrency, resultSetHoldability)); + } + + /** + * TBD + * + * @param sql TBD + * @param resultSetType a result set type; one of + * {@code ResultSet.TYPE_FORWARD_ONLY}, + * {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or + * {@code ResultSet.TYPE_SCROLL_SENSITIVE} + * @param resultSetConcurrency a concurrency type; one of + * {@code ResultSet.CONCUR_READ_ONLY} or + * {@code ResultSet.CONCUR_UPDATABLE} + * @return TBD + * @throws SQLException TBD + * @throws SQLFeatureNotSupportedException TBD + */ + @PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES) + default boolean call(int resultSetType, + int resultSetConcurrency, + StringTemplate sql) throws SQLException { + return SQLTemplateSupport.call(sql, this, + query -> prepareCall(query, resultSetType, resultSetConcurrency)); + } + /** + * TBD + * @param sql TBD + * @return TBD + * @throws SQLException TBD + */ + @PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES) + default boolean call(StringTemplate sql) throws SQLException { + return SQLTemplateSupport.call(sql, this, query -> prepareCall(query)); + } } diff --git a/src/java.sql/share/classes/java/sql/SQLArgument.java b/src/java.sql/share/classes/java/sql/SQLArgument.java new file mode 100644 index 0000000000000..6f54ac7a5a790 --- /dev/null +++ b/src/java.sql/share/classes/java/sql/SQLArgument.java @@ -0,0 +1,155 @@ + +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.sql; + +/** + * TBD + * + * @param TBD + */ +public final class SQLArgument extends SQLValue { + /** + * TBD + * @param value TBD + * @param clazz TBD + * @param targetSqlType TBD + * @param scaleOrLength TBD + * @param TBD + * @return TBD */ + public static SQLArgument inout(T value, Class clazz, SQLType targetSqlType, int scaleOrLength) { + return new SQLArgument<>(value, clazz, targetSqlType, scaleOrLength); + } + + /** + * TBD + * @param value TBD + * @param targetSqlType TBD + * @param scaleOrLength TBD + * @param TBD + * @return TBD */ + public static SQLArgument inout(T value, SQLType targetSqlType, int scaleOrLength) { + return new SQLArgument<>(value, targetSqlType, scaleOrLength); + } + + /** + * TBD + * @param value TBD + * @param clazz TBD + * @param targetSqlType TBD + * @param TBD + * @return TBD + */ + public static SQLArgument inout(T value, Class clazz, SQLType targetSqlType) { + return new SQLArgument<>(value, clazz, targetSqlType); + } + + /** + * TBD + * @param value TBD + * @param targetSqlType TBD + * @param TBD + * @return TBD */ + public static SQLArgument inout(T value, SQLType targetSqlType) { + return new SQLArgument<>(value, targetSqlType); + } + + /** + * TBD + * @param clazz TBD + * @param targetSqlType TBD + * @param TBD + * @return TBD */ + public static SQLArgument out(Class clazz, SQLType targetSqlType) { + return new SQLArgument<>(clazz, targetSqlType); + } + + /** + * TBD + * @param targetSqlType TBD + * @param TBD + * @return TBD + */ + public static SQLArgument out(SQLType targetSqlType) { + return new SQLArgument<>(targetSqlType); + } + + private final boolean in; + private final Class clazz; + + + private SQLArgument(T value, SQLType targetSqlType, int scaleOrLength) { + super(value, targetSqlType, scaleOrLength); + this.clazz = null; + this.in = true; + } + + private SQLArgument(T value, SQLType targetSqlType) { + super(value, targetSqlType); + this.clazz = null; + this.in = true; + } + + private SQLArgument(T value, Class clazz, SQLType targetSqlType, int scaleOrLength) { + super(value, targetSqlType, scaleOrLength); + this.clazz = clazz; + this.in = true; + } + + private SQLArgument(T value, Class clazz, SQLType targetSqlType) { + super(value, targetSqlType); + this.clazz = clazz; + this.in = true; + } + + private SQLArgument(Class clazz, SQLType targetSqlType) { + super(null, targetSqlType); + this.clazz = clazz; + this.in = false; + } + + private SQLArgument(SQLType targetSqlType) { + super(null, targetSqlType); + this.clazz = null; + this.in = false; + } + + /** + * TBD + * @return TBD + */ + public Class targetClass() { + return clazz; + } + + /** + * TBD + * @return TBD + */ + public boolean in() { + return in; + } +} \ No newline at end of file diff --git a/src/java.sql/share/classes/java/sql/SQLTemplateSupport.java b/src/java.sql/share/classes/java/sql/SQLTemplateSupport.java new file mode 100644 index 0000000000000..e49a3da82674e --- /dev/null +++ b/src/java.sql/share/classes/java/sql/SQLTemplateSupport.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.sql; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + + +final class SQLTemplateSupport { + private SQLTemplateSupport() {} + + public interface Fn { R apply(T x) throws SQLException; } + public interface Sup { T get() throws SQLException; } + + public static PreparedStatement prepareStatement(StringTemplate st, Connection conn, Fn f) + throws SQLException { + var ps = getPrepareStatement(st, conn, () -> f.apply(query(st))); + setValues(ps, st.values()); + return ps; + } + + public static boolean call(StringTemplate st, Connection conn, Fn f) + throws SQLException { + var cs = (CallableStatement)prepareStatement(st, conn, f); + boolean res = cs.execute(); + if (res) + SQLTemplateSupport.getValues(cs, st.values()); + return res; + } + + private static String query(StringTemplate st) { + return String.join("?", st.fragments()); + } + + private static PreparedStatement getPrepareStatement(StringTemplate st, Connection conn, Sup supplier) + throws SQLException { + var map = st.getMetaData(SQLTemplateSupport.class, () -> + Collections.synchronizedMap(new HashMap())); + + if (map == null) + return supplier.get(); + + try { + return map.computeIfAbsent(conn, k -> { + try { + return supplier.get(); + } catch (SQLException sqlEx) { + throw new RuntimeException(sqlEx); + } + }); + } catch (RuntimeException ex) { + if (ex.getCause() instanceof SQLException sqlEx) throw sqlEx; + throw ex; + } + } + + private static void setValues(PreparedStatement ps, List values) throws SQLException { + int i = 1; + for (Object value : values) { + if (value instanceof SQLArgument sa) { + if (ps instanceof CallableStatement cs) + cs.registerOutParameter(i, sa.targetSqlType()); + if (!sa.in()) + continue; + } + if (value instanceof SQLValue sv) { + if (sv.hasScaleOrLength()) + ps.setObject(i, sv.value(), sv.targetSqlType(), sv.scaleOrLength()); + else if (sv.targetSqlType() != null) + ps.setObject(i, sv.value(), sv.targetSqlType()); + else + ps.setObject(i, sv.value()); + } else { + ps.setObject(i, value); + } + i++; + } + } + + @SuppressWarnings("unchecked") + private static void getValues(CallableStatement cs, List values) throws SQLException { + int i = 1; + for (Object value : values) { + if (value instanceof SQLArgument arg) { + if (arg.targetClass() != null) + ((SQLArgument)arg).setValue(cs.getObject(i, arg.targetClass())); + else + ((SQLArgument)arg).setValue(cs.getObject(i)); + } + i++; + } + } +} \ No newline at end of file diff --git a/src/java.sql/share/classes/java/sql/SQLValue.java b/src/java.sql/share/classes/java/sql/SQLValue.java new file mode 100644 index 0000000000000..ee031ec7da3e2 --- /dev/null +++ b/src/java.sql/share/classes/java/sql/SQLValue.java @@ -0,0 +1,131 @@ + +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.sql; + +/** + * TBD + * + * @param TBD + */ +public sealed class SQLValue + permits SQLArgument { + private T value; + private final SQLType targetSqlType; + private final int scaleOrLength; + private final boolean hasScaleOrLength; + + /** + * TBD + * + * @param value TBD + * @param targetSqlType TBD + * @param scaleOrLength TBD + */ + protected SQLValue(T value, SQLType targetSqlType, int scaleOrLength) { + this.value = value; + this.targetSqlType = targetSqlType; + this.scaleOrLength = scaleOrLength; + this.hasScaleOrLength = true; + } + + /** + * TBD + * @param value TBD + * @param targetSqlType TBD + */ + protected SQLValue(T value, SQLType targetSqlType) { + this.value = value; + this.targetSqlType = targetSqlType; + this.scaleOrLength = -1; + this.hasScaleOrLength = false; + } + + /** + * TBD + * + * @param x TBD + * @param targetSqlType TBD + * @param scaleOrLength TBD + * @param TBD + * @return TBD + */ + public static SQLValue of(T x, SQLType targetSqlType, int scaleOrLength) { + return new SQLValue<>(x, targetSqlType, scaleOrLength); + } + + /** + * TBD + * + * @param x TBD + * @param targetSqlType TBD + * @param TBD + * @return TBD + */ + public static SQLValue of(T x, SQLType targetSqlType) { + return new SQLValue<>(x, targetSqlType); + } + + /** + * TBD + * @return TBD + */ + public T value() { + return value; + } + + /** + * TBD + * @return TBD + */ + public SQLType targetSqlType() { + return targetSqlType; + } + + /** + * TBD + * @return TBD + */ + public int scaleOrLength() { + return scaleOrLength; + } + + /** + * TBD + * @return TBD + */ + public boolean hasScaleOrLength() { + return hasScaleOrLength; + } + + /** + * TBD + * @param value TBD + */ + protected void setValue(T value) { + this.value = value; + } +} \ No newline at end of file diff --git a/src/java.sql/share/classes/module-info.java b/src/java.sql/share/classes/module-info.java index 0ae1131297c37..5cf5c55866fa0 100644 --- a/src/java.sql/share/classes/module-info.java +++ b/src/java.sql/share/classes/module-info.java @@ -23,6 +23,8 @@ * questions. */ +import jdk.internal.javac.ParticipatesInPreview; + /** * Defines the JDBC API. * @@ -31,6 +33,7 @@ * @moduleGraph * @since 9 */ +@ParticipatesInPreview module java.sql { requires transitive java.logging; requires transitive java.transaction.xa;