| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.openjpa.jdbc.sql; |
| |
| import java.sql.SQLException; |
| import java.util.Objects; |
| |
| import org.apache.openjpa.jdbc.meta.ClassMapping; |
| import org.apache.openjpa.jdbc.meta.RelationId; |
| import org.apache.openjpa.jdbc.schema.Column; |
| import org.apache.openjpa.jdbc.schema.ColumnIO; |
| import org.apache.openjpa.jdbc.schema.ForeignKey; |
| import org.apache.openjpa.jdbc.schema.Table; |
| import org.apache.openjpa.kernel.OpenJPAStateManager; |
| import org.apache.openjpa.lib.util.Localizer; |
| import org.apache.openjpa.util.InvalidStateException; |
| |
| /** |
| * Primary table row that tracks foreign keys and auto-inc columns. |
| * |
| * @author Abe White |
| */ |
| public class PrimaryRow |
| extends RowImpl { |
| |
| // VALID flag in superclass uses 2 << 0 |
| private static final byte PK_SET = 2 << 1; |
| private static final byte PK_WHERE = 2 << 2; |
| private static final byte DEPENDENT = 2 << 4; |
| |
| private static final Localizer _loc = Localizer.forPackage |
| (PrimaryRow.class); |
| |
| private OpenJPAStateManager _pk = null; |
| private ColumnIO _pkIO = null; |
| private OpenJPAStateManager[] _fkSet = null; |
| private ColumnIO[] _fkIO = null; |
| private OpenJPAStateManager[] _fkWhere = null; |
| private OpenJPAStateManager[] _relSet = null; |
| private RelationId[] _callbacks = null; |
| private Object _failed = null; |
| private int _idx = -1; |
| |
| /** |
| * Constructor; supply table and action. |
| */ |
| public PrimaryRow(Table table, int action, OpenJPAStateManager owner) { |
| this(table.getColumns(), action, owner); |
| } |
| |
| protected PrimaryRow(Column[] cols, int action, OpenJPAStateManager owner) { |
| super(cols, action); |
| _pk = owner; |
| } |
| |
| /** |
| * Mark this row as dependent on some other row. |
| */ |
| @Override |
| public boolean isDependent() { |
| return (flags & DEPENDENT) > 0; |
| } |
| |
| /** |
| * Mark this row as dependent on some other row. |
| */ |
| public void setDependent(boolean dependent) { |
| if (dependent) |
| flags |= DEPENDENT; |
| else |
| flags &= ~DEPENDENT; |
| } |
| |
| /** |
| * The index of this row in ordered row list. |
| */ |
| public int getIndex() { |
| return _idx; |
| } |
| |
| /** |
| * The index of this row in ordered row list. |
| */ |
| public void setIndex(int idx) { |
| _idx = idx; |
| } |
| |
| @Override |
| public Object getFailedObject() { |
| return _failed; |
| } |
| |
| @Override |
| public void setFailedObject(Object failed) { |
| _failed = failed; |
| } |
| |
| @Override |
| public OpenJPAStateManager getPrimaryKey() { |
| return _pk; |
| } |
| |
| @Override |
| public void setPrimaryKey(OpenJPAStateManager sm) |
| throws SQLException { |
| setPrimaryKey(null, sm); |
| } |
| |
| @Override |
| public void setPrimaryKey(ColumnIO io, OpenJPAStateManager sm) { |
| _pk = sm; |
| flags |= PK_SET; |
| _pkIO = io; |
| |
| // force valid |
| setValid(true); |
| } |
| |
| @Override |
| public void wherePrimaryKey(OpenJPAStateManager sm) |
| throws SQLException { |
| _pk = sm; |
| flags |= PK_WHERE; |
| |
| // force valid |
| if (getAction() == ACTION_DELETE) |
| setValid(true); |
| } |
| |
| /** |
| * Return the I/O information for the given set foreign key. |
| */ |
| public ColumnIO getForeignKeyIO(ForeignKey fk) { |
| return (_fkIO == null) ? null : _fkIO[fk.getIndex()]; |
| } |
| |
| /** |
| * Return the value for the given foreign key. Values not needed for |
| * constraint analyses are not recorded. |
| */ |
| public OpenJPAStateManager getForeignKeySet(ForeignKey fk) { |
| return (_fkSet == null) ? null : _fkSet[fk.getIndex()]; |
| } |
| |
| /** |
| * Return the value for the given foreign key. Values not needed for |
| * constraint analyses are not recorded. |
| */ |
| public OpenJPAStateManager getForeignKeyWhere(ForeignKey fk) { |
| return (_fkWhere == null) ? null : _fkWhere[fk.getIndex()]; |
| } |
| |
| @Override |
| public void setForeignKey(ForeignKey fk, OpenJPAStateManager sm) |
| throws SQLException { |
| setForeignKey(fk, null, sm); |
| } |
| |
| @Override |
| public void setForeignKey(ForeignKey fk, ColumnIO io, |
| OpenJPAStateManager sm) |
| throws SQLException { |
| if (!delayForeignKey(fk, sm, true)) |
| super.setForeignKey(fk, io, sm); |
| else |
| recordForeignKey(fk, io, sm, true); |
| } |
| |
| @Override |
| public void whereForeignKey(ForeignKey fk, OpenJPAStateManager sm) |
| throws SQLException { |
| if (!delayForeignKey(fk, sm, false)) |
| super.whereForeignKey(fk, sm); |
| else |
| recordForeignKey(fk, null, sm, false); |
| } |
| |
| @Override |
| public void clearForeignKey(ForeignKey fk) |
| throws SQLException { |
| super.clearForeignKey(fk); |
| if (_fkSet != null) |
| _fkSet[fk.getIndex()] = null; |
| if (_fkIO != null) |
| _fkIO[fk.getIndex()] = null; |
| } |
| |
| /** |
| * If this is a delete, delay foreign keys to other deleted objects if the |
| * key is restricted or cascade. If this is an update or insert, delay |
| * foreign keys to other inserts if the key is not logical. If the foreign |
| * key is to a new record and the columns are auto-inc, record it. |
| */ |
| private boolean delayForeignKey(ForeignKey fk, OpenJPAStateManager sm, |
| boolean set) { |
| if (sm == null) |
| return false; |
| |
| if (getAction() == ACTION_DELETE) |
| return sm.isDeleted() && !fk.isDeferred() |
| && (fk.getDeleteAction() == ForeignKey.ACTION_RESTRICT || |
| fk.getDeleteAction() == ForeignKey.ACTION_CASCADE); |
| |
| if (!sm.isNew() || sm.isFlushed()) |
| return false; |
| if (!fk.isDeferred() && !fk.isLogical()) |
| return true; |
| if (fk.isPrimaryKeyAutoAssigned()) |
| return true; |
| return false; |
| } |
| |
| /** |
| * Record a delayed foreign key. |
| */ |
| private void recordForeignKey(ForeignKey fk, ColumnIO io, |
| OpenJPAStateManager sm, boolean set) { |
| if (set) { |
| // force valid |
| if (canSetAny(io, fk.getColumns().length |
| + fk.getConstantColumns().length, false)) |
| setValid(true); |
| |
| if (_fkSet == null) |
| _fkSet = new OpenJPAStateManager[getTable(). |
| getForeignKeys().length]; |
| _fkSet[fk.getIndex()] = sm; |
| |
| if (_fkIO != null) |
| _fkIO[fk.getIndex()] = io; |
| else if (io != null && ((getAction() == ACTION_INSERT |
| && !io.isAllInsertable(fk, false)) |
| || (getAction() != ACTION_INSERT |
| && !io.isAllUpdatable(fk, false)))) { |
| _fkIO = new ColumnIO[_fkSet.length]; |
| _fkIO[fk.getIndex()] = io; |
| } |
| } else { |
| // force valid |
| if (getAction() == ACTION_DELETE) |
| setValid(true); |
| |
| if (_fkWhere == null) |
| _fkWhere = new OpenJPAStateManager[getTable(). |
| getForeignKeys().length]; |
| _fkWhere[fk.getIndex()] = sm; |
| } |
| } |
| |
| /** |
| * Return the recorded value for the given relation id column. Only |
| * values that are dependent on a new, unflushed auto-assigned instance |
| * are recorded. |
| */ |
| public OpenJPAStateManager getRelationIdSet(Column col) { |
| return (_relSet == null) ? null : _relSet[getRelationIdIndex(col)]; |
| } |
| |
| /** |
| * Return the recorded callbacks for the given relation id column. Only |
| * values that are dependent on a new, unflushed auto-assigned instance |
| * are recorded. |
| */ |
| public RelationId getRelationIdCallback(Column col) { |
| return (_callbacks == null) ? null |
| : _callbacks[getRelationIdIndex(col)]; |
| } |
| |
| @Override |
| public void setRelationId(Column col, OpenJPAStateManager sm, |
| RelationId rel) |
| throws SQLException { |
| if (sm == null || sm.getObjectId() != null || !sm.isNew() |
| || sm.isFlushed() || !isPrimaryKeyAutoAssigned(sm)) |
| super.setRelationId(col, sm, rel); |
| else { |
| if (_relSet == null) { |
| Column[] cols = getTable().getRelationIdColumns(); |
| _relSet = new OpenJPAStateManager[cols.length]; |
| _callbacks = new RelationId[cols.length]; |
| } |
| int idx = getRelationIdIndex(col); |
| _relSet[idx] = sm; |
| _callbacks[idx] = rel; |
| } |
| } |
| |
| @Override |
| public void clearRelationId(Column col) |
| throws SQLException { |
| super.clearRelationId(col); |
| if (_relSet != null) { |
| int idx = getRelationIdIndex(col); |
| _relSet[idx] = null; |
| _callbacks[idx] = null; |
| } |
| } |
| |
| /** |
| * Return the index into our relation id array of the value for the |
| * given column. |
| */ |
| private int getRelationIdIndex(Column col) { |
| Column[] cols = getTable().getRelationIdColumns(); |
| for (int i = 0; i < cols.length; i++) |
| if (cols[i] == col) |
| return i; |
| return -1; |
| } |
| |
| /** |
| * Return true if any primary key columns of the given instance are |
| * auto-assigned. |
| */ |
| private static boolean isPrimaryKeyAutoAssigned(OpenJPAStateManager sm) { |
| ClassMapping cls = (ClassMapping) sm.getMetaData(); |
| while (cls.getJoinablePCSuperclassMapping() != null) |
| cls = cls.getJoinablePCSuperclassMapping(); |
| Column[] cols = cls.getPrimaryKeyColumns(); |
| for (int i = 0; i < cols.length; i++) |
| if (cols[i].isAutoAssigned()) |
| return true; |
| return false; |
| } |
| |
| @Override |
| protected void setObject(Column col, Object val, int metaType, |
| boolean overrideDefault) |
| throws SQLException { |
| // make sure we're not setting two different values |
| // unless the given column is an implicit relationship and value |
| // changes from logical default to non-default |
| Object prev = getSet(col); |
| if (prev != null) { |
| if (prev == NULL) |
| prev = null; |
| if (!rowValueEquals(prev, val)) { |
| if (isDefaultValue(prev) || allowsUpdate(col, prev, val)) { |
| super.setObject(col, val, metaType, overrideDefault); |
| return; |
| } else if (!isDefaultValue(val)) { |
| throw new InvalidStateException(_loc.get("diff-values", |
| new Object[]{ col.getFullDBIdentifier().getName(), |
| (prev == null) ? null : prev.getClass(), prev, |
| (val == null) ? null : val.getClass(), val })). |
| setFatal(true); |
| } else { |
| // since not allow to update and the new value is 0 or null, |
| // just return. |
| return; |
| } |
| } |
| } |
| super.setObject(col, val, metaType, overrideDefault); |
| } |
| |
| /** |
| * Allow the given key column value to be updated if the old value is a default value |
| * or the new value is default. |
| * For primary keys we even disallow setting the current value to default |
| */ |
| boolean allowsUpdate(Column col, Object old, Object cur) { |
| if (col.isPrimaryKey() && isDefaultValue(old) && !isDefaultValue(cur)) { |
| // for primary keys we disallow re-setting it to default |
| return false; |
| } |
| |
| return !(col.isPrimaryKey() || col.isRelationId() || col.isImplicitRelation() || col.isUni1MFK()) |
| || isDefaultValue(old) || isDefaultValue(cur); |
| } |
| |
| boolean isDefaultValue(Object val) { |
| return val == null || val == NULL |
| || (val instanceof Number && ((Number)val).longValue() == 0); |
| } |
| |
| /** |
| * Return true if the two values should be considered equal. |
| */ |
| private static boolean rowValueEquals(Object o1, Object o2) { |
| if (Objects.equals(o1, o2)) |
| return true; |
| |
| // check for numeric equality (bug #1151) |
| return o1 instanceof Number && o2 instanceof Number |
| && ((Number) o1).doubleValue() == ((Number) o2).doubleValue(); |
| } |
| |
| @Override |
| protected String generateSQL(DBDictionary dict) { |
| try { |
| if ((flags & PK_SET) > 0) |
| super.setPrimaryKey(_pkIO, _pk); |
| if ((flags & PK_WHERE) > 0) |
| super.wherePrimaryKey(_pk); |
| if (_fkSet != null) { |
| ForeignKey[] fks = getTable().getForeignKeys(); |
| ColumnIO io; |
| for (int i = 0; i < _fkSet.length; i++) { |
| if (_fkSet[i] != null) { |
| io = (_fkIO == null) ? null : _fkIO[i]; |
| super.setForeignKey(fks[i], io, _fkSet[i]); |
| } |
| } |
| } |
| if (_relSet != null) { |
| Column[] cols = getTable().getRelationIdColumns(); |
| for (int i = 0; i < _relSet.length; i++) |
| if (_relSet[i] != null) |
| super.setRelationId(cols[i], _relSet[i], _callbacks[i]); |
| } |
| if (_fkWhere != null) { |
| ForeignKey[] fks = getTable().getForeignKeys(); |
| for (int i = 0; i < _fkWhere.length; i++) |
| if (_fkWhere[i] != null) |
| super.whereForeignKey(fks[i], _fkWhere[i]); |
| } |
| } |
| catch (SQLException se) { |
| throw SQLExceptions.getStore(se, dict); |
| } |
| return super.generateSQL(dict); |
| } |
| |
| @Override |
| protected RowImpl newInstance(Column[] cols, int action) { |
| return new PrimaryRow(cols, action, _pk); |
| } |
| |
| @Override |
| public void copyInto(RowImpl row, boolean whereOnly) { |
| super.copyInto(row, whereOnly); |
| if (!(row instanceof PrimaryRow)) |
| return; |
| |
| PrimaryRow prow = (PrimaryRow) row; |
| prow._pk = _pk; |
| prow._pkIO = _pkIO; |
| if ((flags & PK_WHERE) > 0) |
| prow.flags |= PK_WHERE; |
| if (!whereOnly && (flags & PK_SET) > 0) |
| prow.flags |= PK_SET; |
| |
| if (_fkWhere != null) { |
| if (prow._fkWhere == null) |
| prow._fkWhere = new OpenJPAStateManager[_fkWhere.length]; |
| System.arraycopy(_fkWhere, 0, prow._fkWhere, 0, _fkWhere.length); |
| } |
| if (!whereOnly && _fkSet != null) { |
| if (prow._fkSet == null) |
| prow._fkSet = new OpenJPAStateManager[_fkSet.length]; |
| System.arraycopy(_fkSet, 0, prow._fkSet, 0, _fkSet.length); |
| if (_fkIO != null) { |
| if (prow._fkIO == null) |
| prow._fkIO = new ColumnIO[_fkIO.length]; |
| System.arraycopy(_fkIO, 0, prow._fkIO, 0, _fkIO.length); |
| } |
| } |
| if (!whereOnly && _relSet != null) { |
| if (prow._relSet == null) { |
| prow._relSet = new OpenJPAStateManager[_relSet.length]; |
| prow._callbacks = new RelationId[_callbacks.length]; |
| } |
| System.arraycopy(_relSet, 0, prow._relSet, 0, _relSet.length); |
| System.arraycopy(_callbacks, 0, prow._callbacks, 0, |
| _callbacks.length); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder buf = new StringBuilder(); |
| buf.append("PrimaryRow["); |
| switch (getAction()) { |
| case ACTION_UPDATE: buf.append("UPDATE"); break; |
| case ACTION_INSERT: buf.append("INSERT"); break; |
| case ACTION_DELETE: buf.append("DELETE"); break; |
| default: buf.append("UNKNOWN"); |
| } |
| buf.append(" ").append(getTable().getName()).append("]: "); |
| buf.append(_pk); |
| return buf.toString(); |
| } |
| } |