diff --git a/jOOQ-test/src/org/jooq/test/_/testcases/RoutineAndUDTTests.java b/jOOQ-test/src/org/jooq/test/_/testcases/RoutineAndUDTTests.java index fc4668acde..10e6fd0999 100644 --- a/jOOQ-test/src/org/jooq/test/_/testcases/RoutineAndUDTTests.java +++ b/jOOQ-test/src/org/jooq/test/_/testcases/RoutineAndUDTTests.java @@ -43,6 +43,7 @@ import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; import static org.jooq.impl.Factory.table; import static org.jooq.impl.Factory.val; +import static org.jooq.tools.reflect.Reflect.on; import java.sql.Date; import java.util.Arrays; @@ -837,13 +838,15 @@ extends BaseTest a1 = authors.get(0).getValue(TAuthor_ADDRESS()); UDTRecord a2 = authors.get(1).getValue(TAuthor_ADDRESS()); - Object street1 = a1.getClass().getMethod("getStreet").invoke(a1); - assertEquals("77", street1.getClass().getMethod("getNo").invoke(street1)); - assertEquals("Parliament Hill", street1.getClass().getMethod("getStreet").invoke(street1)); - assertEquals("NW31A9", a1.getClass().getMethod("getZip").invoke(a1)); - assertEquals("Hampstead", a1.getClass().getMethod("getCity").invoke(a1)); - assertEquals("England", "" + a1.getClass().getMethod("getCountry").invoke(a1)); - assertEquals(null, a1.getClass().getMethod("getCode").invoke(a1)); + Object street1 = on(a1).call("getStreet").get(); + assertEquals("77", on(street1).call("getNo").get()); + assertEquals("Parliament Hill", on(street1).call("getStreet").get()); + assertTrue(Arrays.equals(new byte[] { 0x70, 0x70 }, on(street1).call("getF_1323").get())); + assertEquals("NW31A9", on(a1).call("getZip").get()); + assertEquals("Hampstead", on(a1).call("getCity").get()); + assertEquals("England", "" + on(a1).call("getCountry").get()); + assertEquals(null, on(a1).call("getCode").get()); + assertTrue(Arrays.equals(new byte[] { 0x71, 0x71 }, on(a1).call("getF_1323").get())); if (TArrays_NUMBER_R() != null) { assertEquals(Arrays.asList(1, 2, 3), invoke(invoke(street1, "getFloors"), "getList")); @@ -852,13 +855,15 @@ extends BaseTestget()); + assertEquals(null, on(a2).call("getZip").get()); + assertEquals("Rio de Janeiro", on(a2).call("getCity").get()); + assertEquals("Brazil", "" + on(a2).call("getCountry").get()); + assertEquals(2, on(a2).call("getCode").get()); + assertEquals(null, on(a2).call("getF_1323").get()); if (TArrays_NUMBER_R() != null) { assertEquals(null, invoke(street2, "getFloors")); diff --git a/jOOQ-test/src/org/jooq/test/oracle/create.sql b/jOOQ-test/src/org/jooq/test/oracle/create.sql index f7052734a6..9e8fb3a315 100644 --- a/jOOQ-test/src/org/jooq/test/oracle/create.sql +++ b/jOOQ-test/src/org/jooq/test/oracle/create.sql @@ -225,7 +225,9 @@ CREATE TYPE u_date_table AS TABLE OF DATE/ CREATE TYPE u_street_type AS OBJECT ( street VARCHAR2(100), no VARCHAR2(30), - floors u_number_array + floors u_number_array, + f_1323 blob, + f_1326 clob ) / @@ -235,7 +237,9 @@ CREATE TYPE u_address_type AS OBJECT ( city VARCHAR2(50), country VARCHAR2(50), since DATE, - code NUMBER(7) + code NUMBER(7), + f_1323 blob, + f_1326 clob ) / @@ -682,7 +686,9 @@ END p_enhance_address2; CREATE OR REPLACE PROCEDURE p_enhance_address3 (address IN OUT u_address_type) IS BEGIN - address.street := u_street_type('Zwinglistrasse', '17', u_number_array(2)); + address.street.street := 'Zwinglistrasse'; + address.street.no := '17'; + address.street.floors := u_number_array(2); END p_enhance_address3; / diff --git a/jOOQ-test/src/org/jooq/test/oracle/generatedclasses/test/tables/pojos/VAuthor.java b/jOOQ-test/src/org/jooq/test/oracle/generatedclasses/test/tables/pojos/VAuthor.java index fbfc6494b2..c00c65ab11 100644 --- a/jOOQ-test/src/org/jooq/test/oracle/generatedclasses/test/tables/pojos/VAuthor.java +++ b/jOOQ-test/src/org/jooq/test/oracle/generatedclasses/test/tables/pojos/VAuthor.java @@ -10,7 +10,7 @@ package org.jooq.test.oracle.generatedclasses.test.tables.pojos; @javax.persistence.Table(name = "V_AUTHOR", schema = "TEST") public class VAuthor implements java.io.Serializable { - private static final long serialVersionUID = 312765320; + private static final long serialVersionUID = 1871617822; @javax.validation.constraints.NotNull @@ -71,7 +71,7 @@ public class VAuthor implements java.io.Serializable { this.yearOfBirth = yearOfBirth; } - @javax.persistence.Column(name = "ADDRESS", length = 448) + @javax.persistence.Column(name = "ADDRESS", length = 40) public org.jooq.test.oracle.generatedclasses.test.udt.records.UAddressTypeRecord getAddress() { return this.address; } diff --git a/jOOQ-test/src/org/jooq/test/oracle/generatedclasses/test/tables/records/VAuthorRecord.java b/jOOQ-test/src/org/jooq/test/oracle/generatedclasses/test/tables/records/VAuthorRecord.java index 2dbe9e2e2c..4a1f06e85a 100644 --- a/jOOQ-test/src/org/jooq/test/oracle/generatedclasses/test/tables/records/VAuthorRecord.java +++ b/jOOQ-test/src/org/jooq/test/oracle/generatedclasses/test/tables/records/VAuthorRecord.java @@ -10,7 +10,7 @@ package org.jooq.test.oracle.generatedclasses.test.tables.records; @javax.persistence.Table(name = "V_AUTHOR", schema = "TEST") public class VAuthorRecord extends org.jooq.impl.TableRecordImpl { - private static final long serialVersionUID = 1073715627; + private static final long serialVersionUID = 1750224359; /** * An uncommented item @@ -97,7 +97,7 @@ public class VAuthorRecord extends org.jooq.impl.TableRecordImpl { - private static final long serialVersionUID = 781413964; + private static final long serialVersionUID = -849475538; /** * The singleton instance of TEST.U_ADDRESS_TYPE @@ -58,6 +58,16 @@ public class UAddressType extends org.jooq.impl.UDTImpl CODE = createField("CODE", org.jooq.impl.SQLDataType.INTEGER, U_ADDRESS_TYPE); + /** + * An uncommented item + */ + public static final org.jooq.UDTField F_1323 = createField("F_1323", org.jooq.impl.SQLDataType.BLOB, U_ADDRESS_TYPE); + + /** + * An uncommented item + */ + public static final org.jooq.UDTField F_1326 = createField("F_1326", org.jooq.impl.SQLDataType.CLOB, U_ADDRESS_TYPE); + /** * No further instances allowed */ diff --git a/jOOQ-test/src/org/jooq/test/oracle/generatedclasses/test/udt/UStreetType.java b/jOOQ-test/src/org/jooq/test/oracle/generatedclasses/test/udt/UStreetType.java index bdd3341703..c0104031a7 100644 --- a/jOOQ-test/src/org/jooq/test/oracle/generatedclasses/test/udt/UStreetType.java +++ b/jOOQ-test/src/org/jooq/test/oracle/generatedclasses/test/udt/UStreetType.java @@ -8,7 +8,7 @@ package org.jooq.test.oracle.generatedclasses.test.udt; */ public class UStreetType extends org.jooq.impl.UDTImpl { - private static final long serialVersionUID = -1903797174; + private static final long serialVersionUID = -948161688; /** * The singleton instance of TEST.U_STREET_TYPE @@ -43,6 +43,16 @@ public class UStreetType extends org.jooq.impl.UDTImpl FLOORS = createField("FLOORS", org.jooq.impl.SQLDataType.INTEGER.asArrayDataType(org.jooq.test.oracle.generatedclasses.test.udt.records.UNumberArrayRecord.class), U_STREET_TYPE); + /** + * An uncommented item + */ + public static final org.jooq.UDTField F_1323 = createField("F_1323", org.jooq.impl.SQLDataType.BLOB, U_STREET_TYPE); + + /** + * An uncommented item + */ + public static final org.jooq.UDTField F_1326 = createField("F_1326", org.jooq.impl.SQLDataType.CLOB, U_STREET_TYPE); + /** * No further instances allowed */ diff --git a/jOOQ-test/src/org/jooq/test/oracle/generatedclasses/test/udt/records/UAddressTypeRecord.java b/jOOQ-test/src/org/jooq/test/oracle/generatedclasses/test/udt/records/UAddressTypeRecord.java index cb40b88737..7a4ab2f855 100644 --- a/jOOQ-test/src/org/jooq/test/oracle/generatedclasses/test/udt/records/UAddressTypeRecord.java +++ b/jOOQ-test/src/org/jooq/test/oracle/generatedclasses/test/udt/records/UAddressTypeRecord.java @@ -8,7 +8,7 @@ package org.jooq.test.oracle.generatedclasses.test.udt.records; */ public class UAddressTypeRecord extends org.jooq.impl.UDTRecordImpl { - private static final long serialVersionUID = 159216626; + private static final long serialVersionUID = -784892408; /** @@ -95,6 +95,34 @@ public class UAddressTypeRecord extends org.jooq.impl.UDTRecordImpl { - private static final long serialVersionUID = -838066655; + private static final long serialVersionUID = -615019899; /** @@ -53,6 +53,34 @@ public class UStreetTypeRecord extends org.jooq.impl.UDTRecordImpl implements java.lang.Cloneable { - private static final long serialVersionUID = -1438599870; + private static final long serialVersionUID = -805498140; /** * The singleton instance of TEST.U_ADDRESS_TYPE @@ -58,6 +58,16 @@ public class U_ADDRESS_TYPE extends org.jooq.impl.UDTImpl CODE = createField("CODE", org.jooq.impl.SQLDataType.INTEGER, U_ADDRESS_TYPE); + /** + * An uncommented item + */ + public static final org.jooq.UDTField F_1323 = createField("F_1323", org.jooq.impl.SQLDataType.BLOB, U_ADDRESS_TYPE); + + /** + * An uncommented item + */ + public static final org.jooq.UDTField F_1326 = createField("F_1326", org.jooq.impl.SQLDataType.CLOB, U_ADDRESS_TYPE); + /** * No further instances allowed */ diff --git a/jOOQ-test/src/org/jooq/test/oracle3/generatedclasses/udt/U_STREET_TYPE.java b/jOOQ-test/src/org/jooq/test/oracle3/generatedclasses/udt/U_STREET_TYPE.java index 3b7fbc530a..50ccba6b81 100644 --- a/jOOQ-test/src/org/jooq/test/oracle3/generatedclasses/udt/U_STREET_TYPE.java +++ b/jOOQ-test/src/org/jooq/test/oracle3/generatedclasses/udt/U_STREET_TYPE.java @@ -8,7 +8,7 @@ package org.jooq.test.oracle3.generatedclasses.udt; */ public class U_STREET_TYPE extends org.jooq.impl.UDTImpl implements java.lang.Cloneable { - private static final long serialVersionUID = 1786177499; + private static final long serialVersionUID = -598064775; /** * The singleton instance of TEST.U_STREET_TYPE @@ -43,6 +43,16 @@ public class U_STREET_TYPE extends org.jooq.impl.UDTImpl FLOORS = createField("FLOORS", org.jooq.impl.SQLDataType.INTEGER.asArrayDataType(org.jooq.test.oracle3.generatedclasses.udt.records.U_NUMBER_ARRAY.class), U_STREET_TYPE); + /** + * An uncommented item + */ + public static final org.jooq.UDTField F_1323 = createField("F_1323", org.jooq.impl.SQLDataType.BLOB, U_STREET_TYPE); + + /** + * An uncommented item + */ + public static final org.jooq.UDTField F_1326 = createField("F_1326", org.jooq.impl.SQLDataType.CLOB, U_STREET_TYPE); + /** * No further instances allowed */ diff --git a/jOOQ-test/src/org/jooq/test/oracle3/generatedclasses/udt/records/U_ADDRESS_TYPE.java b/jOOQ-test/src/org/jooq/test/oracle3/generatedclasses/udt/records/U_ADDRESS_TYPE.java index 6ea7353fd5..8e19761011 100644 --- a/jOOQ-test/src/org/jooq/test/oracle3/generatedclasses/udt/records/U_ADDRESS_TYPE.java +++ b/jOOQ-test/src/org/jooq/test/oracle3/generatedclasses/udt/records/U_ADDRESS_TYPE.java @@ -8,7 +8,7 @@ package org.jooq.test.oracle3.generatedclasses.udt.records; */ public class U_ADDRESS_TYPE extends org.jooq.impl.UDTRecordImpl implements java.lang.Cloneable { - private static final long serialVersionUID = -1152928371; + private static final long serialVersionUID = -1722726941; /** @@ -95,6 +95,34 @@ public class U_ADDRESS_TYPE extends org.jooq.impl.UDTRecordImpl implements java.lang.Cloneable { - private static final long serialVersionUID = 718004150; + private static final long serialVersionUID = 1993799262; /** @@ -53,6 +53,34 @@ public class U_STREET_TYPE extends org.jooq.impl.UDTRecordImpl routine; - private String sql; + private final Query query; + private final Routine routine; + private String sql; - private final Query[] batchQueries; - private final String[] batchSQL; + private final Query[] batchQueries; + private final String[] batchSQL; // Transient attributes - private transient PreparedStatement statement; - private transient ResultSet resultSet; - private transient Record record; - private transient Result result; + private transient PreparedStatement statement; + private transient ResultSet resultSet; + private transient Record record; + private transient Result result; + + // ------------------------------------------------------------------------ + // XXX: Static utility methods for handling blob / clob lifecycle + // ------------------------------------------------------------------------ + + private static final ThreadLocal> BLOBS = new ThreadLocal>(); + private static final ThreadLocal> CLOBS = new ThreadLocal>(); + + /** + * Clean up blobs and clobs. + *

+ * [#1326] This is necessary in those dialects that have long-lived + * temporary lob objects, which can cause memory leaks in certain contexts, + * where the lobs' underlying session / connection is long-lived as well. + * Specifically, Oracle and ojdbc have some trouble when streaming temporary + * lobs to UDTs: + *

    + *
  1. The lob cannot have a call-scoped life time with UDTs
  2. + *
  3. Freeing the lob after binding will cause an ORA-22275
  4. + *
  5. Not freeing the lob after execution will cause an + * {@link OutOfMemoryError}
  6. + *
+ */ + static final void clean() { + List blobs = BLOBS.get(); + List clobs = CLOBS.get(); + + if (blobs != null) { + for (Blob blob : blobs) { + Util.safeFree(blob); + } + + BLOBS.remove(); + } + + if (clobs != null) { + for (Clob clob : clobs) { + Util.safeFree(clob); + } + + CLOBS.remove(); + } + } + + /** + * Register a blob for later cleanup with {@link #clean()} + */ + static final void register(Blob blob) { + BLOBS.get().add(blob); + } + + /** + * Register a clob for later cleanup with {@link #clean()} + */ + static final void register(Clob clob) { + CLOBS.get().add(clob); + } + + // ------------------------------------------------------------------------ + // XXX: Constructors + // ------------------------------------------------------------------------ DefaultExecuteContext(Configuration configuration) { this(configuration, null, null, null); @@ -110,6 +175,10 @@ class DefaultExecuteContext extends AbstractConfiguration implements ExecuteCont else { this.batchSQL = new String[0]; } + + clean(); + BLOBS.set(new ArrayList()); + CLOBS.set(new ArrayList()); } @Override diff --git a/jOOQ/src/main/java/org/jooq/impl/FieldTypeHelper.java b/jOOQ/src/main/java/org/jooq/impl/FieldTypeHelper.java index c1facca5af..1b07450b2f 100644 --- a/jOOQ/src/main/java/org/jooq/impl/FieldTypeHelper.java +++ b/jOOQ/src/main/java/org/jooq/impl/FieldTypeHelper.java @@ -39,6 +39,7 @@ package org.jooq.impl; import static org.jooq.SQLDialect.CUBRID; import static org.jooq.SQLDialect.POSTGRES; import static org.jooq.impl.Factory.getNewFactory; +import static org.jooq.tools.reflect.Reflect.on; import java.math.BigDecimal; import java.math.BigInteger; @@ -116,6 +117,7 @@ public final class FieldTypeHelper { @SuppressWarnings("unchecked") public static T getFromSQLInput(Configuration configuration, SQLInput stream, Field field) throws SQLException { Class type = field.getType(); + DataType dataType = field.getDataType(); if (type == Blob.class) { return (T) stream.readBlob(); @@ -134,7 +136,21 @@ public final class FieldTypeHelper { return (T) checkWasNull(stream, Byte.valueOf(stream.readByte())); } else if (type == byte[].class) { - return (T) stream.readBytes(); + + // [#1327] Oracle cannot deserialise BLOBs as byte[] from SQLInput + if (dataType.isLob()) { + Blob blob = null; + try { + blob = stream.readBlob(); + return (T) (blob == null ? null : blob.getBytes(1, (int) blob.length())); + } + finally { + Util.safeFree(blob); + } + } + else { + return (T) stream.readBytes(); + } } else if (type == Clob.class) { return (T) stream.readClob(); @@ -216,10 +232,10 @@ public final class FieldTypeHelper { public static void writeToSQLOutput(SQLOutput stream, Field field, T value) throws SQLException { Class type = field.getType(); - writeToSQLOutput(stream, type, value); + writeToSQLOutput(stream, type, field.getDataType(), value); } - public static void writeToSQLOutput(SQLOutput stream, Class type, T value) throws SQLException { + private static void writeToSQLOutput(SQLOutput stream, Class type, DataType dataType, T value) throws SQLException { if (value == null) { stream.writeObject(null); } @@ -239,7 +255,29 @@ public final class FieldTypeHelper { stream.writeByte((Byte) value); } else if (type == byte[].class) { - stream.writeBytes((byte[]) value); + + // [#1327] Oracle cannot serialise BLOBs as byte[] to SQLOutput + // Use reflection to avoid dependency on OJDBC + if (dataType.isLob()) { + Blob blob = null; + + try { + blob = on("oracle.sql.BLOB").call("createTemporary", + on(stream).call("getSTRUCT") + .call("getJavaSqlConnection").get(), + false, + on("oracle.sql.BLOB").get("DURATION_SESSION")).get(); + + blob.setBytes(1, (byte[]) value); + stream.writeBlob(blob); + } + finally { + DefaultExecuteContext.register(blob); + } + } + else { + stream.writeBytes((byte[]) value); + } } else if (type == Clob.class) { stream.writeClob((Clob) value); @@ -263,7 +301,29 @@ public final class FieldTypeHelper { stream.writeShort((Short) value); } else if (type == String.class) { - stream.writeString((String) value); + + // [#1327] Oracle cannot serialise CLOBs as String to SQLOutput + // Use reflection to avoid dependency on OJDBC + if (dataType.isLob()) { + Clob clob = null; + + try { + clob = on("oracle.sql.CLOB").call("createTemporary", + on(stream).call("getSTRUCT") + .call("getJavaSqlConnection").get(), + false, + on("oracle.sql.CLOB").get("DURATION_SESSION")).get(); + + clob.setString(1, (String) value); + stream.writeClob(clob); + } + finally { + DefaultExecuteContext.register(clob); + } + } + else { + stream.writeString((String) value); + } } else if (type == Time.class) { stream.writeTime((Time) value); @@ -301,6 +361,13 @@ public final class FieldTypeHelper { } } + /** + * @deprecated - 2.3.0 - Do not reuse this method + */ + @Deprecated + public static void writeToSQLOutput(SQLOutput stream, Class type, T value) throws SQLException { + writeToSQLOutput(stream, type, null, value); + } static U getFromResultSet(ExecuteContext ctx, Field field, int index) throws SQLException { diff --git a/jOOQ/src/main/java/org/jooq/impl/Util.java b/jOOQ/src/main/java/org/jooq/impl/Util.java index 2cb607ee39..5eaf456bea 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Util.java +++ b/jOOQ/src/main/java/org/jooq/impl/Util.java @@ -46,6 +46,8 @@ import static org.jooq.tools.StringUtils.leftPad; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.sql.Blob; +import java.sql.Clob; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -479,6 +481,9 @@ final class Util { safeClose(ctx.resultSet()); safeClose(ctx.statement()); listener.end(ctx); + + // [#1326] Clean up any potentially remaining temporary lobs + DefaultExecuteContext.clean(); } /** @@ -525,6 +530,30 @@ final class Util { safeClose(statement); } + /** + * Safely free a blob + */ + static final void safeFree(Blob blob) { + if (blob != null) { + try { + blob.free(); + } + catch (Exception ignore) {} + } + } + + /** + * Safely free a clob + */ + static final void safeFree(Clob clob) { + if (clob != null) { + try { + clob.free(); + } + catch (Exception ignore) {} + } + } + /** * Extract an underlying connection */