PageRenderTime 44ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/Spruce.Core/Search/WiqlBuilder.cs

http://spruce.codeplex.com
C# | 248 lines | 165 code | 37 blank | 46 comment | 26 complexity | 7c19ac1bc3ea4760913935306f346a4e MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Diagnostics;
  6. namespace Spruce.Core.Search
  7. {
  8. /// <summary>
  9. /// Generates a WIQL query.
  10. /// </summary>
  11. public class WiqlBuilder
  12. {
  13. /// <summary>
  14. /// The field/operators are stored in LIFO list, as the order of the search query is parsed in operator/field order. So when
  15. /// we peek the stack, we want the last item added, and not a FIFO peek (Queue) where the item at the beginning is given.
  16. /// </summary>
  17. private Stack<object> _queryStack;
  18. /// <summary>
  19. /// NOT operators are given before the field, so keep track of them for the next Field.
  20. /// </summary>
  21. private bool _nextFieldIsNot;
  22. /// <summary>
  23. /// This allows repeated field searches (which is usually another keyword)
  24. /// to be combined into one string, instead of lots of title="x" AND title="y"
  25. /// </summary>
  26. private IDictionary<string, Field> _fieldLookup;
  27. /// <summary>
  28. /// Initializes a new instance of the <see cref="WiqlBuilder"/> class.
  29. /// </summary>
  30. public WiqlBuilder()
  31. {
  32. _queryStack = new Stack<object>();
  33. _fieldLookup = new Dictionary<string, Field>();
  34. }
  35. /// <summary>
  36. ///
  37. /// </summary>
  38. public void And()
  39. {
  40. if (_queryStack.Count > 0)
  41. {
  42. // Only add an AND if there's a field previously in the stack
  43. Field field = _queryStack.Peek() as Field;
  44. if (field != null)
  45. {
  46. _queryStack.Push("AND");
  47. }
  48. }
  49. }
  50. /// <summary>
  51. ///
  52. /// </summary>
  53. public void Or()
  54. {
  55. if (_queryStack.Count > 0)
  56. {
  57. // Only add an OR if there's a field previously in the stack
  58. Field field = _queryStack.Peek() as Field;
  59. if (field != null)
  60. {
  61. _queryStack.Push("OR");
  62. }
  63. }
  64. }
  65. /// <summary>
  66. ///
  67. /// </summary>
  68. public void Not()
  69. {
  70. // Check if Field is in the stack.
  71. if (_queryStack.Count > 0)
  72. {
  73. _nextFieldIsNot = true;
  74. }
  75. }
  76. /// <summary>
  77. ///
  78. /// </summary>
  79. /// <param name="field">If this is blank, then the title field is used as the default.</param>
  80. /// <param name="value"></param>
  81. /// <returns></returns>
  82. public void AppendField(string fieldName,string value,FieldComparison fieldType)
  83. {
  84. if (string.IsNullOrEmpty(fieldName))
  85. fieldName = "Title";
  86. Field field = null;
  87. string key = fieldName + _nextFieldIsNot;
  88. if (_queryStack.Count > 0)
  89. {
  90. field = _queryStack.Peek() as Field;
  91. if (field != null)
  92. {
  93. if (!_fieldLookup.ContainsKey(key) && field.ToString() != "")
  94. {
  95. _queryStack.Push("AND");
  96. }
  97. }
  98. }
  99. if (_fieldLookup.ContainsKey(key))
  100. {
  101. // Combine repeated field definitions into a single = (instead of repeating them with ANDs)
  102. _fieldLookup[key].Value += " " + value;
  103. }
  104. else
  105. {
  106. switch (fieldType)
  107. {
  108. case FieldComparison.User:
  109. field = new UserField();
  110. break;
  111. case FieldComparison.Date:
  112. field = new DateField();
  113. break;
  114. case FieldComparison.Project:
  115. field = new ProjectField();
  116. break;
  117. case FieldComparison.Contains:
  118. field = new ContainsField();
  119. break;
  120. case FieldComparison.ExactMatch:
  121. default:
  122. field = new Field();
  123. break;
  124. }
  125. field.ColumnName = ToColumnName(fieldName);
  126. field.ParameterName = fieldName;
  127. field.Value = value;
  128. field.IsNot = _nextFieldIsNot;
  129. _queryStack.Push(field);
  130. _fieldLookup.Add(key, field);
  131. }
  132. // reset for future fields
  133. _nextFieldIsNot = false;
  134. }
  135. /// <summary>
  136. ///
  137. /// </summary>
  138. /// <param name="parameters">This dictionary is filled with name/value pairs for each field's name and value,
  139. /// for the WIQL query. Can be null.</param>
  140. /// <returns></returns>
  141. public string BuildQuery(Dictionary<string, object> parameters)
  142. {
  143. StringBuilder builder = new StringBuilder();
  144. builder.Append("SELECT * FROM Issue WHERE ");
  145. if (parameters == null)
  146. parameters = new Dictionary<string, object>();
  147. IList<object> stackList = _queryStack.ToList();
  148. stackList = stackList.Reverse().ToList(); // turn our LIFO into a FIFO to preserve the query order
  149. Dictionary<string, int> nameClashLookup = new Dictionary<string, int>();
  150. for (int i = 0; i < stackList.Count; i++)
  151. {
  152. Field field = stackList[i] as Field;
  153. if (field != null)
  154. {
  155. // Check for more than one field check (for OR queries)
  156. if (parameters.ContainsKey(field.ParameterName))
  157. {
  158. int uniqueId = 1;
  159. if (nameClashLookup.ContainsKey(field.ParameterName))
  160. uniqueId = ++nameClashLookup[field.ParameterName];
  161. else
  162. nameClashLookup.Add(field.ParameterName,uniqueId);
  163. field.ParameterName += uniqueId;
  164. }
  165. parameters.Add(field.ParameterName, field.Value);
  166. builder.Append(field);
  167. }
  168. else
  169. {
  170. // This avoids AND/OR being erronously added to the start or end of the query.
  171. // TODO: The And() and Or() methods should really deal with this
  172. if (i > 0 && i < stackList.Count -1)
  173. builder.AppendFormat(" {0} ", stackList[i].ToString());
  174. }
  175. }
  176. return builder.ToString();
  177. }
  178. private string ToColumnName(string name)
  179. {
  180. switch (name.ToLower())
  181. {
  182. case "project":
  183. return "[System.TeamProject]";
  184. case "description":
  185. return "[System.Description]";
  186. case "type":
  187. return "[Work Item Type]";
  188. case "state":
  189. return "State";
  190. case "iteration":
  191. return "[System.IterationPath]";
  192. case "area":
  193. return "[System.AreaPath]";
  194. case "assignedto":
  195. return "[Assigned To]";
  196. case "createdby":
  197. return "[Created By]";
  198. case "resolvedby":
  199. return "[Resolved By]";
  200. case "resolvedon":
  201. return "[Resolved Date]";
  202. case "createdon":
  203. return "[Created Date]";
  204. case "title":
  205. case "":
  206. default:
  207. return "[System.Title]";
  208. }
  209. }
  210. }
  211. }