Modified: incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ParameterNode.java URL: http://svn.apache.org/viewcvs/incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ParameterNode.java?rev=189721&r1=189720&r2=189721&view=diff ============================================================================== --- incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ParameterNode.java (original) +++ incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ParameterNode.java Wed Jun 8 23:48:34 2005 @@ -41,6 +41,7 @@ import org.apache.derby.iapi.sql.ParameterValueSet; import org.apache.derby.iapi.sql.Activation; import org.apache.derby.iapi.reference.ClassName; +import org.apache.derby.iapi.reference.SQLState; import org.apache.derby.iapi.sql.conn.LanguageConnectionContext; @@ -379,6 +380,18 @@ MethodBuilder mb) throws StandardException { + DataTypeDescriptor dtd = getTypeServices(); + if ((dtd != null) && dtd.getTypeId().isXMLTypeId()) { + // We're a parameter that corresponds to an XML column/target, + // which we don't allow. We throw the error here instead of + // in "bindExpression" because at the time of bindExpression, + // we don't know yet what the type is going to be (only when + // the node that points to this parameter calls + // "setDescriptor" do we figure out the type). + throw StandardException.newException( + SQLState.LANG_ATTEMPT_TO_BIND_XML); + } + // PUSHCOMPILE /* Reuse code if possible */ //if (genRetval != null) @@ -402,7 +415,6 @@ // For some types perform host variable checking // to match DB2/JCC where if a host variable is too // big it is not accepted, regardless of any trailing padding. - DataTypeDescriptor dtd = getTypeServices(); switch (dtd.getJDBCTypeId()) { case Types.BINARY: Modified: incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/QueryTreeNode.java URL: http://svn.apache.org/viewcvs/incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/QueryTreeNode.java?rev=189721&r1=189720&r2=189721&view=diff ============================================================================== --- incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/QueryTreeNode.java (original) +++ incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/QueryTreeNode.java Wed Jun 8 23:48:34 2005 @@ -33,6 +33,7 @@ import org.apache.derby.iapi.services.compiler.MethodBuilder; import org.apache.derby.iapi.services.monitor.Monitor; import org.apache.derby.iapi.services.sanity.SanityManager; +import org.apache.derby.iapi.services.io.StoredFormatIds; import org.apache.derby.iapi.error.StandardException; import org.apache.derby.iapi.sql.compile.CompilerContext; import org.apache.derby.iapi.sql.compile.NodeFactory; @@ -976,6 +977,13 @@ case Types.BLOB: constantNode = nf.getNode( C_NodeTypes.BLOB_CONSTANT_NODE, + typeId, + cm); + break; + + case StoredFormatIds.XML_TYPE_ID: + constantNode = nf.getNode( + C_NodeTypes.XML_CONSTANT_NODE, typeId, cm); break; Modified: incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ReadCursorNode.java URL: http://svn.apache.org/viewcvs/incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ReadCursorNode.java?rev=189721&r1=189720&r2=189721&view=diff ============================================================================== --- incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ReadCursorNode.java (original) +++ incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ReadCursorNode.java Wed Jun 8 23:48:34 2005 @@ -81,6 +81,10 @@ // types for this node resultSet.bindUntypedNullsToResultColumns(null); + // Reject any XML values in the select list; JDBC doesn't + // define how we bind these out, so we don't allow it. + resultSet.rejectXMLValues(); + /* Verify that all underlying ResultSets reclaimed their FromList */ if (SanityManager.DEBUG) { Modified: incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ResultColumnList.java URL: http://svn.apache.org/viewcvs/incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ResultColumnList.java?rev=189721&r1=189720&r2=189721&view=diff ============================================================================== --- incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ResultColumnList.java (original) +++ incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ResultColumnList.java Wed Jun 8 23:48:34 2005 @@ -120,6 +120,10 @@ // Is a count mismatch allowed - see set/get methods for details. private boolean countMismatchAllowed; + // Number of RCs in this RCL at "init" time, before additional + // ones were added internally. + private int initialListSize = 0; + public ResultColumnList() { } @@ -1472,6 +1476,11 @@ { insertElementAt(allExpansion.elementAt(inner), index + inner); } + + // If the rc was a "*", we need to set the initial list size + // to the number of columns that are actually returned to + // the user. + markInitialSize(); } else { @@ -1953,6 +1962,71 @@ } /** + * Check for (and reject) XML values directly under the ResultColumns. + * This is done for SELECT/VALUES statements. We reject values + * in this case because JDBC does not define an XML type/binding + * and thus there's no standard way to pass such a type back + * to a JDBC application. + * + * Note that we DO allow an XML column in a top-level RCL + * IF that column was added to the RCL by _us_ instead of + * by the user. For example, if we have a table: + * + * create table t1 (i int, x xml) + * + * and the user query is: + * + * select i from t1 order by x + * + * the "x" column will be added (internally) to the RCL + * as part of ORDER BY processing--and so we need to + * allow that XML column to be bound without throwing + * an error. If, as in this case, the XML column reference + * is invalid (we can't use ORDER BY on an XML column because + * XML values aren't ordered), a more appropriate error + * message should be returned to the user in later processing. + * If we didn't allow for this, the user would get an + * error saying that XML columns are not valid as part + * of the result set--but as far as s/he knows, there + * isn't such a column: only "i" is supposed to be returned + * (the RC for "x" was added to the RCL by _us_ as part of + * ORDER BY processing). + * + * ASSUMPTION: Any RCs that are generated internally and + * added to this RCL (before this RCL is bound) are added + * at the _end_ of the list. If that's true, then any + * RC with an index greater than the size of the initial + * (user-specified) list must have been added internally + * and will not be returned to the user. + * + * @return Nothing + * + * @exception StandardException Thrown if an XML value found + * directly under a ResultColumn + */ + void rejectXMLValues() throws StandardException + { + int sz = size(); + ResultColumn rc = null; + for (int i = 1; i <= sz; i++) { + + if (i > initialListSize) + // this RC was generated internally and will not + // be returned to the user, so don't throw error. + continue; + + rc = getResultColumn(i); + if ((rc != null) && (rc.getType() != null) && + rc.getType().getTypeId().isXMLTypeId()) + { // Disallow it. + throw StandardException.newException( + SQLState.LANG_ATTEMPT_TO_SELECT_XML); + } + + } + } + + /** * Set the resultSetNumber in all of the ResultColumns. * * @param resultSetNumber The resultSetNumber @@ -3947,4 +4021,15 @@ { orderBySelect = src.orderBySelect; } + + /* **** + * Take note of the size of this RCL _before_ we start + * processing/binding it. This is so that, at bind time, + * we can tell if any columns in the RCL were added + * internally by us (i.e. they were not specified by the + * user and thus will not be returned to the user). + */ + protected void markInitialSize() { + initialListSize = size(); + } } Modified: incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ResultSetNode.java URL: http://svn.apache.org/viewcvs/incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ResultSetNode.java?rev=189721&r1=189720&r2=189721&view=diff ============================================================================== --- incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ResultSetNode.java (original) +++ incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ResultSetNode.java Wed Jun 8 23:48:34 2005 @@ -991,6 +991,26 @@ } /** + * Check for (and reject) XML values directly under the ResultColumns. + * This is done for SELECT/VALUES statements. We reject values + * in this case because JDBC does not define an XML type/binding + * and thus there's no standard way to pass such a type back + * to a JDBC application. + * + * @return Nothing + * + * @exception StandardException Thrown if an XML value found + * directly under a ResultColumn + */ + public void rejectXMLValues() throws StandardException + { + if (resultColumns != null) + { + resultColumns.rejectXMLValues(); + } + } + + /** * Rename generated result column names as '1', '2' etc... These will be the result * column names seen by JDBC clients. */ Modified: incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/RowResultSetNode.java URL: http://svn.apache.org/viewcvs/incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/RowResultSetNode.java?rev=189721&r1=189720&r2=189721&view=diff ============================================================================== --- incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/RowResultSetNode.java (original) +++ incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/RowResultSetNode.java Wed Jun 8 23:48:34 2005 @@ -80,6 +80,8 @@ { super.init(null, tableProperties); resultColumns = (ResultColumnList) valuesClause; + if (resultColumns != null) + resultColumns.markInitialSize(); } /** Modified: incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/SelectNode.java URL: http://svn.apache.org/viewcvs/incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/SelectNode.java?rev=189721&r1=189720&r2=189721&view=diff ============================================================================== --- incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/SelectNode.java (original) +++ incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/SelectNode.java Wed Jun 8 23:48:34 2005 @@ -132,6 +132,8 @@ * Consider adding selectAggregates and whereAggregates */ resultColumns = (ResultColumnList) selectList; + if (resultColumns != null) + resultColumns.markInitialSize(); this.fromList = (FromList) fromList; this.whereClause = (ValueNode) whereClause; this.originalWhereClause = (ValueNode) whereClause; Modified: incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/TypeCompilerFactoryImpl.java URL: http://svn.apache.org/viewcvs/incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/TypeCompilerFactoryImpl.java?rev=189721&r1=189720&r2=189721&view=diff ============================================================================== --- incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/TypeCompilerFactoryImpl.java (original) +++ incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/TypeCompilerFactoryImpl.java Wed Jun 8 23:48:34 2005 @@ -29,6 +29,7 @@ import org.apache.derby.iapi.reference.JDBC20Translation; import org.apache.derby.iapi.reference.JDBC30Translation; +import org.apache.derby.iapi.services.io.StoredFormatIds; import java.util.Properties; import java.sql.Types; @@ -65,6 +66,7 @@ static TypeCompiler blobTypeCompiler ; static TypeCompiler clobTypeCompiler ; static TypeCompiler nclobTypeCompiler ; + static TypeCompiler xmlTypeCompiler ; /** * Get a TypeCompiler corresponding to the given TypeId @@ -256,6 +258,13 @@ btc.setTypeId(typeId); return btc; } + + case StoredFormatIds.XML_TYPE_ID: + return xmlTypeCompiler = + getAnInstance(PACKAGE_NAME + "XMLTypeCompiler", + xmlTypeCompiler, + typeId); + } if (SanityManager.DEBUG) Modified: incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/UnaryOperatorNode.java URL: http://svn.apache.org/viewcvs/incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/UnaryOperatorNode.java?rev=189721&r1=189720&r2=189721&view=diff ============================================================================== --- incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/UnaryOperatorNode.java (original) +++ incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/UnaryOperatorNode.java Wed Jun 8 23:48:34 2005 @@ -28,10 +28,15 @@ import org.apache.derby.iapi.sql.dictionary.DataDictionary; import org.apache.derby.iapi.reference.SQLState; +import org.apache.derby.iapi.reference.ClassName; import org.apache.derby.iapi.error.StandardException; import org.apache.derby.iapi.services.sanity.SanityManager; import org.apache.derby.iapi.services.compiler.MethodBuilder; import org.apache.derby.iapi.services.compiler.LocalField; +import org.apache.derby.iapi.services.io.StoredFormatIds; + +import org.apache.derby.iapi.types.TypeId; +import org.apache.derby.iapi.types.DataTypeDescriptor; import java.lang.reflect.Modifier; import org.apache.derby.impl.sql.compile.ExpressionClassBuilder; @@ -39,6 +44,7 @@ import org.apache.derby.iapi.util.JBitSet; import org.apache.derby.iapi.services.classfile.VMOpcode; +import java.sql.Types; import java.util.Vector; /** @@ -50,10 +56,14 @@ * @author Jeff Lichtman */ -public abstract class UnaryOperatorNode extends ValueNode +public class UnaryOperatorNode extends ValueNode { String operator; String methodName; + int operatorType; + + String resultInterfaceType; + String receiverInterfaceType; /** * WARNING: operand may be NULL for COUNT(*). @@ -65,22 +75,87 @@ public final static int NOT = 3; public final static int IS_NULL = 4; + // At the time of adding XML support, it was decided that + // we should avoid creating new OperatorNodes where possible. + // So for the XML-related unary operators we just add the + // necessary code to _this_ class, similar to what is done in + // TernarnyOperatorNode. Subsequent unary operators (whether + // XML-related or not) should follow this example when + // possible. + + public final static int XMLPARSE_OP = 0; + public final static int XMLSERIALIZE_OP = 1; + + // NOTE: in the following 4 arrays, order + // IS important. + + static final String[] UnaryOperators = { + "xmlparse", + "xmlserialize" + }; + + static final String[] UnaryMethodNames = { + "XMLParse", + "XMLSerialize" + }; + + static final String[] UnaryResultTypes = { + ClassName.XMLDataValue, // XMLParse + ClassName.StringDataValue // XMLSerialize + }; + + static final String[] UnaryArgTypes = { + ClassName.StringDataValue, // XMLParse + ClassName.XMLDataValue // XMLSerialize + }; + + // Array to hold Objects that contain primitive + // args required by the operator method call. + private Object [] additionalArgs; + /** * Initializer for a UnaryOperatorNode * * @param operand The operand of the node - * @param operator The name of the operator - * @param methodName The name of the method to call for this operator + * @param operatorOrOpType Either 1) the name of the operator, + * OR 2) an Integer holding the operatorType for this operator. + * @param methodNameOrParams Either 1) name of the method + * to call for this operator, or 2) an array of Objects + * from which primitive method parameters can be + * retrieved. */ public void init( Object operand, - Object operator, - Object methodName) + Object operatorOrOpType, + Object methodNameOrAddedArgs) { this.operand = (ValueNode) operand; - this.operator = (String) operator; - this.methodName = (String) methodName; + if (operatorOrOpType instanceof String) { + // then 2nd and 3rd params are operator and methodName, + // respectively. + this.operator = (String) operatorOrOpType; + this.methodName = (String) methodNameOrAddedArgs; + this.operatorType = -1; + } + else { + // 2nd and 3rd params are operatorType and additional args, + // respectively. + if (SanityManager.DEBUG) { + SanityManager.ASSERT( + ((operatorOrOpType instanceof Integer) && + ((methodNameOrAddedArgs == null) || + (methodNameOrAddedArgs instanceof Object[]))), + "Init params in UnaryOperator node have the " + + "wrong type."); + } + this.operatorType = ((Integer) operatorOrOpType).intValue(); + this.operator = UnaryOperators[this.operatorType]; + this.methodName = UnaryMethodNames[this.operatorType]; + this.resultInterfaceType = UnaryResultTypes[this.operatorType]; + this.receiverInterfaceType = UnaryArgTypes[this.operatorType]; + this.additionalArgs = (Object[])methodNameOrAddedArgs; + } } /** @@ -91,6 +166,7 @@ public void init(Object operand) { this.operand = (ValueNode) operand; + this.operatorType = -1; } /** @@ -103,6 +179,7 @@ void setOperator(String operator) { this.operator = operator; + this.operatorType = -1; } /** @@ -125,6 +202,7 @@ void setMethodName(String methodName) { this.methodName = methodName; + this.operatorType = -1; } /** @@ -257,9 +335,103 @@ operand = operand.genSQLJavaSQLTree(); } + if (operatorType == XMLPARSE_OP) + bindXMLParse(); + else if (operatorType == XMLSERIALIZE_OP) + bindXMLSerialize(); + return this; } + /** + * Bind an XMLPARSE operator. Makes sure the operand type + * is correct, and sets the result type. + * + * @exception StandardException Thrown on error + */ + public void bindXMLParse() throws StandardException + { + // Check the type of the operand - this function is allowed only on + // string value (char) types. + TypeId operandType = operand.getTypeId(); + if (operandType != null) { + switch (operandType.getJDBCTypeId()) + { + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + case Types.CLOB: + break; + default: + { + throw StandardException.newException( + SQLState.LANG_UNARY_FUNCTION_BAD_TYPE, + methodName, + operandType.getSQLTypeName()); + } + } + } + + // The result type of XMLParse() is always an XML type. + setType(DataTypeDescriptor.getBuiltInDataTypeDescriptor( + StoredFormatIds.XML_TYPE_ID)); + } + + /** + * Bind an XMLSERIALIZE operator. Makes sure the operand type + * and target type are both correct, and sets the result type. + * + * @exception StandardException Thrown on error + */ + public void bindXMLSerialize() throws StandardException + { + TypeId operandType; + + // Check the type of the operand - this function is allowed only on + // the XML type. + operandType = operand.getTypeId(); + if ((operandType != null) && !operandType.isXMLTypeId()) + { + throw StandardException.newException( + SQLState.LANG_UNARY_FUNCTION_BAD_TYPE, + methodName, + operandType.getSQLTypeName()); + } + + // Check the target type. We only allow string types to be used as + // the target type. The targetType is stored as the first Object + // in our list of additional parameters, so we have to retrieve + // it from there. + if (SanityManager.DEBUG) { + SanityManager.ASSERT( + ((additionalArgs != null) && (additionalArgs.length > 0)), + "Failed to locate target type for XMLSERIALIZE operator"); + } + + DataTypeDescriptor targetType = + (DataTypeDescriptor)additionalArgs[0]; + + TypeId targetTypeId = targetType.getTypeId(); + switch (targetTypeId.getJDBCTypeId()) + { + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + case Types.CLOB: + break; + default: + { + throw StandardException.newException( + SQLState.LANG_INVALID_XMLSERIALIZE_TYPE, + targetTypeId.getSQLTypeName()); + } + } + + // The result type of XMLSerialize() is always a string; which + // kind of string is determined by the targetType field. + setType(targetType); + } + /** * Preprocess an expression tree. We do a number of transformations * here (including subqueries, IN lists, LIKE and BETWEEN) plus @@ -376,7 +548,22 @@ void bindParameter() throws StandardException { - if (operand.getTypeServices() == null) + if (operatorType == XMLPARSE_OP) { + // According to the SQL/XML standard, the XMLParse parameter + // takes a string operand. RESOLVE: We use CLOB here because + // an XML string can be arbitrarily long...is this okay? + // The SQL/XML spec doesn't state what the type of the param + // should be; only that it "shall be a character type". + ((ParameterNode) operand).setDescriptor( + DataTypeDescriptor.getBuiltInDataTypeDescriptor(Types.CLOB)); + } + else if (operatorType == XMLSERIALIZE_OP) { + // For now, since JDBC has no type defined for XML, we + // don't allow binding to an XML parameter. + throw StandardException.newException( + SQLState.LANG_ATTEMPT_TO_BIND_XML); + } + else if (operand.getTypeServices() == null) { throw StandardException.newException(SQLState.LANG_UNARY_OPERAND_PARM, operator); } @@ -399,7 +586,11 @@ if (operand == null) return; - String resultTypeName = getTypeCompiler().interfaceName(); + String resultTypeName = + (operatorType == -1) + ? getTypeCompiler().interfaceName() + : resultInterfaceType; + // System.out.println("resultTypeName " + resultTypeName + " method " + methodName); // System.out.println("isBooleanTypeId() " + getTypeId().isBooleanTypeId()); @@ -414,7 +605,8 @@ /* Allocate an object for re-use to hold the result of the operator */ LocalField field = acb.newFieldDeclaration(Modifier.PRIVATE, resultTypeName); mb.getField(field); - mb.callMethod(VMOpcode.INVOKEINTERFACE, (String) null, methodName, resultTypeName, 1); + int numParams = 1 + addMethodParams(mb); + mb.callMethod(VMOpcode.INVOKEINTERFACE, (String) null, methodName, resultTypeName, numParams); /* ** Store the result of the method call in the field, so we can re-use @@ -422,7 +614,8 @@ */ mb.putField(field); } else { - mb.callMethod(VMOpcode.INVOKEINTERFACE, (String) null, methodName, resultTypeName, 0); + int numParams = addMethodParams(mb); + mb.callMethod(VMOpcode.INVOKEINTERFACE, (String) null, methodName, resultTypeName, numParams); } } @@ -442,6 +635,9 @@ "cannot get interface without operand"); } + if (operatorType != -1) + return receiverInterfaceType; + return operand.getTypeCompiler().interfaceName(); } @@ -495,4 +691,35 @@ return returnNode; } + + /** + * This method allows different operators to add + * primitive arguments to the generated method call, + * if needed. + * @param mb The MethodBuilder that will make the call. + * @return Number of parameters added. + */ + protected int addMethodParams(MethodBuilder mb) + { + if (operatorType == XMLPARSE_OP) { + // We push whether or not we want to preserve whitespace. + mb.push(((Boolean)additionalArgs[0]).booleanValue()); + return 1; + } + + if (operatorType == XMLSERIALIZE_OP) { + // We push the target type's JDBC type id as well as + // the maximum width, since both are required when + // we actually perform the operation, and both are + // primitive types. + DataTypeDescriptor targetType = + (DataTypeDescriptor)additionalArgs[0]; + mb.push(targetType.getJDBCTypeId()); + mb.push(targetType.getMaximumWidth()); + return 2; + } + + // Default is to add zero params. + return 0; + } } Added: incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/XMLConstantNode.java URL: http://svn.apache.org/viewcvs/incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/XMLConstantNode.java?rev=189721&view=auto ============================================================================== --- incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/XMLConstantNode.java (added) +++ incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/XMLConstantNode.java Wed Jun 8 23:48:34 2005 @@ -0,0 +1,99 @@ +/* + + Derby - Class org.apache.derby.impl.sql.compile.XMLConstantNode + + Copyright 2005 The Apache Software Foundation or its licensors, as applicable. + + Licensed 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.derby.impl.sql.compile; + +import org.apache.derby.iapi.error.StandardException; +import org.apache.derby.iapi.services.compiler.MethodBuilder; +import org.apache.derby.iapi.services.io.StoredFormatIds; +import org.apache.derby.iapi.services.sanity.SanityManager; +import org.apache.derby.iapi.types.TypeId; + +import org.apache.derby.impl.sql.compile.ExpressionClassBuilder; + +import org.apache.derby.iapi.util.ReuseFactory; + +public final class XMLConstantNode extends ConstantNode +{ + /** + * Initializer for an XMLConstantNode. + * + * @param arg1 The TypeId for the type of the node + * + * @exception StandardException + */ + public void init( + Object arg1) + throws StandardException + { + super.init( + arg1, + Boolean.TRUE, + ReuseFactory.getInteger(0)); + } + + /** + * Return the value from this XMLConstantNode as a string. + * + * @return The value of this XMLConstantNode as a string. + * + * @exception StandardException Thrown on error + */ + public String getString() throws StandardException + { + return value.getString(); + } + + /** + * Return an Object representing the bind time value of this + * expression tree. If the expression tree does not evaluate to + * a constant at bind time then we return null. + * + * @return An Object representing the bind time value of this + * expression tree (null if not a bind time constant). + * + * @exception StandardException Thrown on error + */ + Object getConstantValueAsObject() throws StandardException + { + return value.getObject(); + } + + /** + * This generates the proper constant. For an XML value, + * this constant value is simply the XML string (which is + * just null because null values are the only types of + * XML constants we can have). + * + * @param acb The ExpressionClassBuilder for the class being built + * @param mb The method the code to place the code + * + * @return The compiled Expression, + * + * @exception StandardException Thrown on error + */ + void generateConstant(ExpressionClassBuilder acb, MethodBuilder mb) + throws StandardException + { + // The generated java is the expression: + // "#getString()" + mb.push(getString()); + } +} Propchange: incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/XMLConstantNode.java ------------------------------------------------------------------------------ svn:eol-style = native Added: incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/XMLTypeCompiler.java URL: http://svn.apache.org/viewcvs/incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/XMLTypeCompiler.java?rev=189721&view=auto ============================================================================== --- incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/XMLTypeCompiler.java (added) +++ incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/XMLTypeCompiler.java Wed Jun 8 23:48:34 2005 @@ -0,0 +1,179 @@ +/* + + Derby - Class org.apache.derby.impl.sql.compile.XMLTypeCompiler + + Copyright 2005 The Apache Software Foundation or its licensors, as applicable. + + Licensed 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.derby.impl.sql.compile; + +import org.apache.derby.iapi.services.loader.ClassFactory; +import org.apache.derby.iapi.services.sanity.SanityManager; +import org.apache.derby.iapi.services.io.StoredFormatIds; + +import org.apache.derby.iapi.error.StandardException; + +import org.apache.derby.iapi.types.TypeId; +import org.apache.derby.iapi.types.DataTypeDescriptor; + +import org.apache.derby.iapi.reference.ClassName; + +/** + * This class implements TypeCompiler for the XML type. + */ + +public class XMLTypeCompiler extends BaseTypeCompiler +{ + /** + * Tell whether this type (XML) can be compared to the given type. + * Says SQL/XML[2003] spec: + * + * 4.2.2 XML comparison and assignment + * "XML values are not comparable." + * + * @param otherType The TypeId of the other type. + */ + public boolean comparable(TypeId otherType, + boolean forEquals, + ClassFactory cs) + { + // An XML value cannot be compared to any type-- + // not even to other XML values. + return false; + } + + /** + * Tell whether this type (XML) can be converted to the given type. + * + * An XML value can't be converted to any other type, per + * SQL/XML[2003] 6.3 + * + * @see TypeCompiler#convertible + */ + public boolean convertible(TypeId otherType, + boolean forDataTypeFunction) + { + // An XML value cannot be converted to any non-XML type. If + // user wants to convert an XML value to a string, then + // s/he must use the provided SQL/XML serialization operator + // (namely, XMLSERIALIZE). + return otherType.isXMLTypeId(); + } + + /** + * Tell whether this type (XML) is compatible with the given type. + * + * @param otherType The TypeId of the other type. + */ + public boolean compatible(TypeId otherType) + { + // An XML value is not compatible (i.e. cannot be "coalesced") + // into any non-XML type. + return otherType.isXMLTypeId(); + } + + /** + * Tell whether this type (XML) can be stored into from the given type. + * Only XML values can be stored into an XML type, per SQL/XML spec: + * + * 4.2.2 XML comparison and assignment + * Values of XML type are assignable to sites of XML type. + * + * @param otherType The TypeId of the other type. + * @param cf A ClassFactory + */ + public boolean storable(TypeId otherType, ClassFactory cf) + { + // The only type of value that can be stored as XML + // is an XML value. Strings are not allowed. If + // the user wants to store a string value as XML, + // s/he must use the provided XML parse operator + // (namely, XMLPARSE) to parse the string into + // XML. + return otherType.isXMLTypeId(); + } + + /** + * @see TypeCompiler#interfaceName + */ + public String interfaceName() { + return ClassName.XMLDataValue; + } + + /** + * @see TypeCompiler#getCorrespondingPrimitiveTypeName + */ + public String getCorrespondingPrimitiveTypeName() + { + int formatId = getStoredFormatIdFromTypeId(); + if (formatId == StoredFormatIds.XML_TYPE_ID) + return "org.apache.derby.iapi.types.XML"; + + if (SanityManager.DEBUG) { + SanityManager.THROWASSERT( + "unexpected formatId in getCorrespondingPrimitiveTypeName(): " + + formatId); + } + + return null; + } + + /** + * @see TypeCompiler#getCastToCharWidth + * + * While it is true XML values can't be cast to char, this method + * can get called before we finish type checking--so we return a dummy + * value here and let the type check throw the appropriate error. + */ + public int getCastToCharWidth(DataTypeDescriptor dts) + { + return -1; + } + + /** + * @see TypeCompiler#nullMethodName + */ + protected String nullMethodName() + { + int formatId = getStoredFormatIdFromTypeId(); + if (formatId == StoredFormatIds.XML_TYPE_ID) + return "getNullXML"; + + if (SanityManager.DEBUG) { + SanityManager.THROWASSERT( + "unexpected formatId in nullMethodName(): " + formatId); + } + + return null; + } + + /** + * @see TypeCompiler#dataValueMethodName + */ + protected String dataValueMethodName() + { + int formatId = getStoredFormatIdFromTypeId(); + if (formatId == StoredFormatIds.XML_TYPE_ID) + return "getXMLDataValue"; + + if (SanityManager.DEBUG) { + SanityManager.THROWASSERT( + "unexpected formatId in dataValueMethodName() - " + formatId); + } + + return null; + } +} Propchange: incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/XMLTypeCompiler.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/sqlgrammar.jj URL: http://svn.apache.org/viewcvs/incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/sqlgrammar.jj?rev=189721&r1=189720&r2=189721&view=diff ============================================================================== --- incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/sqlgrammar.jj (original) +++ incubator/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/sqlgrammar.jj Wed Jun 8 23:48:34 2005 @@ -86,6 +86,7 @@ import org.apache.derby.impl.sql.compile.TriggerReferencingStruct; import org.apache.derby.impl.sql.compile.UnionNode; import org.apache.derby.impl.sql.compile.IntersectOrExceptNode; +import org.apache.derby.impl.sql.compile.UnaryOperatorNode; import org.apache.derby.impl.sql.compile.UntypedNullConstantNode; import org.apache.derby.impl.sql.compile.UpdateNode; import org.apache.derby.impl.sql.compile.UserTypeConstantNode; @@ -141,6 +142,7 @@ import org.apache.derby.catalog.types.RoutineAliasInfo; import org.apache.derby.iapi.services.io.FormatableProperties; +import org.apache.derby.iapi.services.io.StoredFormatIds; import org.apache.derby.iapi.util.ReuseFactory; import org.apache.derby.iapi.services.io.FormatableBitSet; import org.apache.derby.iapi.util.StringUtil; @@ -683,6 +685,7 @@ case CLOB: case NCLOB: case BINARY: // LARGE OBJECT + case XML: retval = true; break; @@ -1885,6 +1888,9 @@ The next lists should contain non-SQL92 keywords, and should specify whether their keywords are reserved or non-reserved. If they are non-reserved, they need to be added to the identifier() rule. + + NOTE: XML, XMLPARSE, XMLSERIALIZE, and XMLEXISTS are considered reserved + words to comply with the SQL/XML (2003) standard, section 5.1. */ /* NOTE - If you add a keyword, then you must add it to reservedKeyword() @@ -1904,10 +1910,19 @@ | | | +| +| +| +| } /* NOTE - If you add a keyword, then you must add it to reservedKeyword() * or nonReservedKeyword() as well! + * + * NOTE: CONTENT, DOCUMENT, STRIP, WHITESPACE and PASSING are considered NON- + * reserved words to comply with the SQL/XML (2003) standard, section 5.1. + * PRESERVE is also listed as non-reserved in the SQL/XML spec, but + * since that word is already reserved, we leave it alone. */ TOKEN [IGNORE_CASE] : { /* Additional JSQL keywords -- non-SQL92 non-reserved Keywords */ @@ -1915,10 +1930,12 @@ | | | +| | | | | +| | | | @@ -1939,8 +1956,10 @@ | | | +| | | +| | | | @@ -1955,10 +1974,12 @@ | | | +| | | | | +| } TOKEN : @@ -3377,6 +3398,8 @@ typeDescriptor = longType() | typeDescriptor = LOBType() +| + typeDescriptor = XMLType() ) { return typeDescriptor; @@ -3790,6 +3813,55 @@ } /* + * XMLType + */ +DataTypeDescriptor +XMLType() throws StandardException : +{ + DataTypeDescriptor value; +} +{ + + { + checkVersion(DataDictionary.DD_VERSION_DERBY_10_1, "XML"); + return DataTypeDescriptor.getBuiltInDataTypeDescriptor( + StoredFormatIds.XML_TYPE_ID); + } +} + +/* + * xmlDocOrContent + * + * Parse the XML keywords DOCUMENT and CONTENT. We don't + * support CONTENT yet, so we throw an appropriate error + * if we see it. + * + */ +void +xmlDocOrContent() throws StandardException : +{ +} +{ + LOOKAHEAD({ (getToken(1).kind != DOCUMENT) && + (getToken(1).kind != CONTENT) }) + { + throw StandardException.newException( + SQLState.LANG_XML_KEYWORD_MISSING, "DOCUMENT"); + } +| + LOOKAHEAD({ getToken(1).kind == CONTENT }) + { + throw StandardException.newException( + SQLState.LANG_UNSUPPORTED_XML_FEATURE, "CONTENT"); + } +| + LOOKAHEAD({ getToken(1).kind == DOCUMENT }) + { + return; + } +} + +/* * javaType */ DataTypeDescriptor @@ -5995,7 +6067,10 @@ getToken(1).kind == MINUTE || getToken(1).kind == SECOND || getToken(1).kind == LENGTH || - getToken(1).kind == LOCATE ) && + getToken(1).kind == LOCATE || + getToken(1).kind == XMLPARSE || + getToken(1).kind == XMLSERIALIZE || + getToken(1).kind == XMLEXISTS ) && getToken(2).kind == LEFT_PAREN ) } ) @@ -6094,6 +6169,11 @@ value, localCM); } +| + value = xmlFunction() + { + return value; + } } @@ -6144,6 +6224,237 @@ } } +/* + * xmlFunction + * + * This method parses the built-in functions used with + * the XML datatype. + * + */ +ValueNode + xmlFunction() throws StandardException : +{ + ValueNode value; + checkVersion(DataDictionary.DD_VERSION_DERBY_10_1, "XML"); +} +{ + + xmlDocOrContent() value = xmlParseValue() + { + return value; + } +| + value = xmlSerializeValue() + { + return value; + } +| + value = xmlExistsValue() + { + return value; + } +} + +/* + * xmlParseValue + * + * Syntax is as follows: + * + * XMLPARSE( DOCUMENT PRESERVE WHITESPACE ) + * + * The result of this operation will be an XML value, which can either + * be used transiently or else can be stored persistently in a table that + * has an XML column. For example: + * + * ij> CREATE TABLE x_table (id INT, xdoc XML); + * 0 rows inserted/updated/deleted + * ij> INSERT INTO x_table VALUES (1, XMLPARSE(DOCUMENT ' doc ' + * PRESERVE WHITESPACE)); + * 1 row inserted/updated/deleted + * + * We only allow XML documents (as opposed to XML content) to be + * parsed into XML values. Note that we require the "PRESERVE WHITESPACE" + * keyword to be explicit; this is because the SQL/XML (2003) spec says that + * if no whitespace option is given, the default is "STRIP WHITESPACE", which + * we don't support (yet). + * + * By the time we get to this method, the "DOCUMENT" keyword has already + * been parsed. + * + */ +ValueNode + xmlParseValue() throws StandardException : +{ + ValueNode value; + boolean wsOption; +} +{ + value = additiveExpression(null,0,false) wsOption = xmlPreserveWhitespace() { + return (ValueNode) nodeFactory.getNode( + C_NodeTypes.XML_PARSE_OPERATOR_NODE, + value, + ReuseFactory.getInteger(UnaryOperatorNode.XMLPARSE_OP), + new Object[] {(wsOption ? Boolean.TRUE : Boolean.FALSE)}, + getContextManager()); + } +} + +/* + * xmlPreserveWhitespace + * + * For now, we only support the PRESERVE WHITESPACE option. + * + */ +boolean + xmlPreserveWhitespace() throws StandardException : +{ +} +{ + LOOKAHEAD({ (getToken(1).kind != STRIP) && + (getToken(1).kind != PRESERVE) }) + { + throw StandardException.newException( + SQLState.LANG_XML_KEYWORD_MISSING, "PRESERVE WHITESPACE"); + } +| + + { // don't preserve whitespace. + throw StandardException.newException( + SQLState.LANG_UNSUPPORTED_XML_FEATURE, "STRIP WHITESPACE"); + } +| + + { // must preserve whitespace. + return true; + } +} + +/* + * xmlSerializeValue + * + * Syntax is as follows: + * + * XMLSERIALIZE( AS ) + * + * The result of this operation will be a string value with the type specified + * by the user. For example: + * + * ij> SELECT id, XMLSERIALIZE(xdoc AS varchar(30)) FROM x_table; + * ID |2 + * ------------------------------------------ + * 1 | doc + * + */ +ValueNode + xmlSerializeValue() throws StandardException : +{ + ValueNode value; + DataTypeDescriptor targetType; +} +{ + value = additiveExpression(null,0,false) + targetType = xmlSerializeTargetType() + { + return (ValueNode) nodeFactory.getNode( + C_NodeTypes.XML_SERIALIZE_OPERATOR_NODE, + value, + ReuseFactory.getInteger(UnaryOperatorNode.XMLSERIALIZE_OP), + new Object[] {targetType}, + getContextManager()); + } +} + +/* + * xmlSerializeTargetType + * + * Parse the target type of an XMLSERIALIZE operation. + * + */ +DataTypeDescriptor xmlSerializeTargetType() throws StandardException : +{ + DataTypeDescriptor targetType; +} +{ + LOOKAHEAD({ (getToken(1).kind != AS) || (getToken(2).kind == RIGHT_PAREN) }) + { + throw StandardException.newException( + SQLState.LANG_XML_KEYWORD_MISSING, "AS "); + } +| + targetType = dataTypeDDL() + { + return targetType; + } +} + +/* + * xmlExistsValue + * + * Syntax is as follows: + * + * XMLEXISTS( PASSING BY VALUE ) + * + * The result of this operation will be a boolean true/false/unknown value: + * -- Unknown if either or is null; + * -- True if at least one node in the given xml-value matches the given + * XPath expression; + * -- False otherwise. + * + * For example: + * + * ij> SELECT id FROM x_table WHERE XMLEXISTS('/simple' PASSING BY VALUE xdoc); + * ID + * ----------- + * 1 + * + */ +ValueNode + xmlExistsValue() throws StandardException : +{ + ValueNode xpathExpr; + ValueNode xmlValue; +} +{ + xpathExpr = additiveExpression(null,0,false) + xmlPassingMechanism() xmlValue = additiveExpression(null, 0, false) + { + return (ValueNode) nodeFactory.getNode( + C_NodeTypes.XML_EXISTS_OPERATOR_NODE, + xpathExpr, + xmlValue, + ReuseFactory.getInteger(BinaryOperatorNode.XMLEXISTS_OP), + getContextManager()); + } +} + +/* + * xmlPassingMechanism + * + * For now, we only support the BY VALUE option because, + * at query time, we take the XML string value from disk and + * pass it into Xalan, which creates a "copy" of the XML + * value internally (as DOM/DTM) and then executes against + * that. Because Xalan creates this copy, this impl + * is BY VALUE. + * + */ +void + xmlPassingMechanism() throws StandardException : +{ +} +{ + LOOKAHEAD( { getToken(3).kind == REF }) + + { // pass the XML value by reference + throw StandardException.newException( + SQLState.LANG_UNSUPPORTED_XML_FEATURE, "BY REF"); + } +| + + { // pass a 'copy' of the XML value. + return; + } +} /* * numericFunctionType @@ -11432,6 +11743,10 @@ | tok = | tok = | tok = +| tok = +| tok = +| tok = +| tok = ) { // Remember whether last token was a delimited identifier @@ -11476,6 +11791,7 @@ | tok = | tok = | tok = + | tok = | tok = | tok = | tok = @@ -11487,6 +11803,7 @@ | tok = | tok = | tok = + | tok = | tok = | tok = | tok = @@ -11533,10 +11850,12 @@ | tok = | tok = | tok = + | tok = | tok = | tok = | tok = | tok = + | tok = // SQL92 says it is reserved, but we want it to be non-reserved. | tok = | tok = @@ -11571,6 +11890,7 @@ | tok = | tok = | tok = + | tok = | tok = | tok =