FileDocCategorySizeDatePackage
UpdateStatement.javaAPI DocGlassfish v2 API17666Fri May 04 22:35:16 BST 2007com.sun.jdo.spi.persistence.support.sqlstore.sql.generator

UpdateStatement.java

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. 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.html
 * or glassfish/bootstrap/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 glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [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);
    }
}