diff --git a/README.org b/README.org index 515bfb9..947a37e 100644 --- a/README.org +++ b/README.org @@ -557,6 +557,12 @@ Simple links may also be written manually in either sexp or non-sexp form, like: Nothing new yet. +** 0.8.5 + +*Fixes* ++ Predicate ~heading~ incorrectly matched strings as regular expressions, sometimes returning incorrect results. (See [[https://github.com/alphapapa/org-ql/discussions/410][discussion]]. Thanks to [[https://github.com/al3xandru][Alex Popescu]] for reporting.) ++ Predicates ~ancestor~ and ~parent~ did not normalize their sub-queries, sometimes returning incorrect results. ([[https://github.com/alphapapa/org-ql/issues/365][#365]]. Thanks to [[https://github.com/kofm][Gabriele Mongiano]] for reporting.) + ** 0.8.4 *Fixes* diff --git a/org-ql.el b/org-ql.el index 338c52a..3f48a0b 100644 --- a/org-ql.el +++ b/org-ql.el @@ -1461,40 +1461,16 @@ Org effort string, like \"5\" or \"0:05\"." (list :regexp (rx bol (0+ space) ":STYLE:" (1+ space) "habit" (0+ space) eol)))) :body (org-is-habit-p)) -(org-ql-defpred (heading h) (&rest strings) +(org-ql-defpred (heading h) (&rest _strings) "Return non-nil if current entry's heading matches all STRINGS. Matching is done case-insensitively." :coalesce t :normalizers ((`(,predicate-names . ,args) - ;; "h" alias. - `(heading ,@args))) - ;; TODO: Adjust regexp to avoid matching in tag list. - :preambles ((`(,predicate-names) - ;; This clause protects against the case in which the - ;; arguments are nil, which would cause an error in - ;; `rx-to-string' in other clauses. This can happen - ;; with `org-ql-completing-read', e.g. when the input - ;; is "h:" while the user is typing. - (list :regexp (rx bol (1+ "*") (1+ blank) (0+ nonl)) - :case-fold t :query query)) - (`(,predicate-names ,string) - ;; Only one string: match with preamble, then let predicate confirm (because - ;; the match could be in e.g. the tags rather than the heading text). - (list :regexp (rx-to-string `(seq bol (1+ "*") (1+ blank) (0+ nonl) - ,string) - 'no-group) - :case-fold t :query query)) - (`(,predicate-names . ,strings) - ;; Multiple strings: use preamble to match against first - ;; string, then let the predicate match the rest. - (list :regexp (rx-to-string `(seq bol (1+ "*") (1+ blank) (0+ nonl) - ,(car strings)) - 'no-group) - :case-fold t :query query))) - ;; TODO: In Org 9.2+, `org-get-heading' takes 2 more arguments. - :body (let ((heading (org-get-heading 'no-tags 'no-todo)) - (case-fold-search t)) - (--all? (string-match it heading) strings))) + ;; NOTE: Each string argument must be converted to a regexp + ;; for testing by the body, so we just normalize to the + ;; `heading-regexp' predicate, leaving this predicate as + ;; one that merely regexp-quotes its arguments. + `(heading-regexp ,@(mapcar #'regexp-quote args))))) (org-ql-defpred (heading-regexp h*) (&rest regexps) "Return non-nil if current entry's heading matches all REGEXPS (regexp strings). @@ -2101,7 +2077,7 @@ With KEYWORDS, return non-nil if its keyword is one of KEYWORDS." :normalizers ((`(,predicate-names ;; Avoid infinitely compiling already-compiled functions. ,(and query (guard (not (byte-code-function-p query))))) - `(ancestors ,(org-ql--query-predicate (rec query)))) + `(ancestors ,(org-ql--query-predicate (org-ql--normalize-query query)))) (`(,predicate-names) '(ancestors (lambda () t)))) :body (org-with-wide-buffer @@ -2113,7 +2089,7 @@ With KEYWORDS, return non-nil if its keyword is one of KEYWORDS." :normalizers ((`(,predicate-names ;; Avoid infinitely compiling already-compiled functions. ,(and query (guard (not (byte-code-function-p query))))) - `(parent ,(org-ql--query-predicate (rec query)))) + `(parent ,(org-ql--query-predicate (org-ql--normalize-query query)))) (`(,predicate-names) '(parent (lambda () t)))) :body (org-with-wide-buffer diff --git a/org-ql.info b/org-ql.info index 8e58136..fb0383c 100644 --- a/org-ql.info +++ b/org-ql.info @@ -73,6 +73,7 @@ Functions / Macros Changelog * 0.9-pre: 09-pre. +* 0.8.5: 085. * 0.8.4: 084. * 0.8.3: 083. * 0.8.2: 082. @@ -1034,6 +1035,7 @@ releases. * Menu: * 0.9-pre: 09-pre. +* 0.8.5: 085. * 0.8.4: 084. * 0.8.3: 083. * 0.8.2: 082. @@ -1071,7 +1073,7 @@ releases. * 0.1: 01.  -File: README.info, Node: 09-pre, Next: 084, Up: Changelog +File: README.info, Node: 09-pre, Next: 085, Up: Changelog 5.1 0.9-pre =========== @@ -1079,9 +1081,26 @@ File: README.info, Node: 09-pre, Next: 084, Up: Changelog Nothing new yet.  -File: README.info, Node: 084, Next: 083, Prev: 09-pre, Up: Changelog +File: README.info, Node: 085, Next: 084, Prev: 09-pre, Up: Changelog -5.2 0.8.4 +5.2 0.8.5 +========= + +*Fixes* + • Predicate ‘heading’ incorrectly matched strings as regular + expressions, sometimes returning incorrect results. (See + discussion (https://github.com/alphapapa/org-ql/discussions/410). + Thanks to Alex Popescu (https://github.com/al3xandru) for + reporting.) + • Predicates ‘ancestor’ and ‘parent’ did not normalize their + sub-queries, sometimes returning incorrect results. (#365 + (https://github.com/alphapapa/org-ql/issues/365). Thanks to + Gabriele Mongiano (https://github.com/kofm) for reporting.) + + +File: README.info, Node: 084, Next: 083, Prev: 085, Up: Changelog + +5.3 0.8.4 ========= *Fixes* @@ -1095,7 +1114,7 @@ File: README.info, Node: 084, Next: 083, Prev: 09-pre, Up: Changelog  File: README.info, Node: 083, Next: 082, Prev: 084, Up: Changelog -5.3 0.8.3 +5.4 0.8.3 ========= *Fixes* @@ -1108,7 +1127,7 @@ File: README.info, Node: 083, Next: 082, Prev: 084, Up: Changelog  File: README.info, Node: 082, Next: 081, Prev: 083, Up: Changelog -5.4 0.8.2 +5.5 0.8.2 ========= *Fixes* @@ -1122,7 +1141,7 @@ File: README.info, Node: 082, Next: 081, Prev: 083, Up: Changelog  File: README.info, Node: 081, Next: 08, Prev: 082, Up: Changelog -5.5 0.8.1 +5.6 0.8.1 ========= *Fixes* @@ -1137,7 +1156,7 @@ File: README.info, Node: 081, Next: 08, Prev: 082, Up: Changelog  File: README.info, Node: 08, Next: 074, Prev: 081, Up: Changelog -5.6 0.8 +5.7 0.8 ======= *Additions* @@ -1193,7 +1212,7 @@ File: README.info, Node: 08, Next: 074, Prev: 081, Up: Changelog  File: README.info, Node: 074, Next: 073, Prev: 08, Up: Changelog -5.7 0.7.4 +5.8 0.7.4 ========= *Fixes* @@ -1203,7 +1222,7 @@ File: README.info, Node: 074, Next: 073, Prev: 08, Up: Changelog  File: README.info, Node: 073, Next: 072, Prev: 074, Up: Changelog -5.8 0.7.3 +5.9 0.7.3 ========= *Fixes* @@ -1221,8 +1240,8 @@ File: README.info, Node: 073, Next: 072, Prev: 074, Up: Changelog  File: README.info, Node: 072, Next: 071, Prev: 073, Up: Changelog -5.9 0.7.2 -========= +5.10 0.7.2 +========== *Fixes* • Timestamp predicates are more tolerant of partial input (e.g. @@ -1242,7 +1261,7 @@ File: README.info, Node: 072, Next: 071, Prev: 073, Up: Changelog  File: README.info, Node: 071, Next: 07, Prev: 072, Up: Changelog -5.10 0.7.1 +5.11 0.7.1 ========== *Fixes* @@ -1261,7 +1280,7 @@ File: README.info, Node: 071, Next: 07, Prev: 072, Up: Changelog  File: README.info, Node: 07, Next: 063, Prev: 071, Up: Changelog -5.11 0.7 +5.12 0.7 ======== *Added* @@ -1320,7 +1339,7 @@ File: README.info, Node: 07, Next: 063, Prev: 071, Up: Changelog  File: README.info, Node: 063, Next: 062, Prev: 07, Up: Changelog -5.12 0.6.3 +5.13 0.6.3 ========== *Fixed* @@ -1336,7 +1355,7 @@ File: README.info, Node: 063, Next: 062, Prev: 07, Up: Changelog  File: README.info, Node: 062, Next: 061, Prev: 063, Up: Changelog -5.13 0.6.2 +5.14 0.6.2 ========== *Fixed* @@ -1347,7 +1366,7 @@ File: README.info, Node: 062, Next: 061, Prev: 063, Up: Changelog  File: README.info, Node: 061, Next: 06, Prev: 062, Up: Changelog -5.14 0.6.1 +5.15 0.6.1 ========== *Fixed* @@ -1365,7 +1384,7 @@ File: README.info, Node: 061, Next: 06, Prev: 062, Up: Changelog  File: README.info, Node: 06, Next: 052, Prev: 061, Up: Changelog -5.15 0.6 +5.16 0.6 ======== *Added* @@ -1432,7 +1451,7 @@ File: README.info, Node: 06, Next: 052, Prev: 061, Up: Changelog  File: README.info, Node: 052, Next: 051, Prev: 06, Up: Changelog -5.16 0.5.2 +5.17 0.5.2 ========== *Fixed* @@ -1443,7 +1462,7 @@ File: README.info, Node: 052, Next: 051, Prev: 06, Up: Changelog  File: README.info, Node: 051, Next: 05, Prev: 052, Up: Changelog -5.17 0.5.1 +5.18 0.5.1 ========== *Fixed* @@ -1456,7 +1475,7 @@ File: README.info, Node: 051, Next: 05, Prev: 052, Up: Changelog  File: README.info, Node: 05, Next: 049, Prev: 051, Up: Changelog -5.18 0.5 +5.19 0.5 ======== *Added* @@ -1497,7 +1516,7 @@ File: README.info, Node: 05, Next: 049, Prev: 051, Up: Changelog  File: README.info, Node: 049, Next: 048, Prev: 05, Up: Changelog -5.19 0.4.9 +5.20 0.4.9 ========== *Fixed* @@ -1508,7 +1527,7 @@ File: README.info, Node: 049, Next: 048, Prev: 05, Up: Changelog  File: README.info, Node: 048, Next: 047, Prev: 049, Up: Changelog -5.20 0.4.8 +5.21 0.4.8 ========== *Fixed* @@ -1520,7 +1539,7 @@ File: README.info, Node: 048, Next: 047, Prev: 049, Up: Changelog  File: README.info, Node: 047, Next: 046, Prev: 048, Up: Changelog -5.21 0.4.7 +5.22 0.4.7 ========== *Fixed* @@ -1533,7 +1552,7 @@ File: README.info, Node: 047, Next: 046, Prev: 048, Up: Changelog  File: README.info, Node: 046, Next: 045, Prev: 047, Up: Changelog -5.22 0.4.6 +5.23 0.4.6 ========== *Fixed* @@ -1546,7 +1565,7 @@ File: README.info, Node: 046, Next: 045, Prev: 047, Up: Changelog  File: README.info, Node: 045, Next: 044, Prev: 046, Up: Changelog -5.23 0.4.5 +5.24 0.4.5 ========== *Fixed* @@ -1558,7 +1577,7 @@ File: README.info, Node: 045, Next: 044, Prev: 046, Up: Changelog  File: README.info, Node: 044, Next: 043, Prev: 045, Up: Changelog -5.24 0.4.4 +5.25 0.4.4 ========== *Fixed* @@ -1570,7 +1589,7 @@ File: README.info, Node: 044, Next: 043, Prev: 045, Up: Changelog  File: README.info, Node: 043, Next: 042, Prev: 044, Up: Changelog -5.25 0.4.3 +5.26 0.4.3 ========== *Fixed* @@ -1580,7 +1599,7 @@ File: README.info, Node: 043, Next: 042, Prev: 044, Up: Changelog  File: README.info, Node: 042, Next: 041, Prev: 043, Up: Changelog -5.26 0.4.2 +5.27 0.4.2 ========== *Fixed* @@ -1589,7 +1608,7 @@ File: README.info, Node: 042, Next: 041, Prev: 043, Up: Changelog  File: README.info, Node: 041, Next: 04, Prev: 042, Up: Changelog -5.27 0.4.1 +5.28 0.4.1 ========== *Fixed* @@ -1599,7 +1618,7 @@ File: README.info, Node: 041, Next: 04, Prev: 042, Up: Changelog  File: README.info, Node: 04, Next: 032, Prev: 041, Up: Changelog -5.28 0.4 +5.29 0.4 ======== _Note:_ The next release, 0.5, may include changes which will require @@ -1680,7 +1699,7 @@ automatically, as they will be pushed to the ‘master’ branch when ready.  File: README.info, Node: 032, Next: 031, Prev: 04, Up: Changelog -5.29 0.3.2 +5.30 0.3.2 ========== *Fixed* @@ -1693,7 +1712,7 @@ File: README.info, Node: 032, Next: 031, Prev: 04, Up: Changelog  File: README.info, Node: 031, Next: 03, Prev: 032, Up: Changelog -5.30 0.3.1 +5.31 0.3.1 ========== *Fixed* @@ -1703,7 +1722,7 @@ File: README.info, Node: 031, Next: 03, Prev: 032, Up: Changelog  File: README.info, Node: 03, Next: 023, Prev: 031, Up: Changelog -5.31 0.3 +5.32 0.3 ======== *Added* @@ -1771,7 +1790,7 @@ File: README.info, Node: 03, Next: 023, Prev: 031, Up: Changelog  File: README.info, Node: 023, Next: 022, Prev: 03, Up: Changelog -5.32 0.2.3 +5.33 0.2.3 ========== *Fixed* @@ -1781,7 +1800,7 @@ File: README.info, Node: 023, Next: 022, Prev: 03, Up: Changelog  File: README.info, Node: 022, Next: 021, Prev: 023, Up: Changelog -5.33 0.2.2 +5.34 0.2.2 ========== *Fixed* @@ -1792,7 +1811,7 @@ File: README.info, Node: 022, Next: 021, Prev: 023, Up: Changelog  File: README.info, Node: 021, Next: 02, Prev: 022, Up: Changelog -5.34 0.2.1 +5.35 0.2.1 ========== *Fixed* @@ -1802,7 +1821,7 @@ File: README.info, Node: 021, Next: 02, Prev: 022, Up: Changelog  File: README.info, Node: 02, Next: 01, Prev: 021, Up: Changelog -5.35 0.2 +5.36 0.2 ======== *Added* @@ -1885,7 +1904,7 @@ File: README.info, Node: 02, Next: 01, Prev: 021, Up: Changelog  File: README.info, Node: 01, Prev: 02, Up: Changelog -5.36 0.1 +5.37 0.1 ======== First tagged release. @@ -1943,81 +1962,82 @@ GPLv3  Tag Table: Node: Top225 -Node: Contents1926 -Node: Screenshots2049 -Node: Installation2167 -Node: Quelpa2681 -Node: Helm support3209 -Node: Usage3612 -Node: Commands4010 -Node: org-ql-find4475 -Node: org-ql-open-link5383 -Node: org-ql-refile6238 -Node: org-ql-search6566 -Node: helm-org-ql8497 -Node: org-ql-view8875 -Node: org-ql-view-sidebar9405 -Node: org-ql-view-recent-items9785 -Node: org-ql-sparse-tree10281 -Node: Queries11081 -Node: Non-sexp query syntax12198 -Node: General predicates13957 -Node: Ancestor/descendant predicates20882 -Node: Date/time predicates22010 -Node: Functions / Macros25134 -Node: Agenda-like views25432 -Ref: Function org-ql-block25594 -Node: Listing / acting-on results26855 -Ref: Caching27063 -Ref: Function org-ql-select27976 -Ref: Function org-ql-query30402 -Ref: Macro org-ql (deprecated)32176 -Node: Custom predicates32491 -Ref: Macro org-ql-defpred32715 -Node: Dynamic block36156 -Node: Links38880 -Node: Tips39567 -Node: Changelog39891 -Node: 09-pre40782 -Node: 08440889 -Node: 08341344 -Node: 08241685 -Node: 08142078 -Node: 0842499 -Node: 07445223 -Node: 07345446 -Node: 07246178 -Node: 07147097 -Node: 0747908 -Node: 06350774 -Node: 06251307 -Node: 06151614 -Node: 0652184 -Node: 05255240 -Node: 05155542 -Node: 0555967 -Node: 04957498 -Node: 04857780 -Node: 04758129 -Node: 04658538 -Node: 04558946 -Node: 04459307 -Node: 04359666 -Node: 04259869 -Node: 04160030 -Node: 0460277 -Node: 03264378 -Node: 03164781 -Node: 0364978 -Node: 02368278 -Node: 02268512 -Node: 02168792 -Node: 0268997 -Node: 0173075 -Node: Notes73176 -Node: Comparison with Org Agenda searches73338 -Node: org-sidebar74227 -Node: License74506 +Node: Contents1940 +Node: Screenshots2063 +Node: Installation2181 +Node: Quelpa2695 +Node: Helm support3223 +Node: Usage3626 +Node: Commands4024 +Node: org-ql-find4489 +Node: org-ql-open-link5397 +Node: org-ql-refile6252 +Node: org-ql-search6580 +Node: helm-org-ql8511 +Node: org-ql-view8889 +Node: org-ql-view-sidebar9419 +Node: org-ql-view-recent-items9799 +Node: org-ql-sparse-tree10295 +Node: Queries11095 +Node: Non-sexp query syntax12212 +Node: General predicates13971 +Node: Ancestor/descendant predicates20896 +Node: Date/time predicates22024 +Node: Functions / Macros25148 +Node: Agenda-like views25446 +Ref: Function org-ql-block25608 +Node: Listing / acting-on results26869 +Ref: Caching27077 +Ref: Function org-ql-select27990 +Ref: Function org-ql-query30416 +Ref: Macro org-ql (deprecated)32190 +Node: Custom predicates32505 +Ref: Macro org-ql-defpred32729 +Node: Dynamic block36170 +Node: Links38894 +Node: Tips39581 +Node: Changelog39905 +Node: 09-pre40810 +Node: 08540917 +Node: 08441576 +Node: 08342028 +Node: 08242369 +Node: 08142762 +Node: 0843183 +Node: 07445907 +Node: 07346130 +Node: 07246862 +Node: 07147783 +Node: 0748594 +Node: 06351460 +Node: 06251993 +Node: 06152300 +Node: 0652870 +Node: 05255926 +Node: 05156228 +Node: 0556653 +Node: 04958184 +Node: 04858466 +Node: 04758815 +Node: 04659224 +Node: 04559632 +Node: 04459993 +Node: 04360352 +Node: 04260555 +Node: 04160716 +Node: 0460963 +Node: 03265064 +Node: 03165467 +Node: 0365664 +Node: 02368964 +Node: 02269198 +Node: 02169478 +Node: 0269683 +Node: 0173761 +Node: Notes73862 +Node: Comparison with Org Agenda searches74024 +Node: org-sidebar74913 +Node: License75192  End Tag Table diff --git a/tests/test-org-ql.el b/tests/test-org-ql.el index 7bf6a9b..8d2b84c 100644 --- a/tests/test-org-ql.el +++ b/tests/test-org-ql.el @@ -218,7 +218,8 @@ with keyword arg NOW in PLIST." (it "coalesces a single AND clause that uses two predicates (and preserves predicate order)" (expect (org-ql--normalize-query '(and (rifle "foo") (rifle "bar") (heading "baz") (heading "buz"))) - :to-equal '(and (rifle :regexps '("foo" "bar")) (heading "baz" "buz")))) + ;; NOTE: `heading' is normalized to `heading-regexp'. + :to-equal '(and (rifle :regexps '("foo" "bar")) (heading-regexp "baz" "buz")))) (it "preserves independent OR clauses" (expect (org-ql--normalize-query '(and (or (rifle "foo") (rifle "bar")) (or (rifle "baz") (rifle "buz")))) @@ -253,6 +254,18 @@ with keyword arg NOW in PLIST." (expect (org-ql--normalize-query "\"quoted phrase\"") :to-equal '(rifle :regexps '("\"quoted phrase\"")))) + (describe "Ancestor/Parent predicates" + ;; NOTE: Because the ancestor and parent predicates byte-compile their + ;; subquery predicates, we have to test the byte-compiled forms here. + (expect (org-ql--normalize-query '(ancestors "scheduled")) + ;; No colon after keyword, so not a predicate query. + :to-equal ;; '(ancestors (rifle :regexps '("scheduled"))) + `(ancestors #[nil "\300\301\302\"\207" [rifle :regexps ("scheduled")] 3])) + (expect (org-ql--normalize-query '(parent "scheduled")) + ;; No colon after keyword, so not a predicate query. + :to-equal ;; '(parent (rifle :regexps '("scheduled"))) + `(parent #[nil "\300\301\302\"\207" [rifle :regexps ("scheduled")] 3]))) + (describe "Plain strings" (it "normalizes plain strings to the default predicate (using AND)" (expect (org-ql--normalize-query '(and "string1" "string2")) @@ -1112,7 +1125,12 @@ with keyword arg NOW in PLIST." '("Take over the world"))) (org-ql-it "with two arguments" (org-ql-expect ('(heading "Take over" "world")) - '("Take over the world")))) + '("Take over the world"))) + (org-ql-it "does not match strings as regexps" + (org-ql-expect ('(heading "over")) + '("Take over the universe" "Take over the world" "Take over Mars" "Take over the moon")) + (org-ql-expect ('(heading "[over]")) + nil))) (describe "(heading-regexp)" (org-ql-it "with one argument"