/* * 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. */ /* * Statement.java * * Created on March 3, 2000 * */ 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.database.DBVendorType; import com.sun.jdo.spi.persistence.support.sqlstore.model.LocalFieldDesc; import com.sun.jdo.spi.persistence.support.sqlstore.sql.constraint.*; import org.glassfish.persistence.common.I18NHelper; import java.sql.SQLException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.ResourceBundle; /** * This class is used to represent a SQL statement. */ public abstract class Statement extends Object implements Cloneable { private static final Integer ONE = new Integer(1); protected static final int OP_PREFIX_MASK = 0x001; // 1 protected static final int OP_INFIX_MASK = 0x002; // 2 protected static final int OP_POSTFIX_MASK = 0x004; // 4 protected static final int OP_PAREN_MASK = 0x008; // 8 protected static final int OP_ORDERBY_MASK = 0x010; // 16 protected static final int OP_WHERE_MASK = 0x020; // 32 protected static final int OP_IRREGULAR_MASK = 0x040; // 64 protected static final int OP_OTHER_MASK = 0x080; // 128 protected static final int OP_PARAM_MASK = 0x100; // 256 protected static final int OP_BINOP_MASK = 2 * OP_PARAM_MASK | OP_WHERE_MASK | OP_INFIX_MASK; // 546 protected static final int OP_FUNC_MASK = OP_PARAM_MASK | OP_PREFIX_MASK | OP_PAREN_MASK | OP_WHERE_MASK; // 297 protected static final int OP_PCOUNT_MASK = 3 * OP_PARAM_MASK; // 768 protected StringBuffer statementText; private String quoteCharStart; private String quoteCharEnd; /** array of ColumnRef */ protected ArrayList columns; Constraint constraint; protected InputDesc inputDesc; int action; /** array of QueryTable */ public ArrayList tableList; protected DBVendorType vendorType; protected ArrayList secondaryTableStatements; /** * I18N message handler */ protected final static ResourceBundle messages = I18NHelper.loadBundle( "com.sun.jdo.spi.persistence.support.sqlstore.Bundle", // NOI18N Statement.class.getClassLoader()); public Statement(DBVendorType vendorType) { inputDesc = new InputDesc(); columns = new ArrayList(); constraint = new Constraint(); tableList = new ArrayList(); this.vendorType = vendorType; if (vendorType.getQuoteSpecialOnly() == false) { // DO NOT SUPPORT QUOTING OTHERWISE this.quoteCharStart = vendorType.getQuoteCharStart(); this.quoteCharEnd = vendorType.getQuoteCharEnd(); } } public void addQueryTable(QueryTable table) { // // First check to make sure this is not a duplicate. // if (tableList.indexOf(table) == -1) { tableList.add(table); } } protected ColumnRef getColumnRef(ColumnElement columnElement) { // // Check whether this column has already been added. // If so, simply return. // int size = columns.size(); for (int i = 0; i < size; i++) { ColumnRef cref = (ColumnRef) columns.get(i); if (cref.getColumnElement() == columnElement) return cref; } return null; } protected void addColumnRef(ColumnRef columnRef) { columnRef.setIndex(columns.size()+1); columns.add(columnRef); } /** * Adds a comparison on local field <CODE>lf</CODE> and value <CODE>value</CODE>. */ public void addConstraint(LocalFieldDesc lf, Object value) { int operation; if (value == null) { operation = ActionDesc.OP_NULL; } else { constraint.addValue(value, lf); if (lf.isPrimitiveMappedToNullableColumn() || (vendorType.mapEmptyStringToNull() && lf.getType() == String.class)) { // Primitive fields mapped to nullable columns might hold // a default value indicting that the actual db column is null. // Databases might treat zero length Strings as null. operation = ActionDesc.OP_MAYBE_NULL; } else { // Object type fields or primitive fields // mapped to non nullable columns are compared exactly operation = ActionDesc.OP_EQ; } } constraint.addField(lf); constraint.addOperation(operation); } public DBVendorType getVendorType() { return vendorType; } public void appendTableText(StringBuffer text, QueryTable table) { appendQuotedText(text, table.getTableDesc().getName()); text.append(" t"); // NOI18N text.append(table.getTableIndex()); } /** * Append <code>text</code> surrounded by quote char to <code>buffer</code> * @param buffer The given buffer * @param text The given text */ protected void appendQuotedText(StringBuffer buffer, String text) { buffer.append(quoteCharStart); buffer.append(text); buffer.append(quoteCharEnd); } /** * Returns the SQL text for the query described by this object. * * @return The text of the SQL query described by this object. */ public String getText() { if (statementText == null) { generateStatementText(); } return statementText.toString(); } /** * Generates the SQL text for the query described by this object. */ protected abstract void generateStatementText(); /** * Processes the constraint stack and adds it to the query. * This means turning the constraint stack into SQL text and * adding it to the where clause. * * @return Where clause based on the constraint stack. */ public StringBuffer processConstraints() { StringBuffer whereText = new StringBuffer(); List stack = constraint.getConstraints(); while (stack.size() > 0) { ConstraintNode node = (ConstraintNode) stack.get(stack.size() - 1); if (!(node instanceof ConstraintOperation)) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.generic.notinstanceof", // NOI18N node.getClass().getName(), "ConstraintOperation")); // NOI18N } processRootConstraint((ConstraintOperation) node, stack, whereText); } return whereText; } protected void processRootConstraint(ConstraintOperation opNode, List stack, StringBuffer whereText) { int op = opNode.operation; int opInfo = operationFormat(op); if ((opInfo & OP_WHERE_MASK) > 0) { String constraint = getWhereText(stack); if (whereText.length() > 0 && constraint.length() > 0) { // This is neccessary, if the constraint stack is "un-balanced", // see OrderingTest#ordering006 for an example. whereText.append(" and "); } whereText.append(constraint); } else { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "sqlstore.sql.generator.statement.unexpectedconstraint", op)); // NOI18N } } /** * Get QueryPlan for this statement * @return QueryPlan for this statement */ public abstract QueryPlan getQueryPlan(); /** * Constructs the where clause for the statement from * the constraint stack. * * @param stack * The stack parameter holds the constraint stack to be decoded. * * RESOLVE: We don't support constraints on multiple statements yet. * We would need to sort constraints out by statement and do something * about constraints that span statements (e.g. t1.c1 = t2.c2). */ protected String getWhereText(List stack) { StringBuffer result = new StringBuffer(); ConstraintNode node; if (stack.size() == 0) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.constraint.stackempty")); // NOI18N } node = (ConstraintNode) stack.get(stack.size() - 1); stack.remove(stack.size() - 1); if (node instanceof ConstraintParamIndex) { processConstraintParamIndex((ConstraintParamIndex) node, result); } else if (node instanceof ConstraintValue) { processConstraintValue((ConstraintValue) node, result); } else if (node instanceof ConstraintField) { processConstraintField((ConstraintField) node, result); } else if (node instanceof ConstraintConstant) { result.append(((ConstraintConstant) node).value.toString()); } else if (node instanceof ConstraintOperation) { processConstraintOperation((ConstraintOperation) node, stack, result); } else { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.constraint.illegalnode", // NOI18N node.getClass().getName())); } return result.toString(); } protected void processConstraintParamIndex(ConstraintParamIndex node, StringBuffer result) { // DB2 requires cast for parameter markers involved in numeric expressions. result.append(vendorType.getParameterMarker(node.getType())); Integer index = node.getIndex(); inputDesc.values.add(new InputParamValue(index, getColumnElementForValueNode(node) )); } protected void processConstraintValue(ConstraintValue node, StringBuffer result) { boolean generateValueInSQLStatement = false; String strToAppend = "?"; // NOI18N // DB2 requires cast for parameter markers involved in numeric expressions. // For DB2, do not generate parameter markers, but inline values in generated SQL. // TODO: Inline numerics for all the databases not just DB2. if (vendorType.isInlineNumeric()) { // TODO: Ask Michael to pass on type for values also as enums. // Suggestion : Convert FieldTypeEnumeration to a class. Implement methods like // isNumeric(int type) for following if condition Object value = node.getValue(); if (value != null && value instanceof Number) { generateValueInSQLStatement = true; strToAppend = value.toString(); } } result.append(strToAppend); if(!generateValueInSQLStatement) { // We have added a "?" to sql. Add value to inputDesc generateInputValueForConstraintValueNode(node); } } protected QueryPlan getOriginalPlan(ConstraintField fieldNode) { return (fieldNode.originalPlan != null) ? fieldNode.originalPlan : getQueryPlan(); } private void processConstraintField(ConstraintField fieldNode, StringBuffer result) { LocalFieldDesc desc = null; QueryPlan thePlan = getOriginalPlan(fieldNode); if (fieldNode instanceof ConstraintFieldDesc) { desc = ((ConstraintFieldDesc) fieldNode).desc; } else if (fieldNode instanceof ConstraintFieldName) { desc = thePlan.config.getLocalFieldDesc(((ConstraintFieldName) fieldNode).name); } else { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.generic.notinstanceof", // NOI18N fieldNode.getClass().getName(), "ConstraintFieldDesc/ConstraintFieldName")); // NOI18N } generateColumnText(desc, thePlan, result); } /** * Generates the column text for field <code>desc</code>. * The column has to be associated to the corresponding query table * from the list <code>tableList</code>. * For fields mapped to multiple columns choose one column to be included, * as all mapped columns should have the same value. * * @param desc Local field descriptor to be included in the constraint text. * @param thePlan Query plan corresponding to <code>desc</code>. * @param sb String buffer taking the resulting text. */ protected void generateColumnText(LocalFieldDesc desc, QueryPlan thePlan, StringBuffer sb) { QueryTable table = null; ColumnElement column = null; Iterator iter = desc.getColumnElements(); while (iter.hasNext() && table == null) { column = (ColumnElement) iter.next(); // For updates, the member variable tableList is complete // at this point and includes only the table being updated. // For selects, new tables are still added to tableList // when join constraints are processed. Take the table list // from the query plan to find the table matching the column. if (action == QueryPlan.ACT_SELECT) { table = thePlan.findQueryTable(column.getDeclaringTable()); } else { table = findQueryTable(column.getDeclaringTable()); } } if (table == null) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.configuration.fieldnotable", // NOI18N desc.getName())); } // Select statements might include columns from several tables. // Qualify the column with the table index. if (action == QueryPlan.ACT_SELECT) { sb.append("t").append(table.getTableIndex()).append("."); // NOI18N } appendQuotedText(sb, column.getName().getName()); } /** * Matches the table element <code>tableElement</code> to the * corresponding query table from the list <code>tableList</code>. * * @param tableElement Table element to be found. * @return Query table object corresponding to table element. * @see QueryPlan#findQueryTable(TableElement) */ protected QueryTable findQueryTable(TableElement tableElement) { QueryTable table = null; for (Iterator iter = tableList.iterator(); iter.hasNext() && table == null; ) { QueryTable t = (QueryTable) iter.next(); if (t.getTableDesc().getTableElement() == tableElement) { // if (t.getTableDesc().getTableElement().equals(tableElement)) { table = t; } } return table; } private void processConstraintOperation(ConstraintOperation opNode, List stack, StringBuffer result) { int opCode = opNode.operation; int format = operationFormat(opCode); if ((format & OP_IRREGULAR_MASK) == 0) { processFunctionOrBinaryOperation(format, opCode, stack, result); } else { processIrregularOperation(opNode, opCode, stack, result); } } private void processFunctionOrBinaryOperation(int format, int opCode, List stack, StringBuffer result) { if ((format & OP_PREFIX_MASK) > 0) { result.append(prefixOperator(opCode)); } if ((format & OP_PCOUNT_MASK) > 0) { if ((format & OP_PAREN_MASK) > 0) { result.append("("); // NOI18N } result.append(getWhereText(stack)); for (int i = 0; i < ((format & OP_PCOUNT_MASK) / OP_PARAM_MASK) - 1; i++) { if ((format & OP_INFIX_MASK) > 0) { // opCode for which OP_BINOP_MASK is set result.append(infixOperator(opCode, i - 1)); } else { // opCode for which OP_FUNC_MASK is set result.append(", "); // NOI18N } result.append(getWhereText(stack)); } if ((format & OP_PAREN_MASK) > 0) { result.append(")"); // NOI18N } } if ((format & OP_POSTFIX_MASK) > 0) { result.append(postfixOperator(opCode)); } } protected void processIrregularOperation(ConstraintOperation opNode, int opCode, List stack, StringBuffer result) { switch (opCode) { case ActionDesc.OP_NULL: case ActionDesc.OP_NOTNULL: processNullOperation(opCode, stack, result); break; case ActionDesc.OP_BETWEEN: if (stack.size() < 3) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.constraint.stackempty")); // NOI18N } if (!(stack.get(stack.size() - 1) instanceof ConstraintField)) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.constraint.needfieldnode")); // NOI18N } else { result.append(getWhereText(stack)); result.append(" between "); // NOI18N } result.append(getWhereText(stack)); result.append(" and "); result.append(getWhereText(stack)); break; case ActionDesc.OP_IN: case ActionDesc.OP_NOTIN: processInOperation(opCode, stack, result); break; case ActionDesc.OP_NOTEXISTS: case ActionDesc.OP_EXISTS: if (!(opNode instanceof ConstraintSubquery)) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.generic.notinstanceof", // NOI18N opNode.getClass().getName(), "ConstraintSubquery")); // NOI18N } ConstraintSubquery sqNode = (ConstraintSubquery) opNode; result.append(prefixOperator(opCode)); result.append("("); // NOI18N Statement sqstmt = (Statement) sqNode.plan.statements.get(0); result.append(sqstmt.getText()); result.append(")"); // NOI18N break; case ActionDesc.OP_LIKE_ESCAPE: if (stack.size() < 3) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.constraint.stackempty")); // NOI18N } if (vendorType.supportsLikeEscape()) { if (!(stack.get(stack.size() - 1) instanceof ConstraintField)) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.constraint.needfieldnode")); // NOI18N } else { result.append(getWhereText(stack)); result.append(" LIKE "); // NOI18N } result.append(getWhereText(stack)); result.append(vendorType.getLeftLikeEscape()); result.append(" ESCAPE "); // NOI18N result.append(getWhereText(stack)); result.append(vendorType.getRightLikeEscape()); } else { throw new JDOFatalInternalException( I18NHelper.getMessage(messages, "sqlstore.sql.generator.statement.likeescapenotsupported")); //NOI18N } break; case ActionDesc.OP_SUBSTRING: if (stack.size() < 3) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.constraint.stackempty")); // NOI18N } result.append(vendorType.getSubstring()); result.append("("); // NOI18N result.append(getWhereText(stack)); result.append(vendorType.getSubstringFrom()); result.append(getWhereText(stack)); result.append(vendorType.getSubstringFor()); result.append(getWhereText(stack)); result.append(")"); // NOI18N break; case ActionDesc.OP_POSITION: if (stack.size() < 2) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.constraint.stackempty")); // NOI18N } result.append(vendorType.getPosition()); result.append("("); // NOI18N boolean swap = vendorType.isPositionSearchSource(); if (swap) { ConstraintNode expr = (ConstraintNode)stack.remove(stack.size() - 1); ConstraintNode pattern = (ConstraintNode)stack.remove(stack.size() - 1); stack.add(expr); stack.add(pattern); } result.append(getWhereText(stack)); result.append(vendorType.getPositionSep()); result.append(" ").append(getWhereText(stack)); // NOI18N result.append(")"); // NOI18N break; case ActionDesc.OP_POSITION_START: if (stack.size() < 3) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.constraint.stackempty")); // NOI18N } boolean swapArgs = vendorType.isPositionSearchSource(); boolean threeArgs = vendorType.isPositionThreeArgs(); if (threeArgs) { if (swapArgs) { ConstraintNode expr = (ConstraintNode)stack.remove(stack.size() - 1); ConstraintNode pattern = (ConstraintNode)stack.remove(stack.size() - 1); stack.add(expr); stack.add(pattern); } result.append(vendorType.getPosition()); result.append("("); // NOI18N result.append(getWhereText(stack)); result.append(vendorType.getPositionSep()); result.append(" ").append(getWhereText(stack)); // NOI18N result.append(vendorType.getPositionSep()); result.append(" ").append(getWhereText(stack)); // NOI18N result.append(")"); // NOI18N } else { //twoArgs ConstraintValue valueNode = (ConstraintValue)stack.remove(stack.size() - 3); if (valueNode != null && ONE.equals(valueNode.getValue())) { stack.add(new ConstraintOperation(ActionDesc.OP_POSITION)); result.append(getWhereText(stack)); } else { throw new JDOFatalInternalException( I18NHelper.getMessage(messages, "sqlstore.sql.generator.statement.positionthreeargsnotsupported")); // NOI18N } } break; case ActionDesc.OP_MAYBE_NULL: processMaybeNullOperation(stack, result); break; case ActionDesc.OP_MOD: if (stack.size() < 2) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.constraint.stackempty")); // NOI18N } result.append(prefixOperator(opCode)); result.append("("); // NOI18N result.append(getWhereText(stack)); result.append(", "); // NOI18N result.append(getWhereText(stack)); result.append(")"); // NOI18N break; case ActionDesc.OP_CONCAT: processConcatOperation(opCode, stack, result); break; default: throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.constraint.illegalop", // NOI18N "" + opCode)); // NOI18N } } private void processConcatOperation(int opCode, List stack, StringBuffer result) { if (stack.size() < 2) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.constraint.stackempty")); // NOI18N } String concatCast = vendorType.getConcatCast(); if (concatCast.length() != 0) { result.append(concatCast); //Opening brace for concat cast result.append("( "); // NOI18N } // Concat is a binary infix operator. Process it manually here. // Resolve: Why should CONCAT operation be inside braces. // Opening brace around CONCAT operation result.append("( "); // NOI18N result.append(getWhereText(stack)); result.append(infixOperator(opCode, 0)); result.append(getWhereText(stack)); // Closing brace around CONCAT operation result.append(" ) "); // NOI18N if (concatCast.length() != 0) { // Closing brace for concat cast result.append(" ) "); // NOI18N } } private void processMaybeNullOperation(List stack, StringBuffer result) { ConstraintValue valueNode = null; ConstraintField fieldNode = null; if (stack.size() < 2) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.constraint.stackempty")); // NOI18N } if (!(stack.get(stack.size() - 1) instanceof ConstraintField)) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.constraint.needfieldnode")); // NOI18N } else { fieldNode = (ConstraintField) stack.get(stack.size() - 1); stack.remove(stack.size() - 1); } if (!(stack.get(stack.size() - 1) instanceof ConstraintValue)) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.constraint.needvalnode")); // NOI18N } else { valueNode = (ConstraintValue) stack.get(stack.size() - 1); stack.remove(stack.size() - 1); } Object value = valueNode.getValue(); if (value instanceof String) { String v = (String) value; if (v.length() == 0) { stack.add(fieldNode); stack.add(new ConstraintOperation(ActionDesc.OP_NULL)); } else { stack.add(valueNode); stack.add(fieldNode); stack.add(new ConstraintOperation(ActionDesc.OP_EQ)); } } else { stack.add(valueNode); stack.add(fieldNode); stack.add(new ConstraintOperation(ActionDesc.OP_EQ)); // // This takes care of the case where a nullable database column // is mapped to a primitive field. // This check adds a "OR numericColumn IS NULL" to the constraint. // See FieldDesc#setValue(StateManager, Object) for the conversions // done on binding a primitive field from the store. // // RESOLVE: Don't we have to do this in each OP_EQ comparison // involving primitive Fields? // boolean maybeNull = false; if ((value instanceof Number) && ((Number) value).doubleValue() == 0) { maybeNull = true; } else if ((value instanceof Boolean) && ((Boolean) value).booleanValue() == false) { maybeNull = true; } else if ((value instanceof Character) && ((Character) value).charValue() == '\0') { maybeNull = true; } if (maybeNull) { stack.add(fieldNode); stack.add(new ConstraintOperation(ActionDesc.OP_NULL)); stack.add(new ConstraintOperation(ActionDesc.OP_OR)); } } if (stack.size() > 0) { result.append(getWhereText(stack)); } } private void processNullOperation(int opCode, List stack, StringBuffer result) { String nullComparisionFunctionName = vendorType.getNullComparisonFunctionName(); if( nullComparisionFunctionName.length() != 0) { // Null comparision for LOB type fields is // through function for this DB Object nextNode = stack.get(stack.size() - 1); if( nextNode != null) { if ( nextNode instanceof ConstraintFieldName) { ConstraintFieldName fieldNode = (ConstraintFieldName) nextNode; QueryPlan originalPlan = getQueryPlan(); if (fieldNode.originalPlan != null) { originalPlan = fieldNode.originalPlan; } LocalFieldDesc desc = (LocalFieldDesc) originalPlan.config.getField(fieldNode.name); if ( desc.isMappedToLob() ) { // Add a dummy ConstraintOperation Node corresponding // to null comparision func. stack.add (new ConstraintOperation( ActionDesc.OP_NULL_COMPARISION_FUNCTION) ); } } } else { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.constraint.stackempty")); // NOI18N } } result.append (getWhereText(stack)); String str = (opCode == ActionDesc.OP_NULL) ? vendorType.getIsNull() : vendorType.getIsNotNull(); result.append(str); } private void processInOperation(int opCode, List stack, StringBuffer result) { //We are trying to construct a where clause like following in the quotes here // where "(t0.field1, t0.field2, t0.field3,...) in // ( select t1.fld1, t1.fld2, t2.fld3,...where ....)" StringBuffer c = new StringBuffer(); if (stack.size() < 2) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.constraint.stackempty")); // NOI18N } //Append the first bracket "(" to the result result.append("("); // NOI18N if (!(stack.get(stack.size() - 1) instanceof ConstraintField)) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.constraint.needfieldnode")); // NOI18N } else { //append "t0.field1" to c c.append(getWhereText(stack)); } //Append ", t0.field2, t0fld3,...." while (stack.size() > 1 && (stack.get(stack.size() - 1) instanceof ConstraintField)) { c.replace(0, 0, ", "); // NOI18N c.replace(0, 0, getWhereText(stack)); } result.append(c.toString()); result.append(") "); // NOI18N if (opCode == ActionDesc.OP_NOTIN) { result.append("not "); // NOI18N } result.append("in ("); // NOI18N ConstraintNode currentNode = (ConstraintNode)stack.remove(stack.size() - 1); if ( ! ( currentNode instanceof ConstraintSubquery)) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.generic.notinstanceof", // NOI18N currentNode.getClass().getName(), "ConstraintSubquery")); // NOI18N } else { ConstraintSubquery sqnode = (ConstraintSubquery) currentNode; Statement sqstmt = (Statement) sqnode.plan.statements.get(0); //Append the subquery i.e. "select t1.fld1, t1.fld2, t2.fld3,...where ...." result.append(sqstmt.getText()); //Close the final bracket result.append(")"); // NOI18N //Append the Input values to the InputDesc of current statement inputDesc.values.addAll(sqstmt.inputDesc.values); } /* //This is old code that takes care of case when we just have a list of values //as the parameter to the IN clause. We might uncomment and enhance this code //when we implement the functionality to have list of values inside IN clause. if (!(stack.get(stack.size() - 1) instanceof ConstraintValue)) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.constraint.needvalnode")); // NOI18N } else { result.append(((ConstraintValue) stack.get(stack.size() - 1)).value.toString()); stack.remove(stack.size() - 1); } result.append(")"); // NOI18N */ } /** * Gets the column information for the field to which <code>node</code> * is bound. * @param node The input node. * @return Returns null if the node isn't bound to a field. Otherwise * returns ColumnElement for the primary column of the field to which the * node is bound. */ private static ColumnElement getColumnElementForValueNode(ConstraintValue node) { ColumnElement columnElement = null; LocalFieldDesc field = node.getLocalField(); if(field != null) { //For fields mapped to multiple columns, we assume //that all the columns have the same value in the database. //Hence we only use the primary column in where clause. columnElement = field.getPrimaryColumn(); } return columnElement; } /** * Generates InputValue for <code>node</code>. * @param node The input node. */ protected void generateInputValueForConstraintValueNode(ConstraintValue node) { inputDesc.values.add(new InputValue(node.getValue(), getColumnElementForValueNode(node) )); } //only operations end with ")" or nothing can be here, cf. getWhereText protected String infixOperator(int operation, int position) { // // InfixOperator // The InfixOperator method returns the SQL text for the operator specified // by the operation parameter. // // operation // The operation parameter specifies which operation for which to return // SQL text. Legal values are defined by the ConstraintOperation class. // StringBuffer result = new StringBuffer(); switch (operation) { case ActionDesc.OP_ADD: result.append(" + "); // NOI18N break; case ActionDesc.OP_AND: result.append(" and "); break; case ActionDesc.OP_DIV: result.append(" / "); // NOI18N break; case ActionDesc.OP_EQ: result.append(" = "); // NOI18N break; case ActionDesc.OP_GE: result.append(" >= "); // NOI18N break; case ActionDesc.OP_GT: result.append(" > "); // NOI18N break; case ActionDesc.OP_LE: result.append(" <= "); // NOI18N break; case ActionDesc.OP_LT: result.append(" < "); // NOI18N break; case ActionDesc.OP_NE: result.append(" "); result.append(vendorType.getNotEqual()); result.append(" "); break; case ActionDesc.OP_OR: result.append(" or "); // NOI18N break; case ActionDesc.OP_LIKE: result.append(" like "); // NOI18N break; case ActionDesc.OP_MUL: result.append(" * "); // NOI18N break; case ActionDesc.OP_SUB: result.append(" - "); // NOI1N8 break; case ActionDesc.OP_MOD: result.append(" % "); // NOI1N8 break; case ActionDesc.OP_BETWEEN: if (position == 1) { result.append(" between "); // NOI18N } else { result.append(" and "); } break; case ActionDesc.OP_CONCAT: result.append(vendorType.getStringConcat()); break; default: throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.constraint.illegalop", // NOI18N "" + operation)); // NOI18N } return result.toString(); } protected int operationFormat(int operation) { int format = 0; switch (operation) { // Binary operators case ActionDesc.OP_EQ: case ActionDesc.OP_NE: case ActionDesc.OP_GT: case ActionDesc.OP_GE: case ActionDesc.OP_LT: case ActionDesc.OP_LE: case ActionDesc.OP_AND: case ActionDesc.OP_MUL: case ActionDesc.OP_LIKE: format = OP_BINOP_MASK; break; // Binary operators that require Parenthesis case ActionDesc.OP_ADD: case ActionDesc.OP_SUB: case ActionDesc.OP_DIV: case ActionDesc.OP_OR: format = OP_BINOP_MASK | OP_PAREN_MASK; break; // Functions case ActionDesc.OP_ABS: case ActionDesc.OP_SQRT: case ActionDesc.OP_LENGTH: case ActionDesc.OP_LTRIM: case ActionDesc.OP_NOT: case ActionDesc.OP_NULL_COMPARISION_FUNCTION: format = OP_FUNC_MASK; break; // Irregular operators case ActionDesc.OP_BETWEEN: case ActionDesc.OP_LIKE_ESCAPE: case ActionDesc.OP_IN: case ActionDesc.OP_NOTIN: case ActionDesc.OP_NULL: case ActionDesc.OP_NOTNULL: case ActionDesc.OP_MAYBE_NULL: case ActionDesc.OP_CONCAT: case ActionDesc.OP_EQUIJOIN: format = OP_IRREGULAR_MASK | OP_WHERE_MASK; break; // Irregular operators that can never be at the root of a where clause. // Hence they do not have OP_WHERE_MASK set. case ActionDesc.OP_SUBSTRING: case ActionDesc.OP_POSITION: case ActionDesc.OP_POSITION_START: format = OP_IRREGULAR_MASK; break; case ActionDesc.OP_NOTEXISTS: case ActionDesc.OP_EXISTS: format = OP_IRREGULAR_MASK | OP_WHERE_MASK | OP_PREFIX_MASK; break; case ActionDesc.OP_MOD: format = vendorType.isModOperationUsingFunction() ? OP_IRREGULAR_MASK : OP_BINOP_MASK | OP_PAREN_MASK; break; case ActionDesc.OP_DISTINCT: format = OP_OTHER_MASK; break; case ActionDesc.OP_ORDERBY: case ActionDesc.OP_ORDERBY_DESC: format = OP_ORDERBY_MASK; break; case ActionDesc.OP_RTRIM: case ActionDesc.OP_RTRIMFIXED: if (vendorType.isAnsiTrim()) { format = OP_WHERE_MASK | OP_PREFIX_MASK | OP_POSTFIX_MASK | OP_PARAM_MASK; } else { format = OP_FUNC_MASK; } break; //TODO: Check how can this masks be optimized case ActionDesc.OP_LEFTJOIN: format = OP_IRREGULAR_MASK | OP_WHERE_MASK | OP_INFIX_MASK; format = format | OP_POSTFIX_MASK; break; case ActionDesc.OP_RIGHTJOIN: format = OP_IRREGULAR_MASK | OP_WHERE_MASK | OP_INFIX_MASK; format = format | OP_PREFIX_MASK; break; default: throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.constraint.illegalop", // NOI18N "" + operation)); // NOI18N } return format; } protected String postfixOperator(int operation) { // // PostfixOperator // The PostfixOperator method returns the SQL text for the operator specified // by the operation parameter. // // operation // The operation parameter specifies which operation for which to return // SQL text. Legal values are defined by the ConstraintOperation class. // StringBuffer result = new StringBuffer(); switch (operation) { case ActionDesc.OP_RTRIM: result.append(vendorType.getRtrimPost()); break; case ActionDesc.OP_RTRIMFIXED: result.append(postfixOperator(ActionDesc.OP_RTRIM)); break; case ActionDesc.OP_LEFTJOIN: result.append(vendorType.getLeftJoinPost()); break; default: throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.constraint.illegalop", // NOI18N "" + operation)); // NOI18N } return result.toString(); } protected String prefixOperator(int operation) { // // PrefixOperator // The PrefixOperator method returns the SQL text for the operator specified // by the operation parameter. // // operation // The operation parameter specifies which operation for which to return // SQL text. Legal values are defined by the ConstraintOperation class. // StringBuffer result = new StringBuffer(); switch (operation) { case ActionDesc.OP_ABS: result.append(vendorType.getAbs()); // NOI18N break; case ActionDesc.OP_LENGTH: result.append(vendorType.getCharLength()); // NOI18N break; case ActionDesc.OP_RTRIM: result.append(vendorType.getRtrim()); break; case ActionDesc.OP_RTRIMFIXED: result.append(prefixOperator(ActionDesc.OP_RTRIM)); break; case ActionDesc.OP_NOT: result.append("not "); // NOI18N break; case ActionDesc.OP_SQRT: result.append(vendorType.getSqrt()); break; case ActionDesc.OP_NOTEXISTS: result.append("not exists "); // NOI18N break; case ActionDesc.OP_EXISTS: result.append("exists "); // NOI18N break; case ActionDesc.OP_NULL_COMPARISION_FUNCTION: result.append(vendorType.getNullComparisonFunctionName()); break; case ActionDesc.OP_MOD: result.append(vendorType.getModFunctionName()); // NOI18N break; default: throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.constraint.illegalop", "" + operation)); // NOI18N } return result.toString(); } public void addSecondaryTableStatement(Statement s) { if (s == null) return; if (secondaryTableStatements == null) secondaryTableStatements = new ArrayList(); secondaryTableStatements.add(s); } public ArrayList getSecondaryTableStatements() { return secondaryTableStatements; } public ArrayList getQueryTables() { return tableList; } public ArrayList getColumnRefs() { return columns; } public void setAction(int action) { this.action = action; } public int getAction() { return action; } public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { // // shouldn't happen. // return null; } } /** * Binds input valus corrsponding to this <code>Statement</code> object to * database statement s. * @param s The database statement * @throws SQLException */ public void bindInputValues(DBStatement s) throws SQLException { for (int i = 0, size = inputDesc.values.size(); i < size; i++) { InputValue inputVal = (InputValue) inputDesc.values.get(i); s.bindInputColumn(i + 1, inputVal.getValue(), inputVal.getColumnElement(), vendorType); } } /** * Gets input values corrsponding to this <code>Statement</code>. * @return An Object array containing input values. */ private Object[] getInputValues() { final int size = inputDesc.values.size(); Object[] inputValues = new Object[size]; for (int i = 0; i < size; i++) { InputValue inputValue = (InputValue) inputDesc.values.get(i); inputValues[i] = inputValue.getValue(); } return inputValues; } /** * Gets formatted sql text corrsponding to this statement object. The text * also contains values for input to the statement. * @return formatted sql text corrsponding to this statement object. */ public String getFormattedSQLText() { return formatSqlText(getText(), getInputValues()); } /** * The formatSqlText method returns a string containing the text of * the SQL statement about to be executed and the input values for the * placeholders. * @param sqlText Specifies the text of the SQL statement to be executed. * @param input Holds the input values used for the SQL statement. * @return The SQL text and the input values formatted into a printable * string. */ static protected String formatSqlText(String sqlText, Object[] input) { StringBuffer str = new StringBuffer(); str.append(I18NHelper.getMessage(messages, "sqlstore.sql.generator.statement.sqlStatement") ); //NOI18N str.append("<").append(sqlText).append("> "); // NOI18N if (input != null && input.length > 0) { str.append(I18NHelper.getMessage(messages, "sqlstore.sql.generator.statement.withinputvalues")); // NOI18N for (int i = 0; i < input.length; i++) { if (i > 0) { str.append(", "); // NOI18N } Object inputValue = input[i]; if (inputValue == null) { str.append("<null>"); // NOI18N } else { str.append(inputValue.getClass().getName()); str.append(":"); // NOI18N str.append(inputValue.toString()); } } } else { str.append(I18NHelper.getMessage(messages, "sqlstore.sql.generator.statement.withnoinputvalues")); // NOI18N } return str.toString(); } }