/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2017 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://oss.oracle.com/licenses/CDDL+GPL-1.1
* or LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
/*
* UpdateQueryPlan.java
*
* Created on October 3, 2001
*
*/
package com.sun.jdo.spi.persistence.support.sqlstore.sql.generator;
import org.netbeans.modules.dbschema.ColumnElement;
import org.netbeans.modules.dbschema.TableElement;
import com.sun.jdo.api.persistence.support.JDOFatalInternalException;
import com.sun.jdo.spi.persistence.support.sqlstore.ActionDesc;
import com.sun.jdo.spi.persistence.support.sqlstore.SQLStoreManager;
import com.sun.jdo.spi.persistence.support.sqlstore.Transaction;
import com.sun.jdo.spi.persistence.support.sqlstore.model.*;
import com.sun.jdo.spi.persistence.support.sqlstore.sql.UpdateJoinTableDesc;
import com.sun.jdo.spi.persistence.support.sqlstore.sql.UpdateObjectDescImpl;
import org.glassfish.persistence.common.I18NHelper;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
/**
* This class is used to generated update/insert/delete statements.
*/
public class UpdateQueryPlan extends QueryPlan {
/** Flag indicating whether we use batch update. */
private boolean batch = false;
private UpdateObjectDescImpl updateDesc;
public UpdateQueryPlan(ActionDesc desc, SQLStoreManager store) {
super(desc, store);
this.updateDesc = (UpdateObjectDescImpl) desc;
this.action = getAction(updateDesc.getUpdateAction());
}
private static int getAction(int updateAction) {
if (updateAction == ActionDesc.LOG_CREATE) {
return ACT_INSERT;
} else if (updateAction == ActionDesc.LOG_DESTROY) {
return ACT_DELETE;
} else if (updateAction == ActionDesc.LOG_UPDATE) {
return ACT_UPDATE;
} else {
return ACT_NOOP;
}
}
/**
* Specifies an field the data for which needs to be updated,
* and the mapped columns for which therefor need to be updated.
* For update queries the column will be put in the set lists,
* and for insert queries the column will be put into the
* insert values lists.
*
* @param fieldDesc Updated field corresponding to a column in the database.
* @param value New value.
*/
private void addColumn(LocalFieldDesc fieldDesc, Object value) {
// Ignore secondary tracked fields.
if ((fieldDesc.sqlProperties & FieldDesc.PROP_SECONDARY_TRACKED_FIELD) > 0) {
return;
}
for (Iterator iter = fieldDesc.getColumnElements(); iter.hasNext(); ) {
ColumnElement columnElement = (ColumnElement) iter.next();
TableElement tableElement = columnElement.getDeclaringTable();
if (tableElement == null) {
throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
"core.configuration.fieldnotable", // NOI18N
fieldDesc.getName()));
}
QueryTable t = findQueryTable(tableElement);
UpdateStatement s = null;
if (t == null) {
t = addQueryTable(tableElement, null);
s = (UpdateStatement) addStatement(t);
} else {
s = (UpdateStatement) getStatement(t);
}
if (fieldDesc.isVersion() && action == ACT_UPDATE) {
// For update, version columns will be flagged specially.
s.addVersionColumn(columnElement);
} else {
s.addColumn(columnElement, value);
}
}
}
public void build() {
build(false);
}
/**
* Builds a UpdateQueryPlan for an object based update
* type (i.e. insert, update, delete) ActionDesc.
*
* @param batch Flag indicating whether we use batch update.
*/
public void build(boolean batch) {
if ((status & ST_BUILT) > 0) return;
this.batch = batch;
generateStatements();
addColumns();
if (statements.size() > 0) {
processStatements();
}
processJoinTables();
status = status | ST_BUILT;
}
private void generateStatements() {
// For insert and delete we build a statement for each table
if ((action == ACT_DELETE) || (action == ACT_INSERT)) {
Iterator iter = config.getTables();
while (iter.hasNext()) {
TableDesc t = (TableDesc) iter.next();
// Skip join tables
if (!t.isJoinTable()) {
if (findQueryTable(t.getTableElement()) == null)
addStatement(addQueryTable(t));
}
}
// For insert statements, we need to reverse the order.
if (action == ACT_INSERT) {
int i = 0;
int j = statements.size() - 1;
while (i < j) {
Statement s = (Statement) statements.get(i);
statements.set(i, statements.get(j));
statements.set(j, s);
i++;
j--;
}
}
}
}
/**
* For inserts and updates we figure out values (they might be hidden)
* and add the columns necessary. <code>addColumn</code> will figure out
* which tables need to be included.
*/
private void addColumns() {
if ((action == ACT_UPDATE) || (action == ACT_INSERT)) {
List updatedFields = updateDesc.getUpdatedFields();
int size = (updatedFields != null) ? updatedFields.size() : 0;
for (int i = 0; i < size; i++) {
LocalFieldDesc f = (LocalFieldDesc) updatedFields.get(i);
Object value = batch ? f : updateDesc.getAfterValue(f);
addColumn(f, value);
}
}
}
private void addConstraints(UpdateStatement statement,
ArrayList localFields,
ArrayList foreignFields,
ArrayList columns) {
boolean isBeforeImageRequired = updateDesc.isBeforeImageRequired();
for (int i = 0; i < localFields.size(); i++) {
LocalFieldDesc lf = (LocalFieldDesc) localFields.get(i);
LocalFieldDesc ff = (LocalFieldDesc) foreignFields.get(i);
ColumnElement ce = (ColumnElement) columns.get(i);
addConstraint(statement, lf, ff, ce, isBeforeImageRequired);
}
// Add the constraint on the version field if needed.
if (getConfig().hasVersionConsistency() && action != ACT_INSERT) {
QueryTable table = (QueryTable) statement.getQueryTables().get(0);
LocalFieldDesc versionField = table.getTableDesc().getVersionField();
ColumnElement ce = (ColumnElement) versionField.getColumnElements().next();
addConstraint(statement, versionField, versionField, ce, false);
}
statement.markConstraintAdded();
}
private void addConstraint(UpdateStatement statement,
LocalFieldDesc lf,
LocalFieldDesc ff,
ColumnElement ce,
boolean isBeforeImageRequired) {
if (action != ACT_INSERT) {
if (batch) {
statement.addConstraint(ce, lf, ff);
}
else {
Object value = isBeforeImageRequired ?
updateDesc.getBeforeValue(ff) : updateDesc.getAfterValue(ff);
statement.addConstraint(lf, value);
}
} else {
Object value = batch ? ff : updateDesc.getAfterValue(ff);
statement.addColumn(ce, value);
}
}
private void addSecondaryTableConstraint(UpdateStatement statement) {
QueryTable table = (QueryTable) statement.getQueryTables().get(0);
ReferenceKeyDesc key = table.getTableDesc().getPrimaryTableKey();
ArrayList localFields = key.getReferencingKey().getFields();
ArrayList foreignFields = key.getReferencedKey().getFields();
ArrayList columns = key.getReferencingKey().getColumns();
addConstraints(statement, localFields, foreignFields, columns);
}
private void addBasetableConstraint(UpdateStatement statement) {
QueryTable table = (QueryTable) statement.getQueryTables().get(0);
KeyDesc key = table.getTableDesc().getKey();
ArrayList localFields = key.getFields();
ArrayList columns = key.getColumns();
addConstraints(statement, localFields, localFields, columns);
}
private void processRelatedStatements(UpdateStatement statement) {
ArrayList secondaryTableStatements = statement.getSecondaryTableStatements();
if (secondaryTableStatements != null) {
for (int i = 0; i < secondaryTableStatements.size(); i++) {
UpdateStatement secondaryTableStatement = (UpdateStatement) secondaryTableStatements.get(i);
if (!secondaryTableStatement.isConstraintAdded()) {
processRelatedStatements(secondaryTableStatement);
addSecondaryTableConstraint(secondaryTableStatement);
}
}
}
}
protected void processStatements() {
int size = statements.size();
if (size > 1) {
super.processStatements();
for (int i = 0; i < size; i++) {
UpdateStatement statement = (UpdateStatement) statements.get(i);
if (!statement.isConstraintAdded())
processRelatedStatements(statement);
}
}
UpdateStatement masterStatement = null;
if (size == 1)
masterStatement = (UpdateStatement) statements.get(0);
else {
// Look for the master statement. It should be the one
// with no constraints added.
for (int i = 0; i < size; i++) {
masterStatement = (UpdateStatement) statements.get(i);
if (!masterStatement.isConstraintAdded()) break;
}
}
if (action != ACT_INSERT)
addBasetableConstraint(masterStatement);
if ((action != ACT_INSERT) && (updateDesc.getConcurrency() != null))
updateDesc.getConcurrency().update(this);
}
private void processJoinTables() {
Collection fields = updateDesc.getUpdatedJoinTableFields();
if (fields == null) return;
Iterator fieldIter = fields.iterator();
ArrayList deleteStatements = new ArrayList();
ArrayList insertStatements = new ArrayList();
while (fieldIter.hasNext()) {
ForeignFieldDesc f = (ForeignFieldDesc) fieldIter.next();
Collection descs = updateDesc.getUpdateJoinTableDescs(f);
Iterator descIter = descs.iterator();
ColumnElement c = (ColumnElement) f.assocLocalColumns.get(0);
QueryTable t = addQueryTable(config.findTableDesc(c.getDeclaringTable()));
while (descIter.hasNext()) {
UpdateJoinTableDesc desc = (UpdateJoinTableDesc) descIter.next();
int action = getAction(desc.getAction());
UpdateStatement s = (UpdateStatement) createStatement(t);
s.setAction(action);
if (action == ACT_INSERT) {
insertStatements.add(s);
} else if (action == ACT_DELETE) {
// RESOLVE: There are redundant deletes from join tables that causes
// update to fail with no rows affected. To work around this problem
// for now, we set the minAffectedRows to 0.
// We need to figure out why there are redundant deletes.
s.minAffectedRows = 0;
deleteStatements.add(s);
}
s.addLocalConstraints(action, f, desc.getParentStateManager());
s.addForeignConstraints(action, f, desc.getForeignStateManager());
}
}
// All join table delete statements have to go first and all
// join table insert statements have to go last.
ArrayList oldStatements = statements;
statements = deleteStatements;
statements.addAll(oldStatements);
statements.addAll(insertStatements);
}
protected Statement newStatement() {
return new UpdateStatement(store.getVendorType(), this, batch);
}
/**
* Determines if the amount of batched operations exceeded a threshold.
* @param tran the transaction
* @return true if the amount of batched operations exceeded a threshold
*/
public boolean checkBatchThreshold(Transaction tran)
{
for (int i = 0, size = statements.size(); i < size; i++) {
UpdateStatement updateStatement = (UpdateStatement) statements.get(i);
if (updateStatement.exceedsBatchThreshold(tran))
return true;
}
return false;
}
}