diff --git a/jOOQ-codegen/src/main/java/org/jooq/util/DefaultGenerator.java b/jOOQ-codegen/src/main/java/org/jooq/util/DefaultGenerator.java index 2377de2e32..155b82b644 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/util/DefaultGenerator.java +++ b/jOOQ-codegen/src/main/java/org/jooq/util/DefaultGenerator.java @@ -501,6 +501,7 @@ public class DefaultGenerator extends AbstractGenerator { out.println(");"); } + @SuppressWarnings("unused") protected void generateUniqueKeySuppressHidingWarning(GenerationWriter out, UniqueKeyDefinition uniqueKey) { out.print("\"hiding\", "); } @@ -548,6 +549,7 @@ public class DefaultGenerator extends AbstractGenerator { out.println(");"); } + @SuppressWarnings("unused") protected void generateForeignKeySuppressHidingWarning(GenerationWriter out, ForeignKeyDefinition foreignKey) { out.print("\"hiding\", "); } @@ -1651,6 +1653,64 @@ public class DefaultGenerator extends AbstractGenerator { } } + // [#1596] UpdatableTables can provide fields for optimistic locking + // if properly configured + if (baseClass == UpdatableTableImpl.class) { + patternLoop: for (String pattern : database.getRecordVersionFields()) { + for (ColumnDefinition column : table.getColumns()) { + if ((column.getName().matches(pattern.trim()) || + column.getQualifiedName().matches(pattern.trim()))) { + + out.println(); + out.println("\t@Override"); + out.print("\tpublic "); + out.print(TableField.class); + out.print("<"); + out.print(strategy.getFullJavaClassName(table, Mode.RECORD)); + out.print(", "); + out.print(getJavaType(column.getType())); + out.println("> getRecordVersion() {"); + + out.print("\t\treturn "); + out.print(strategy.getFullJavaIdentifier(column)); + out.println(";"); + + out.println("\t}"); + + // Avoid generating this method twice + break patternLoop; + } + } + } + + timestampLoop: for (String pattern : database.getRecordTimestampFields()) { + for (ColumnDefinition column : table.getColumns()) { + if ((column.getName().matches(pattern.trim()) || + column.getQualifiedName().matches(pattern.trim()))) { + + out.println(); + out.println("\t@Override"); + out.print("\tpublic "); + out.print(TableField.class); + out.print("<"); + out.print(strategy.getFullJavaClassName(table, Mode.RECORD)); + out.print(", "); + out.print(getJavaType(column.getType())); + out.println("> getRecordTimestamp() {"); + + out.print("\t\treturn "); + out.print(strategy.getFullJavaIdentifier(column)); + out.println(";"); + + out.println("\t}"); + + // Avoid generating this method twice + break timestampLoop; + } + } + } + } + // [#117] With instance fields, it makes sense to create a // type-safe table alias if (generateInstanceFields()) { diff --git a/jOOQ-codegen/src/main/java/org/jooq/util/GenerationTool.java b/jOOQ-codegen/src/main/java/org/jooq/util/GenerationTool.java index faf9701030..43b35992f2 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/util/GenerationTool.java +++ b/jOOQ-codegen/src/main/java/org/jooq/util/GenerationTool.java @@ -118,11 +118,11 @@ public class GenerationTool { // TODO [#1201] Add better error handling here xml = xml.replaceAll( "<(\\w+:)?configuration xmlns(:\\w+)?=\"http://www.jooq.org/xsd/jooq-codegen-\\d+\\.\\d+\\.\\d+.xsd\">", - "<$1configuration xmlns$2=\"http://www.jooq.org/xsd/jooq-codegen-2.4.0.xsd\">"); + "<$1configuration xmlns$2=\"http://www.jooq.org/xsd/jooq-codegen-2.5.0.xsd\">"); xml = xml.replace( "", - ""); + ""); main(JAXB.unmarshal(new StringReader(xml), Configuration.class)); } @@ -346,6 +346,8 @@ public class GenerationTool { database.setConfiguredSchemata(schemata); database.setIncludes(defaultString(g.getDatabase().getIncludes()).split(",")); database.setExcludes(defaultString(g.getDatabase().getExcludes()).split(",")); + database.setRecordVersionFields(defaultString(g.getDatabase().getRecordVersionFields()).split(",")); + database.setRecordTimestampFields(defaultString(g.getDatabase().getRecordTimestampFields()).split(",")); database.setConfiguredMasterDataTables(g.getDatabase().getMasterDataTables()); database.setConfiguredCustomTypes(g.getDatabase().getCustomTypes()); database.setConfiguredEnumTypes(g.getDatabase().getEnumTypes()); diff --git a/jOOQ-meta/pom.xml b/jOOQ-meta/pom.xml index 308624bf55..28c0d321c9 100644 --- a/jOOQ-meta/pom.xml +++ b/jOOQ-meta/pom.xml @@ -32,7 +32,7 @@ false src/main/resources/xsd - jooq-codegen-2.4.0.xsd + jooq-codegen-2.5.0.xsd org.jooq.util.jaxb diff --git a/jOOQ-meta/src/main/java/org/jooq/util/AbstractDatabase.java b/jOOQ-meta/src/main/java/org/jooq/util/AbstractDatabase.java index 9ed2ad3f18..7ee746b546 100644 --- a/jOOQ-meta/src/main/java/org/jooq/util/AbstractDatabase.java +++ b/jOOQ-meta/src/main/java/org/jooq/util/AbstractDatabase.java @@ -74,6 +74,8 @@ public abstract class AbstractDatabase implements Database { private Connection connection; private String[] excludes; private String[] includes; + private String[] recordVersionFields; + private String[] recordTimestampFields; private boolean supportsUnsignedTypes; private boolean dateAsTimestamp; private List configuredSchemata; @@ -228,6 +230,26 @@ public abstract class AbstractDatabase implements Database { return includes; } + @Override + public void setRecordVersionFields(String[] recordVersionFields) { + this.recordVersionFields = recordVersionFields; + } + + @Override + public String[] getRecordVersionFields() { + return recordVersionFields; + } + + @Override + public void setRecordTimestampFields(String[] recordTimestampFields) { + this.recordTimestampFields = recordTimestampFields; + } + + @Override + public String[] getRecordTimestampFields() { + return recordTimestampFields; + } + @Override public final void setConfiguredMasterDataTables(List configuredMasterDataTables) { this.configuredMasterDataTables = configuredMasterDataTables; diff --git a/jOOQ-meta/src/main/java/org/jooq/util/Database.java b/jOOQ-meta/src/main/java/org/jooq/util/Database.java index 39f0dddb54..5489b330b4 100644 --- a/jOOQ-meta/src/main/java/org/jooq/util/Database.java +++ b/jOOQ-meta/src/main/java/org/jooq/util/Database.java @@ -188,6 +188,18 @@ public interface Database { */ void setConfiguredSchemata(List schemata); + /** + * Database objects matching any of these regular expressions will not be + * generated. + */ + void setExcludes(String[] excludes); + + /** + * Database objects matching any of these regular expressions will not be + * generated. + */ + String[] getExcludes(); + /** * Only database objects matching any of these regular expressions will be * generated. @@ -200,6 +212,30 @@ public interface Database { */ String[] getIncludes(); + /** + * Table columns matching these regular expressions will be considered as + * record version fields in generated code + */ + void setRecordVersionFields(String[] recordVersionFields); + + /** + * Table columns matching these regular expressions will be considered as + * record version fields in generated code + */ + String[] getRecordVersionFields(); + + /** + * Table columns matching these regular expressions will be considered as + * record timestamp fields in generated code + */ + void setRecordTimestampFields(String[] recordTimestampFields); + + /** + * Table columns matching these regular expressions will be considered as + * record timestamp fields in generated code + */ + String[] getRecordTimestampFields(); + /** * Database objects matching any of these table names will be generated as * master data tables. @@ -259,18 +295,6 @@ public interface Database { */ ForcedType getConfiguredForcedType(Definition definition); - /** - * Database objects matching any of these regular expressions will not be - * generated. - */ - void setExcludes(String[] excludes); - - /** - * Database objects matching any of these regular expressions will not be - * generated. - */ - String[] getExcludes(); - /** * Get the dialect for this database */ diff --git a/jOOQ-meta/src/main/resources/xsd/jooq-codegen-2.5.0.xsd b/jOOQ-meta/src/main/resources/xsd/jooq-codegen-2.5.0.xsd new file mode 100644 index 0000000000..6d4629fd32 --- /dev/null +++ b/jOOQ-meta/src/main/resources/xsd/jooq-codegen-2.5.0.xsd @@ -0,0 +1,394 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jOOQ-test/configuration/org/jooq/configuration/lukas/derby/library.xml b/jOOQ-test/configuration/org/jooq/configuration/lukas/derby/library.xml index e1f3d57fc7..45e8a682a7 100644 --- a/jOOQ-test/configuration/org/jooq/configuration/lukas/derby/library.xml +++ b/jOOQ-test/configuration/org/jooq/configuration/lukas/derby/library.xml @@ -14,6 +14,8 @@ .* T_BOOK_DETAILS false + REC_VERSION + REC_TIMESTAMP true diff --git a/jOOQ-test/configuration/org/jooq/configuration/lukas/h2/library.xml b/jOOQ-test/configuration/org/jooq/configuration/lukas/h2/library.xml index a3fc7aff5e..ab854ac34f 100644 --- a/jOOQ-test/configuration/org/jooq/configuration/lukas/h2/library.xml +++ b/jOOQ-test/configuration/org/jooq/configuration/lukas/h2/library.xml @@ -12,6 +12,8 @@ org.jooq.util.h2.H2Database .* T_BOOK_DETAILS,SYSTEM_SEQUENCE.* + REC_VERSION + REC_TIMESTAMP false true PUBLIC diff --git a/jOOQ-test/configuration/org/jooq/configuration/lukas/hsqldb/library.xml b/jOOQ-test/configuration/org/jooq/configuration/lukas/hsqldb/library.xml index bf71f89924..93d006a4e5 100644 --- a/jOOQ-test/configuration/org/jooq/configuration/lukas/hsqldb/library.xml +++ b/jOOQ-test/configuration/org/jooq/configuration/lukas/hsqldb/library.xml @@ -21,6 +21,8 @@ org.jooq.util.hsqldb.HSQLDBDatabase .* T_BOOK_DETAILS,S_TRIGGERS_SEQUENCE + REC_VERSION + REC_TIMESTAMP false true diff --git a/jOOQ-test/src/org/jooq/test/BaseTest.java b/jOOQ-test/src/org/jooq/test/BaseTest.java index 3e9de8bdb1..d7729f4f89 100644 --- a/jOOQ-test/src/org/jooq/test/BaseTest.java +++ b/jOOQ-test/src/org/jooq/test/BaseTest.java @@ -35,6 +35,8 @@ */ package org.jooq.test; +import static org.jooq.tools.reflect.Reflect.on; + import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.BigInteger; @@ -445,6 +447,14 @@ public abstract class BaseTest< return delegate.TBook_CONTENT_PDF(); } + protected TableField TBook_REC_TIMESTAMP() { + return delegate.TBook_REC_TIMESTAMP(); + } + + protected TableField TBook_REC_VERSION() { + return delegate.TBook_REC_VERSION(); + } + protected TableField> TBook_STATUS() { return delegate.TBook_STATUS(); } @@ -692,6 +702,22 @@ public abstract class BaseTest< catch (InterruptedException ignore) {} } + /** + * Convenience method to create a new dummy book + */ + @SuppressWarnings("unchecked") + protected final B newBook(int id) { + B record = create().newRecord(TBook()); + + record.setValue(TBook_ID(), id); + record.setValue(TBook_AUTHOR_ID(), 1); + record.setValue(TBook_TITLE(), "XX"); + record.setValue(TBook_PUBLISHED_IN(), 2000); + record.setValue((Field)TBook_LANGUAGE_ID(), on(TBook_LANGUAGE_ID().getDataType().getType()).get("en")); + + return record; + } + @SuppressWarnings("unchecked") protected Sequence SAuthorID() throws IllegalAccessException, NoSuchFieldException { return (Sequence) cSequences().getField("S_AUTHOR_ID").get(cSequences()); diff --git a/jOOQ-test/src/org/jooq/test/_/testcases/CRUDTests.java b/jOOQ-test/src/org/jooq/test/_/testcases/CRUDTests.java index bba3d2b8f2..5f7a028f8f 100644 --- a/jOOQ-test/src/org/jooq/test/_/testcases/CRUDTests.java +++ b/jOOQ-test/src/org/jooq/test/_/testcases/CRUDTests.java @@ -36,6 +36,8 @@ package org.jooq.test._.testcases; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.fail; import static org.jooq.SQLDialect.SQLITE; @@ -43,6 +45,7 @@ import static org.jooq.impl.Factory.count; import static org.jooq.impl.Factory.table; import java.sql.Date; +import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -511,6 +514,97 @@ extends BaseTest> void testStoreLocked0( + private > void testStoreWithOptimisticLock0( UpdatableTable table, TableField id, TableField string) throws Exception { Factory create = create(new Settings().withExecuteWithOptimisticLocking(true)); @@ -649,8 +743,10 @@ extends BaseTest { - private static final long serialVersionUID = -372410209; + private static final long serialVersionUID = -2016102727; /** * The singleton instance of TEST.T_BOOK @@ -88,6 +88,11 @@ public class TBook extends org.jooq.impl.UpdatableTableImpl CONTENT_PDF = createField("CONTENT_PDF", org.jooq.impl.SQLDataType.BLOB, T_BOOK); + /** + * The table column TEST.T_BOOK.REC_TIMESTAMP + */ + public static final org.jooq.TableField REC_TIMESTAMP = createField("REC_TIMESTAMP", org.jooq.impl.SQLDataType.TIMESTAMP, T_BOOK); + /** * No further instances allowed */ @@ -111,4 +116,9 @@ public class TBook extends org.jooq.impl.UpdatableTableImpl> getReferences() { return java.util.Arrays.>asList(org.jooq.test.derby.generatedclasses.Keys.FK_T_BOOK_AUTHOR_ID, org.jooq.test.derby.generatedclasses.Keys.FK_T_BOOK_CO_AUTHOR_ID); } + + @Override + public org.jooq.TableField getRecordTimestamp() { + return org.jooq.test.derby.generatedclasses.tables.TBook.REC_TIMESTAMP; + } } diff --git a/jOOQ-test/src/org/jooq/test/derby/generatedclasses/tables/VBook.java b/jOOQ-test/src/org/jooq/test/derby/generatedclasses/tables/VBook.java index a5309e006b..3e07fd5db9 100644 --- a/jOOQ-test/src/org/jooq/test/derby/generatedclasses/tables/VBook.java +++ b/jOOQ-test/src/org/jooq/test/derby/generatedclasses/tables/VBook.java @@ -8,7 +8,7 @@ package org.jooq.test.derby.generatedclasses.tables; */ public class VBook extends org.jooq.impl.TableImpl { - private static final long serialVersionUID = -751869820; + private static final long serialVersionUID = 1581939470; /** * The singleton instance of TEST.V_BOOK @@ -68,6 +68,11 @@ public class VBook extends org.jooq.impl.TableImpl CONTENT_PDF = createField("CONTENT_PDF", org.jooq.impl.SQLDataType.BLOB, V_BOOK); + /** + * The table column TEST.V_BOOK.REC_TIMESTAMP + */ + public static final org.jooq.TableField REC_TIMESTAMP = createField("REC_TIMESTAMP", org.jooq.impl.SQLDataType.TIMESTAMP, V_BOOK); + /** * No further instances allowed */ diff --git a/jOOQ-test/src/org/jooq/test/derby/generatedclasses/tables/records/TBookRecord.java b/jOOQ-test/src/org/jooq/test/derby/generatedclasses/tables/records/TBookRecord.java index ec117ce121..dc1c5c7bd4 100644 --- a/jOOQ-test/src/org/jooq/test/derby/generatedclasses/tables/records/TBookRecord.java +++ b/jOOQ-test/src/org/jooq/test/derby/generatedclasses/tables/records/TBookRecord.java @@ -8,7 +8,7 @@ package org.jooq.test.derby.generatedclasses.tables.records; */ public class TBookRecord extends org.jooq.impl.UpdatableRecordImpl { - private static final long serialVersionUID = 859890348; + private static final long serialVersionUID = -2014054404; /** * The table column TEST.T_BOOK.ID @@ -246,6 +246,20 @@ public class TBookRecord extends org.jooq.impl.UpdatableRecordImplTEST.T_BOOK.REC_TIMESTAMP + */ + public void setRecTimestamp(java.sql.Timestamp value) { + setValue(org.jooq.test.derby.generatedclasses.tables.TBook.REC_TIMESTAMP, value); + } + + /** + * The table column TEST.T_BOOK.REC_TIMESTAMP + */ + public java.sql.Timestamp getRecTimestamp() { + return getValue(org.jooq.test.derby.generatedclasses.tables.TBook.REC_TIMESTAMP); + } + /** * Create a detached TBookRecord */ diff --git a/jOOQ-test/src/org/jooq/test/derby/generatedclasses/tables/records/VBookRecord.java b/jOOQ-test/src/org/jooq/test/derby/generatedclasses/tables/records/VBookRecord.java index fe8f5d4c94..bcce0cd575 100644 --- a/jOOQ-test/src/org/jooq/test/derby/generatedclasses/tables/records/VBookRecord.java +++ b/jOOQ-test/src/org/jooq/test/derby/generatedclasses/tables/records/VBookRecord.java @@ -8,7 +8,7 @@ package org.jooq.test.derby.generatedclasses.tables.records; */ public class VBookRecord extends org.jooq.impl.TableRecordImpl { - private static final long serialVersionUID = 1509266158; + private static final long serialVersionUID = 1642491642; /** * The table column TEST.V_BOOK.ID @@ -136,6 +136,20 @@ public class VBookRecord extends org.jooq.impl.TableRecordImplTEST.V_BOOK.REC_TIMESTAMP + */ + public void setRecTimestamp(java.sql.Timestamp value) { + setValue(org.jooq.test.derby.generatedclasses.tables.VBook.REC_TIMESTAMP, value); + } + + /** + * The table column TEST.V_BOOK.REC_TIMESTAMP + */ + public java.sql.Timestamp getRecTimestamp() { + return getValue(org.jooq.test.derby.generatedclasses.tables.VBook.REC_TIMESTAMP); + } + /** * Create a detached VBookRecord */ diff --git a/jOOQ-test/src/org/jooq/test/derby/reset.sql b/jOOQ-test/src/org/jooq/test/derby/reset.sql index 4cbdafbc11..20d3914ad8 100644 --- a/jOOQ-test/src/org/jooq/test/derby/reset.sql +++ b/jOOQ-test/src/org/jooq/test/derby/reset.sql @@ -39,13 +39,13 @@ INSERT INTO t_author VALUES (next value for s_author_id, 'George', 'Orwell', '19 INSERT INTO t_author VALUES (next value for s_author_id, 'Paulo', 'Coelho', '1947-08-24', 1947, null) / -INSERT INTO t_book VALUES (1, 1, null, null, '1984', 1948, 1, 'To know and not to know, to be conscious of complete truthfulness while telling carefully constructed lies, to hold simultaneously two opinions which cancelled out, knowing them to be contradictory and believing in both of them, to use logic against logic, to repudiate morality while laying claim to it, to believe that democracy was impossible and that the Party was the guardian of democracy, to forget, whatever it was necessary to forget, then to draw it back into memory again at the moment when it was needed, and then promptly to forget it again, and above all, to apply the same process to the process itself -- that was the ultimate subtlety; consciously to induce unconsciousness, and then, once again, to become unconscious of the act of hypnosis you had just performed. Even to understand the word ''doublethink'' involved the use of doublethink..', null) +INSERT INTO t_book VALUES (1, 1, null, null, '1984', 1948, 1, 'To know and not to know, to be conscious of complete truthfulness while telling carefully constructed lies, to hold simultaneously two opinions which cancelled out, knowing them to be contradictory and believing in both of them, to use logic against logic, to repudiate morality while laying claim to it, to believe that democracy was impossible and that the Party was the guardian of democracy, to forget, whatever it was necessary to forget, then to draw it back into memory again at the moment when it was needed, and then promptly to forget it again, and above all, to apply the same process to the process itself -- that was the ultimate subtlety; consciously to induce unconsciousness, and then, once again, to become unconscious of the act of hypnosis you had just performed. Even to understand the word ''doublethink'' involved the use of doublethink..', null, '2010-01-01 00:00:00') / -INSERT INTO t_book VALUES (2, 1, null, null, 'Animal Farm', 1945, 1, null, null) +INSERT INTO t_book VALUES (2, 1, null, null, 'Animal Farm', 1945, 1, null, null, '2010-01-01 00:00:00') / -INSERT INTO t_book VALUES (3, 2, null, null, 'O Alquimista', 1988, 4, null, null) +INSERT INTO t_book VALUES (3, 2, null, null, 'O Alquimista', 1988, 4, null, null, null) / -INSERT INTO t_book VALUES (4, 2, null, null, 'Brida', 1990, 2, null, null) +INSERT INTO t_book VALUES (4, 2, null, null, 'Brida', 1990, 2, null, null, null) / INSERT INTO t_book_store (name) VALUES diff --git a/jOOQ-test/src/org/jooq/test/h2/create.sql b/jOOQ-test/src/org/jooq/test/h2/create.sql index 84e50137ff..1637f1f10f 100644 --- a/jOOQ-test/src/org/jooq/test/h2/create.sql +++ b/jOOQ-test/src/org/jooq/test/h2/create.sql @@ -241,6 +241,9 @@ CREATE TABLE t_book ( CONTENT_TEXT CLOB, CONTENT_PDF BLOB, + REC_VERSION INT, + REC_TIMESTAMP TIMESTAMP, + CONSTRAINT pk_t_book PRIMARY KEY (ID), CONSTRAINT fk_t_book_author_id FOREIGN KEY (AUTHOR_ID) REFERENCES T_AUTHOR(ID), CONSTRAINT fk_t_book_co_author_id FOREIGN KEY (CO_AUTHOR_ID) REFERENCES T_AUTHOR(ID), diff --git a/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/TBook.java b/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/TBook.java index d670ba2648..573f38da3d 100644 --- a/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/TBook.java +++ b/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/TBook.java @@ -10,7 +10,7 @@ package org.jooq.test.h2.generatedclasses.tables; */ public class TBook extends org.jooq.impl.UpdatableTableImpl { - private static final long serialVersionUID = 929253956; + private static final long serialVersionUID = 1427117992; /** * The singleton instance of PUBLIC.T_BOOK @@ -90,6 +90,16 @@ public class TBook extends org.jooq.impl.UpdatableTableImpl CONTENT_PDF = createField("CONTENT_PDF", org.jooq.impl.SQLDataType.BLOB, T_BOOK); + /** + * The table column PUBLIC.T_BOOK.REC_VERSION + */ + public static final org.jooq.TableField REC_VERSION = createField("REC_VERSION", org.jooq.impl.SQLDataType.INTEGER, T_BOOK); + + /** + * The table column PUBLIC.T_BOOK.REC_TIMESTAMP + */ + public static final org.jooq.TableField REC_TIMESTAMP = createField("REC_TIMESTAMP", org.jooq.impl.SQLDataType.TIMESTAMP, T_BOOK); + /** * No further instances allowed */ @@ -113,4 +123,14 @@ public class TBook extends org.jooq.impl.UpdatableTableImpl> getReferences() { return java.util.Arrays.>asList(org.jooq.test.h2.generatedclasses.Keys.FK_T_BOOK_AUTHOR_ID, org.jooq.test.h2.generatedclasses.Keys.FK_T_BOOK_CO_AUTHOR_ID); } + + @Override + public org.jooq.TableField getRecordVersion() { + return org.jooq.test.h2.generatedclasses.tables.TBook.REC_VERSION; + } + + @Override + public org.jooq.TableField getRecordTimestamp() { + return org.jooq.test.h2.generatedclasses.tables.TBook.REC_TIMESTAMP; + } } diff --git a/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/VBook.java b/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/VBook.java index c9eb353f10..78a4be50c8 100644 --- a/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/VBook.java +++ b/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/VBook.java @@ -8,7 +8,7 @@ package org.jooq.test.h2.generatedclasses.tables; */ public class VBook extends org.jooq.impl.TableImpl { - private static final long serialVersionUID = 1929897654; + private static final long serialVersionUID = 712214512; /** * The singleton instance of PUBLIC.V_BOOK @@ -68,6 +68,16 @@ public class VBook extends org.jooq.impl.TableImpl CONTENT_PDF = createField("CONTENT_PDF", org.jooq.impl.SQLDataType.BLOB, V_BOOK); + /** + * The table column PUBLIC.V_BOOK.REC_VERSION + */ + public static final org.jooq.TableField REC_VERSION = createField("REC_VERSION", org.jooq.impl.SQLDataType.INTEGER, V_BOOK); + + /** + * The table column PUBLIC.V_BOOK.REC_TIMESTAMP + */ + public static final org.jooq.TableField REC_TIMESTAMP = createField("REC_TIMESTAMP", org.jooq.impl.SQLDataType.TIMESTAMP, V_BOOK); + /** * No further instances allowed */ diff --git a/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/daos/TBookDao.java b/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/daos/TBookDao.java index 273d9af18d..fd4320556b 100644 --- a/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/daos/TBookDao.java +++ b/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/daos/TBookDao.java @@ -98,4 +98,18 @@ public class TBookDao extends org.jooq.impl.DAOImpl fetchByContentPdf(byte[]... values) { return fetch(org.jooq.test.h2.generatedclasses.tables.TBook.CONTENT_PDF, values); } + + /** + * Fetch records that have REC_VERSION IN (values) + */ + public java.util.List fetchByRecVersion(java.lang.Integer... values) { + return fetch(org.jooq.test.h2.generatedclasses.tables.TBook.REC_VERSION, values); + } + + /** + * Fetch records that have REC_TIMESTAMP IN (values) + */ + public java.util.List fetchByRecTimestamp(java.sql.Timestamp... values) { + return fetch(org.jooq.test.h2.generatedclasses.tables.TBook.REC_TIMESTAMP, values); + } } diff --git a/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/interfaces/ITBook.java b/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/interfaces/ITBook.java index 54f7e14bcf..0ae5c1b96d 100644 --- a/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/interfaces/ITBook.java +++ b/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/interfaces/ITBook.java @@ -139,4 +139,24 @@ public interface ITBook extends java.io.Serializable { * Some binary content of the book */ public byte[] getContentPdf(); + + /** + * The table column PUBLIC.T_BOOK.REC_VERSION + */ + public void setRecVersion(java.lang.Integer value); + + /** + * The table column PUBLIC.T_BOOK.REC_VERSION + */ + public java.lang.Integer getRecVersion(); + + /** + * The table column PUBLIC.T_BOOK.REC_TIMESTAMP + */ + public void setRecTimestamp(java.sql.Timestamp value); + + /** + * The table column PUBLIC.T_BOOK.REC_TIMESTAMP + */ + public java.sql.Timestamp getRecTimestamp(); } diff --git a/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/interfaces/IVBook.java b/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/interfaces/IVBook.java index 0fec1174e9..cd03b9af7d 100644 --- a/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/interfaces/IVBook.java +++ b/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/interfaces/IVBook.java @@ -97,4 +97,24 @@ public interface IVBook extends java.io.Serializable { * The table column PUBLIC.V_BOOK.CONTENT_PDF */ public byte[] getContentPdf(); + + /** + * The table column PUBLIC.V_BOOK.REC_VERSION + */ + public void setRecVersion(java.lang.Integer value); + + /** + * The table column PUBLIC.V_BOOK.REC_VERSION + */ + public java.lang.Integer getRecVersion(); + + /** + * The table column PUBLIC.V_BOOK.REC_TIMESTAMP + */ + public void setRecTimestamp(java.sql.Timestamp value); + + /** + * The table column PUBLIC.V_BOOK.REC_TIMESTAMP + */ + public java.sql.Timestamp getRecTimestamp(); } diff --git a/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/pojos/TBook.java b/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/pojos/TBook.java index 053bd29f56..340a9629ce 100644 --- a/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/pojos/TBook.java +++ b/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/pojos/TBook.java @@ -10,7 +10,7 @@ package org.jooq.test.h2.generatedclasses.tables.pojos; */ public class TBook implements org.jooq.test.h2.generatedclasses.tables.interfaces.ITBook { - private static final long serialVersionUID = -1516275994; + private static final long serialVersionUID = 805254564; private java.lang.Integer id; private java.lang.Integer authorId; @@ -21,6 +21,8 @@ public class TBook implements org.jooq.test.h2.generatedclasses.tables.interface private org.jooq.test.h2.generatedclasses.enums.TLanguage languageId; private java.lang.String contentText; private byte[] contentPdf; + private java.lang.Integer recVersion; + private java.sql.Timestamp recTimestamp; @Override public java.lang.Integer getId() { @@ -111,4 +113,24 @@ public class TBook implements org.jooq.test.h2.generatedclasses.tables.interface public void setContentPdf(byte[] contentPdf) { this.contentPdf = contentPdf; } + + @Override + public java.lang.Integer getRecVersion() { + return this.recVersion; + } + + @Override + public void setRecVersion(java.lang.Integer recVersion) { + this.recVersion = recVersion; + } + + @Override + public java.sql.Timestamp getRecTimestamp() { + return this.recTimestamp; + } + + @Override + public void setRecTimestamp(java.sql.Timestamp recTimestamp) { + this.recTimestamp = recTimestamp; + } } diff --git a/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/pojos/VBook.java b/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/pojos/VBook.java index 621da596cd..574c78a8d9 100644 --- a/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/pojos/VBook.java +++ b/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/pojos/VBook.java @@ -8,17 +8,19 @@ package org.jooq.test.h2.generatedclasses.tables.pojos; */ public class VBook implements org.jooq.test.h2.generatedclasses.tables.interfaces.IVBook { - private static final long serialVersionUID = -1917104757; + private static final long serialVersionUID = 118771387; - private java.lang.Integer id; - private java.lang.Integer authorId; - private java.lang.Integer coAuthorId; - private java.lang.Integer detailsId; - private java.lang.String title; - private java.lang.Integer publishedIn; - private java.lang.Integer languageId; - private java.lang.String contentText; - private byte[] contentPdf; + private java.lang.Integer id; + private java.lang.Integer authorId; + private java.lang.Integer coAuthorId; + private java.lang.Integer detailsId; + private java.lang.String title; + private java.lang.Integer publishedIn; + private java.lang.Integer languageId; + private java.lang.String contentText; + private byte[] contentPdf; + private java.lang.Integer recVersion; + private java.sql.Timestamp recTimestamp; @Override public java.lang.Integer getId() { @@ -109,4 +111,24 @@ public class VBook implements org.jooq.test.h2.generatedclasses.tables.interface public void setContentPdf(byte[] contentPdf) { this.contentPdf = contentPdf; } + + @Override + public java.lang.Integer getRecVersion() { + return this.recVersion; + } + + @Override + public void setRecVersion(java.lang.Integer recVersion) { + this.recVersion = recVersion; + } + + @Override + public java.sql.Timestamp getRecTimestamp() { + return this.recTimestamp; + } + + @Override + public void setRecTimestamp(java.sql.Timestamp recTimestamp) { + this.recTimestamp = recTimestamp; + } } diff --git a/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/records/TBookRecord.java b/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/records/TBookRecord.java index 63cb320d30..501e705677 100644 --- a/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/records/TBookRecord.java +++ b/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/records/TBookRecord.java @@ -10,7 +10,7 @@ package org.jooq.test.h2.generatedclasses.tables.records; */ public class TBookRecord extends org.jooq.impl.UpdatableRecordImpl implements org.jooq.test.h2.generatedclasses.tables.interfaces.ITBook { - private static final long serialVersionUID = -832621196; + private static final long serialVersionUID = 1968843840; /** * The book ID @@ -266,6 +266,38 @@ public class TBookRecord extends org.jooq.impl.UpdatableRecordImplPUBLIC.T_BOOK.REC_VERSION + */ + @Override + public void setRecVersion(java.lang.Integer value) { + setValue(org.jooq.test.h2.generatedclasses.tables.TBook.REC_VERSION, value); + } + + /** + * The table column PUBLIC.T_BOOK.REC_VERSION + */ + @Override + public java.lang.Integer getRecVersion() { + return getValue(org.jooq.test.h2.generatedclasses.tables.TBook.REC_VERSION); + } + + /** + * The table column PUBLIC.T_BOOK.REC_TIMESTAMP + */ + @Override + public void setRecTimestamp(java.sql.Timestamp value) { + setValue(org.jooq.test.h2.generatedclasses.tables.TBook.REC_TIMESTAMP, value); + } + + /** + * The table column PUBLIC.T_BOOK.REC_TIMESTAMP + */ + @Override + public java.sql.Timestamp getRecTimestamp() { + return getValue(org.jooq.test.h2.generatedclasses.tables.TBook.REC_TIMESTAMP); + } + /** * Create a detached TBookRecord */ diff --git a/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/records/VBookRecord.java b/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/records/VBookRecord.java index 53b58fe786..885f3c1b81 100644 --- a/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/records/VBookRecord.java +++ b/jOOQ-test/src/org/jooq/test/h2/generatedclasses/tables/records/VBookRecord.java @@ -8,7 +8,7 @@ package org.jooq.test.h2.generatedclasses.tables.records; */ public class VBookRecord extends org.jooq.impl.TableRecordImpl implements org.jooq.test.h2.generatedclasses.tables.interfaces.IVBook { - private static final long serialVersionUID = 2107473895; + private static final long serialVersionUID = -1383413389; /** * The table column PUBLIC.V_BOOK.ID @@ -154,6 +154,38 @@ public class VBookRecord extends org.jooq.impl.TableRecordImplPUBLIC.V_BOOK.REC_VERSION + */ + @Override + public void setRecVersion(java.lang.Integer value) { + setValue(org.jooq.test.h2.generatedclasses.tables.VBook.REC_VERSION, value); + } + + /** + * The table column PUBLIC.V_BOOK.REC_VERSION + */ + @Override + public java.lang.Integer getRecVersion() { + return getValue(org.jooq.test.h2.generatedclasses.tables.VBook.REC_VERSION); + } + + /** + * The table column PUBLIC.V_BOOK.REC_TIMESTAMP + */ + @Override + public void setRecTimestamp(java.sql.Timestamp value) { + setValue(org.jooq.test.h2.generatedclasses.tables.VBook.REC_TIMESTAMP, value); + } + + /** + * The table column PUBLIC.V_BOOK.REC_TIMESTAMP + */ + @Override + public java.sql.Timestamp getRecTimestamp() { + return getValue(org.jooq.test.h2.generatedclasses.tables.VBook.REC_TIMESTAMP); + } + /** * Create a detached VBookRecord */ diff --git a/jOOQ-test/src/org/jooq/test/h2/reset.sql b/jOOQ-test/src/org/jooq/test/h2/reset.sql index 195b335b66..b595afbe77 100644 --- a/jOOQ-test/src/org/jooq/test/h2/reset.sql +++ b/jOOQ-test/src/org/jooq/test/h2/reset.sql @@ -39,13 +39,13 @@ INSERT INTO t_author VALUES (next value for s_author_id, 'George', 'Orwell', '19 INSERT INTO t_author VALUES (next value for s_author_id, 'Paulo', 'Coelho', '1947-08-24', 1947, null); / -INSERT INTO t_book VALUES (1, 1, null, null, '1984', 1948, 1, 'To know and not to know, to be conscious of complete truthfulness while telling carefully constructed lies, to hold simultaneously two opinions which cancelled out, knowing them to be contradictory and believing in both of them, to use logic against logic, to repudiate morality while laying claim to it, to believe that democracy was impossible and that the Party was the guardian of democracy, to forget, whatever it was necessary to forget, then to draw it back into memory again at the moment when it was needed, and then promptly to forget it again, and above all, to apply the same process to the process itself -- that was the ultimate subtlety; consciously to induce unconsciousness, and then, once again, to become unconscious of the act of hypnosis you had just performed. Even to understand the word ''doublethink'' involved the use of doublethink..', null); +INSERT INTO t_book VALUES (1, 1, null, null, '1984', 1948, 1, 'To know and not to know, to be conscious of complete truthfulness while telling carefully constructed lies, to hold simultaneously two opinions which cancelled out, knowing them to be contradictory and believing in both of them, to use logic against logic, to repudiate morality while laying claim to it, to believe that democracy was impossible and that the Party was the guardian of democracy, to forget, whatever it was necessary to forget, then to draw it back into memory again at the moment when it was needed, and then promptly to forget it again, and above all, to apply the same process to the process itself -- that was the ultimate subtlety; consciously to induce unconsciousness, and then, once again, to become unconscious of the act of hypnosis you had just performed. Even to understand the word ''doublethink'' involved the use of doublethink..', null, 1, '2010-01-01 00:00:00'); / -INSERT INTO t_book VALUES (2, 1, null, null, 'Animal Farm', 1945, 1, null, null); +INSERT INTO t_book VALUES (2, 1, null, null, 'Animal Farm', 1945, 1, null, null, null, '2010-01-01 00:00:00'); / -INSERT INTO t_book VALUES (3, 2, null, null, 'O Alquimista', 1988, 4, null, null); +INSERT INTO t_book VALUES (3, 2, null, null, 'O Alquimista', 1988, 4, null, null, 1, null); / -INSERT INTO t_book VALUES (4, 2, null, null, 'Brida', 1990, 2, null, null); +INSERT INTO t_book VALUES (4, 2, null, null, 'Brida', 1990, 2, null, null, null, null); / INSERT INTO t_book_store (name) VALUES diff --git a/jOOQ-test/src/org/jooq/test/hsqldb/create.sql b/jOOQ-test/src/org/jooq/test/hsqldb/create.sql index ad481a67f2..817014400f 100644 --- a/jOOQ-test/src/org/jooq/test/hsqldb/create.sql +++ b/jOOQ-test/src/org/jooq/test/hsqldb/create.sql @@ -259,6 +259,8 @@ CREATE TABLE t_book ( CONTENT_TEXT LONGVARCHAR, CONTENT_PDF LONGVARBINARY, + REC_VERSION INT, + CONSTRAINT pk_t_book PRIMARY KEY (ID), CONSTRAINT fk_t_book_author_id FOREIGN KEY (AUTHOR_ID) REFERENCES T_AUTHOR(ID), CONSTRAINT fk_t_book_co_author_id FOREIGN KEY (CO_AUTHOR_ID) REFERENCES T_AUTHOR(ID), diff --git a/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/TBook.java b/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/TBook.java index 96e3321e51..08ff3d1bd4 100644 --- a/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/TBook.java +++ b/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/TBook.java @@ -8,7 +8,7 @@ package org.jooq.test.hsqldb.generatedclasses.tables; */ public class TBook extends org.jooq.impl.UpdatableTableImpl { - private static final long serialVersionUID = -1805777047; + private static final long serialVersionUID = -440675106; /** * The singleton instance of PUBLIC.T_BOOK @@ -88,6 +88,11 @@ public class TBook extends org.jooq.impl.UpdatableTableImpl CONTENT_PDF = createField("CONTENT_PDF", org.jooq.impl.SQLDataType.VARBINARY, this); + /** + * The table column PUBLIC.T_BOOK.REC_VERSION + */ + public final org.jooq.TableField REC_VERSION = createField("REC_VERSION", org.jooq.impl.SQLDataType.INTEGER, this); + public TBook() { super("T_BOOK", org.jooq.test.hsqldb.generatedclasses.Public.PUBLIC); } @@ -113,6 +118,11 @@ public class TBook extends org.jooq.impl.UpdatableTableImpl>asList(org.jooq.test.hsqldb.generatedclasses.Keys.FK_T_BOOK_AUTHOR_ID, org.jooq.test.hsqldb.generatedclasses.Keys.FK_T_BOOK_CO_AUTHOR_ID); } + @Override + public org.jooq.TableField getRecordVersion() { + return org.jooq.test.hsqldb.generatedclasses.tables.TBook.T_BOOK.REC_VERSION; + } + @Override public org.jooq.test.hsqldb.generatedclasses.tables.TBook as(java.lang.String alias) { return new org.jooq.test.hsqldb.generatedclasses.tables.TBook(alias); diff --git a/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/VBook.java b/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/VBook.java index 8e60235004..0dd80ed1a6 100644 --- a/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/VBook.java +++ b/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/VBook.java @@ -8,7 +8,7 @@ package org.jooq.test.hsqldb.generatedclasses.tables; */ public class VBook extends org.jooq.impl.TableImpl { - private static final long serialVersionUID = -1177700829; + private static final long serialVersionUID = 424431476; /** * The singleton instance of PUBLIC.V_BOOK @@ -68,6 +68,11 @@ public class VBook extends org.jooq.impl.TableImpl CONTENT_PDF = createField("CONTENT_PDF", org.jooq.impl.SQLDataType.VARBINARY, this); + /** + * The table column PUBLIC.V_BOOK.REC_VERSION + */ + public final org.jooq.TableField REC_VERSION = createField("REC_VERSION", org.jooq.impl.SQLDataType.INTEGER, this); + public VBook() { super("V_BOOK", org.jooq.test.hsqldb.generatedclasses.Public.PUBLIC); } diff --git a/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/interfaces/ITBook.java b/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/interfaces/ITBook.java index c52bc1b8c1..0cff240df4 100644 --- a/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/interfaces/ITBook.java +++ b/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/interfaces/ITBook.java @@ -137,4 +137,14 @@ public interface ITBook extends java.io.Serializable { * The table column PUBLIC.T_BOOK.CONTENT_PDF */ public byte[] getContentPdf(); + + /** + * The table column PUBLIC.T_BOOK.REC_VERSION + */ + public void setRecVersion(java.lang.Integer value); + + /** + * The table column PUBLIC.T_BOOK.REC_VERSION + */ + public java.lang.Integer getRecVersion(); } diff --git a/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/interfaces/IVBook.java b/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/interfaces/IVBook.java index 5bbf897bb9..992964e9a5 100644 --- a/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/interfaces/IVBook.java +++ b/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/interfaces/IVBook.java @@ -97,4 +97,14 @@ public interface IVBook extends java.io.Serializable { * The table column PUBLIC.V_BOOK.CONTENT_PDF */ public byte[] getContentPdf(); + + /** + * The table column PUBLIC.V_BOOK.REC_VERSION + */ + public void setRecVersion(java.lang.Integer value); + + /** + * The table column PUBLIC.V_BOOK.REC_VERSION + */ + public java.lang.Integer getRecVersion(); } diff --git a/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/records/TBookRecord.java b/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/records/TBookRecord.java index b1834a5b49..50042e6b7e 100644 --- a/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/records/TBookRecord.java +++ b/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/records/TBookRecord.java @@ -8,7 +8,7 @@ package org.jooq.test.hsqldb.generatedclasses.tables.records; */ public class TBookRecord extends org.jooq.impl.UpdatableRecordImpl implements org.jooq.test.hsqldb.generatedclasses.tables.interfaces.ITBook { - private static final long serialVersionUID = -1623058826; + private static final long serialVersionUID = -454420402; /** * The table column PUBLIC.T_BOOK.ID @@ -264,6 +264,22 @@ public class TBookRecord extends org.jooq.impl.UpdatableRecordImplPUBLIC.T_BOOK.REC_VERSION + */ + @Override + public void setRecVersion(java.lang.Integer value) { + setValue(org.jooq.test.hsqldb.generatedclasses.tables.TBook.T_BOOK.REC_VERSION, value); + } + + /** + * The table column PUBLIC.T_BOOK.REC_VERSION + */ + @Override + public java.lang.Integer getRecVersion() { + return getValue(org.jooq.test.hsqldb.generatedclasses.tables.TBook.T_BOOK.REC_VERSION); + } + /** * Create a detached TBookRecord */ diff --git a/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/records/VBookRecord.java b/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/records/VBookRecord.java index 6cd3616eb2..6e719bbca1 100644 --- a/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/records/VBookRecord.java +++ b/jOOQ-test/src/org/jooq/test/hsqldb/generatedclasses/tables/records/VBookRecord.java @@ -8,7 +8,7 @@ package org.jooq.test.hsqldb.generatedclasses.tables.records; */ public class VBookRecord extends org.jooq.impl.TableRecordImpl implements org.jooq.test.hsqldb.generatedclasses.tables.interfaces.IVBook { - private static final long serialVersionUID = 50732295; + private static final long serialVersionUID = -374904553; /** * The table column PUBLIC.V_BOOK.ID @@ -154,6 +154,22 @@ public class VBookRecord extends org.jooq.impl.TableRecordImplPUBLIC.V_BOOK.REC_VERSION + */ + @Override + public void setRecVersion(java.lang.Integer value) { + setValue(org.jooq.test.hsqldb.generatedclasses.tables.VBook.V_BOOK.REC_VERSION, value); + } + + /** + * The table column PUBLIC.V_BOOK.REC_VERSION + */ + @Override + public java.lang.Integer getRecVersion() { + return getValue(org.jooq.test.hsqldb.generatedclasses.tables.VBook.V_BOOK.REC_VERSION); + } + /** * Create a detached VBookRecord */ diff --git a/jOOQ-test/src/org/jooq/test/hsqldb/reset.sql b/jOOQ-test/src/org/jooq/test/hsqldb/reset.sql index ae40ac2222..c195c41ba4 100644 --- a/jOOQ-test/src/org/jooq/test/hsqldb/reset.sql +++ b/jOOQ-test/src/org/jooq/test/hsqldb/reset.sql @@ -37,10 +37,10 @@ INSERT INTO t_658_ref VALUES ('A', 1, 1, 'B', 2, 2)/ INSERT INTO t_author VALUES (next value for s_author_id, 'George', 'Orwell', '1903-06-25', 1903, null)/ INSERT INTO t_author VALUES (next value for s_author_id, 'Paulo', 'Coelho', '1947-08-24', 1947, null)/ -INSERT INTO t_book VALUES (1, 1, null, null, '1984', 1948, 1, 'To know and not to know, to be conscious of complete truthfulness while telling carefully constructed lies, to hold simultaneously two opinions which cancelled out, knowing them to be contradictory and believing in both of them, to use logic against logic, to repudiate morality while laying claim to it, to believe that democracy was impossible and that the Party was the guardian of democracy, to forget, whatever it was necessary to forget, then to draw it back into memory again at the moment when it was needed, and then promptly to forget it again, and above all, to apply the same process to the process itself -- that was the ultimate subtlety; consciously to induce unconsciousness, and then, once again, to become unconscious of the act of hypnosis you had just performed. Even to understand the word ''doublethink'' involved the use of doublethink..', null)/ -INSERT INTO t_book VALUES (2, 1, null, null, 'Animal Farm', 1945, 1, null, null)/ -INSERT INTO t_book VALUES (3, 2, null, null, 'O Alquimista', 1988, 4, null, null)/ -INSERT INTO t_book VALUES (4, 2, null, null, 'Brida', 1990, 2, null, null)/ +INSERT INTO t_book VALUES (1, 1, null, null, '1984', 1948, 1, 'To know and not to know, to be conscious of complete truthfulness while telling carefully constructed lies, to hold simultaneously two opinions which cancelled out, knowing them to be contradictory and believing in both of them, to use logic against logic, to repudiate morality while laying claim to it, to believe that democracy was impossible and that the Party was the guardian of democracy, to forget, whatever it was necessary to forget, then to draw it back into memory again at the moment when it was needed, and then promptly to forget it again, and above all, to apply the same process to the process itself -- that was the ultimate subtlety; consciously to induce unconsciousness, and then, once again, to become unconscious of the act of hypnosis you had just performed. Even to understand the word ''doublethink'' involved the use of doublethink..', null, 1)/ +INSERT INTO t_book VALUES (2, 1, null, null, 'Animal Farm', 1945, 1, null, null, null)/ +INSERT INTO t_book VALUES (3, 2, null, null, 'O Alquimista', 1988, 4, null, null, 1)/ +INSERT INTO t_book VALUES (4, 2, null, null, 'Brida', 1990, 2, null, null, null)/ INSERT INTO t_book_store (name) VALUES ('Orell Füssli'), diff --git a/jOOQ-test/src/org/jooq/test/jOOQDerbyTest.java b/jOOQ-test/src/org/jooq/test/jOOQDerbyTest.java index f2227b15f5..7f65005f09 100644 --- a/jOOQ-test/src/org/jooq/test/jOOQDerbyTest.java +++ b/jOOQ-test/src/org/jooq/test/jOOQDerbyTest.java @@ -47,6 +47,7 @@ import static org.jooq.test.derby.generatedclasses.Tables.V_BOOK; import java.math.BigDecimal; import java.math.BigInteger; import java.sql.Date; +import java.sql.Timestamp; import org.jooq.ArrayRecord; import org.jooq.DataType; @@ -186,6 +187,16 @@ public class jOOQDerbyTest extends jOOQAbstractTest< return TBook.TITLE; } + @Override + protected TableField TBook_REC_VERSION() { + return super.TBook_REC_VERSION(); + } + + @Override + protected TableField TBook_REC_TIMESTAMP() { + return TBook.REC_TIMESTAMP; + } + @Override protected UpdatableTable TBookStore() { return TBookStore.T_BOOK_STORE; diff --git a/jOOQ-test/src/org/jooq/test/jOOQH2Test.java b/jOOQ-test/src/org/jooq/test/jOOQH2Test.java index 376ba9ab0d..9388c78e4a 100644 --- a/jOOQ-test/src/org/jooq/test/jOOQH2Test.java +++ b/jOOQ-test/src/org/jooq/test/jOOQH2Test.java @@ -47,6 +47,7 @@ import static org.jooq.test.h2.generatedclasses.Tables.V_BOOK; import java.math.BigDecimal; import java.math.BigInteger; import java.sql.Date; +import java.sql.Timestamp; import org.jooq.ArrayRecord; import org.jooq.DAO; @@ -198,6 +199,16 @@ public class jOOQH2Test extends jOOQAbstractTest< return TBook.TITLE; } + @Override + protected TableField TBook_REC_VERSION() { + return TBook.REC_VERSION; + } + + @Override + protected TableField TBook_REC_TIMESTAMP() { + return TBook.REC_TIMESTAMP; + } + @Override protected UpdatableTable TBookStore() { return TBookStore.T_BOOK_STORE; diff --git a/jOOQ-test/src/org/jooq/test/jOOQHSQLDBTest.java b/jOOQ-test/src/org/jooq/test/jOOQHSQLDBTest.java index 9b78e7b63c..092223575a 100644 --- a/jOOQ-test/src/org/jooq/test/jOOQHSQLDBTest.java +++ b/jOOQ-test/src/org/jooq/test/jOOQHSQLDBTest.java @@ -57,6 +57,7 @@ import static org.jooq.test.hsqldb.generatedclasses.Tables.V_LIBRARY; import java.math.BigDecimal; import java.math.BigInteger; import java.sql.Date; +import java.sql.Timestamp; import org.jooq.ArrayRecord; import org.jooq.DataType; @@ -185,6 +186,16 @@ public class jOOQHSQLDBTest extends jOOQAbstractTest< return T_BOOK.TITLE; } + @Override + protected TableField TBook_REC_VERSION() { + return T_BOOK.REC_VERSION; + } + + @Override + protected TableField TBook_REC_TIMESTAMP() { + return super.TBook_REC_TIMESTAMP(); + } + @Override protected UpdatableTable TBookStore() { return T_BOOK_STORE; diff --git a/jOOQ/src/main/java/org/jooq/TableRecord.java b/jOOQ/src/main/java/org/jooq/TableRecord.java index 26b4dad9cf..801bc78416 100644 --- a/jOOQ/src/main/java/org/jooq/TableRecord.java +++ b/jOOQ/src/main/java/org/jooq/TableRecord.java @@ -41,6 +41,7 @@ import java.sql.Statement; import org.jooq.conf.Settings; import org.jooq.exception.DataAccessException; import org.jooq.exception.DataChangedException; +import org.jooq.impl.Factory; /** * A record originating from a single table @@ -61,6 +62,8 @@ public interface TableRecord> extends Record { * Depending on the state of the provided keys' value, an * INSERT or an UPDATE statement is executed. *

+ *

Statement type

+ *

*

    *
  • If this record was created by client code, an INSERT * statement is executed
  • @@ -72,11 +75,62 @@ public interface TableRecord> extends Record { * record. *
  • If this record was loaded by jOOQ, and the provided keys' value was * not changed, an UPDATE statement is executed.
  • - *
  • If an UPDATE statement is executed and + *
+ *

+ * In either statement type, only those fields are inserted/updated, which + * had been explicitly set by client code, in order to allow for + * DEFAULT values to be applied by the underlying RDBMS. If no + * fields were modified, neither an UPDATE nor an + * INSERT will be executed. + *

Automatic value generation

+ *

+ *

    + *
  • IDENTITY columns + *

    + * If there is an IDENTITY column defined on the record's + * underlying table (see {@link Table#getIdentity()}), then the + * auto-generated IDENTITY value is refreshed automatically on + * INSERT's. Refreshing is done using + * {@link Statement#getGeneratedKeys()}, where this is supported by the JDBC + * driver. See also {@link InsertQuery#getReturnedRecord()} for more details + *

  • + *
  • VERSION and TIMESTAMP columns + *

    + * jOOQ can auto-generate "version" and "timestamp" values that can be used + * for optimistic locking. If this is an {@link UpdatableRecord} and if this + * record returns fields for either + * {@link UpdatableTable#getRecordVersion()} or + * {@link UpdatableTable#getRecordTimestamp()}, then these values are set + * onto the INSERT or UPDATE statement being + * executed. On execution success, the generated values are set to this + * record. Use the code-generation configuration to specify naming patterns + * for auto-generated "version" and "timestamp" columns. + *

    + * Should you want to circumvent jOOQ-generated updates to these columns, + * you can render an INSERT or UPDATE statement + * manually using the various {@link Factory#insertInto(Table)}, + * {@link Factory#update(Table)} methods.

  • + *
+ *

Optimistic locking

+ *

+ * If an UPDATE statement is executed and * {@link Settings#isExecuteWithOptimisticLocking()} is set to * true, then this record will first be compared with the - * latest state in the database. In order to compare this record with the - * latest state, the database record will be locked pessimistically using a + * latest state in the database. There are two modes of operation for + * optimistic locking: + *

    + *
  • With VERSION and/or TIMESTAMP columns configured + *

    + * This is the preferred way of using optimistic locking in jOOQ. If this is + * an {@link UpdatableRecord} and if this record returns fields for either + * {@link UpdatableTable#getRecordVersion()} or + * {@link UpdatableTable#getRecordTimestamp()}, then these values are + * compared to the corresponding value in the database in the + * WHERE clause of the executed DELETE statement.

  • + *
  • Without any specific column configurations + *

    + * In order to compare this record with the latest state, the database + * record will be locked pessimistically using a * SELECT .. FOR UPDATE statement. Not all databases support * the FOR UPDATE clause natively. Namely, the following * databases will show slightly different behaviour: @@ -92,12 +146,7 @@ public interface TableRecord> extends Record { *

    * See {@link LockProvider#setForUpdate(boolean)} for more details

  • *
- *

- * In either statement, only those fields are inserted/updated, which had - * been explicitly set by client code, in order to allow for - * DEFAULT values to be applied by the underlying RDBMS. If no - * fields were modified, neither an UPDATE nor an - * INSERT will be executed. + *

Statement examples

*

* Possible statements are *

    @@ -109,15 +158,9 @@ public interface TableRecord> extends Record { *
          * UPDATE [table]
          * SET [modified fields = modified values, excluding keys]
    -     * WHERE [key fields = key values]
    + * WHERE [key fields = key values] + * AND [version/timestamp fields = version/timestamp values] *
- *

- * If there is an IDENTITY column defined on the record's - * underlying table (see {@link Table#getIdentity()}), then the - * auto-generated IDENTITY value is refreshed automatically on - * INSERT's. Refreshing is done using - * {@link Statement#getGeneratedKeys()}, where this is supported by the JDBC - * driver. * * @param keys The key fields used for deciding whether to execute an * INSERT or UPDATE statement. If an @@ -135,14 +178,26 @@ public interface TableRecord> extends Record { * Deletes this record from the database, based on the value of the provided * keys. *

- * The executed statement is

-     * DELETE FROM [table]
-     * WHERE [key fields = key values]
+ *

Optimistic locking

*

- * If {@link Settings#isExecuteWithOptimisticLocking()} is set to + * If a DELETE statement is executed and + * {@link Settings#isExecuteWithOptimisticLocking()} is set to * true, then this record will first be compared with the - * latest state in the database. In order to compare this record with the - * latest state, the database record will be locked pessimistically using a + * latest state in the database. There are two modes of operation for + * optimistic locking: + *

    + *
  • With VERSION and/or TIMESTAMP columns configured + *

    + * This is the preferred way of using optimistic locking in jOOQ. If this is + * an {@link UpdatableRecord} and if this record returns fields for either + * {@link UpdatableTable#getRecordVersion()} or + * {@link UpdatableTable#getRecordTimestamp()}, then these values are + * compared to the corresponding value in the database in the + * WHERE clause of the executed DELETE statement.

  • + *
  • Without any specific column configurations + *

    + * In order to compare this record with the latest state, the database + * record will be locked pessimistically using a * SELECT .. FOR UPDATE statement. Not all databases support * the FOR UPDATE clause natively. Namely, the following * databases will show slightly different behaviour: @@ -153,10 +208,17 @@ public interface TableRecord> extends Record { * {@link ResultSet#CONCUR_UPDATABLE}.

  • *
  • {@link SQLDialect#SQLITE}: No pessimistic locking is possible. Client * code must assure that no race-conditions can occur between jOOQ's - * checking of database record state and the actual UPDATE
  • + * checking of database record state and the actual DELETE *
*

- * See {@link LockProvider#setForUpdate(boolean)} for more details + * See {@link LockProvider#setForUpdate(boolean)} for more details + * + *

Statement examples

+ *

+ * The executed statement is

+     * DELETE FROM [table]
+     * WHERE [key fields = key values]
+     * AND [version/timestamp fields = version/timestamp values]
* * @param keys The key fields for the DELETE statement's * WHERE clause. diff --git a/jOOQ/src/main/java/org/jooq/UpdatableRecord.java b/jOOQ/src/main/java/org/jooq/UpdatableRecord.java index 92b479716f..e9acefa3a8 100644 --- a/jOOQ/src/main/java/org/jooq/UpdatableRecord.java +++ b/jOOQ/src/main/java/org/jooq/UpdatableRecord.java @@ -41,6 +41,7 @@ import java.sql.Statement; import org.jooq.conf.Settings; import org.jooq.exception.DataAccessException; import org.jooq.exception.DataChangedException; +import org.jooq.impl.Factory; /** * A common interface for records that can be stored back to the database again. @@ -87,6 +88,8 @@ public interface UpdatableRecord> extends Updatable * Depending on the state of the primary key's or main unique key's value, * an INSERT or an UPDATE statement is executed. *

+ *

Statement type

+ *

*

    *
  • If this record was created by client code, an INSERT * statement is executed
  • @@ -98,11 +101,62 @@ public interface UpdatableRecord> extends Updatable * record. *
  • If this record was loaded by jOOQ, and the primary key value was not * changed, an UPDATE statement is executed.
  • - *
  • If an UPDATE statement is executed and + *
+ *

+ * In either statement type, only those fields are inserted/updated, which + * had been explicitly set by client code, in order to allow for + * DEFAULT values to be applied by the underlying RDBMS. If no + * fields were modified, neither an UPDATE nor an + * INSERT will be executed. + *

Automatic value generation

+ *

+ *

    + *
  • IDENTITY columns + *

    + * If there is an IDENTITY column defined on the record's + * underlying table (see {@link Table#getIdentity()}), then the + * auto-generated IDENTITY value is refreshed automatically on + * INSERT's. Refreshing is done using + * {@link Statement#getGeneratedKeys()}, where this is supported by the JDBC + * driver. See also {@link InsertQuery#getReturnedRecord()} for more details + *

  • + *
  • VERSION and TIMESTAMP columns + *

    + * jOOQ can auto-generate "version" and "timestamp" values that can be used + * for optimistic locking. If this is an {@link UpdatableRecord} and if this + * record returns fields for either + * {@link UpdatableTable#getRecordVersion()} or + * {@link UpdatableTable#getRecordTimestamp()}, then these values are set + * onto the INSERT or UPDATE statement being + * executed. On execution success, the generated values are set to this + * record. Use the code-generation configuration to specify naming patterns + * for auto-generated "version" and "timestamp" columns. + *

    + * Should you want to circumvent jOOQ-generated updates to these columns, + * you can render an INSERT or UPDATE statement + * manually using the various {@link Factory#insertInto(Table)}, + * {@link Factory#update(Table)} methods.

  • + *
+ *

Optimistic locking

+ *

+ * If an UPDATE statement is executed and * {@link Settings#isExecuteWithOptimisticLocking()} is set to * true, then this record will first be compared with the - * latest state in the database. In order to compare this record with the - * latest state, the database record will be locked pessimistically using a + * latest state in the database. There are two modes of operation for + * optimistic locking: + *

    + *
  • With VERSION and/or TIMESTAMP columns configured + *

    + * This is the preferred way of using optimistic locking in jOOQ. If this is + * an {@link UpdatableRecord} and if this record returns fields for either + * {@link UpdatableTable#getRecordVersion()} or + * {@link UpdatableTable#getRecordTimestamp()}, then these values are + * compared to the corresponding value in the database in the + * WHERE clause of the executed DELETE statement.

  • + *
  • Without any specific column configurations + *

    + * In order to compare this record with the latest state, the database + * record will be locked pessimistically using a * SELECT .. FOR UPDATE statement. Not all databases support * the FOR UPDATE clause natively. Namely, the following * databases will show slightly different behaviour: @@ -118,35 +172,22 @@ public interface UpdatableRecord> extends Updatable *

    * See {@link LockProvider#setForUpdate(boolean)} for more details

  • *
+ *

Statement examples

*

- * In either INSERT or UPDATE statement, only - * those fields are inserted/updated, which had been explicitly set by - * client code, in order to allow for DEFAULT values to be - * applied by the underlying RDBMS. If no fields were modified, neither an - * UPDATE nor an INSERT will be executed. Possible - * statements are + * Possible statements are *

    *
  • *
    -     * INSERT INTO [table] ([modified fields, including main key])
    -     * VALUES ([modified values, including main key])
  • + * INSERT INTO [table] ([modified fields, including keys]) + * VALUES ([modified values, including keys]) *
  • *
          * UPDATE [table]
    -     * SET [modified fields = modified values, excluding main key]
    -     * WHERE [main key fields = main key values]
  • + * SET [modified fields = modified values, excluding keys] + * WHERE [key fields = key values] + * AND [version/timestamp fields = version/timestamp values] *
*

- * If there is an IDENTITY column defined on the record's - * underlying table (see {@link Table#getIdentity()}), then the - * auto-generated IDENTITY value is refreshed automatically on - * INSERT's. Similarly, all members of the primary key are - * refreshed, to ensure that trigger-generated values will be available - * after INSERT. Normally, primary key and - * IDENTITY columns coincide, but this doesn't have to be. - * Refreshing is done using {@link Statement#getGeneratedKeys()}, where this - * is supported by the JDBC driver. - *

* This is in fact the same as calling * store(getTable().getMainKey().getFieldsArray()) * @@ -163,14 +204,26 @@ public interface UpdatableRecord> extends Updatable * Deletes this record from the database, based on the value of the primary * key or main unique key. *

- * The executed statement is

-     * DELETE FROM [table]
-     * WHERE [main key fields = main key values]
+ *

Optimistic locking

*

- * If {@link Settings#isExecuteWithOptimisticLocking()} is set to + * If a DELETE statement is executed and + * {@link Settings#isExecuteWithOptimisticLocking()} is set to * true, then this record will first be compared with the - * latest state in the database. In order to compare this record with the - * latest state, the database record will be locked pessimistically using a + * latest state in the database. There are two modes of operation for + * optimistic locking: + *

    + *
  • With VERSION and/or TIMESTAMP columns configured + *

    + * This is the preferred way of using optimistic locking in jOOQ. If this is + * an {@link UpdatableRecord} and if this record returns fields for either + * {@link UpdatableTable#getRecordVersion()} or + * {@link UpdatableTable#getRecordTimestamp()}, then these values are + * compared to the corresponding value in the database in the + * WHERE clause of the executed DELETE statement.

  • + *
  • Without any specific column configurations + *

    + * In order to compare this record with the latest state, the database + * record will be locked pessimistically using a * SELECT .. FOR UPDATE statement. Not all databases support * the FOR UPDATE clause natively. Namely, the following * databases will show slightly different behaviour: @@ -181,10 +234,17 @@ public interface UpdatableRecord> extends Updatable * {@link ResultSet#CONCUR_UPDATABLE}.

  • *
  • {@link SQLDialect#SQLITE}: No pessimistic locking is possible. Client * code must assure that no race-conditions can occur between jOOQ's - * checking of database record state and the actual UPDATE
  • + * checking of database record state and the actual DELETE *
*

- * See {@link LockProvider#setForUpdate(boolean)} for more details + * See {@link LockProvider#setForUpdate(boolean)} for more details + * + *

Statement examples

+ *

+ * The executed statement is

+     * DELETE FROM [table]
+     * WHERE [key fields = key values]
+     * AND [version/timestamp fields = version/timestamp values]
*

* This is in fact the same as calling * delete(getTable().getMainKey().getFieldsArray()) diff --git a/jOOQ/src/main/java/org/jooq/UpdatableTable.java b/jOOQ/src/main/java/org/jooq/UpdatableTable.java index aef71ac0a8..d29ce2e528 100644 --- a/jOOQ/src/main/java/org/jooq/UpdatableTable.java +++ b/jOOQ/src/main/java/org/jooq/UpdatableTable.java @@ -37,6 +37,8 @@ package org.jooq; import java.util.List; +import org.jooq.conf.Settings; + /** * A common interface for tables whose records can be stored back to the * database again. @@ -79,4 +81,54 @@ public interface UpdatableTable extends Updatable, Table */ List> getReferencesFrom(Table other); + /** + * A "version" field holding record version information used for optimistic + * locking + *

+ * jOOQ supports optimistic locking in {@link UpdatableRecord#store()} and + * {@link UpdatableRecord#delete()} if + * {@link Settings#isExecuteWithOptimisticLocking()} is enabled. Optimistic + * locking is performed in a single UPDATE or + * DELETE statement if tables provide a "version" or + * "timestamp" field, or in two steps using an additional + * SELECT .. FOR UPDATE statement otherwise. + *

+ * This method is overridden in generated subclasses if their corresponding + * tables have been configured accordingly. A table may have both a + * "version" and a "timestamp" field. + * + * @return The "version" field, or null, if this table has no + * "version" field. + * @see #getRecordTimestamp() + * @see UpdatableRecord#store() + * @see UpdatableRecord#delete() + * @see Settings#isExecuteWithOptimisticLocking() + */ + TableField getRecordVersion(); + + /** + * A "timestamp" field holding record timestamp information used for + * optimistic locking + *

+ * jOOQ supports optimistic locking in {@link UpdatableRecord#store()} and + * {@link UpdatableRecord#delete()} if + * {@link Settings#isExecuteWithOptimisticLocking()} is enabled. Optimistic + * locking is performed in a single UPDATE or + * DELETE statement if tables provide a "version" or + * "timestamp" field, or in two steps using an additional + * SELECT .. FOR UPDATE statement otherwise. + *

+ * This method is overridden in generated subclasses if their corresponding + * tables have been configured accordingly. A table may have both a + * "version" and a "timestamp" field. + * + * @return The "timestamp" field, or null, if this table has no + * "timestamp" field. + * @see #getRecordVersion() + * @see UpdatableRecord#store() + * @see UpdatableRecord#delete() + * @see Settings#isExecuteWithOptimisticLocking() + */ + TableField getRecordTimestamp(); + } diff --git a/jOOQ/src/main/java/org/jooq/exception/DataChangedException.java b/jOOQ/src/main/java/org/jooq/exception/DataChangedException.java index eae01028eb..b8b27ef858 100644 --- a/jOOQ/src/main/java/org/jooq/exception/DataChangedException.java +++ b/jOOQ/src/main/java/org/jooq/exception/DataChangedException.java @@ -41,7 +41,7 @@ import org.jooq.UpdatableRecord; * An error occurred while storing a record whose underlying data had already * been changed * - * @see UpdatableRecord#storeLocked() + * @see UpdatableRecord#store() * @author Lukas Eder */ public class DataChangedException extends DataAccessException { diff --git a/jOOQ/src/main/java/org/jooq/impl/TableRecordImpl.java b/jOOQ/src/main/java/org/jooq/impl/TableRecordImpl.java index f4cc37c63d..9e28e7130e 100644 --- a/jOOQ/src/main/java/org/jooq/impl/TableRecordImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/TableRecordImpl.java @@ -38,6 +38,8 @@ package org.jooq.impl; import static java.lang.Boolean.TRUE; import static org.jooq.impl.Factory.val; +import java.math.BigInteger; +import java.sql.Timestamp; import java.util.Collection; import java.util.LinkedHashSet; @@ -54,6 +56,7 @@ import org.jooq.Table; import org.jooq.TableField; import org.jooq.TableRecord; import org.jooq.UpdatableRecord; +import org.jooq.UpdatableTable; import org.jooq.UpdateQuery; import org.jooq.exception.DataChangedException; import org.jooq.exception.InvalidResultException; @@ -94,9 +97,29 @@ public class TableRecordImpl> extends AbstractRecord im return (Table) getFieldProvider(); } + /** + * Subclasses may override this method to provide an identity + */ + Collection> getReturning() { + Collection> result = new LinkedHashSet>(); + + Identity identity = getTable().getIdentity(); + if (identity != null) { + result.add(identity.getField()); + } + + return result; + } + + /** + * Convenience casting method, in case it is known that casting will succeed + */ + UpdatableTable getUpdatableTable() { + return (UpdatableTable) getTable(); + } + @Override public final int storeUsing(TableField... keys) { - boolean checkIfChanged = isExecuteWithOptimisticLocking(); boolean executeUpdate = false; for (TableField field : keys) { @@ -116,7 +139,7 @@ public class TableRecordImpl> extends AbstractRecord im int result = 0; if (executeUpdate) { - result = storeUpdate(keys, checkIfChanged); + result = storeUpdate(keys); } else { result = storeInsert(); @@ -129,6 +152,7 @@ public class TableRecordImpl> extends AbstractRecord im private final boolean isExecuteWithOptimisticLocking() { Configuration configuration = getConfiguration(); + // This can be null when the current record is detached if (configuration != null) { return TRUE.equals(configuration.getSettings().isExecuteWithOptimisticLocking()); } @@ -136,16 +160,17 @@ public class TableRecordImpl> extends AbstractRecord im return false; } - @SuppressWarnings("unchecked") private final int storeInsert() { Factory create = create(); InsertQuery insert = create.insertQuery(getTable()); + addChangedValues(insert); - for (Field field : getFields()) { - if (getValue0(field).isChanged()) { - addValue(insert, (TableField) field); - } - } + // Don't store records if no value was set by client code + if (!insert.isExecutable()) return 0; + + // [#1596] Set timestamp and/or version columns to appropriate values + BigInteger version = addRecordVersion(insert); + Timestamp timestamp = addRecordTimestamp(insert); // [#814] Refresh identity and/or main unique key values // [#1002] Consider also identity columns of non-updatable records @@ -158,11 +183,17 @@ public class TableRecordImpl> extends AbstractRecord im int result = insert.execute(); - // If an insert was successful try fetching the generated IDENTITY value - if (key != null && !key.isEmpty() && result > 0) { - if (insert.getReturnedRecord() != null) { - for (Field field : key) { - setValue0(field, new Value(insert.getReturnedRecord().getValue(field))); + if (result > 0) { + + // [#1596] If insert was successful, update timestamp and/or version columns + setRecordVersionAndTimestamp(version, timestamp); + + // If an insert was successful try fetching the generated IDENTITY value + if (key != null && !key.isEmpty()) { + if (insert.getReturnedRecord() != null) { + for (Field field : key) { + setValue0(field, new Value(insert.getReturnedRecord().getValue(field))); + } } } } @@ -170,51 +201,115 @@ public class TableRecordImpl> extends AbstractRecord im return result; } - /** - * Subclasses may override this method to provide an identity - */ - Collection> getReturning() { - Collection> result = new LinkedHashSet>(); - - Identity identity = getTable().getIdentity(); - if (identity != null) { - result.add(identity.getField()); - } - - return result; - } - - @SuppressWarnings("unchecked") - private final int storeUpdate(TableField[] keys, boolean checkIfChanged) { + private final int storeUpdate(TableField[] keys) { UpdateQuery update = create().updateQuery(getTable()); + addChangedValues(update); + addConditions(update, keys); - for (Field field : getFields()) { - if (getValue0(field).isChanged()) { - addValue(update, (TableField) field); + // Don't store records if no value was set by client code + if (!update.isExecutable()) return 0; + + // [#1596] Set timestamp and/or version columns to appropriate values + BigInteger version = addRecordVersion(update); + Timestamp timestamp = addRecordTimestamp(update); + + if (isExecuteWithOptimisticLocking()) { + + // [#1596] Add additional conditions for version and/or timestamp columns + if (isTimestampOrVersionAvailable()) { + addConditionForVersionAndTimestamp(update); + } + + // [#1547] Try fetching the Record again first, and compare this + // Record's original values with the ones in the database + else { + checkIfChanged(keys); } } - for (Field field : keys) { - addCondition(update, field); - } - - // [#1547] If optimistic locking checks are requested, try fetching the - // Record again first, and compare this Record's original values with - // the ones in the database - if (checkIfChanged && update.isExecutable()) { - checkIfChanged(keys); - } - - return update.execute(); + // [#1596] Check if the record was really changed in the database + int result = update.execute(); + checkIfChanged(result, version, timestamp); + return result; } - private void checkIfChanged(TableField[] keys) { - SimpleSelectQuery select = create().selectQuery(getTable()); + private final void addConditionForVersionAndTimestamp(ConditionProvider query) { + TableField v = getUpdatableTable().getRecordVersion(); + TableField t = getUpdatableTable().getRecordTimestamp(); - for (Field field : keys) { - addCondition(select, field); + if (v != null) addCondition(query, v); + if (t != null) addCondition(query, t); + } + + private final boolean isTimestampOrVersionAvailable() { + if (getTable() instanceof UpdatableTable) { + UpdatableTable table = (UpdatableTable) getTable(); + + return table.getRecordTimestamp() != null || table.getRecordVersion() != null; } + return false; + } + + @Override + public final int deleteUsing(TableField... keys) { + try { + DeleteQuery delete = create().deleteQuery(getTable()); + addConditions(delete, keys); + + if (isExecuteWithOptimisticLocking()) { + + // [#1596] Add additional conditions for version and/or timestamp columns + if (isTimestampOrVersionAvailable()) { + addConditionForVersionAndTimestamp(delete); + } + + // [#1547] Try fetching the Record again first, and compare this + // Record's original values with the ones in the database + else { + checkIfChanged(keys); + } + } + + int result = delete.execute(); + checkIfChanged(result, null, null); + return result; + } + + // [#673] If store() is called after delete(), a new INSERT should + // be executed and the record should be recreated + finally { + for (Field field : getFields()) { + getValue0(field).setChanged(true); + } + } + } + + @Override + public final void refreshUsing(TableField... keys) { + SimpleSelectQuery select = create().selectQuery(getTable()); + addConditions(select, keys); + + if (select.execute() == 1) { + AbstractRecord record = (AbstractRecord) select.getResult().get(0); + + for (Field field : getFields()) { + setValue0(field, record.getValue0(field)); + } + } + else { + throw new InvalidResultException("Exactly one row expected for refresh. Record does not exist in database."); + } + } + + /** + * Perform an additional SELECT .. FOR UPDATE to check if the underlying + * database record has been changed compared to this record. + */ + private final void checkIfChanged(TableField[] keys) { + SimpleSelectQuery select = create().selectQuery(getTable()); + addConditions(select, keys); + // [#1547] SQLite doesn't support FOR UPDATE. CUBRID and SQL Server // can simulate it, though! if (create().getDialect() != SQLDialect.SQLITE) { @@ -240,53 +335,21 @@ public class TableRecordImpl> extends AbstractRecord im } } - @Override - public final int deleteUsing(TableField... keys) { - boolean checkIfChanged = isExecuteWithOptimisticLocking(); + /** + * Check if a database record was changed in the database. + */ + private final void checkIfChanged(int result, BigInteger version, Timestamp timestamp) { - try { - DeleteQuery delete = create().deleteQuery(getTable()); - - for (Field field : keys) { - addCondition(delete, field); - } - - // [#1547] If optimistic locking checks are requested, try fetching the - // Record again first, and compare this Record's original values with - // the ones in the database - if (checkIfChanged && delete.isExecutable()) { - checkIfChanged(keys); - } - - return delete.execute(); + // [#1596] If update/delete was successful, update version and/or + // timestamp columns. + // [#673] Do this also for deletions, in case a deleted record is re-added + if (result > 0) { + setRecordVersionAndTimestamp(version, timestamp); } - // [#673] If store() is called after delete(), a new INSERT should - // be executed and the record should be recreated - finally { - for (Field field : getFields()) { - getValue0(field).setChanged(true); - } - } - } - - @Override - public final void refreshUsing(TableField... keys) { - SimpleSelectQuery select = create().selectQuery(getTable()); - - for (Field field : keys) { - addCondition(select, field); - } - - if (select.execute() == 1) { - AbstractRecord record = (AbstractRecord) select.getResult().get(0); - - for (Field field : getFields()) { - setValue0(field, record.getValue0(field)); - } - } - else { - throw new InvalidResultException("Exactly one row expected for refresh. Record does not exist in database."); + // [#1596] No records were updated due to version and/or timestamp change + else if (isExecuteWithOptimisticLocking()) { + throw new DataChangedException("Database record has been changed or doesn't exist any longer"); } } @@ -299,16 +362,105 @@ public class TableRecordImpl> extends AbstractRecord im } /** - * Extracted method to ensure generic type safety. + * Add primary key conditions to a query + */ + private final void addConditions(ConditionProvider query, TableField[] keys) { + for (Field field : keys) { + addCondition(query, field); + } + } + + /** + * Add a field condition to a query */ private final void addCondition(ConditionProvider provider, Field field) { provider.addConditions(field.equal(getValue(field))); } + /** + * Set all changed values of this record to a store query + */ + private final void addChangedValues(StoreQuery query) { + for (Field field : getFields()) { + if (getValue0(field).isChanged()) { + addValue(query, field); + } + } + } + /** * Extracted method to ensure generic type safety. */ private final void addValue(StoreQuery store, Field field) { - store.addValue(field, val(getValue(field), field)); + addValue(store, field, getValue(field)); + } + + /** + * Extracted method to ensure generic type safety. + */ + private final void addValue(StoreQuery store, Field field, Object value) { + store.addValue(field, val(value, field)); + } + + /** + * Set an updated timestamp value to a store query + */ + private final Timestamp addRecordTimestamp(StoreQuery store) { + Timestamp result = null; + + if (isTimestampOrVersionAvailable()) { + TableField timestamp = getUpdatableTable().getRecordTimestamp(); + + if (timestamp != null) { + + // Use Timestamp locally, to provide maximum precision + result = new Timestamp(System.currentTimeMillis()); + addValue(store, timestamp, result); + } + } + + return result; + } + + /** + * Set an updated version value to a store query + */ + private final BigInteger addRecordVersion(StoreQuery store) { + BigInteger result = null; + + if (isTimestampOrVersionAvailable()) { + TableField version = getUpdatableTable().getRecordVersion(); + + if (version != null) { + Number value = getValue(version); + + // Use BigInteger locally to avoid arithmetic overflows + if (value == null) { + result = BigInteger.ONE; + } + else { + result = new BigInteger(value.toString()).add(BigInteger.ONE); + } + + addValue(store, version, result); + } + } + + return result; + } + + /** + * Set a generated version and timestamp value onto this record after + * successfully storing the record. + */ + private final void setRecordVersionAndTimestamp(BigInteger version, Timestamp timestamp) { + if (version != null) { + TableField field = getUpdatableTable().getRecordVersion(); + setValue0(field, new Value(field.getDataType().convert(version))); + } + if (timestamp != null) { + TableField field = getUpdatableTable().getRecordTimestamp(); + setValue0(field, new Value(field.getDataType().convert(timestamp))); + } } } diff --git a/jOOQ/src/main/java/org/jooq/impl/UpdatableTableImpl.java b/jOOQ/src/main/java/org/jooq/impl/UpdatableTableImpl.java index 916737e594..6d377482ca 100644 --- a/jOOQ/src/main/java/org/jooq/impl/UpdatableTableImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/UpdatableTableImpl.java @@ -42,6 +42,7 @@ import org.jooq.ForeignKey; import org.jooq.Record; import org.jooq.Schema; import org.jooq.Table; +import org.jooq.TableField; import org.jooq.UniqueKey; import org.jooq.UpdatableTable; @@ -91,6 +92,26 @@ public class UpdatableTableImpl extends TableImpl implement return Collections.emptyList(); } + /** + * {@inheritDoc} + *

+ * Subclasses may override this method + */ + @Override + public TableField getRecordVersion() { + return null; + } + + /** + * {@inheritDoc} + *

+ * Subclasses may override this method + */ + @Override + public TableField getRecordTimestamp() { + return null; + } + @Override public final List> getReferencesFrom(Table other) { return other.getReferencesTo(this);