[#1596] Add support for optimistic locking using generated information

about "timestamp" or "version" columns
This commit is contained in:
Lukas Eder 2012-07-27 12:35:24 +02:00
parent 8155574f65
commit f3f93dfa29
45 changed files with 1525 additions and 206 deletions

View File

@ -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()) {

View File

@ -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(
"<configuration>",
"<configuration xmlns=\"http://www.jooq.org/xsd/jooq-codegen-2.4.0.xsd\">");
"<configuration xmlns=\"http://www.jooq.org/xsd/jooq-codegen-2.5.0.xsd\">");
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());

View File

@ -32,7 +32,7 @@
<strict>false</strict>
<schemaDirectory>src/main/resources/xsd</schemaDirectory>
<schemaIncludes>
<include>jooq-codegen-2.4.0.xsd</include>
<include>jooq-codegen-2.5.0.xsd</include>
</schemaIncludes>
<generatePackage>org.jooq.util.jaxb</generatePackage>
<args>

View File

@ -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<Schema> 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<MasterDataTable> configuredMasterDataTables) {
this.configuredMasterDataTables = configuredMasterDataTables;

View File

@ -188,6 +188,18 @@ public interface Database {
*/
void setConfiguredSchemata(List<Schema> 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
*/

View File

@ -0,0 +1,394 @@
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://www.jooq.org/xsd/jooq-codegen-2.5.0.xsd"
targetNamespace="http://www.jooq.org/xsd/jooq-codegen-2.5.0.xsd"
elementFormDefault="qualified">
<element name="configuration">
<complexType>
<all>
<!--
The JDBC configuration element contains information about how
to set up the database connection used for source code generation
-->
<element name="jdbc" type="tns:Jdbc" minOccurs="1"
maxOccurs="1" />
<!--
The GENERATOR configuration element contains information about
source code generation itself
-->
<element name="generator" type="tns:Generator" minOccurs="1"
maxOccurs="1" />
</all>
</complexType>
</element>
<complexType name="Jdbc">
<all>
<!-- The JDBC driver -->
<element name="driver" type="string" minOccurs="1" maxOccurs="1" />
<!-- The JDBC connection URL -->
<element name="url" type="string" minOccurs="1" maxOccurs="1" />
<!-- Deprecated. Use database schema configuration elements instead -->
<element name="schema" type="string" minOccurs="0" maxOccurs="1" />
<!--
The JDBC connection user. Be sure this user has all required
GRANTs to the dictionary views/tables to generate the desired artefacts
-->
<element name="user" type="string" minOccurs="0" maxOccurs="1" />
<!-- The JDBC connection password -->
<element name="password" type="string" minOccurs="0" maxOccurs="1" />
<!--
Enlist custom JDBC driver properties that are provided to the
java.sql.DriverManager when fetching a connection
-->
<element name="properties" type="tns:Properties" minOccurs="0" maxOccurs="1" />
</all>
</complexType>
<complexType name="Properties">
<sequence>
<element name="property" type="tns:Property" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
</complexType>
<complexType name="Property">
<all>
<element name="key" type="string" minOccurs="1" maxOccurs="1"/>
<element name="value" type="string" minOccurs="1" maxOccurs="1"/>
</all>
</complexType>
<complexType name="Generator">
<all>
<!--
The class used to generate source code. You may override this with
your custom source code generator
-->
<element name="name" type="string" default="org.jooq.util.DefaultGenerator"
minOccurs="0" maxOccurs="1" />
<!-- The naming strategy used for class and field names -->
<element name="strategy" type="tns:Strategy" minOccurs="0" maxOccurs="1" />
<!-- The jooq-meta configuration -->
<element name="database" type="tns:Database" minOccurs="1" maxOccurs="1" />
<!-- The jooq-codegen configuration -->
<element name="generate" type="tns:Generate" minOccurs="0" maxOccurs="1" />
<!-- Some information about generation output -->
<element name="target" type="tns:Target" minOccurs="0" maxOccurs="1" />
</all>
</complexType>
<complexType name="Strategy">
<all>
<!--
The class used to provide a naming strategy for generated source
code. You may override this with your custom naming strategy
-->
<element name="name" type="string" minOccurs="0" maxOccurs="1"
default="org.jooq.util.DefaultGeneratorStrategy" />
</all>
</complexType>
<complexType name="Database">
<all>
<!--
The database dialect from jooq-meta. Available dialects are
named org.util.[database].[database]Database. Known values are:
org.jooq.util.ase.ASEDatabase
org.jooq.util.cubrid.CUBRIDDatabase
org.jooq.util.db2.DB2Database
org.jooq.util.derby.DerbyDatabase
org.jooq.util.h2.H2Database
org.jooq.util.hsqldb.HSQLDBDatabase
org.jooq.util.ingres.IngresDatabase
org.jooq.util.mysql.MySQLDatabase
org.jooq.util.oracle.OracleDatabase
org.jooq.util.postgres.PostgresDatabase
org.jooq.util.sqlite.SQLiteDatabaes
org.jooq.util.sqlserver.SQLServerDatabase
org.jooq.util.sybase.SybaseDatabase
You can also provide your own org.jooq.util.Database implementation
here, if your database is currently not supported
-->
<element name="name" type="string" minOccurs="1" maxOccurs="1" />
<!--
All elements that are generated from your schema (several Java
regular expressions, separated by comma) Watch out for
case-sensitivity. Depending on your database, this might be
important! You can create case-insensitive regular expressions
using this syntax: (?i:expr)A comma-separated list of regular
expressions
-->
<element name="includes" type="string" default=".*" minOccurs="0" maxOccurs="1" />
<!--
All elements that are excluded from your schema (several Java
regular expressions, separated by comma). Excludes match before
includes
-->
<element name="excludes" type="string" default="" minOccurs="0" maxOccurs="1" />
<!--
All table and view columns that are used as "version" fields for
optimistic locking (several Java regular expressions, separated by comma).
See UpdatableRecord.store() and UpdatableRecord.delete() for details
-->
<element name="recordVersionFields" type="string" default="" minOccurs="0" maxOccurs="1" />
<!--
All table and view columns that are used as "timestamp" fields for
optimistic locking (several Java regular expressions, separated by comma).
See UpdatableRecord.store() and UpdatableRecord.delete() for details
-->
<element name="recordTimestampFields" type="string" default="" minOccurs="0" maxOccurs="1" />
<!--
Generate java.sql.Timestamp fields for DATE columns. This is
particularly useful for Oracle databases
-->
<element name="dateAsTimestamp" type="boolean" default="false" minOccurs="0" maxOccurs="1" />
<!--
Generate jOOU data types for your unsigned data types, which are
not natively supported in Java
-->
<element name="unsignedTypes" type="boolean" default="true" minOccurs="0" maxOccurs="1" />
<!--
The schema that is used locally as a source for meta information.
This could be your development schema or the production schema, etc
This cannot be combined with the schemata element.
If left empty (and without any schemata element), jOOQ will generate all available schemata.
For backwards compatibility, this defaults to jdbc/schema
-->
<element name="inputSchema" type="string" default="" minOccurs="0" maxOccurs="1" />
<!--
The schema that is used in generated source code. This will be the
production schema. Use this to override your local development
schema name for source code generation. If not specified, this
will be the same as the input-schema.
-->
<element name="outputSchema" type="string" default="" minOccurs="0" maxOccurs="1" />
<!--
A configuration element to configure several input and/or output
schemata for jooq-meta, in case you're using jooq-meta in a multi-
schema environment
-->
<element name="schemata" type="tns:Schemata" minOccurs="0" maxOccurs="1"/>
<!--
A configuration element to configure master data table enum classes
-->
<element name="masterDataTables" type="tns:MasterDataTables" minOccurs="0" maxOccurs="1"/>
<!--
A configuration element to configure custom types introduced to jOOQ
using converters
This is EXPERIMENTAL functionality. Use at your own risk
-->
<element name="customTypes" type="tns:CustomTypes" minOccurs="0" maxOccurs="1"/>
<!--
A configuration element to configure synthetic enum types
This is EXPERIMENTAL / DEPRECATED functionality. Do not re-use
-->
<element name="enumTypes" type="tns:EnumTypes" minOccurs="0" maxOccurs="1"/>
<!--
A configuration element to configure type overrides for generated
artefacts (e.g. in combination with enumTypes)
This is EXPERIMENTAL functionality. Use at your own risk
-->
<element name="forcedTypes" type="tns:ForcedTypes" minOccurs="0" maxOccurs="1"/>
</all>
</complexType>
<complexType name="Schemata">
<sequence>
<!--
A configuration element for a single schema in multi-schema
environments
-->
<element name="schema" type="tns:Schema" minOccurs="1" maxOccurs="unbounded"/>
</sequence>
</complexType>
<complexType name="Schema">
<all>
<!-- See also database/inputSchema -->
<element name="inputSchema" type="string" default="" minOccurs="1" maxOccurs="1" />
<!-- See also database/outputSchema -->
<element name="outputSchema" type="string" default="" minOccurs="0" maxOccurs="1" />
</all>
</complexType>
<complexType name="MasterDataTables">
<sequence>
<!-- A configuration element for a master data table -->
<element name="masterDataTable" type="tns:MasterDataTable" minOccurs="0" maxOccurs="unbounded" />
</sequence>
</complexType>
<complexType name="CustomTypes">
<sequence>
<!-- A configuration element for a custom type -->
<element name="customType" type="tns:CustomType" minOccurs="0" maxOccurs="unbounded" />
</sequence>
</complexType>
<complexType name="EnumTypes">
<sequence>
<!-- A configuration element for a synthetic enum type -->
<element name="enumType" type="tns:EnumType" minOccurs="0" maxOccurs="unbounded" />
</sequence>
</complexType>
<complexType name="ForcedTypes">
<sequence>
<!-- A configuration element for a forced type override -->
<element name="forcedType" type="tns:ForcedType" minOccurs="0" maxOccurs="unbounded" />
</sequence>
</complexType>
<complexType name="MasterDataTable">
<all>
<!-- The name of a master data table -->
<element name="name" type="string" minOccurs="1" maxOccurs="1" />
<!-- The column used for enum literals -->
<element name="literal" type="string" minOccurs="0" maxOccurs="1" />
<!-- The column used for documentation -->
<element name="description" type="string" minOccurs="0" maxOccurs="1" />
</all>
</complexType>
<complexType name="CustomType">
<all>
<!-- The name of the custom type -->
<element name="name" type="string" minOccurs="1" maxOccurs="1" />
<!-- A converter implementation for the custom type -->
<element name="converter" type="string" minOccurs="1" maxOccurs="1" />
</all>
</complexType>
<complexType name="EnumType">
<all>
<!-- The name of the synthetic enum type -->
<element name="name" type="string" minOccurs="1" maxOccurs="1" />
<!-- A comma separated (CSV format) list of enum literals -->
<element name="literals" type="string" minOccurs="1" maxOccurs="1" />
</all>
</complexType>
<complexType name="ForcedType">
<all>
<!-- The name of the type to be forced upon various artefacts -->
<element name="name" type="string" minOccurs="1" maxOccurs="1" />
<!--
A comma-separated list of Java regular expressions matching
columns, parameters, attributes, etc to be forced to have this
type
-->
<element name="expressions" type="string" minOccurs="1" maxOccurs="1" />
</all>
</complexType>
<complexType name="Generate">
<all>
<!--
Primary key / foreign key relations should be generated and used.
This is a prerequisite for various advanced features
-->
<element name="relations" type="boolean" default="false" minOccurs="0" maxOccurs="1" />
<!--
Generate navigation methods to navigate foreign key relationships
directly from Record classes. This is only relevant if relations
is set to true, too
-->
<element name="navigationMethods" type="boolean" default="true" minOccurs="0" maxOccurs="1" />
<!-- Generate deprecated code for backwards compatibility -->
<element name="deprecated" type="boolean" default="true" minOccurs="0" maxOccurs="1" />
<!--
Generate instance fields in your tables, as opposed to static
fields. This simplifies aliasing
-->
<element name="instanceFields" type="boolean" default="true" minOccurs="0" maxOccurs="1" />
<!--
Generate the javax.annotation.Generated annotation to indicate
jOOQ version used for source code
-->
<element name="generatedAnnotation" type="boolean" default="true" minOccurs="0" maxOccurs="1" />
<!--
Generate TableRecord classes. Disable this when you don't
need the additional type-safety
-->
<element name="records" type="boolean" default="true" minOccurs="0" maxOccurs="1" />
<!--
Generate POJOs for usage of the ResultQuery.fetchInto(Class) API
-->
<element name="pojos" type="boolean" default="false" minOccurs="0" maxOccurs="1" />
<!--
Generate interfaces that will be implemented by records and/or pojos.
You can also use these interfaces in Record.into(Class<?>) and similar
methods, to let jOOQ return proxy objects for them.
-->
<element name="interfaces" type="boolean" default="false" minOccurs="0" maxOccurs="1" />
<!--
Generate DAOs in addition to POJO classes
-->
<element name="daos" type="boolean" default="false" minOccurs="0" maxOccurs="1" />
<!--
Annotate POJOs and Records with JPA annotations for increased
compatibility and better integration with JPA/Hibernate, etc
-->
<element name="jpaAnnotations" type="boolean" default="false" minOccurs="0" maxOccurs="1" />
<!--
Annotate POJOs and Records with JSR-303 validation annotations
-->
<element name="validationAnnotations" type="boolean" default="false" minOccurs="0" maxOccurs="1" />
</all>
</complexType>
<complexType name="Target">
<all>
<!--
The destination package of your generated classes (within the
destination directory)
-->
<element name="packageName" type="string" default="org.jooq.generated" minOccurs="0" maxOccurs="1" />
<!-- The destination directory of your generated classes -->
<element name="directory" type="string" default="target/generated-sources/jooq" />
</all>
</complexType>
</schema>

View File

@ -14,6 +14,8 @@
<includes>.*</includes>
<excludes>T_BOOK_DETAILS</excludes>
<dateAsTimestamp>false</dateAsTimestamp>
<recordVersionFields>REC_VERSION</recordVersionFields>
<recordTimestampFields>REC_TIMESTAMP</recordTimestampFields>
<unsignedTypes>true</unsignedTypes>
<masterDataTables>
<masterDataTable>

View File

@ -12,6 +12,8 @@
<name>org.jooq.util.h2.H2Database</name>
<includes>.*</includes>
<excludes>T_BOOK_DETAILS,SYSTEM_SEQUENCE.*</excludes>
<recordVersionFields>REC_VERSION</recordVersionFields>
<recordTimestampFields>REC_TIMESTAMP</recordTimestampFields>
<dateAsTimestamp>false</dateAsTimestamp>
<unsignedTypes>true</unsignedTypes>
<inputSchema>PUBLIC</inputSchema>

View File

@ -21,6 +21,8 @@
<name>org.jooq.util.hsqldb.HSQLDBDatabase</name>
<includes>.*</includes>
<excludes>T_BOOK_DETAILS,S_TRIGGERS_SEQUENCE</excludes>
<recordVersionFields>REC_VERSION</recordVersionFields>
<recordTimestampFields>REC_TIMESTAMP</recordTimestampFields>
<dateAsTimestamp>false</dateAsTimestamp>
<unsignedTypes>true</unsignedTypes>
<masterDataTables>

View File

@ -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<B, Timestamp> TBook_REC_TIMESTAMP() {
return delegate.TBook_REC_TIMESTAMP();
}
protected TableField<B, Integer> TBook_REC_VERSION() {
return delegate.TBook_REC_VERSION();
}
protected TableField<B, ? extends Enum<?>> 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<Object>)TBook_LANGUAGE_ID(), on(TBook_LANGUAGE_ID().getDataType().getType()).get("en"));
return record;
}
@SuppressWarnings("unchecked")
protected Sequence<? extends Number> SAuthorID() throws IllegalAccessException, NoSuchFieldException {
return (Sequence<? extends Number>) cSequences().getField("S_AUTHOR_ID").get(cSequences());

View File

@ -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<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658,
assertEquals("Rösslitor", store.getValue(TBookStore_NAME()));
}
@Test
public void testUpdatablesVersionAndTimestamp() throws Exception {
if (TBook_REC_TIMESTAMP() == null && TBook_REC_VERSION() == null) {
log.info("SKIPPING", "Record version and timestamp tests");
}
jOOQAbstractTest.reset = false;
Factory create = create(new Settings().withExecuteWithOptimisticLocking(true));
boolean t = TBook_REC_TIMESTAMP() != null;
boolean v = TBook_REC_VERSION() != null;
// Test data integrity check
// -------------------------
if (t) assertEquals(2, create.selectCount().from(TBook()).where(TBook_REC_TIMESTAMP().isNotNull()).fetchOne(0));
if (v) assertEquals(2, create.selectCount().from(TBook()).where(TBook_REC_VERSION().isNotNull()).fetchOne(0));
// Version and timestamp shouldn't change when there are constraint violations
// -------------------------
B book1 = create.newRecord(TBook());
book1.setValue(TBook_ID(), 5);
try {
book1.store();
fail();
}
catch (DataAccessException expected) {}
if (t) assertNull(book1.getValue(TBook_REC_TIMESTAMP()));
if (v) assertNull(book1.getValue(TBook_REC_VERSION()));
// Test non-nullability of version and timestamp for new books
// -------------------------
B book2 = newBook(5);
assertEquals(1, book2.store());
Timestamp t2 = t ? book2.getValue(TBook_REC_TIMESTAMP()) : null;
Integer v2 = v ? book2.getValue(TBook_REC_VERSION()) : null;
if (t) assertNotNull(t2);
if (v) assertNotNull(v2);
// Test immutability of version and timestamp for non-stored books
// -------------------------
book2.refresh();
assertEquals(0, book2.store());
assertEquals(t2, t ? book2.getValue(TBook_REC_TIMESTAMP()) : null);
assertEquals(v2, v ? book2.getValue(TBook_REC_VERSION()) : null);
// Test resetting of version and timestamp for copied books
// -------------------------
B book3 = book2.copy();
book3.setValue(TBook_ID(), 6);
assertEquals(1, book3.store());
Timestamp t3 = t ? book3.getValue(TBook_REC_TIMESTAMP()) : null;
Integer v3 = v ? book3.getValue(TBook_REC_VERSION()) : null;
if (t) assertNotNull(t3);
if (v) assertNotNull(v3);
if (t && t2 != null) assertFalse(t2.equals(t3));
if (v && v2 != null) assertFalse(v2.equals(v3));
// Check if updating all records will lead to updated version and timestamp values
// -------------------------
// BOOK[ID=4] has version and timestamp set to null
B book4 = create().fetchOne(TBook(), TBook_ID().equal(4));
book4.setValue(TBook_TITLE(), "Blah");
assertEquals(1, book4.store());
Timestamp t4 = t ? book4.getValue(TBook_REC_TIMESTAMP()) : null;
Integer v4 = v ? book4.getValue(TBook_REC_VERSION()) : null;
if (t) assertNotNull(t4);
if (v) assertEquals(Integer.valueOf(1), v4);
book4.refresh();
if (t) assertEquals(t4, book4.getValue(TBook_REC_TIMESTAMP()));
if (v) assertEquals(v4, book4.getValue(TBook_REC_VERSION()));
// Increment both values
book4.setValue(TBook_TITLE(), "Blah 1");
assertEquals(1, book4.store());
Timestamp t4a = t ? book4.getValue(TBook_REC_TIMESTAMP()) : null;
Integer v4a = v ? book4.getValue(TBook_REC_VERSION()) : null;
if (t) assertNotNull(t4a);
if (v) assertEquals(Integer.valueOf(2), v4a);
book4.refresh();
if (t) assertEquals(t4a, book4.getValue(TBook_REC_TIMESTAMP()));
if (v) assertEquals(v4a, book4.getValue(TBook_REC_VERSION()));
// Don't change the book
assertEquals(0, book4.store());
if (t) assertEquals(t4a, book4.getValue(TBook_REC_TIMESTAMP()));
if (v) assertEquals(v4a, book4.getValue(TBook_REC_VERSION()));
book4.refresh();
if (t) assertEquals(t4a, book4.getValue(TBook_REC_TIMESTAMP()));
if (v) assertEquals(v4a, book4.getValue(TBook_REC_VERSION()));
}
@SuppressWarnings("unchecked")
@Test
public void testNonUpdatables() throws Exception {
@ -579,17 +673,17 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658,
}
@Test
public void testStoreLocked() throws Exception {
public void testStoreWithOptimisticLock() throws Exception {
jOOQAbstractTest.reset = false;
testStoreLocked0(TBook(), TBook_ID(), TBook_TITLE());
testStoreWithOptimisticLock0(TBook(), TBook_ID(), TBook_TITLE());
// Avoid referential integrity problems for subsequent test
create().executeDelete(TBook());
testStoreLocked0(TAuthor(), TAuthor_ID(), TAuthor_LAST_NAME());
testStoreWithOptimisticLock0(TAuthor(), TAuthor_ID(), TAuthor_LAST_NAME());
}
private <R extends UpdatableRecord<R>> void testStoreLocked0(
private <R extends UpdatableRecord<R>> void testStoreWithOptimisticLock0(
UpdatableTable<R> table, TableField<R, Integer> id, TableField<R, String> string) throws Exception {
Factory create = create(new Settings().withExecuteWithOptimisticLocking(true));
@ -649,8 +743,10 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658,
}
catch (DataChangedException expected) {}
// Restore the book, then it should work
// Restore the book, refresh the copy, then it should work
assertEquals(1, record4.store());
record5.refresh();
record5.setValue(string, "New Title 5");
assertEquals(1, record5.store());
assertEquals("New Title 5", create.fetchOne(table, id.equal(1)).getValue(string));

View File

@ -212,6 +212,8 @@ CREATE TABLE t_book (
CONTENT_TEXT CLOB,
CONTENT_PDF BLOB,
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),

View File

@ -8,7 +8,7 @@ package org.jooq.test.derby.generatedclasses.tables;
*/
public class TBook extends org.jooq.impl.UpdatableTableImpl<org.jooq.test.derby.generatedclasses.tables.records.TBookRecord> {
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<org.jooq.test.derby.
*/
public static final org.jooq.TableField<org.jooq.test.derby.generatedclasses.tables.records.TBookRecord, byte[]> CONTENT_PDF = createField("CONTENT_PDF", org.jooq.impl.SQLDataType.BLOB, T_BOOK);
/**
* The table column <code>TEST.T_BOOK.REC_TIMESTAMP</code>
*/
public static final org.jooq.TableField<org.jooq.test.derby.generatedclasses.tables.records.TBookRecord, java.sql.Timestamp> 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<org.jooq.test.derby.
public java.util.List<org.jooq.ForeignKey<org.jooq.test.derby.generatedclasses.tables.records.TBookRecord, ?>> getReferences() {
return java.util.Arrays.<org.jooq.ForeignKey<org.jooq.test.derby.generatedclasses.tables.records.TBookRecord, ?>>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<org.jooq.test.derby.generatedclasses.tables.records.TBookRecord, java.sql.Timestamp> getRecordTimestamp() {
return org.jooq.test.derby.generatedclasses.tables.TBook.REC_TIMESTAMP;
}
}

View File

@ -8,7 +8,7 @@ package org.jooq.test.derby.generatedclasses.tables;
*/
public class VBook extends org.jooq.impl.TableImpl<org.jooq.test.derby.generatedclasses.tables.records.VBookRecord> {
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<org.jooq.test.derby.generated
*/
public static final org.jooq.TableField<org.jooq.test.derby.generatedclasses.tables.records.VBookRecord, byte[]> CONTENT_PDF = createField("CONTENT_PDF", org.jooq.impl.SQLDataType.BLOB, V_BOOK);
/**
* The table column <code>TEST.V_BOOK.REC_TIMESTAMP</code>
*/
public static final org.jooq.TableField<org.jooq.test.derby.generatedclasses.tables.records.VBookRecord, java.sql.Timestamp> REC_TIMESTAMP = createField("REC_TIMESTAMP", org.jooq.impl.SQLDataType.TIMESTAMP, V_BOOK);
/**
* No further instances allowed
*/

View File

@ -8,7 +8,7 @@ package org.jooq.test.derby.generatedclasses.tables.records;
*/
public class TBookRecord extends org.jooq.impl.UpdatableRecordImpl<org.jooq.test.derby.generatedclasses.tables.records.TBookRecord> {
private static final long serialVersionUID = 859890348;
private static final long serialVersionUID = -2014054404;
/**
* The table column <code>TEST.T_BOOK.ID</code>
@ -246,6 +246,20 @@ public class TBookRecord extends org.jooq.impl.UpdatableRecordImpl<org.jooq.test
return getValue(org.jooq.test.derby.generatedclasses.tables.TBook.CONTENT_PDF);
}
/**
* The table column <code>TEST.T_BOOK.REC_TIMESTAMP</code>
*/
public void setRecTimestamp(java.sql.Timestamp value) {
setValue(org.jooq.test.derby.generatedclasses.tables.TBook.REC_TIMESTAMP, value);
}
/**
* The table column <code>TEST.T_BOOK.REC_TIMESTAMP</code>
*/
public java.sql.Timestamp getRecTimestamp() {
return getValue(org.jooq.test.derby.generatedclasses.tables.TBook.REC_TIMESTAMP);
}
/**
* Create a detached TBookRecord
*/

View File

@ -8,7 +8,7 @@ package org.jooq.test.derby.generatedclasses.tables.records;
*/
public class VBookRecord extends org.jooq.impl.TableRecordImpl<org.jooq.test.derby.generatedclasses.tables.records.VBookRecord> {
private static final long serialVersionUID = 1509266158;
private static final long serialVersionUID = 1642491642;
/**
* The table column <code>TEST.V_BOOK.ID</code>
@ -136,6 +136,20 @@ public class VBookRecord extends org.jooq.impl.TableRecordImpl<org.jooq.test.der
return getValue(org.jooq.test.derby.generatedclasses.tables.VBook.CONTENT_PDF);
}
/**
* The table column <code>TEST.V_BOOK.REC_TIMESTAMP</code>
*/
public void setRecTimestamp(java.sql.Timestamp value) {
setValue(org.jooq.test.derby.generatedclasses.tables.VBook.REC_TIMESTAMP, value);
}
/**
* The table column <code>TEST.V_BOOK.REC_TIMESTAMP</code>
*/
public java.sql.Timestamp getRecTimestamp() {
return getValue(org.jooq.test.derby.generatedclasses.tables.VBook.REC_TIMESTAMP);
}
/**
* Create a detached VBookRecord
*/

View File

@ -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

View File

@ -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),

View File

@ -10,7 +10,7 @@ package org.jooq.test.h2.generatedclasses.tables;
*/
public class TBook extends org.jooq.impl.UpdatableTableImpl<org.jooq.test.h2.generatedclasses.tables.records.TBookRecord> {
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<org.jooq.test.h2.gen
*/
public static final org.jooq.TableField<org.jooq.test.h2.generatedclasses.tables.records.TBookRecord, byte[]> CONTENT_PDF = createField("CONTENT_PDF", org.jooq.impl.SQLDataType.BLOB, T_BOOK);
/**
* The table column <code>PUBLIC.T_BOOK.REC_VERSION</code>
*/
public static final org.jooq.TableField<org.jooq.test.h2.generatedclasses.tables.records.TBookRecord, java.lang.Integer> REC_VERSION = createField("REC_VERSION", org.jooq.impl.SQLDataType.INTEGER, T_BOOK);
/**
* The table column <code>PUBLIC.T_BOOK.REC_TIMESTAMP</code>
*/
public static final org.jooq.TableField<org.jooq.test.h2.generatedclasses.tables.records.TBookRecord, java.sql.Timestamp> 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<org.jooq.test.h2.gen
public java.util.List<org.jooq.ForeignKey<org.jooq.test.h2.generatedclasses.tables.records.TBookRecord, ?>> getReferences() {
return java.util.Arrays.<org.jooq.ForeignKey<org.jooq.test.h2.generatedclasses.tables.records.TBookRecord, ?>>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<org.jooq.test.h2.generatedclasses.tables.records.TBookRecord, java.lang.Integer> getRecordVersion() {
return org.jooq.test.h2.generatedclasses.tables.TBook.REC_VERSION;
}
@Override
public org.jooq.TableField<org.jooq.test.h2.generatedclasses.tables.records.TBookRecord, java.sql.Timestamp> getRecordTimestamp() {
return org.jooq.test.h2.generatedclasses.tables.TBook.REC_TIMESTAMP;
}
}

View File

@ -8,7 +8,7 @@ package org.jooq.test.h2.generatedclasses.tables;
*/
public class VBook extends org.jooq.impl.TableImpl<org.jooq.test.h2.generatedclasses.tables.records.VBookRecord> {
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<org.jooq.test.h2.generatedcla
*/
public static final org.jooq.TableField<org.jooq.test.h2.generatedclasses.tables.records.VBookRecord, byte[]> CONTENT_PDF = createField("CONTENT_PDF", org.jooq.impl.SQLDataType.BLOB, V_BOOK);
/**
* The table column <code>PUBLIC.V_BOOK.REC_VERSION</code>
*/
public static final org.jooq.TableField<org.jooq.test.h2.generatedclasses.tables.records.VBookRecord, java.lang.Integer> REC_VERSION = createField("REC_VERSION", org.jooq.impl.SQLDataType.INTEGER, V_BOOK);
/**
* The table column <code>PUBLIC.V_BOOK.REC_TIMESTAMP</code>
*/
public static final org.jooq.TableField<org.jooq.test.h2.generatedclasses.tables.records.VBookRecord, java.sql.Timestamp> REC_TIMESTAMP = createField("REC_TIMESTAMP", org.jooq.impl.SQLDataType.TIMESTAMP, V_BOOK);
/**
* No further instances allowed
*/

View File

@ -98,4 +98,18 @@ public class TBookDao extends org.jooq.impl.DAOImpl<org.jooq.test.h2.generatedcl
public java.util.List<org.jooq.test.h2.generatedclasses.tables.pojos.TBook> fetchByContentPdf(byte[]... values) {
return fetch(org.jooq.test.h2.generatedclasses.tables.TBook.CONTENT_PDF, values);
}
/**
* Fetch records that have <code>REC_VERSION IN (values)</code>
*/
public java.util.List<org.jooq.test.h2.generatedclasses.tables.pojos.TBook> fetchByRecVersion(java.lang.Integer... values) {
return fetch(org.jooq.test.h2.generatedclasses.tables.TBook.REC_VERSION, values);
}
/**
* Fetch records that have <code>REC_TIMESTAMP IN (values)</code>
*/
public java.util.List<org.jooq.test.h2.generatedclasses.tables.pojos.TBook> fetchByRecTimestamp(java.sql.Timestamp... values) {
return fetch(org.jooq.test.h2.generatedclasses.tables.TBook.REC_TIMESTAMP, values);
}
}

View File

@ -139,4 +139,24 @@ public interface ITBook extends java.io.Serializable {
* Some binary content of the book
*/
public byte[] getContentPdf();
/**
* The table column <code>PUBLIC.T_BOOK.REC_VERSION</code>
*/
public void setRecVersion(java.lang.Integer value);
/**
* The table column <code>PUBLIC.T_BOOK.REC_VERSION</code>
*/
public java.lang.Integer getRecVersion();
/**
* The table column <code>PUBLIC.T_BOOK.REC_TIMESTAMP</code>
*/
public void setRecTimestamp(java.sql.Timestamp value);
/**
* The table column <code>PUBLIC.T_BOOK.REC_TIMESTAMP</code>
*/
public java.sql.Timestamp getRecTimestamp();
}

View File

@ -97,4 +97,24 @@ public interface IVBook extends java.io.Serializable {
* The table column <code>PUBLIC.V_BOOK.CONTENT_PDF</code>
*/
public byte[] getContentPdf();
/**
* The table column <code>PUBLIC.V_BOOK.REC_VERSION</code>
*/
public void setRecVersion(java.lang.Integer value);
/**
* The table column <code>PUBLIC.V_BOOK.REC_VERSION</code>
*/
public java.lang.Integer getRecVersion();
/**
* The table column <code>PUBLIC.V_BOOK.REC_TIMESTAMP</code>
*/
public void setRecTimestamp(java.sql.Timestamp value);
/**
* The table column <code>PUBLIC.V_BOOK.REC_TIMESTAMP</code>
*/
public java.sql.Timestamp getRecTimestamp();
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -10,7 +10,7 @@ package org.jooq.test.h2.generatedclasses.tables.records;
*/
public class TBookRecord extends org.jooq.impl.UpdatableRecordImpl<org.jooq.test.h2.generatedclasses.tables.records.TBookRecord> 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.UpdatableRecordImpl<org.jooq.test
return getValue(org.jooq.test.h2.generatedclasses.tables.TBook.CONTENT_PDF);
}
/**
* The table column <code>PUBLIC.T_BOOK.REC_VERSION</code>
*/
@Override
public void setRecVersion(java.lang.Integer value) {
setValue(org.jooq.test.h2.generatedclasses.tables.TBook.REC_VERSION, value);
}
/**
* The table column <code>PUBLIC.T_BOOK.REC_VERSION</code>
*/
@Override
public java.lang.Integer getRecVersion() {
return getValue(org.jooq.test.h2.generatedclasses.tables.TBook.REC_VERSION);
}
/**
* The table column <code>PUBLIC.T_BOOK.REC_TIMESTAMP</code>
*/
@Override
public void setRecTimestamp(java.sql.Timestamp value) {
setValue(org.jooq.test.h2.generatedclasses.tables.TBook.REC_TIMESTAMP, value);
}
/**
* The table column <code>PUBLIC.T_BOOK.REC_TIMESTAMP</code>
*/
@Override
public java.sql.Timestamp getRecTimestamp() {
return getValue(org.jooq.test.h2.generatedclasses.tables.TBook.REC_TIMESTAMP);
}
/**
* Create a detached TBookRecord
*/

View File

@ -8,7 +8,7 @@ package org.jooq.test.h2.generatedclasses.tables.records;
*/
public class VBookRecord extends org.jooq.impl.TableRecordImpl<org.jooq.test.h2.generatedclasses.tables.records.VBookRecord> implements org.jooq.test.h2.generatedclasses.tables.interfaces.IVBook {
private static final long serialVersionUID = 2107473895;
private static final long serialVersionUID = -1383413389;
/**
* The table column <code>PUBLIC.V_BOOK.ID</code>
@ -154,6 +154,38 @@ public class VBookRecord extends org.jooq.impl.TableRecordImpl<org.jooq.test.h2.
return getValue(org.jooq.test.h2.generatedclasses.tables.VBook.CONTENT_PDF);
}
/**
* The table column <code>PUBLIC.V_BOOK.REC_VERSION</code>
*/
@Override
public void setRecVersion(java.lang.Integer value) {
setValue(org.jooq.test.h2.generatedclasses.tables.VBook.REC_VERSION, value);
}
/**
* The table column <code>PUBLIC.V_BOOK.REC_VERSION</code>
*/
@Override
public java.lang.Integer getRecVersion() {
return getValue(org.jooq.test.h2.generatedclasses.tables.VBook.REC_VERSION);
}
/**
* The table column <code>PUBLIC.V_BOOK.REC_TIMESTAMP</code>
*/
@Override
public void setRecTimestamp(java.sql.Timestamp value) {
setValue(org.jooq.test.h2.generatedclasses.tables.VBook.REC_TIMESTAMP, value);
}
/**
* The table column <code>PUBLIC.V_BOOK.REC_TIMESTAMP</code>
*/
@Override
public java.sql.Timestamp getRecTimestamp() {
return getValue(org.jooq.test.h2.generatedclasses.tables.VBook.REC_TIMESTAMP);
}
/**
* Create a detached VBookRecord
*/

View File

@ -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

View File

@ -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),

View File

@ -8,7 +8,7 @@ package org.jooq.test.hsqldb.generatedclasses.tables;
*/
public class TBook extends org.jooq.impl.UpdatableTableImpl<org.jooq.test.hsqldb.generatedclasses.tables.records.TBookRecord> {
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<org.jooq.test.hsqldb
*/
public final org.jooq.TableField<org.jooq.test.hsqldb.generatedclasses.tables.records.TBookRecord, byte[]> CONTENT_PDF = createField("CONTENT_PDF", org.jooq.impl.SQLDataType.VARBINARY, this);
/**
* The table column <code>PUBLIC.T_BOOK.REC_VERSION</code>
*/
public final org.jooq.TableField<org.jooq.test.hsqldb.generatedclasses.tables.records.TBookRecord, java.lang.Integer> 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<org.jooq.test.hsqldb
return java.util.Arrays.<org.jooq.ForeignKey<org.jooq.test.hsqldb.generatedclasses.tables.records.TBookRecord, ?>>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<org.jooq.test.hsqldb.generatedclasses.tables.records.TBookRecord, java.lang.Integer> 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);

View File

@ -8,7 +8,7 @@ package org.jooq.test.hsqldb.generatedclasses.tables;
*/
public class VBook extends org.jooq.impl.TableImpl<org.jooq.test.hsqldb.generatedclasses.tables.records.VBookRecord> {
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<org.jooq.test.hsqldb.generate
*/
public final org.jooq.TableField<org.jooq.test.hsqldb.generatedclasses.tables.records.VBookRecord, byte[]> CONTENT_PDF = createField("CONTENT_PDF", org.jooq.impl.SQLDataType.VARBINARY, this);
/**
* The table column <code>PUBLIC.V_BOOK.REC_VERSION</code>
*/
public final org.jooq.TableField<org.jooq.test.hsqldb.generatedclasses.tables.records.VBookRecord, java.lang.Integer> REC_VERSION = createField("REC_VERSION", org.jooq.impl.SQLDataType.INTEGER, this);
public VBook() {
super("V_BOOK", org.jooq.test.hsqldb.generatedclasses.Public.PUBLIC);
}

View File

@ -137,4 +137,14 @@ public interface ITBook extends java.io.Serializable {
* The table column <code>PUBLIC.T_BOOK.CONTENT_PDF</code>
*/
public byte[] getContentPdf();
/**
* The table column <code>PUBLIC.T_BOOK.REC_VERSION</code>
*/
public void setRecVersion(java.lang.Integer value);
/**
* The table column <code>PUBLIC.T_BOOK.REC_VERSION</code>
*/
public java.lang.Integer getRecVersion();
}

View File

@ -97,4 +97,14 @@ public interface IVBook extends java.io.Serializable {
* The table column <code>PUBLIC.V_BOOK.CONTENT_PDF</code>
*/
public byte[] getContentPdf();
/**
* The table column <code>PUBLIC.V_BOOK.REC_VERSION</code>
*/
public void setRecVersion(java.lang.Integer value);
/**
* The table column <code>PUBLIC.V_BOOK.REC_VERSION</code>
*/
public java.lang.Integer getRecVersion();
}

View File

@ -8,7 +8,7 @@ package org.jooq.test.hsqldb.generatedclasses.tables.records;
*/
public class TBookRecord extends org.jooq.impl.UpdatableRecordImpl<org.jooq.test.hsqldb.generatedclasses.tables.records.TBookRecord> implements org.jooq.test.hsqldb.generatedclasses.tables.interfaces.ITBook {
private static final long serialVersionUID = -1623058826;
private static final long serialVersionUID = -454420402;
/**
* The table column <code>PUBLIC.T_BOOK.ID</code>
@ -264,6 +264,22 @@ public class TBookRecord extends org.jooq.impl.UpdatableRecordImpl<org.jooq.test
return getValue(org.jooq.test.hsqldb.generatedclasses.tables.TBook.T_BOOK.CONTENT_PDF);
}
/**
* The table column <code>PUBLIC.T_BOOK.REC_VERSION</code>
*/
@Override
public void setRecVersion(java.lang.Integer value) {
setValue(org.jooq.test.hsqldb.generatedclasses.tables.TBook.T_BOOK.REC_VERSION, value);
}
/**
* The table column <code>PUBLIC.T_BOOK.REC_VERSION</code>
*/
@Override
public java.lang.Integer getRecVersion() {
return getValue(org.jooq.test.hsqldb.generatedclasses.tables.TBook.T_BOOK.REC_VERSION);
}
/**
* Create a detached TBookRecord
*/

View File

@ -8,7 +8,7 @@ package org.jooq.test.hsqldb.generatedclasses.tables.records;
*/
public class VBookRecord extends org.jooq.impl.TableRecordImpl<org.jooq.test.hsqldb.generatedclasses.tables.records.VBookRecord> implements org.jooq.test.hsqldb.generatedclasses.tables.interfaces.IVBook {
private static final long serialVersionUID = 50732295;
private static final long serialVersionUID = -374904553;
/**
* The table column <code>PUBLIC.V_BOOK.ID</code>
@ -154,6 +154,22 @@ public class VBookRecord extends org.jooq.impl.TableRecordImpl<org.jooq.test.hsq
return getValue(org.jooq.test.hsqldb.generatedclasses.tables.VBook.V_BOOK.CONTENT_PDF);
}
/**
* The table column <code>PUBLIC.V_BOOK.REC_VERSION</code>
*/
@Override
public void setRecVersion(java.lang.Integer value) {
setValue(org.jooq.test.hsqldb.generatedclasses.tables.VBook.V_BOOK.REC_VERSION, value);
}
/**
* The table column <code>PUBLIC.V_BOOK.REC_VERSION</code>
*/
@Override
public java.lang.Integer getRecVersion() {
return getValue(org.jooq.test.hsqldb.generatedclasses.tables.VBook.V_BOOK.REC_VERSION);
}
/**
* Create a detached VBookRecord
*/

View File

@ -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'),

View File

@ -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<TBookRecord, Integer> TBook_REC_VERSION() {
return super.TBook_REC_VERSION();
}
@Override
protected TableField<TBookRecord, Timestamp> TBook_REC_TIMESTAMP() {
return TBook.REC_TIMESTAMP;
}
@Override
protected UpdatableTable<TBookStoreRecord> TBookStore() {
return TBookStore.T_BOOK_STORE;

View File

@ -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<TBookRecord, Integer> TBook_REC_VERSION() {
return TBook.REC_VERSION;
}
@Override
protected TableField<TBookRecord, Timestamp> TBook_REC_TIMESTAMP() {
return TBook.REC_TIMESTAMP;
}
@Override
protected UpdatableTable<TBookStoreRecord> TBookStore() {
return TBookStore.T_BOOK_STORE;

View File

@ -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<TBookRecord, Integer> TBook_REC_VERSION() {
return T_BOOK.REC_VERSION;
}
@Override
protected TableField<TBookRecord, Timestamp> TBook_REC_TIMESTAMP() {
return super.TBook_REC_TIMESTAMP();
}
@Override
protected UpdatableTable<TBookStoreRecord> TBookStore() {
return T_BOOK_STORE;

View File

@ -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<R extends TableRecord<R>> extends Record {
* Depending on the state of the provided keys' value, an
* <code>INSERT</code> or an <code>UPDATE</code> statement is executed.
* <p>
* <h3>Statement type</h3>
* <p>
* <ul>
* <li>If this record was created by client code, an <code>INSERT</code>
* statement is executed</li>
@ -72,11 +75,62 @@ public interface TableRecord<R extends TableRecord<R>> extends Record {
* record.</li>
* <li>If this record was loaded by jOOQ, and the provided keys' value was
* not changed, an <code>UPDATE</code> statement is executed.</li>
* <li>If an <code>UPDATE</code> statement is executed and
* </ul>
* <p>
* In either statement type, only those fields are inserted/updated, which
* had been explicitly set by client code, in order to allow for
* <code>DEFAULT</code> values to be applied by the underlying RDBMS. If no
* fields were modified, neither an <code>UPDATE</code> nor an
* <code>INSERT</code> will be executed.
* <h3>Automatic value generation</h3>
* <p>
* <ul>
* <li><strong>IDENTITY columns</strong>
* <p>
* If there is an <code>IDENTITY</code> column defined on the record's
* underlying table (see {@link Table#getIdentity()}), then the
* auto-generated <code>IDENTITY</code> value is refreshed automatically on
* <code>INSERT</code>'s. Refreshing is done using
* {@link Statement#getGeneratedKeys()}, where this is supported by the JDBC
* driver. See also {@link InsertQuery#getReturnedRecord()} for more details
* </li>
* <li><strong>VERSION and TIMESTAMP columns</strong>
* <p>
* 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 <code>INSERT</code> or <code>UPDATE</code> 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.
* <p>
* Should you want to circumvent jOOQ-generated updates to these columns,
* you can render an <code>INSERT</code> or <code>UPDATE</code> statement
* manually using the various {@link Factory#insertInto(Table)},
* {@link Factory#update(Table)} methods.</li>
* </ul>
* <h3>Optimistic locking</h3>
* <p>
* If an <code>UPDATE</code> statement is executed and
* {@link Settings#isExecuteWithOptimisticLocking()} is set to
* <code>true</code>, 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:
* <ul>
* <li><strong>With VERSION and/or TIMESTAMP columns configured</strong>
* <p>
* 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
* <code>WHERE</code> clause of the executed <code>DELETE</code> statement.</li>
* <li><strong>Without any specific column configurations</strong>
* <p>
* In order to compare this record with the latest state, the database
* record will be locked pessimistically using a
* <code>SELECT .. FOR UPDATE</code> statement. Not all databases support
* the <code>FOR UPDATE</code> clause natively. Namely, the following
* databases will show slightly different behaviour:
@ -92,12 +146,7 @@ public interface TableRecord<R extends TableRecord<R>> extends Record {
* <p>
* See {@link LockProvider#setForUpdate(boolean)} for more details</li>
* </ul>
* <p>
* In either statement, only those fields are inserted/updated, which had
* been explicitly set by client code, in order to allow for
* <code>DEFAULT</code> values to be applied by the underlying RDBMS. If no
* fields were modified, neither an <code>UPDATE</code> nor an
* <code>INSERT</code> will be executed.
* <h3>Statement examples</h3>
* <p>
* Possible statements are
* <ul>
@ -109,15 +158,9 @@ public interface TableRecord<R extends TableRecord<R>> extends Record {
* <code><pre>
* UPDATE [table]
* SET [modified fields = modified values, excluding keys]
* WHERE [key fields = key values]</pre></code></li>
* WHERE [key fields = key values]
* AND [version/timestamp fields = version/timestamp values]</pre></code></li>
* </ul>
* <p>
* If there is an <code>IDENTITY</code> column defined on the record's
* underlying table (see {@link Table#getIdentity()}), then the
* auto-generated <code>IDENTITY</code> value is refreshed automatically on
* <code>INSERT</code>'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
* <code>INSERT</code> or <code>UPDATE</code> statement. If an
@ -135,14 +178,26 @@ public interface TableRecord<R extends TableRecord<R>> extends Record {
* Deletes this record from the database, based on the value of the provided
* keys.
* <p>
* The executed statement is <code><pre>
* DELETE FROM [table]
* WHERE [key fields = key values]</pre></code>
* <h3>Optimistic locking</h3>
* <p>
* If {@link Settings#isExecuteWithOptimisticLocking()} is set to
* If a <code>DELETE</code> statement is executed and
* {@link Settings#isExecuteWithOptimisticLocking()} is set to
* <code>true</code>, 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:
* <ul>
* <li><strong>With VERSION and/or TIMESTAMP columns configured</strong>
* <p>
* 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
* <code>WHERE</code> clause of the executed <code>DELETE</code> statement.</li>
* <li><strong>Without any specific column configurations</strong>
* <p>
* In order to compare this record with the latest state, the database
* record will be locked pessimistically using a
* <code>SELECT .. FOR UPDATE</code> statement. Not all databases support
* the <code>FOR UPDATE</code> clause natively. Namely, the following
* databases will show slightly different behaviour:
@ -153,10 +208,17 @@ public interface TableRecord<R extends TableRecord<R>> extends Record {
* {@link ResultSet#CONCUR_UPDATABLE}.</li>
* <li> {@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 <code>UPDATE</code></li>
* checking of database record state and the actual <code>DELETE</code></li>
* </ul>
* <p>
* See {@link LockProvider#setForUpdate(boolean)} for more details
* See {@link LockProvider#setForUpdate(boolean)} for more details</li>
* </ul>
* <h3>Statement examples</h3>
* <p>
* The executed statement is <code><pre>
* DELETE FROM [table]
* WHERE [key fields = key values]
* AND [version/timestamp fields = version/timestamp values]</pre></code>
*
* @param keys The key fields for the <code>DELETE</code> statement's
* <code>WHERE</code> clause.

View File

@ -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<R extends UpdatableRecord<R>> extends Updatable
* Depending on the state of the primary key's or main unique key's value,
* an <code>INSERT</code> or an <code>UPDATE</code> statement is executed.
* <p>
* <h3>Statement type</h3>
* <p>
* <ul>
* <li>If this record was created by client code, an <code>INSERT</code>
* statement is executed</li>
@ -98,11 +101,62 @@ public interface UpdatableRecord<R extends UpdatableRecord<R>> extends Updatable
* record.</li>
* <li>If this record was loaded by jOOQ, and the primary key value was not
* changed, an <code>UPDATE</code> statement is executed.</li>
* <li>If an <code>UPDATE</code> statement is executed and
* </ul>
* <p>
* In either statement type, only those fields are inserted/updated, which
* had been explicitly set by client code, in order to allow for
* <code>DEFAULT</code> values to be applied by the underlying RDBMS. If no
* fields were modified, neither an <code>UPDATE</code> nor an
* <code>INSERT</code> will be executed.
* <h3>Automatic value generation</h3>
* <p>
* <ul>
* <li><strong>IDENTITY columns</strong>
* <p>
* If there is an <code>IDENTITY</code> column defined on the record's
* underlying table (see {@link Table#getIdentity()}), then the
* auto-generated <code>IDENTITY</code> value is refreshed automatically on
* <code>INSERT</code>'s. Refreshing is done using
* {@link Statement#getGeneratedKeys()}, where this is supported by the JDBC
* driver. See also {@link InsertQuery#getReturnedRecord()} for more details
* </li>
* <li><strong>VERSION and TIMESTAMP columns</strong>
* <p>
* 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 <code>INSERT</code> or <code>UPDATE</code> 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.
* <p>
* Should you want to circumvent jOOQ-generated updates to these columns,
* you can render an <code>INSERT</code> or <code>UPDATE</code> statement
* manually using the various {@link Factory#insertInto(Table)},
* {@link Factory#update(Table)} methods.</li>
* </ul>
* <h3>Optimistic locking</h3>
* <p>
* If an <code>UPDATE</code> statement is executed and
* {@link Settings#isExecuteWithOptimisticLocking()} is set to
* <code>true</code>, 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:
* <ul>
* <li><strong>With VERSION and/or TIMESTAMP columns configured</strong>
* <p>
* 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
* <code>WHERE</code> clause of the executed <code>DELETE</code> statement.</li>
* <li><strong>Without any specific column configurations</strong>
* <p>
* In order to compare this record with the latest state, the database
* record will be locked pessimistically using a
* <code>SELECT .. FOR UPDATE</code> statement. Not all databases support
* the <code>FOR UPDATE</code> clause natively. Namely, the following
* databases will show slightly different behaviour:
@ -118,35 +172,22 @@ public interface UpdatableRecord<R extends UpdatableRecord<R>> extends Updatable
* <p>
* See {@link LockProvider#setForUpdate(boolean)} for more details</li>
* </ul>
* <h3>Statement examples</h3>
* <p>
* In either <code>INSERT</code> or <code>UPDATE</code> statement, only
* those fields are inserted/updated, which had been explicitly set by
* client code, in order to allow for <code>DEFAULT</code> values to be
* applied by the underlying RDBMS. If no fields were modified, neither an
* <code>UPDATE</code> nor an <code>INSERT</code> will be executed. Possible
* statements are
* Possible statements are
* <ul>
* <li>
* <code><pre>
* INSERT INTO [table] ([modified fields, including main key])
* VALUES ([modified values, including main key])</pre></code></li>
* INSERT INTO [table] ([modified fields, including keys])
* VALUES ([modified values, including keys])</pre></code></li>
* <li>
* <code><pre>
* UPDATE [table]
* SET [modified fields = modified values, excluding main key]
* WHERE [main key fields = main key values]</pre></code></li>
* SET [modified fields = modified values, excluding keys]
* WHERE [key fields = key values]
* AND [version/timestamp fields = version/timestamp values]</pre></code></li>
* </ul>
* <p>
* If there is an <code>IDENTITY</code> column defined on the record's
* underlying table (see {@link Table#getIdentity()}), then the
* auto-generated <code>IDENTITY</code> value is refreshed automatically on
* <code>INSERT</code>'s. Similarly, all members of the primary key are
* refreshed, to ensure that trigger-generated values will be available
* after <code>INSERT</code>. Normally, primary key and
* <code>IDENTITY</code> columns coincide, but this doesn't have to be.
* Refreshing is done using {@link Statement#getGeneratedKeys()}, where this
* is supported by the JDBC driver.
* <p>
* This is in fact the same as calling
* <code>store(getTable().getMainKey().getFieldsArray())</code>
*
@ -163,14 +204,26 @@ public interface UpdatableRecord<R extends UpdatableRecord<R>> extends Updatable
* Deletes this record from the database, based on the value of the primary
* key or main unique key.
* <p>
* The executed statement is <code><pre>
* DELETE FROM [table]
* WHERE [main key fields = main key values]</pre></code>
* <h3>Optimistic locking</h3>
* <p>
* If {@link Settings#isExecuteWithOptimisticLocking()} is set to
* If a <code>DELETE</code> statement is executed and
* {@link Settings#isExecuteWithOptimisticLocking()} is set to
* <code>true</code>, 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:
* <ul>
* <li><strong>With VERSION and/or TIMESTAMP columns configured</strong>
* <p>
* 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
* <code>WHERE</code> clause of the executed <code>DELETE</code> statement.</li>
* <li><strong>Without any specific column configurations</strong>
* <p>
* In order to compare this record with the latest state, the database
* record will be locked pessimistically using a
* <code>SELECT .. FOR UPDATE</code> statement. Not all databases support
* the <code>FOR UPDATE</code> clause natively. Namely, the following
* databases will show slightly different behaviour:
@ -181,10 +234,17 @@ public interface UpdatableRecord<R extends UpdatableRecord<R>> extends Updatable
* {@link ResultSet#CONCUR_UPDATABLE}.</li>
* <li> {@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 <code>UPDATE</code></li>
* checking of database record state and the actual <code>DELETE</code></li>
* </ul>
* <p>
* See {@link LockProvider#setForUpdate(boolean)} for more details
* See {@link LockProvider#setForUpdate(boolean)} for more details</li>
* </ul>
* <h3>Statement examples</h3>
* <p>
* The executed statement is <code><pre>
* DELETE FROM [table]
* WHERE [key fields = key values]
* AND [version/timestamp fields = version/timestamp values]</pre></code>
* <p>
* This is in fact the same as calling
* <code>delete(getTable().getMainKey().getFieldsArray())</code>

View File

@ -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<R extends Record> extends Updatable<R>, Table<R>
*/
<O extends Record> List<ForeignKey<O, R>> getReferencesFrom(Table<O> other);
/**
* A "version" field holding record version information used for optimistic
* locking
* <p>
* 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 <code>UPDATE</code> or
* <code>DELETE</code> statement if tables provide a "version" or
* "timestamp" field, or in two steps using an additional
* <code>SELECT .. FOR UPDATE</code> statement otherwise.
* <p>
* 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 <code>null</code>, if this table has no
* "version" field.
* @see #getRecordTimestamp()
* @see UpdatableRecord#store()
* @see UpdatableRecord#delete()
* @see Settings#isExecuteWithOptimisticLocking()
*/
TableField<R, ? extends Number> getRecordVersion();
/**
* A "timestamp" field holding record timestamp information used for
* optimistic locking
* <p>
* 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 <code>UPDATE</code> or
* <code>DELETE</code> statement if tables provide a "version" or
* "timestamp" field, or in two steps using an additional
* <code>SELECT .. FOR UPDATE</code> statement otherwise.
* <p>
* 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 <code>null</code>, if this table has no
* "timestamp" field.
* @see #getRecordVersion()
* @see UpdatableRecord#store()
* @see UpdatableRecord#delete()
* @see Settings#isExecuteWithOptimisticLocking()
*/
TableField<R, ? extends java.util.Date> getRecordTimestamp();
}

View File

@ -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 {

View File

@ -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<R extends TableRecord<R>> extends AbstractRecord im
return (Table<R>) getFieldProvider();
}
/**
* Subclasses may override this method to provide an identity
*/
Collection<Field<?>> getReturning() {
Collection<Field<?>> result = new LinkedHashSet<Field<?>>();
Identity<R, ?> 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<R> getUpdatableTable() {
return (UpdatableTable<R>) getTable();
}
@Override
public final int storeUsing(TableField<R, ?>... keys) {
boolean checkIfChanged = isExecuteWithOptimisticLocking();
boolean executeUpdate = false;
for (TableField<R, ?> field : keys) {
@ -116,7 +139,7 @@ public class TableRecordImpl<R extends TableRecord<R>> 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<R extends TableRecord<R>> 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<R extends TableRecord<R>> extends AbstractRecord im
return false;
}
@SuppressWarnings("unchecked")
private final int storeInsert() {
Factory create = create();
InsertQuery<R> insert = create.insertQuery(getTable());
addChangedValues(insert);
for (Field<?> field : getFields()) {
if (getValue0(field).isChanged()) {
addValue(insert, (TableField<R, ?>) 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<R extends TableRecord<R>> 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<Object>(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<Object>(insert.getReturnedRecord().getValue(field)));
}
}
}
}
@ -170,51 +201,115 @@ public class TableRecordImpl<R extends TableRecord<R>> extends AbstractRecord im
return result;
}
/**
* Subclasses may override this method to provide an identity
*/
Collection<Field<?>> getReturning() {
Collection<Field<?>> result = new LinkedHashSet<Field<?>>();
Identity<R, ?> identity = getTable().getIdentity();
if (identity != null) {
result.add(identity.getField());
}
return result;
}
@SuppressWarnings("unchecked")
private final int storeUpdate(TableField<R, ?>[] keys, boolean checkIfChanged) {
private final int storeUpdate(TableField<R, ?>[] keys) {
UpdateQuery<R> update = create().updateQuery(getTable());
addChangedValues(update);
addConditions(update, keys);
for (Field<?> field : getFields()) {
if (getValue0(field).isChanged()) {
addValue(update, (TableField<R, ?>) 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<R, ?>[] keys) {
SimpleSelectQuery<R> select = create().selectQuery(getTable());
private final void addConditionForVersionAndTimestamp(ConditionProvider query) {
TableField<R, ?> v = getUpdatableTable().getRecordVersion();
TableField<R, ?> 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<R> table = (UpdatableTable<R>) getTable();
return table.getRecordTimestamp() != null || table.getRecordVersion() != null;
}
return false;
}
@Override
public final int deleteUsing(TableField<R, ?>... keys) {
try {
DeleteQuery<R> 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<R, ?>... keys) {
SimpleSelectQuery<R> 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<R, ?>[] keys) {
SimpleSelectQuery<R> 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<R extends TableRecord<R>> extends AbstractRecord im
}
}
@Override
public final int deleteUsing(TableField<R, ?>... 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<R> 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<R, ?>... keys) {
SimpleSelectQuery<R> 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<R extends TableRecord<R>> extends AbstractRecord im
}
/**
* Extracted method to ensure generic type safety.
* Add primary key conditions to a query
*/
private final void addConditions(ConditionProvider query, TableField<R, ?>[] keys) {
for (Field<?> field : keys) {
addCondition(query, field);
}
}
/**
* Add a field condition to a query
*/
private final <T> void addCondition(ConditionProvider provider, Field<T> field) {
provider.addConditions(field.equal(getValue(field)));
}
/**
* Set all changed values of this record to a store query
*/
private final void addChangedValues(StoreQuery<R> query) {
for (Field<?> field : getFields()) {
if (getValue0(field).isChanged()) {
addValue(query, field);
}
}
}
/**
* Extracted method to ensure generic type safety.
*/
private final <T> void addValue(StoreQuery<?> store, Field<T> field) {
store.addValue(field, val(getValue(field), field));
addValue(store, field, getValue(field));
}
/**
* Extracted method to ensure generic type safety.
*/
private final <T> void addValue(StoreQuery<?> store, Field<T> 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<R, ? extends java.util.Date> 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<R, ? extends Number> 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<R, ?> field = getUpdatableTable().getRecordVersion();
setValue0(field, new Value<Object>(field.getDataType().convert(version)));
}
if (timestamp != null) {
TableField<R, ?> field = getUpdatableTable().getRecordTimestamp();
setValue0(field, new Value<Object>(field.getDataType().convert(timestamp)));
}
}
}

View File

@ -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<R extends Record> extends TableImpl<R> implement
return Collections.emptyList();
}
/**
* {@inheritDoc}
* <p>
* Subclasses may override this method
*/
@Override
public TableField<R, ? extends Number> getRecordVersion() {
return null;
}
/**
* {@inheritDoc}
* <p>
* Subclasses may override this method
*/
@Override
public TableField<R, ? extends java.util.Date> getRecordTimestamp() {
return null;
}
@Override
public final <O extends Record> List<ForeignKey<O, R>> getReferencesFrom(Table<O> other) {
return other.getReferencesTo(this);