parser.spec.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. const SearchContext = require("../../src/services/search/search_context");
  2. const parse = require('../../src/services/search/services/parse');
  3. function tokens(toks, cur = 0) {
  4. return toks.map(arg => {
  5. if (Array.isArray(arg)) {
  6. return tokens(arg, cur);
  7. }
  8. else {
  9. cur += arg.length;
  10. return {
  11. token: arg,
  12. inQuotes: false,
  13. startIndex: cur - arg.length,
  14. endIndex: cur - 1
  15. };
  16. }
  17. });
  18. }
  19. function assertIsArchived(exp) {
  20. expect(exp.constructor.name).toEqual("PropertyComparisonExp");
  21. expect(exp.propertyName).toEqual("isArchived");
  22. expect(exp.operator).toEqual("=");
  23. expect(exp.comparedValue).toEqual("false");
  24. }
  25. describe("Parser", () => {
  26. it("fulltext parser without content", () => {
  27. const rootExp = parse({
  28. fulltextTokens: tokens(["hello", "hi"]),
  29. expressionTokens: [],
  30. searchContext: new SearchContext({includeNoteContent: false, excludeArchived: true})
  31. });
  32. expect(rootExp.constructor.name).toEqual("AndExp");
  33. expect(rootExp.subExpressions[0].constructor.name).toEqual("PropertyComparisonExp");
  34. expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
  35. expect(rootExp.subExpressions[1].subExpressions[0].constructor.name).toEqual("NoteFlatTextExp");
  36. expect(rootExp.subExpressions[1].subExpressions[0].tokens).toEqual(["hello", "hi"]);
  37. });
  38. it("fulltext parser with content", () => {
  39. const rootExp = parse({
  40. fulltextTokens: tokens(["hello", "hi"]),
  41. expressionTokens: [],
  42. searchContext: new SearchContext({includeNoteContent: true})
  43. });
  44. expect(rootExp.constructor.name).toEqual("AndExp");
  45. assertIsArchived(rootExp.subExpressions[0]);
  46. expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
  47. const subs = rootExp.subExpressions[1].subExpressions;
  48. expect(subs[0].constructor.name).toEqual("NoteFlatTextExp");
  49. expect(subs[0].tokens).toEqual(["hello", "hi"]);
  50. expect(subs[1].constructor.name).toEqual("NoteContentFulltextExp");
  51. expect(subs[1].tokens).toEqual(["hello", "hi"]);
  52. });
  53. it("simple label comparison", () => {
  54. const rootExp = parse({
  55. fulltextTokens: [],
  56. expressionTokens: tokens(["#mylabel", "=", "text"]),
  57. searchContext: new SearchContext()
  58. });
  59. expect(rootExp.constructor.name).toEqual("AndExp");
  60. assertIsArchived(rootExp.subExpressions[0]);
  61. expect(rootExp.subExpressions[1].constructor.name).toEqual("LabelComparisonExp");
  62. expect(rootExp.subExpressions[1].attributeType).toEqual("label");
  63. expect(rootExp.subExpressions[1].attributeName).toEqual("mylabel");
  64. expect(rootExp.subExpressions[1].comparator).toBeTruthy();
  65. });
  66. it("simple attribute negation", () => {
  67. let rootExp = parse({
  68. fulltextTokens: [],
  69. expressionTokens: tokens(["#!mylabel"]),
  70. searchContext: new SearchContext()
  71. });
  72. expect(rootExp.constructor.name).toEqual("AndExp");
  73. assertIsArchived(rootExp.subExpressions[0]);
  74. expect(rootExp.subExpressions[1].constructor.name).toEqual("NotExp");
  75. expect(rootExp.subExpressions[1].subExpression.constructor.name).toEqual("AttributeExistsExp");
  76. expect(rootExp.subExpressions[1].subExpression.attributeType).toEqual("label");
  77. expect(rootExp.subExpressions[1].subExpression.attributeName).toEqual("mylabel");
  78. rootExp = parse({
  79. fulltextTokens: [],
  80. expressionTokens: tokens(["~!myrelation"]),
  81. searchContext: new SearchContext()
  82. });
  83. expect(rootExp.constructor.name).toEqual("AndExp");
  84. assertIsArchived(rootExp.subExpressions[0]);
  85. expect(rootExp.subExpressions[1].constructor.name).toEqual("NotExp");
  86. expect(rootExp.subExpressions[1].subExpression.constructor.name).toEqual("AttributeExistsExp");
  87. expect(rootExp.subExpressions[1].subExpression.attributeType).toEqual("relation");
  88. expect(rootExp.subExpressions[1].subExpression.attributeName).toEqual("myrelation");
  89. });
  90. it("simple label AND", () => {
  91. const rootExp = parse({
  92. fulltextTokens: [],
  93. expressionTokens: tokens(["#first", "=", "text", "and", "#second", "=", "text"]),
  94. searchContext: new SearchContext(true)
  95. });
  96. expect(rootExp.constructor.name).toEqual("AndExp");
  97. assertIsArchived(rootExp.subExpressions[0]);
  98. expect(rootExp.subExpressions[1].constructor.name).toEqual("AndExp");
  99. const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions;
  100. expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
  101. expect(firstSub.attributeName).toEqual("first");
  102. expect(secondSub.constructor.name).toEqual("LabelComparisonExp");
  103. expect(secondSub.attributeName).toEqual("second");
  104. });
  105. it("simple label AND without explicit AND", () => {
  106. const rootExp = parse({
  107. fulltextTokens: [],
  108. expressionTokens: tokens(["#first", "=", "text", "#second", "=", "text"]),
  109. searchContext: new SearchContext()
  110. });
  111. expect(rootExp.constructor.name).toEqual("AndExp");
  112. assertIsArchived(rootExp.subExpressions[0]);
  113. expect(rootExp.subExpressions[1].constructor.name).toEqual("AndExp");
  114. const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions;
  115. expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
  116. expect(firstSub.attributeName).toEqual("first");
  117. expect(secondSub.constructor.name).toEqual("LabelComparisonExp");
  118. expect(secondSub.attributeName).toEqual("second");
  119. });
  120. it("simple label OR", () => {
  121. const rootExp = parse({
  122. fulltextTokens: [],
  123. expressionTokens: tokens(["#first", "=", "text", "or", "#second", "=", "text"]),
  124. searchContext: new SearchContext()
  125. });
  126. expect(rootExp.constructor.name).toEqual("AndExp");
  127. assertIsArchived(rootExp.subExpressions[0]);
  128. expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
  129. const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions;
  130. expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
  131. expect(firstSub.attributeName).toEqual("first");
  132. expect(secondSub.constructor.name).toEqual("LabelComparisonExp");
  133. expect(secondSub.attributeName).toEqual("second");
  134. });
  135. it("fulltext and simple label", () => {
  136. const rootExp = parse({
  137. fulltextTokens: tokens(["hello"]),
  138. expressionTokens: tokens(["#mylabel", "=", "text"]),
  139. searchContext: new SearchContext({excludeArchived: true})
  140. });
  141. expect(rootExp.constructor.name).toEqual("AndExp");
  142. const [firstSub, secondSub, thirdSub] = rootExp.subExpressions;
  143. expect(firstSub.constructor.name).toEqual("PropertyComparisonExp");
  144. expect(firstSub.propertyName).toEqual('isArchived');
  145. expect(secondSub.constructor.name).toEqual("OrExp");
  146. expect(secondSub.subExpressions[0].constructor.name).toEqual("NoteFlatTextExp");
  147. expect(secondSub.subExpressions[0].tokens).toEqual(["hello"]);
  148. expect(thirdSub.constructor.name).toEqual("LabelComparisonExp");
  149. expect(thirdSub.attributeName).toEqual("mylabel");
  150. });
  151. it("label sub-expression", () => {
  152. const rootExp = parse({
  153. fulltextTokens: [],
  154. expressionTokens: tokens(["#first", "=", "text", "or", ["#second", "=", "text", "and", "#third", "=", "text"]]),
  155. searchContext: new SearchContext()
  156. });
  157. expect(rootExp.constructor.name).toEqual("AndExp");
  158. assertIsArchived(rootExp.subExpressions[0]);
  159. expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
  160. const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions;
  161. expect(firstSub.constructor.name).toEqual("LabelComparisonExp");
  162. expect(firstSub.attributeName).toEqual("first");
  163. expect(secondSub.constructor.name).toEqual("AndExp");
  164. const [firstSubSub, secondSubSub] = secondSub.subExpressions;
  165. expect(firstSubSub.constructor.name).toEqual("LabelComparisonExp");
  166. expect(firstSubSub.attributeName).toEqual("second");
  167. expect(secondSubSub.constructor.name).toEqual("LabelComparisonExp");
  168. expect(secondSubSub.attributeName).toEqual("third");
  169. });
  170. it("label sub-expression without explicit operator", () => {
  171. const rootExp = parse({
  172. fulltextTokens: [],
  173. expressionTokens: tokens(["#first", ["#second", "or", "#third"], "#fourth"]),
  174. searchContext: new SearchContext()
  175. });
  176. expect(rootExp.constructor.name).toEqual("AndExp");
  177. assertIsArchived(rootExp.subExpressions[0]);
  178. expect(rootExp.subExpressions[1].constructor.name).toEqual("AndExp");
  179. const [firstSub, secondSub, thirdSub] = rootExp.subExpressions[1].subExpressions;
  180. expect(firstSub.constructor.name).toEqual("AttributeExistsExp");
  181. expect(firstSub.attributeName).toEqual("first");
  182. expect(secondSub.constructor.name).toEqual("OrExp");
  183. const [firstSubSub, secondSubSub] = secondSub.subExpressions;
  184. expect(firstSubSub.constructor.name).toEqual("AttributeExistsExp");
  185. expect(firstSubSub.attributeName).toEqual("second");
  186. expect(secondSubSub.constructor.name).toEqual("AttributeExistsExp");
  187. expect(secondSubSub.attributeName).toEqual("third");
  188. expect(thirdSub.constructor.name).toEqual("AttributeExistsExp");
  189. expect(thirdSub.attributeName).toEqual("fourth");
  190. });
  191. });
  192. describe("Invalid expressions", () => {
  193. it("incomplete comparison", () => {
  194. const searchContext = new SearchContext();
  195. parse({
  196. fulltextTokens: [],
  197. expressionTokens: tokens(["#first", "="]),
  198. searchContext
  199. });
  200. expect(searchContext.error).toEqual('Misplaced or incomplete expression "="')
  201. });
  202. it("comparison between labels is impossible", () => {
  203. let searchContext = new SearchContext();
  204. searchContext.originalQuery = "#first = #second";
  205. parse({
  206. fulltextTokens: [],
  207. expressionTokens: tokens(["#first", "=", "#second"]),
  208. searchContext
  209. });
  210. expect(searchContext.error).toEqual(`Error near token "#second" in "#first = #second", it's possible to compare with constant only.`);
  211. searchContext = new SearchContext();
  212. searchContext.originalQuery = "#first = note.relations.second";
  213. parse({
  214. fulltextTokens: [],
  215. expressionTokens: tokens(["#first", "=", "note", ".", "relations", "second"]),
  216. searchContext
  217. });
  218. expect(searchContext.error).toEqual(`Error near token "note" in "#first = note.relations.second", it's possible to compare with constant only.`);
  219. const rootExp = parse({
  220. fulltextTokens: [],
  221. expressionTokens: [
  222. { token: "#first", inQuotes: false },
  223. { token: "=", inQuotes: false },
  224. { token: "#second", inQuotes: true },
  225. ],
  226. searchContext: new SearchContext()
  227. });
  228. expect(rootExp.constructor.name).toEqual("AndExp");
  229. assertIsArchived(rootExp.subExpressions[0]);
  230. expect(rootExp.subExpressions[1].constructor.name).toEqual("LabelComparisonExp");
  231. expect(rootExp.subExpressions[1].attributeType).toEqual("label");
  232. expect(rootExp.subExpressions[1].attributeName).toEqual("first");
  233. expect(rootExp.subExpressions[1].comparator).toBeTruthy();
  234. });
  235. it("searching by relation without note property", () => {
  236. const searchContext = new SearchContext();
  237. parse({
  238. fulltextTokens: [],
  239. expressionTokens: tokens(["~first", "=", "text", "-", "abc"]),
  240. searchContext
  241. });
  242. expect(searchContext.error).toEqual('Relation can be compared only with property, e.g. ~relation.title=hello in ""')
  243. });
  244. });