1 //------------------------------------------------------------------------------
2 // <copyright file="querybuilder.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
8 namespace MS.Internal.Xml.XPath {
11 using System.Xml.XPath;
12 using System.Diagnostics;
13 using System.Collections;
14 using System.Collections.Generic;
15 using FT = Function.FunctionType;
17 internal sealed class QueryBuilder {
18 // Note: Up->Doun, Down->Up:
19 // For operators order is normal: 1 + 2 --> Operator+(1, 2)
20 // For pathes order is reversed: a/b -> ChildQuery_B(input: ChildQuery_A(input: ContextQuery()))
21 // Input flags. We pass them Up->Down.
22 // Using them upper query set states wich controls how inner query will be built.
26 PosFilter = 0x02, // Node has this flag set when it has position predicate applied to it
27 Filter = 0x04, // Subtree we compiling will be filtered. i.e. Flag not set on rightmost filter.
29 // Output props. We return them Down->Up.
30 // These are properties of Query tree we have built already.
31 // These properties are closely related to QueryProps exposed by Query node itself.
32 // They have the following difference:
33 // QueryProps describe property of node they are (belong like Reverse)
34 // these Props describe acumulated properties of the tree (like nonFlat)
37 PosFilter = 0x01, // This filter or inner filter was positional: foo[1] or foo[1][true()]
38 HasPosition = 0x02, // Expression may ask position() of the context
39 HasLast = 0x04, // Expression may ask last() of the context
40 NonFlat = 0x08, // Some nodes may be descendent of otheres
43 // comment are aproximate. This is my best understanding:
45 private bool allowVar;
46 private bool allowKey;
47 private bool allowCurrent;
48 private bool needContext;
49 private BaseAxisQuery firstInput; // Input of the leftmost predicate. Set by leftmost predicate, used in rightmost one
51 private void Reset() {
56 private Query ProcessAxis(Axis root, Flags flags, out Props props) {
58 if (root.Prefix.Length > 0) {
63 if (root.Input != null) {
64 Flags inputFlags = Flags.None;
65 if ((flags & Flags.PosFilter) == 0) {
66 Axis input = root.Input as Axis;
69 root.TypeOfAxis == Axis.AxisType.Child &&
70 input.TypeOfAxis == Axis.AxisType.DescendantOrSelf && input.NodeType == XPathNodeType.All
73 if (input.Input != null) {
74 qyGrandInput = ProcessNode(input.Input, Flags.SmartDesc, out props);
76 qyGrandInput = new ContextQuery();
79 result = new DescendantQuery(qyGrandInput, root.Name, root.Prefix, root.NodeType, false, input.AbbrAxis);
80 if ((props & Props.NonFlat) != 0) {
81 result = new DocumentOrderQuery(result);
83 props |= Props.NonFlat;
87 if (root.TypeOfAxis == Axis.AxisType.Descendant || root.TypeOfAxis == Axis.AxisType.DescendantOrSelf) {
88 inputFlags |= Flags.SmartDesc;
92 qyInput = ProcessNode(root.Input, inputFlags, out props);
94 qyInput = new ContextQuery();
99 switch (root.TypeOfAxis) {
100 case Axis.AxisType.Ancestor:
101 result = new XPathAncestorQuery(qyInput , root.Name, root.Prefix, root.NodeType, false);
102 props |= Props.NonFlat;
104 case Axis.AxisType.AncestorOrSelf:
105 result = new XPathAncestorQuery(qyInput, root.Name, root.Prefix, root.NodeType, true);
106 props |= Props.NonFlat;
108 case Axis.AxisType.Child:
109 if ((props & Props.NonFlat) != 0) {
110 result = new CacheChildrenQuery(qyInput, root.Name, root.Prefix, root.NodeType);
112 result = new ChildrenQuery(qyInput, root.Name, root.Prefix, root.NodeType);
115 case Axis.AxisType.Parent:
116 result = new ParentQuery(qyInput, root.Name, root.Prefix, root.NodeType);
118 case Axis.AxisType.Descendant:
119 if ((flags & Flags.SmartDesc) != 0) {
120 result = new DescendantOverDescendantQuery(qyInput, false, root.Name, root.Prefix, root.NodeType, /*abbrAxis:*/false);
122 result = new DescendantQuery(qyInput, root.Name, root.Prefix, root.NodeType, false, /*abbrAxis:*/false);
123 if ((props & Props.NonFlat) != 0) {
124 result = new DocumentOrderQuery(result);
127 props |= Props.NonFlat;
129 case Axis.AxisType.DescendantOrSelf:
130 if ((flags & Flags.SmartDesc) != 0) {
131 result = new DescendantOverDescendantQuery(qyInput, true, root.Name, root.Prefix, root.NodeType, root.AbbrAxis);
133 result = new DescendantQuery(qyInput, root.Name, root.Prefix, root.NodeType, true, root.AbbrAxis);
134 if ((props & Props.NonFlat) != 0) {
135 result = new DocumentOrderQuery(result);
138 props |= Props.NonFlat;
140 case Axis.AxisType.Preceding:
141 result = new PrecedingQuery(qyInput, root.Name, root.Prefix, root.NodeType);
142 props |= Props.NonFlat;
144 case Axis.AxisType.Following:
145 result = new FollowingQuery(qyInput, root.Name, root.Prefix, root.NodeType);
146 props |= Props.NonFlat;
148 case Axis.AxisType.FollowingSibling:
149 result = new FollSiblingQuery(qyInput, root.Name, root.Prefix, root.NodeType);
150 if ((props & Props.NonFlat) != 0) {
151 result = new DocumentOrderQuery(result);
154 case Axis.AxisType.PrecedingSibling:
155 result = new PreSiblingQuery(qyInput, root.Name, root.Prefix, root.NodeType);
157 case Axis.AxisType.Attribute:
158 result = new AttributeQuery(qyInput, root.Name, root.Prefix, root.NodeType);
160 case Axis.AxisType.Self:
161 result = new XPathSelfQuery(qyInput, root.Name, root.Prefix, root.NodeType);
163 case Axis.AxisType.Namespace:
164 if ((root.NodeType == XPathNodeType.All || root.NodeType == XPathNodeType.Element || root.NodeType == XPathNodeType.Attribute) && root.Prefix.Length == 0) {
165 result = new NamespaceQuery(qyInput, root.Name, root.Prefix, root.NodeType);
167 result = new EmptyQuery();
171 throw XPathException.Create(Res.Xp_NotSupported, query);
177 private bool CanBeNumber(Query q) {
179 q.StaticType == XPathResultType.Any ||
180 q.StaticType == XPathResultType.Number
184 private Query ProcessFilter(Filter root, Flags flags, out Props props) {
185 bool first = ((flags & Flags.Filter) == 0);
188 Query cond = ProcessNode(root.Condition, Flags.None, out propsCond);
192 (propsCond & (Props.HasPosition | Props.HasLast)) != 0
194 propsCond |= Props.HasPosition;
195 flags |= Flags.PosFilter;
198 // We don't want DescendantOverDescendant pattern to be recognized here (in case descendent::foo[expr]/descendant::bar)
199 // So we clean this flag here:
200 flags &= ~Flags.SmartDesc;
201 // ToDo: Instead it would be nice to wrap descendent::foo[expr] into special query that will flatten it -- i.e.
202 // remove all nodes that are descendant of other nodes. This is very easy becuase for sorted nodesets all children
203 // follow its parent. One step caching. This can be easyly done by rightmost DescendantQuery itsef.
204 // Interesting note! Can we garatee that DescendantOverDescendant returns flat nodeset? This defenetely true if it's input is flat.
206 Query qyInput = ProcessNode(root.Input, flags | Flags.Filter, out props);
208 if (root.Input.Type != AstNode.AstType.Filter) {
209 // Props.PosFilter is for nested filters only.
210 // We clean it here to avoid cleaning it in all other ast nodes.
211 props &= ~Props.PosFilter;
213 if ((propsCond & Props.HasPosition) != 0) {
214 // this condition is positional rightmost filter should be avare of this.
215 props |= Props.PosFilter;
218 /*merging predicates*/ {
219 FilterQuery qyFilter = qyInput as FilterQuery;
220 if (qyFilter != null && (propsCond & Props.HasPosition) == 0 && qyFilter.Condition.StaticType != XPathResultType.Any) {
221 Query prevCond = qyFilter.Condition;
222 if (prevCond.StaticType == XPathResultType.Number) {
223 prevCond = new LogicalExpr(Operator.Op.EQ, new NodeFunctions(FT.FuncPosition, null), prevCond);
225 cond = new BooleanExpr(Operator.Op.AND, prevCond, cond);
226 qyInput = qyFilter.qyInput;
230 if ((props & Props.PosFilter) != 0 && qyInput is DocumentOrderQuery) {
231 qyInput = ((DocumentOrderQuery)qyInput).input;
233 if (firstInput == null) {
234 firstInput = qyInput as BaseAxisQuery;
237 bool merge = (qyInput.Properties & QueryProps.Merge ) != 0;
238 bool reverse = (qyInput.Properties & QueryProps.Reverse) != 0;
239 if ((propsCond & Props.HasPosition) != 0) {
241 qyInput = new ReversePositionQuery(qyInput);
242 } else if ((propsCond & Props.HasLast) != 0) {
243 qyInput = new ForwardPositionQuery(qyInput);
247 if (first && firstInput != null) {
248 if (merge && (props & Props.PosFilter) != 0) {
249 qyInput = new FilterQuery(qyInput, cond, /*noPosition:*/false);
250 Query parent = firstInput.qyInput;
251 if (! (parent is ContextQuery)) { // we don't need to wrap filter with MergeFilterQuery when cardinality is parent <: ?
252 firstInput.qyInput = new ContextQuery();
254 return new MergeFilterQuery(parent, qyInput);
261 return new FilterQuery(qyInput, cond, /*noPosition:*/(propsCond & Props.HasPosition) == 0);
264 private Query ProcessOperator(Operator root, out Props props) {
265 Props props1, props2;
266 Query op1 = ProcessNode(root.Operand1, Flags.None, out props1);
267 Query op2 = ProcessNode(root.Operand2, Flags.None, out props2);
268 props = props1 | props2;
269 switch (root.OperatorType) {
270 case Operator.Op.PLUS :
271 case Operator.Op.MINUS :
272 case Operator.Op.MUL :
273 case Operator.Op.MOD :
274 case Operator.Op.DIV:
275 return new NumericExpr(root.OperatorType, op1, op2);
276 case Operator.Op.LT :
277 case Operator.Op.GT :
278 case Operator.Op.LE :
279 case Operator.Op.GE :
280 case Operator.Op.EQ :
281 case Operator.Op.NE :
282 return new LogicalExpr(root.OperatorType, op1, op2);
283 case Operator.Op.OR :
284 case Operator.Op.AND :
285 return new BooleanExpr(root.OperatorType, op1, op2);
286 case Operator.Op.UNION :
287 props |= Props.NonFlat;
288 return new UnionExpr(op1, op2);
289 default : return null;
293 private Query ProcessVariable(Variable root) {
296 throw XPathException.Create(Res.Xp_InvalidKeyPattern, query);
298 return new VariableQuery(root.Localname, root.Prefix);
301 private Query ProcessFunction(Function root, out Props props) {
304 switch (root.TypeOfFunction) {
306 qy = new NodeFunctions(root.TypeOfFunction, null);
307 props |= Props.HasLast;
309 case FT.FuncPosition:
310 qy = new NodeFunctions(root.TypeOfFunction, null);
311 props |= Props.HasPosition;
314 return new NodeFunctions(FT.FuncCount,
315 ProcessNode((AstNode)(root.ArgumentList[0]), Flags.None, out props)
318 qy = new IDQuery(ProcessNode((AstNode)(root.ArgumentList[0]), Flags.None, out props));
319 props |= Props.NonFlat;
321 case FT.FuncLocalName:
322 case FT.FuncNameSpaceUri:
324 if (root.ArgumentList != null && root.ArgumentList.Count > 0) {
325 return new NodeFunctions(root.TypeOfFunction,
326 ProcessNode((AstNode)(root.ArgumentList[0]), Flags.None, out props)
329 return new NodeFunctions(root.TypeOfFunction, null);
333 case FT.FuncStartsWith:
334 case FT.FuncContains:
335 case FT.FuncSubstringBefore:
336 case FT.FuncSubstringAfter:
337 case FT.FuncSubstring:
338 case FT.FuncStringLength:
339 case FT.FuncNormalize:
340 case FT.FuncTranslate:
341 return new StringFunctions(root.TypeOfFunction, ProcessArguments(root.ArgumentList, out props));
347 if (root.ArgumentList != null && root.ArgumentList.Count > 0) {
348 return new NumberFunctions(root.TypeOfFunction,
349 ProcessNode((AstNode)root.ArgumentList[0], Flags.None, out props)
352 return new NumberFunctions(Function.FunctionType.FuncNumber, null);
356 return new BooleanFunctions(root.TypeOfFunction, null);
360 return new BooleanFunctions(root.TypeOfFunction,
361 ProcessNode((AstNode)root.ArgumentList[0], Flags.None, out props)
363 case FT.FuncUserDefined:
365 if (! allowCurrent && root.Name == "current" && root.Prefix.Length == 0) {
366 throw XPathException.Create(Res.Xp_CurrentNotAllowed);
368 if (! allowKey && root.Name == "key" && root.Prefix.Length == 0) {
369 throw XPathException.Create(Res.Xp_InvalidKeyPattern, query);
371 qy = new FunctionQuery(root.Prefix, root.Name, ProcessArguments(root.ArgumentList, out props));
372 props |= Props.NonFlat;
375 throw XPathException.Create(Res.Xp_NotSupported, query);
379 List<Query> ProcessArguments(ArrayList args, out Props props) {
380 int numArgs = args != null ? args.Count : 0;
381 List<Query> argList = new List<Query>(numArgs);
383 for (int count = 0; count < numArgs; count++) {
385 argList.Add(ProcessNode((AstNode)args[count], Flags.None, out argProps));
391 private int parseDepth = 0;
392 private const int MaxParseDepth = 1024;
394 private Query ProcessNode(AstNode root, Flags flags, out Props props) {
396 if (++parseDepth > MaxParseDepth) {
397 throw XPathException.Create(Res.Xp_QueryTooComplex);
400 Debug.Assert(root != null, "root != null");
404 case AstNode.AstType.Axis:
405 result = ProcessAxis((Axis)root, flags, out props);
407 case AstNode.AstType.Operator:
408 result = ProcessOperator((Operator)root, out props);
410 case AstNode.AstType.Filter:
411 result = ProcessFilter((Filter)root, flags, out props);
413 case AstNode.AstType.ConstantOperand:
414 result = new OperandQuery(((Operand)root).OperandValue);
416 case AstNode.AstType.Variable:
417 result = ProcessVariable((Variable)root);
419 case AstNode.AstType.Function:
420 result = ProcessFunction((Function)root, out props);
422 case AstNode.AstType.Group:
423 result = new GroupQuery(ProcessNode(((Group)root).GroupNode, Flags.None, out props));
425 case AstNode.AstType.Root:
426 result = new AbsoluteQuery();
429 Debug.Assert(false, "Unknown QueryType encountered!!");
436 private Query Build(AstNode root, string query) {
440 Query result = ProcessNode(root, Flags.None, out props);
444 internal Query Build(string query, bool allowVar, bool allowKey) {
445 this.allowVar = allowVar;
446 this.allowKey = allowKey;
447 this.allowCurrent = true;
448 return Build(XPathParser.ParseXPathExpresion(query), query);
451 internal Query Build(string query, out bool needContext) {
452 Query result = Build(query, true, true);
453 needContext = this.needContext;
457 internal Query BuildPatternQuery(string query, bool allowVar, bool allowKey) {
458 this.allowVar = allowVar;
459 this.allowKey = allowKey;
460 this.allowCurrent = false;
461 return Build(XPathParser.ParseXPathPattern(query), query);
464 internal Query BuildPatternQuery(string query, out bool needContext) {
465 Query result = BuildPatternQuery(query, true, true);
466 needContext = this.needContext;