/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 1997-2010 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://glassfish.dev.java.net/public/CDDL+GPL_1_1.html * or packager/legal/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 packager/legal/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. */ /* * UpdateStatement.java * * Created on October 3, 2001 * */ package com.sun.jdo.spi.persistence.support.sqlstore.sql.generator; import org.netbeans.modules.dbschema.ColumnElement; import com.sun.jdo.spi.persistence.support.sqlstore.SQLStateManager; import com.sun.jdo.spi.persistence.support.sqlstore.Transaction; import com.sun.jdo.spi.persistence.support.sqlstore.database.DBVendorType; import com.sun.jdo.spi.persistence.support.sqlstore.model.ForeignFieldDesc; import com.sun.jdo.spi.persistence.support.sqlstore.model.LocalFieldDesc; import com.sun.jdo.spi.persistence.support.sqlstore.sql.UpdateObjectDescImpl; import com.sun.jdo.spi.persistence.support.sqlstore.sql.constraint.ConstraintValue; import java.sql.Connection; import java.sql.SQLException; import java.util.*; /** * This class is used to generate update/insert/delete statements. */ public class UpdateStatement extends Statement implements Cloneable { public int minAffectedRows; private Map dbStatementCache = new HashMap(); /** The UpdateQueryplan */ UpdateQueryPlan plan; /** List of ColumnRef for the where clause used during batch. */ private List columnRefsForWhereClause; /** List of version columns */ private List versionColumns; /** Flag indicating whether we use batch. */ private boolean batch = false; /** Insert values for INSERT statements. */ private StringBuffer values; /** */ private boolean isConstraintAdded; /** Name of the USE_BATCH property. */ public static final String UPDATE_VERSION_COL_PROPERTY = "com.sun.jdo.spi.persistence.support.sqlstore.sql.generator.UPDATE_VERSION_COL"; // NOI18N /** * Property to swich on/off updating of version col. * Note, the default is true, meaning we try to update version col if the * property is not specified. */ private static final boolean UPDATE_VERSION_COL = Boolean.valueOf( System.getProperty(UPDATE_VERSION_COL_PROPERTY, "true")).booleanValue(); // NOI18N public UpdateStatement(DBVendorType vendorType, UpdateQueryPlan plan, boolean batch) { super(vendorType); this.plan = plan; columnRefsForWhereClause = new ArrayList(); this.batch = batch; minAffectedRows = 1; } public void addColumn(ColumnElement columnElement, Object value) { addColumnRef(new ColumnRef(columnElement, value)); } /** * Batch helper method. Adds the columnElement to the list of * ColumnRefs for the where clause and then calls addConstraint. */ protected void addConstraint(ColumnElement columnElement, LocalFieldDesc lf, Object value) { columnRefsForWhereClause.add(new ColumnRef(columnElement, value)); addConstraint(lf, value); } /** Calculates the index of the where clause ColumnRefs */ private void calculateWhereClauseColumnRefIndexes() { // calculate where clause column ref indexes // NOTE, the sqlstore processes the constraints in reverse order, // so start with the last index and decrement int nextIndex = columns.size() + columnRefsForWhereClause.size(); for (Iterator i = columnRefsForWhereClause.iterator(); i.hasNext(); ) { ColumnRef columnRef = (ColumnRef)i.next(); columnRef.setIndex(nextIndex--); } } public boolean isConstraintAdded() { return isConstraintAdded; } public void markConstraintAdded() { isConstraintAdded = true; } /** @inheritDoc */ public QueryPlan getQueryPlan() { return plan; } /** * @inheritDoc */ protected void generateStatementText() { statementText = new StringBuffer(); StringBuffer columnList = generateColumnText(); StringBuffer constraint = processConstraints(); String tableName = ((QueryTable) tableList.get(0)).getTableDesc().getName(); // Create the query filling in the column list, table name, etc. switch (action) { case QueryPlan.ACT_UPDATE: statementText.append("update ");// NOI18N appendQuotedText(statementText, tableName); statementText.append(" set ").append(columnList).append(" where ").append(constraint); // NOI18N break; case QueryPlan.ACT_DELETE: statementText.append("delete from ");// NOI18N appendQuotedText(statementText, tableName); statementText.append(" where ").append(constraint); // NOI18N break; case QueryPlan.ACT_INSERT: statementText.append("insert into ");// NOI18N appendQuotedText(statementText, tableName); statementText.append("(").append(columnList).// NOI18N append(") values ").append("(").append(values).append(")"); // NOI18N break; } calculateWhereClauseColumnRefIndexes(); } private StringBuffer generateColumnText() { StringBuffer columnList = new StringBuffer(); int numValues = -1; for (int i = 0; i < columns.size(); i++) { ColumnRef c = (ColumnRef) columns.get(i); if (columnList.length() > 0) { columnList.append(", "); // NOI18N } switch (action) { case QueryPlan.ACT_UPDATE: appendQuotedText(columnList, c.getName()); columnList.append("= ?"); // NOI18N break; case QueryPlan.ACT_INSERT: appendQuotedText(columnList, c.getName()); if (i == 0) { values = new StringBuffer().append(" ?"); // NOI18N } else { values.append(", ?"); // NOI18N } break; } // Do not create an InputValue in the case of batch update. // Method bindInputValues will get the value using the ColumnRef. if (!batch && ((action == QueryPlan.ACT_UPDATE) || (action == QueryPlan.ACT_INSERT))) { numValues = numValues + 1; InputValue val = new InputValue(c.getValue(), c.getColumnElement()); inputDesc.values.add(numValues, val); } } appendVersionColumnUpdateClause(columnList); return columnList; } /** * Appends clause to update version column. The generated clause will be of * the following form * <code> versionColumnName = versionColumnNane + 1 </code> * @param setClause Text for the set clause of update statement */ private void appendVersionColumnUpdateClause(StringBuffer setClause) { if(UPDATE_VERSION_COL) { if (versionColumns != null) { for (int i = 0; i < versionColumns.size(); i++) { ColumnElement columnElement = (ColumnElement) versionColumns.get(i); String columnName = columnElement.getName().getName(); setClause.append(", "); appendQuotedText(setClause, columnName); setClause.append(" = "); appendQuotedText(setClause, columnName); setClause.append(" + "); setClause.append("1"); } } } } public void addLocalConstraints(int action, ForeignFieldDesc f, SQLStateManager sm) { for (int i = 0; i < f.localFields.size(); i++) { LocalFieldDesc lf = (LocalFieldDesc) f.localFields.get(i); if (action == QueryPlan.ACT_INSERT) { // For inserts into the join table, we get the values we are inserting // for the parent object and the added object. ColumnElement lc = (ColumnElement) f.assocLocalColumns.get(i); addColumn(lc, lf.getValue(sm)); } else if (action == QueryPlan.ACT_DELETE) { LocalFieldDesc alf = (LocalFieldDesc) f.assocLocalFields.get(i); // For deletes from the join table, we get the constraint values // from the parent object and the remove object. addConstraint(alf, lf.getValue(sm)); } } } public void addForeignConstraints(int action, ForeignFieldDesc f, SQLStateManager sm) { for (int i = 0; i < f.foreignFields.size(); i++) { LocalFieldDesc ff = (LocalFieldDesc) f.foreignFields.get(i); if (action == QueryPlan.ACT_INSERT) { // For inserts into the join table, we get the values we are inserting // for the parent object and the added object. ColumnElement fc = (ColumnElement) f.assocForeignColumns.get(i); addColumn(fc, ff.getValue(sm)); } else if (action == QueryPlan.ACT_DELETE) { LocalFieldDesc aff = (LocalFieldDesc) f.assocForeignFields.get(i); // For deletes from the join table, we get the constraint values // from the parent object and the remove object. addConstraint(aff, ff.getValue(sm)); } } } /** * Redefines processConstraintValue in order to skip the creation of * an InputValue in the case of batch. */ protected void processConstraintValue(ConstraintValue node, StringBuffer result) { result.append("?"); // NOI18N if (!batch) generateInputValueForConstraintValueNode(node); } /** * Returns the cached db statement for the specified connection. * If there is not any statement for this connection in the cache, * then a new statement is created. * @param tran the transaction * @param conn the connection * @return the statement */ public DBStatement getDBStatement(Transaction tran, Connection conn) throws SQLException { DBStatement dbStatement = null; synchronized (dbStatementCache) { // dbStatement cachelookup dbStatement = (DBStatement)dbStatementCache.get(tran); if (dbStatement == null) { dbStatement = new DBStatement(conn, getText(), tran.getUpdateTimeout()); // put dbStatement in cache dbStatementCache.put(tran, dbStatement); } } return dbStatement; } /** */ public boolean exceedsBatchThreshold(Transaction tran) { synchronized (dbStatementCache) { DBStatement dbStatement = (DBStatement)dbStatementCache.get(tran); return (dbStatement != null) && dbStatement.exceedsBatchThreshold(); } } /** * Removes the db statement for the specified connection from the cache * and closes this statement. * @param tran the transaction */ public DBStatement removeDBStatement(Transaction tran) { synchronized (dbStatementCache) { DBStatement s = (DBStatement)dbStatementCache.remove(tran); return s; } } public void bindInputColumns(DBStatement s, UpdateObjectDescImpl updateDesc) throws SQLException { // bind set clause (if necessary) for (Iterator i = getColumnRefs().iterator(); i.hasNext(); ) { bindInputColumn(s, (ColumnRef)i.next(), updateDesc, false ); } // bind where clause (if necessary) for (Iterator i = columnRefsForWhereClause.iterator(); i.hasNext(); ) { bindInputColumn(s, (ColumnRef) i.next(), updateDesc, updateDesc.isBeforeImageRequired()); } } /** * Binds the value in the specified update descriptor corresponding * with the specified column reference to the specified statement. * @param stmt the statement * @param columnRef the column reference * @param updateDesc the update descriptor * @throws SQLException thrown by setter methods on java.sql.PreparedStatement */ private void bindInputColumn(DBStatement stmt, ColumnRef columnRef, UpdateObjectDescImpl updateDesc, boolean getBeforeValue) throws SQLException { Object inputValue = getInputValue(updateDesc, columnRef, getBeforeValue); stmt.bindInputColumn(columnRef.getIndex(), inputValue, columnRef.getColumnElement(), vendorType); } /** * Get Input values to be bound to this statement. * @param updateDesc The update descriptor. * @return An Object array containing input values to be bound to this statement. */ private Object[] getInputValues(UpdateObjectDescImpl updateDesc) { Object[] inputValues = new Object[getColumnRefs().size() + columnRefsForWhereClause.size()]; for (Iterator i = getColumnRefs().iterator(); i.hasNext(); ) { ColumnRef columnRef = (ColumnRef)i.next(); // columnRef's index are 1 based. inputValues[columnRef.getIndex() - 1] = getInputValue(updateDesc, columnRef, false); } final boolean getBeforeValue = updateDesc.isBeforeImageRequired(); for (Iterator i = columnRefsForWhereClause.iterator(); i.hasNext(); ) { ColumnRef columnRef = (ColumnRef)i.next(); inputValues[columnRef.getIndex() - 1] = getInputValue(updateDesc, columnRef, getBeforeValue); } return inputValues; } /** * Gets formatted sql text corrsponding to this statement object. The text * also contains values for input to the statement. * @param updateDesc the updateDesc. * @return formatted sql text corrsponding to this statement object. */ public String getFormattedSQLText(UpdateObjectDescImpl updateDesc) { return formatSqlText(getText(), getInputValues(updateDesc)); } /** * Gets input value corrsponding to given columnRef from given updateDesc * @param updateDesc updateDesc pointing to the input values * @param columnRef The columnRef. It always contains * the <code>LocalFieldDesc</code> for the field. * @param getBeforeValue If true, value returned is fetched from beforeImage * if false, the value returned is fetched from afterImage. * @return input value corrsponding to given columnRef from given updateDesc. */ private static Object getInputValue(UpdateObjectDescImpl updateDesc, ColumnRef columnRef, boolean getBeforeValue) { Object value; LocalFieldDesc field = (LocalFieldDesc) columnRef.getValue(); if (field.isVersion()) { // Bind the value from the after image for version fields, // as they're incremented internally after each flush. // Version fields must not be modified from "outside". value = updateDesc.getAfterValue(field); } else { value = getBeforeValue ? updateDesc.getBeforeValue(field) : updateDesc.getAfterValue(field); } return value; } public void addVersionColumn(ColumnElement versionColumn) { if (versionColumns == null) { versionColumns = new ArrayList(); } versionColumns.add(versionColumn); } }