/* * eXist Open Source Native XML Database * Copyright (C) 2001-06 Wolfgang M. Meier * wolfgang@exist-db.org * http://exist.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * $Id$ */ package org.exist.xquery; import java.util.Iterator; import org.exist.dom.QName; import org.exist.xquery.util.ExpressionDumper; import org.exist.xquery.value.GroupedValueSequence; import org.exist.xquery.value.GroupedValueSequenceTable; import org.exist.xquery.value.Item; import org.exist.xquery.value.OrderedValueSequence; import org.exist.xquery.value.PreorderedValueSequence; import org.exist.xquery.value.Sequence; import org.exist.xquery.value.Type; import org.exist.xquery.value.ValueSequence; /** * Implements an XQuery let-expression. * * @author Wolfgang Meier <wolfgang@exist-db.org> */ public class LetExpr extends BindingExpression { public LetExpr(XQueryContext context) { super(context); } /* (non-Javadoc) * @see org.exist.xquery.BindingExpression#analyze(org.exist.xquery.Expression, int, org.exist.xquery.OrderSpec[]) */ public void analyze(AnalyzeContextInfo contextInfo, OrderSpec orderBy[], GroupSpec groupBy[]) throws XPathException { // bv : Declare the grouping variable if(groupVarName != null){ LocalVariable groupVar = new LocalVariable(QName.parse(context, groupVarName, null)); groupVar.setSequenceType(sequenceType); context.declareVariableBinding(groupVar); } // bv : Declare grouping key variable(s) if(groupBy!= null){ for(int i=0;i<groupBy.length;i++){ LocalVariable groupKeyVar = new LocalVariable(QName.parse(context, groupBy[i].getKeyVarName(),null)); groupKeyVar.setSequenceType(sequenceType); context.declareVariableBinding(groupKeyVar); } } // Save the local variable stack LocalVariable mark = context.markLocalVariables(false); try { contextInfo.setParent(this); AnalyzeContextInfo varContextInfo = new AnalyzeContextInfo(contextInfo); inputSequence.analyze(varContextInfo); // Declare the iteration variable LocalVariable inVar = new LocalVariable(QName.parse(context, varName, null)); inVar.setSequenceType(sequenceType); inVar.setStaticType(varContextInfo.getStaticReturnType()); context.declareVariableBinding(inVar); if(whereExpr != null) { AnalyzeContextInfo newContextInfo = new AnalyzeContextInfo(contextInfo); newContextInfo.setFlags(contextInfo.getFlags() | IN_PREDICATE | IN_WHERE_CLAUSE); whereExpr.analyze(newContextInfo); } //Reset the context position context.setContextPosition(0); if(returnExpr instanceof BindingExpression) { ((BindingExpression)returnExpr).analyze(contextInfo, orderBy,groupBy); } else { if(orderBy != null) { for(int i = 0; i < orderBy.length; i++) orderBy[i].analyze(contextInfo); } if(groupBy != null) { for(int i = 0; i < groupBy.length; i++) groupBy[i].analyze(contextInfo); } returnExpr.analyze(contextInfo); } } finally { // restore the local variable stack context.popLocalVariables(mark); } } /* (non-Javadoc) * @see org.exist.xquery.Expression#eval(org.exist.xquery.StaticContext, org.exist.dom.DocumentSet, org.exist.xquery.value.Sequence, org.exist.xquery.value.Item) */ public Sequence eval(Sequence contextSequence, Item contextItem, Sequence resultSequence, GroupedValueSequenceTable groupedSequence) throws XPathException { if (context.getProfiler().isEnabled()) { context.getProfiler().start(this); context.getProfiler().message(this, Profiler.DEPENDENCIES, "DEPENDENCIES", Dependency.getDependenciesName(this.getDependencies())); if (contextSequence != null) context.getProfiler().message(this, Profiler.START_SEQUENCES, "CONTEXT SEQUENCE", contextSequence); if (contextItem != null) context.getProfiler().message(this, Profiler.START_SEQUENCES, "CONTEXT ITEM", contextItem.toSequence()); if (resultSequence != null) context.getProfiler().message(this, Profiler.START_SEQUENCES, "RESULT SEQUENCE", resultSequence); } context.expressionStart(this); context.pushDocumentContext(); try { //bv : Declare grouping variables and initiate grouped sequence LocalVariable groupVar = null; LocalVariable groupKeyVar[] = null; if(groupSpecs != null){ groupedSequence = new GroupedValueSequenceTable(groupSpecs,toGroupVarName,context); groupVar = new LocalVariable(QName.parse(context, groupVarName, null)); groupVar.setSequenceType(sequenceType); context.declareVariableBinding(groupVar); groupKeyVar = new LocalVariable[groupSpecs.length]; for(int i=0;i<groupSpecs.length;i++){ groupKeyVar[i] = new LocalVariable(QName.parse(context, groupSpecs[i].getKeyVarName(),null)); groupKeyVar[i].setSequenceType(sequenceType); context.declareVariableBinding(groupKeyVar[i]); } } // Save the local variable stack LocalVariable mark = context.markLocalVariables(false); Sequence in; boolean fastOrderBy; try { // evaluate input sequence in = inputSequence.eval(contextSequence, null); clearContext(getExpressionId(), in); // Declare the iteration variable LocalVariable var = new LocalVariable(QName.parse(context, varName, null)); var.setSequenceType(sequenceType); context.declareVariableBinding(var); var.setValue(in); if (sequenceType == null) var.checkType(); //Just because it makes conversions ! var.setContextDocs(inputSequence.getContextDocSet()); registerUpdateListener(in); if (whereExpr != null) { Sequence filtered = applyWhereExpression(null); // TODO: don't use returnsType here if (filtered.isEmpty()) { if (context.getProfiler().isEnabled()) context.getProfiler().end(this, "", Sequence.EMPTY_SEQUENCE); return Sequence.EMPTY_SEQUENCE; } else if (filtered.getItemType() == Type.BOOLEAN && !filtered.effectiveBooleanValue()) { if (context.getProfiler().isEnabled()) context.getProfiler().end(this, "", Sequence.EMPTY_SEQUENCE); return Sequence.EMPTY_SEQUENCE; } } // Check if we can speed up the processing of the "order by" clause. fastOrderBy = in.isPersistentSet() && checkOrderSpecs(in); // PreorderedValueSequence applies the order specs to all items // in one single processing step if(fastOrderBy) { in = new PreorderedValueSequence(orderSpecs, in.toNodeSet(), getExpressionId()); } // Otherwise, if there's an order by clause, wrap the result into // an OrderedValueSequence. OrderedValueSequence will compute // order expressions for every item when it is added to the result sequence. if(resultSequence == null) { if(orderSpecs != null && !fastOrderBy) resultSequence = new OrderedValueSequence(orderSpecs, in.getItemCount()); } if(groupedSequence==null){ if(returnExpr instanceof BindingExpression) { if (resultSequence == null) resultSequence = new ValueSequence(); ((BindingExpression)returnExpr).eval(null, null, resultSequence,null); } else { in = returnExpr.eval(null); if (resultSequence == null) resultSequence = in; else resultSequence.addAll(in); } } else{ /* bv : special processing for groupby : if returnExpr is a Binding expression, pass the groupedSequence. Else, add item to groupedSequence and don't evaluate here ! */ if(returnExpr instanceof BindingExpression) { if (resultSequence == null) resultSequence = new ValueSequence(); ((BindingExpression)returnExpr).eval(null, null, resultSequence,groupedSequence); } else{ Sequence toGroupSequence = context.resolveVariable(groupedSequence.getToGroupVarName()).getValue(); groupedSequence.addAll(toGroupSequence); } } if (sequenceType != null) { int actualCardinality; if (var.getValue().isEmpty()) actualCardinality = Cardinality.EMPTY; else if (var.getValue().hasMany()) actualCardinality = Cardinality.MANY; else actualCardinality = Cardinality.ONE; //Type.EMPTY is *not* a subtype of other types ; checking cardinality first if (!Cardinality.checkCardinality(sequenceType.getCardinality(), actualCardinality)) throw new XPathException(this, "XPTY0004: Invalid cardinality for variable $" + varName + ". Expected " + Cardinality.getDescription(sequenceType.getCardinality()) + ", got " + Cardinality.getDescription(actualCardinality)); //TODO : ignore nodes right now ; they are returned as xs:untypedAtomicType if (!Type.subTypeOf(sequenceType.getPrimaryType(), Type.NODE)) { if (!var.getValue().isEmpty() && !Type.subTypeOf(var.getValue().getItemType(), sequenceType.getPrimaryType())) throw new XPathException(this, "XPTY0004: Invalid type for variable $" + varName + ". Expected " + Type.getTypeName(sequenceType.getPrimaryType()) + ", got " +Type.getTypeName(var.getValue().getItemType())); //Here is an attempt to process the nodes correctly } else { //Same as above : we probably may factorize if (!var.getValue().isEmpty() && !Type.subTypeOf(var.getValue().getItemType(), sequenceType.getPrimaryType())) throw new XPathException(this, "XPTY0004: Invalid type for variable $" + varName + ". Expected " + Type.getTypeName(sequenceType.getPrimaryType()) + ", got " +Type.getTypeName(var.getValue().getItemType())); } } } finally { // Restore the local variable stack context.popLocalVariables(mark); } //Special processing for groupBy : one return per group in groupedSequence if(groupSpecs!=null){ for(Iterator it = groupedSequence.iterate(); it.hasNext(); ){ Object key = it.next(); GroupedValueSequence currentGroup = (GroupedValueSequence)groupedSequence.get(key); context.proceed(this); // set the grouping variable to current group nodes groupVar.setValue(currentGroup); groupVar.checkType(); //set value of grouping keys for the current group for(int i=0; i< groupKeyVar.length ; i ++){ groupKeyVar[i].setValue(currentGroup.getGroupKey().itemAt(i).toSequence()); } //evaluate real return expression Sequence val = groupReturnExpr.eval(null); resultSequence.addAll(val); } } if(orderSpecs != null && !fastOrderBy) ((OrderedValueSequence)resultSequence).sort(); clearContext(getExpressionId(), in); // // Restore the local variable stack // context.popLocalVariables(mark); if (context.getProfiler().isEnabled()) context.getProfiler().end(this, "", resultSequence); actualReturnType = resultSequence.getItemType(); return resultSequence; } finally { context.popDocumentContext(); context.expressionEnd(this); } } /* (non-Javadoc) * @see org.exist.xquery.Expression#returnsType() */ public int returnsType() { if (sequenceType != null) return sequenceType.getPrimaryType(); //Type.ITEM by default : this may change *after* evaluation return actualReturnType; } /* (non-Javadoc) * @see org.exist.xquery.Expression#dump(org.exist.xquery.util.ExpressionDumper) */ public void dump(ExpressionDumper dumper) { dumper.display("let ", line); dumper.startIndent(); dumper.display("$").display(varName); dumper.display(" := "); inputSequence.dump(dumper); dumper.endIndent(); if(whereExpr != null) { dumper.nl().display("where "); whereExpr.dump(dumper); } if(groupSpecs != null) { dumper.display("group "); dumper.display("$").display(toGroupVarName); dumper.display(" as "); dumper.display("$").display(groupVarName); dumper.display(" by "); for(int i = 0; i < groupSpecs.length; i++) { if(i > 0) dumper.display(", "); dumper.display(groupSpecs[i].getGroupExpression().toString()); dumper.display(" as "); dumper.display("$").display(groupSpecs[i].getKeyVarName()); } dumper.nl(); } if(orderSpecs != null) { dumper.nl().display("order by "); for(int i = 0; i < orderSpecs.length; i++) { if(i > 0) dumper.display(", "); //TODO : toString() or... dump ? dumper.display(orderSpecs[i].toString()); } } //TODO : QuantifiedExpr if (returnExpr instanceof LetExpr) dumper.display(", "); else dumper.nl().display("return "); dumper.startIndent(); returnExpr.dump(dumper); dumper.endIndent(); } public String toString() { StringBuilder result = new StringBuilder(); result.append("let "); result.append("$").append(varName); result.append(" := "); result.append(inputSequence.toString()); result.append(" "); if(whereExpr != null) { result.append(" where "); result.append(whereExpr.toString()); } if(groupSpecs != null) { result.append("group "); result.append("$").append(toGroupVarName); result.append(" as "); result.append("$").append(groupVarName); result.append(" by "); for(int i = 0; i < groupSpecs.length; i++) { if(i > 0) result.append(", "); result.append(groupSpecs[i].getGroupExpression().toString()); result.append(" as "); result.append("$").append(groupSpecs[i].getKeyVarName()); } result.append(" "); } if(orderSpecs != null) { result.append(" order by "); for(int i = 0; i < orderSpecs.length; i++) { if(i > 0) result.append(", "); result.append(orderSpecs[i].toString()); } } //TODO : QuantifiedExpr if (returnExpr instanceof LetExpr) result.append(", "); else result.append("return "); result.append(returnExpr.toString()); return result.toString(); } public void accept(ExpressionVisitor visitor) { visitor.visitLetExpression(this); } }