[#2764] Let CRUD operations be able to perform UPDATEs on primary keys
This commit is contained in:
parent
d8180e98e5
commit
d3ea56750e
@ -35,6 +35,7 @@
|
||||
*/
|
||||
package org.jooq.test._.testcases;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
@ -776,4 +777,70 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, UU, I, IPK, T7
|
||||
}
|
||||
catch (DataChangedException expected) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdatablesWithUpdatablePK() throws Exception {
|
||||
DSLContext create = create();
|
||||
create.configuration().settings().setUpdatablePrimaryKeys(true);
|
||||
|
||||
// Create and insert
|
||||
T639 r1 = create.newRecord(T639());
|
||||
r1.setValue(T639_ID(), 1);
|
||||
assertEquals(1, (int) r1.getValue(T639_ID()));
|
||||
assertNull(r1.original(T639_ID()));
|
||||
assertTrue(r1.changed(T639_ID()));
|
||||
assertEquals(1, r1.store());
|
||||
assertEquals(1, (int) r1.getValue(T639_ID()));
|
||||
assertEquals(1, (int) r1.original(T639_ID()));
|
||||
assertFalse(r1.changed(T639_ID()));
|
||||
assertEquals(1, (int) create.select(T639_ID()).from(T639()).fetchOne().value1());
|
||||
|
||||
// Refresh will not consider the new PK value
|
||||
r1.setValue(T639_ID(), 2);
|
||||
assertEquals(2, (int) r1.getValue(T639_ID()));
|
||||
assertEquals(1, (int) r1.original(T639_ID()));
|
||||
assertTrue(r1.changed(T639_ID()));
|
||||
r1.refresh();
|
||||
assertEquals(1, (int) r1.getValue(T639_ID()));
|
||||
assertEquals(1, (int) r1.original(T639_ID()));
|
||||
assertFalse(r1.changed(T639_ID()));
|
||||
|
||||
// Update with store() will update to the new PK value
|
||||
r1.setValue(T639_ID(), 2);
|
||||
assertEquals(1, r1.store());
|
||||
assertEquals(2, (int) r1.getValue(T639_ID()));
|
||||
assertEquals(2, (int) r1.original(T639_ID()));
|
||||
assertFalse(r1.changed(T639_ID()));
|
||||
assertEquals(2, (int) create.select(T639_ID()).from(T639()).fetchOne().value1());
|
||||
|
||||
// Force update with update() will update to the new PK value
|
||||
r1.setValue(T639_ID(), 3);
|
||||
assertEquals(1, r1.update());
|
||||
assertEquals(3, (int) r1.getValue(T639_ID()));
|
||||
assertEquals(3, (int) r1.original(T639_ID()));
|
||||
assertFalse(r1.changed(T639_ID()));
|
||||
assertEquals(3, (int) create.select(T639_ID()).from(T639()).fetchOne().value1());
|
||||
|
||||
// Force insert with insert() will create a new record
|
||||
r1.setValue(T639_ID(), 4);
|
||||
assertEquals(1, r1.insert());
|
||||
assertEquals(4, (int) r1.getValue(T639_ID()));
|
||||
assertEquals(4, (int) r1.original(T639_ID()));
|
||||
assertFalse(r1.changed(T639_ID()));
|
||||
assertEquals(asList(3, 4), create.select(T639_ID()).from(T639()).fetch(T639_ID()));
|
||||
|
||||
// Copy is not affected
|
||||
T639 r2 = r1.copy();
|
||||
assertNull(r2.getValue(T639_ID()));
|
||||
assertNull(r2.original(T639_ID()));
|
||||
assertFalse(r2.changed(T639_ID()));
|
||||
|
||||
// Delete will not consider the new PK value
|
||||
r1.setValue(T639_ID(), 5);
|
||||
assertEquals(1, r1.delete());
|
||||
assertEquals(5, (int) r1.getValue(T639_ID()));
|
||||
assertEquals(4, (int) r1.original(T639_ID()));
|
||||
assertTrue(r1.changed(T639_ID()));
|
||||
assertEquals(3, (int) create.select(T639_ID()).from(T639()).fetchOne().value1());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1688,6 +1688,11 @@ public abstract class jOOQAbstractTest<
|
||||
new CRUDTests(this).testUpdatablesInsertUpdate();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdatablesWithUpdatablePK() throws Exception {
|
||||
new CRUDTests(this).testUpdatablesWithUpdatablePK();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdatablesPK() throws Exception {
|
||||
new CRUDTests(this).testUpdatablesPK();
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
<schemaDirectory>src/main/resources/xsd</schemaDirectory>
|
||||
<bindingDirectory>src/main/resources/xjb</bindingDirectory>
|
||||
<schemaIncludes>
|
||||
<include>jooq-runtime-3.1.0.xsd</include>
|
||||
<include>jooq-runtime-3.2.0.xsd</include>
|
||||
</schemaIncludes>
|
||||
<generatePackage>org.jooq.conf</generatePackage>
|
||||
<args>
|
||||
|
||||
@ -39,6 +39,7 @@ import static org.jooq.conf.ParamType.INDEXED;
|
||||
import static org.jooq.conf.ParamType.INLINED;
|
||||
import static org.jooq.conf.StatementType.PREPARED_STATEMENT;
|
||||
import static org.jooq.conf.StatementType.STATIC_STATEMENT;
|
||||
import static org.jooq.tools.StringUtils.defaultIfNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
@ -138,6 +139,13 @@ public final class SettingsTools {
|
||||
return getStatementType(settings) == STATIC_STATEMENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether primary keys should be updatable.
|
||||
*/
|
||||
public static final boolean updatablePrimaryKeys(Settings settings) {
|
||||
return defaultIfNull(settings.isUpdatablePrimaryKeys(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy access to {@link RenderMapping}.
|
||||
*/
|
||||
|
||||
@ -37,11 +37,13 @@
|
||||
package org.jooq.impl;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.jooq.conf.SettingsTools.updatablePrimaryKeys;
|
||||
import static org.jooq.impl.Utils.getAnnotatedGetter;
|
||||
import static org.jooq.impl.Utils.getAnnotatedMembers;
|
||||
import static org.jooq.impl.Utils.getMatchingGetter;
|
||||
import static org.jooq.impl.Utils.getMatchingMembers;
|
||||
import static org.jooq.impl.Utils.hasColumnAnnotations;
|
||||
import static org.jooq.impl.Utils.settings;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.sql.ResultSet;
|
||||
@ -294,6 +296,11 @@ abstract class AbstractRecord extends AbstractStore implements Record {
|
||||
val.setValue(value);
|
||||
}
|
||||
|
||||
// [#2764] Users may override updatability of primary key values
|
||||
else if (updatablePrimaryKeys(settings(this))) {
|
||||
val.setValue(value);
|
||||
}
|
||||
|
||||
// [#979] If the primary key is being changed, all other fields' flags
|
||||
// need to be set to true for in case this record is stored again, an
|
||||
// INSERT statement will thus be issued
|
||||
|
||||
@ -36,12 +36,14 @@
|
||||
package org.jooq.impl;
|
||||
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import static org.jooq.conf.SettingsTools.updatablePrimaryKeys;
|
||||
import static org.jooq.impl.RecordDelegate.delegate;
|
||||
import static org.jooq.impl.RecordDelegate.RecordLifecycleType.DELETE;
|
||||
import static org.jooq.impl.RecordDelegate.RecordLifecycleType.INSERT;
|
||||
import static org.jooq.impl.RecordDelegate.RecordLifecycleType.REFRESH;
|
||||
import static org.jooq.impl.RecordDelegate.RecordLifecycleType.STORE;
|
||||
import static org.jooq.impl.RecordDelegate.RecordLifecycleType.UPDATE;
|
||||
import static org.jooq.impl.Utils.settings;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.sql.Timestamp;
|
||||
@ -146,16 +148,27 @@ public class UpdatableRecordImpl<R extends UpdatableRecord<R>> extends TableReco
|
||||
|
||||
for (TableField<R, ?> field : keys) {
|
||||
|
||||
// If any primary key value is null or changed, execute an insert
|
||||
if (getValue(field) == null || getValue0(field).isChanged()) {
|
||||
executeUpdate = false;
|
||||
break;
|
||||
// [#2764] If primary key values are allowed to be changed,
|
||||
// inserting is only possible without prior loading of pk values
|
||||
if (updatablePrimaryKeys(settings(this))) {
|
||||
if (original(field) == null) {
|
||||
executeUpdate = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If primary key values are unchanged, updates are possible
|
||||
// [#2764] Primary key value changes are interpreted as record copies
|
||||
else {
|
||||
executeUpdate = true;
|
||||
|
||||
// If any primary key value is null or changed, execute an insert
|
||||
if (getValue(field) == null || getValue0(field).isChanged()) {
|
||||
executeUpdate = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, updates are possible
|
||||
executeUpdate = true;
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
|
||||
@ -39,6 +39,7 @@ import static java.lang.Boolean.FALSE;
|
||||
import static org.jooq.SQLDialect.CUBRID;
|
||||
import static org.jooq.SQLDialect.POSTGRES;
|
||||
import static org.jooq.conf.ParamType.INLINED;
|
||||
import static org.jooq.conf.SettingsTools.updatablePrimaryKeys;
|
||||
import static org.jooq.impl.DSL.escape;
|
||||
import static org.jooq.impl.DSL.getDataType;
|
||||
import static org.jooq.impl.DSL.nullSafe;
|
||||
@ -345,18 +346,36 @@ final class Utils {
|
||||
* Get an attachable's configuration or a new {@link DefaultConfiguration}
|
||||
* if <code>null</code>.
|
||||
*/
|
||||
static Configuration configuration(AttachableInternal attachable) {
|
||||
return configuration(attachable.configuration());
|
||||
static Configuration configuration(Attachable attachable) {
|
||||
return configuration(attachable instanceof AttachableInternal
|
||||
? ((AttachableInternal) attachable).configuration()
|
||||
: null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an configuration or a new {@link DefaultConfiguration} if
|
||||
* Get a configuration or a new {@link DefaultConfiguration} if
|
||||
* <code>null</code>.
|
||||
*/
|
||||
static Configuration configuration(Configuration configuration) {
|
||||
return configuration != null ? configuration : new DefaultConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a configuration's settings or default settings if the configuration
|
||||
* is <code>null</code>.
|
||||
*/
|
||||
static Settings settings(Attachable attachable) {
|
||||
return configuration(attachable).settings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a configuration's settings or default settings if the configuration
|
||||
* is <code>null</code>.
|
||||
*/
|
||||
static Settings settings(Configuration configuration) {
|
||||
return configuration(configuration).settings();
|
||||
}
|
||||
|
||||
private static final boolean attachRecords(Configuration configuration) {
|
||||
if (configuration != null) {
|
||||
Settings settings = configuration.settings();
|
||||
@ -1292,7 +1311,14 @@ final class Utils {
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
static final <T> void addCondition(org.jooq.ConditionProvider provider, Record record, Field<T> field) {
|
||||
provider.addConditions(field.equal(record.getValue(field)));
|
||||
|
||||
// [#2764] If primary keys are allowed to be changed, the
|
||||
if (updatablePrimaryKeys(settings(record))) {
|
||||
provider.addConditions(field.equal(record.original(field)));
|
||||
}
|
||||
else {
|
||||
provider.addConditions(field.equal(record.getValue(field)));
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
@ -12,12 +12,12 @@
|
||||
<jaxb:globalBindings>
|
||||
|
||||
<!-- Force all classes implements Serializable -->
|
||||
<xjc:serializable uid="310" />
|
||||
<xjc:serializable uid="320" />
|
||||
</jaxb:globalBindings>
|
||||
|
||||
|
||||
<!-- Annotate the following classes with @SuppressWarnings -->
|
||||
<jaxb:bindings schemaLocation="../xsd/jooq-runtime-3.1.0.xsd" multiple="true" node="//xs:complexType">
|
||||
<jaxb:bindings schemaLocation="../xsd/jooq-runtime-3.2.0.xsd" multiple="true" node="//xs:complexType">
|
||||
<inheritance:extends>org.jooq.conf.SettingsBase</inheritance:extends>
|
||||
<inheritance:implements>java.lang.Cloneable</inheritance:implements>
|
||||
<annox:annotate>
|
||||
|
||||
182
jOOQ/src/main/resources/xsd/jooq-runtime-3.2.0.xsd
Normal file
182
jOOQ/src/main/resources/xsd/jooq-runtime-3.2.0.xsd
Normal file
@ -0,0 +1,182 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<schema
|
||||
xmlns="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:jooq-runtime="http://www.jooq.org/xsd/jooq-runtime-3.2.0.xsd"
|
||||
targetNamespace="http://www.jooq.org/xsd/jooq-runtime-3.2.0.xsd"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<element name="settings" type="jooq-runtime:Settings"/>
|
||||
|
||||
<complexType name="Settings">
|
||||
<all>
|
||||
<!-- Whether any schema name should be rendered at all.
|
||||
Use this for single-schema environments, or when all objects are made
|
||||
available using synonyms -->
|
||||
<element name="renderSchema" type="boolean" minOccurs="0" maxOccurs="1" default="true"/>
|
||||
|
||||
<!-- Configure render mapping for runtime schema / table rewriting in
|
||||
generated SQL -->
|
||||
<element name="renderMapping" type="jooq-runtime:RenderMapping" minOccurs="0" maxOccurs="1"/>
|
||||
|
||||
<!-- Whether rendered schema, table, column names, etc should be quoted
|
||||
in rendered SQL, or transformed in any other way.
|
||||
This is set to "QUOTED" by default for backwards-compatibility -->
|
||||
<element name="renderNameStyle" type="jooq-runtime:RenderNameStyle" minOccurs="0" maxOccurs="1" default="QUOTED"/>
|
||||
|
||||
<!-- Whether SQL keywords should be rendered with upper or lower case -->
|
||||
<element name="renderKeywordStyle" type="jooq-runtime:RenderKeywordStyle" minOccurs="0" maxOccurs="1" default="LOWER"/>
|
||||
|
||||
<!-- Whether rendered SQL should be pretty-printed -->
|
||||
<element name="renderFormatted" type="boolean" minOccurs="0" maxOccurs="1" default="false"/>
|
||||
|
||||
<!-- Whether rendered bind values should be rendered as:
|
||||
|
||||
- question marks
|
||||
- named parameters
|
||||
- inlined values
|
||||
|
||||
This value is overridden by statementType == STATIC_STATEMENT, in
|
||||
case of which, this defaults to INLINED
|
||||
-->
|
||||
<element name="paramType" type="jooq-runtime:ParamType" minOccurs="0" maxOccurs="1" default="INDEXED"/>
|
||||
|
||||
<!-- The type of statement that is to be executed -->
|
||||
<element name="statementType" type="jooq-runtime:StatementType" minOccurs="0" maxOccurs="1" default="PREPARED_STATEMENT"/>
|
||||
|
||||
<!-- When set to true, this will add jOOQ's default logging ExecuteListeners -->
|
||||
<element name="executeLogging" type="boolean" minOccurs="0" maxOccurs="1" default="true"/>
|
||||
|
||||
<!-- Whether store() and delete() methods should be executed with optimistic locking -->
|
||||
<element name="executeWithOptimisticLocking" type="boolean" minOccurs="0" maxOccurs="1" default="false"/>
|
||||
|
||||
<!-- Whether fetched records should be attached to the fetching configuration -->
|
||||
<element name="attachRecords" type="boolean" minOccurs="0" maxOccurs="1" default="true"/>
|
||||
|
||||
<!-- Whether primary key values are deemed to be "updatable" in jOOQ
|
||||
|
||||
Setting this to "true" will allow for updating primary key values through
|
||||
UpdatableRecord.store() and UpdatableRecord.update()
|
||||
-->
|
||||
<element name="updatablePrimaryKeys" type="boolean" minOccurs="0" maxOccurs="1" default="false"/>
|
||||
</all>
|
||||
</complexType>
|
||||
|
||||
<complexType name="RenderMapping">
|
||||
<all>
|
||||
<!-- The default schema as defined in org.jooq.Schema.getName()
|
||||
This schema will be omitted in rendered SQL -->
|
||||
<element name="defaultSchema" type="string" minOccurs="0" maxOccurs="1"/>
|
||||
|
||||
<!-- The mapped schemata configuration -->
|
||||
<element name="schemata" type="jooq-runtime:MappedSchemata" minOccurs="0" maxOccurs="1"/>
|
||||
</all>
|
||||
</complexType>
|
||||
|
||||
<complexType name="MappedSchemata">
|
||||
<sequence>
|
||||
<element name="schema" type="jooq-runtime:MappedSchema" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</sequence>
|
||||
</complexType>
|
||||
|
||||
<complexType name="MappedSchema">
|
||||
<all>
|
||||
<!-- The input schema as defined in org.jooq.Schema.getName() -->
|
||||
<element name="input" type="string" minOccurs="1" maxOccurs="1"/>
|
||||
|
||||
<!-- The output schema as it will be rendered in SQL
|
||||
When this is omitted, you can still apply table mapping -->
|
||||
<element name="output" type="string" minOccurs="0" maxOccurs="1"/>
|
||||
|
||||
<!-- Configure table mapping for runtime table rewriting in
|
||||
generated SQL -->
|
||||
<element name="tables" type="jooq-runtime:MappedTables" minOccurs="0" maxOccurs="1"/>
|
||||
</all>
|
||||
</complexType>
|
||||
|
||||
<complexType name="MappedTables">
|
||||
<sequence>
|
||||
<element name="table" type="jooq-runtime:MappedTable" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</sequence>
|
||||
</complexType>
|
||||
|
||||
<complexType name="MappedTable">
|
||||
<all>
|
||||
<!-- The input schema as defined in org.jooq.Table.getName() -->
|
||||
<element name="input" type="string" minOccurs="1" maxOccurs="1"/>
|
||||
|
||||
<!-- The output schema as it will be rendered in SQL -->
|
||||
<element name="output" type="string" minOccurs="1" maxOccurs="1"/>
|
||||
</all>
|
||||
</complexType>
|
||||
|
||||
<simpleType name="ParamType">
|
||||
<restriction base="string">
|
||||
|
||||
<!-- Execute statements with indexed parameters, the way JDBC expects them -->
|
||||
<enumeration value="INDEXED"/>
|
||||
|
||||
<!-- Execute statements with named parameters -->
|
||||
<enumeration value="NAMED"/>
|
||||
|
||||
<!-- Execute statements with inlined parameters -->
|
||||
<enumeration value="INLINED"/>
|
||||
</restriction>
|
||||
</simpleType>
|
||||
|
||||
<simpleType name="StatementType">
|
||||
<restriction base="string">
|
||||
|
||||
<!-- Execute statements with inlined bind values, avoiding JDBC's
|
||||
PreparedStatements -->
|
||||
<enumeration value="STATIC_STATEMENT"/>
|
||||
|
||||
<!-- Execute statements with bind values, using JDBC's
|
||||
PreparedStatements -->
|
||||
<enumeration value="PREPARED_STATEMENT"/>
|
||||
</restriction>
|
||||
</simpleType>
|
||||
|
||||
<simpleType name="RenderNameStyle">
|
||||
<restriction base="string">
|
||||
|
||||
<!-- Render object names quoted, as defined in the database. Use this
|
||||
to stay on the safe side with case-sensitivity and special
|
||||
characters. For instance:
|
||||
Oracle : "SYS"."ALL_TAB_COLS"
|
||||
MySQL : `information_schema`.`TABLES`
|
||||
SQL Server: [INFORMATION_SCHEMA].[TABLES] -->
|
||||
<enumeration value="QUOTED"/>
|
||||
|
||||
<!-- Render object names, as defined in the database. For instance:
|
||||
Oracle : SYS.ALL_TAB_COLS
|
||||
MySQL : information_schema.TABLES
|
||||
SQL Server: INFORMATION_SCHEMA.TABLES -->
|
||||
<enumeration value="AS_IS"/>
|
||||
|
||||
<!-- Force rendering object names in lower case. For instance:
|
||||
Oracle : sys.all_tab_cols
|
||||
MySQL : information_schema.tables
|
||||
SQL Server: information_schema.tables -->
|
||||
<enumeration value="LOWER"/>
|
||||
|
||||
<!-- Force rendering object names in upper case. For instance:
|
||||
Oracle : SYS.ALL_TAB_COLS
|
||||
MySQL : INFORMATION_SCHEMA.TABLES
|
||||
SQL Server: INFORMATION_SCHEMA.TABLES -->
|
||||
<enumeration value="UPPER"/>
|
||||
</restriction>
|
||||
</simpleType>
|
||||
|
||||
<simpleType name="RenderKeywordStyle">
|
||||
<restriction base="string">
|
||||
|
||||
<!-- Keywords are rendered in lower case. For instance:
|
||||
select .. from .. where .. -->
|
||||
<enumeration value="LOWER"/>
|
||||
|
||||
<!-- Keywords are rendered in upper case. For instance:
|
||||
SELECT .. FROM .. WHERE .. -->
|
||||
<enumeration value="UPPER"/>
|
||||
</restriction>
|
||||
</simpleType>
|
||||
</schema>
|
||||
Loading…
Reference in New Issue
Block a user