flowquery 1.0.61__tar.gz → 1.0.63__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. {flowquery-1.0.61 → flowquery-1.0.63}/PKG-INFO +5 -5
  2. {flowquery-1.0.61 → flowquery-1.0.63}/flowquery.egg-info/PKG-INFO +5 -5
  3. {flowquery-1.0.61 → flowquery-1.0.63}/pyproject.toml +5 -5
  4. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/expressions/operator.py +14 -0
  5. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/aggregated_return.py +9 -0
  6. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/aggregated_with.py +36 -9
  7. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/group_by.py +52 -29
  8. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/order_by.py +5 -0
  9. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/parser.py +14 -0
  10. {flowquery-1.0.61 → flowquery-1.0.63}/src/tokenization/tokenizer.py +15 -4
  11. {flowquery-1.0.61 → flowquery-1.0.63}/README.md +0 -0
  12. {flowquery-1.0.61 → flowquery-1.0.63}/flowquery.egg-info/SOURCES.txt +0 -0
  13. {flowquery-1.0.61 → flowquery-1.0.63}/flowquery.egg-info/dependency_links.txt +0 -0
  14. {flowquery-1.0.61 → flowquery-1.0.63}/flowquery.egg-info/entry_points.txt +0 -0
  15. {flowquery-1.0.61 → flowquery-1.0.63}/flowquery.egg-info/requires.txt +0 -0
  16. {flowquery-1.0.61 → flowquery-1.0.63}/flowquery.egg-info/top_level.txt +0 -0
  17. {flowquery-1.0.61 → flowquery-1.0.63}/setup.cfg +0 -0
  18. {flowquery-1.0.61 → flowquery-1.0.63}/src/__init__.py +0 -0
  19. {flowquery-1.0.61 → flowquery-1.0.63}/src/__main__.py +0 -0
  20. {flowquery-1.0.61 → flowquery-1.0.63}/src/compute/__init__.py +0 -0
  21. {flowquery-1.0.61 → flowquery-1.0.63}/src/compute/flowquery.py +0 -0
  22. {flowquery-1.0.61 → flowquery-1.0.63}/src/compute/provenance.py +0 -0
  23. {flowquery-1.0.61 → flowquery-1.0.63}/src/compute/runner.py +0 -0
  24. {flowquery-1.0.61 → flowquery-1.0.63}/src/extensibility.py +0 -0
  25. {flowquery-1.0.61 → flowquery-1.0.63}/src/graph/__init__.py +0 -0
  26. {flowquery-1.0.61 → flowquery-1.0.63}/src/graph/bindings.py +0 -0
  27. {flowquery-1.0.61 → flowquery-1.0.63}/src/graph/data.py +0 -0
  28. {flowquery-1.0.61 → flowquery-1.0.63}/src/graph/data_cache.py +0 -0
  29. {flowquery-1.0.61 → flowquery-1.0.63}/src/graph/data_resolver.py +0 -0
  30. {flowquery-1.0.61 → flowquery-1.0.63}/src/graph/database.py +0 -0
  31. {flowquery-1.0.61 → flowquery-1.0.63}/src/graph/hops.py +0 -0
  32. {flowquery-1.0.61 → flowquery-1.0.63}/src/graph/node.py +0 -0
  33. {flowquery-1.0.61 → flowquery-1.0.63}/src/graph/node_data.py +0 -0
  34. {flowquery-1.0.61 → flowquery-1.0.63}/src/graph/node_reference.py +0 -0
  35. {flowquery-1.0.61 → flowquery-1.0.63}/src/graph/pattern.py +0 -0
  36. {flowquery-1.0.61 → flowquery-1.0.63}/src/graph/pattern_expression.py +0 -0
  37. {flowquery-1.0.61 → flowquery-1.0.63}/src/graph/patterns.py +0 -0
  38. {flowquery-1.0.61 → flowquery-1.0.63}/src/graph/physical_node.py +0 -0
  39. {flowquery-1.0.61 → flowquery-1.0.63}/src/graph/physical_relationship.py +0 -0
  40. {flowquery-1.0.61 → flowquery-1.0.63}/src/graph/relationship.py +0 -0
  41. {flowquery-1.0.61 → flowquery-1.0.63}/src/graph/relationship_data.py +0 -0
  42. {flowquery-1.0.61 → flowquery-1.0.63}/src/graph/relationship_match_collector.py +0 -0
  43. {flowquery-1.0.61 → flowquery-1.0.63}/src/graph/relationship_reference.py +0 -0
  44. {flowquery-1.0.61 → flowquery-1.0.63}/src/graph/virtual_sources.py +0 -0
  45. {flowquery-1.0.61 → flowquery-1.0.63}/src/io/__init__.py +0 -0
  46. {flowquery-1.0.61 → flowquery-1.0.63}/src/io/command_line.py +0 -0
  47. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/__init__.py +0 -0
  48. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/alias.py +0 -0
  49. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/alias_option.py +0 -0
  50. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/ast_node.py +0 -0
  51. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/base_parser.py +0 -0
  52. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/components/__init__.py +0 -0
  53. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/components/csv.py +0 -0
  54. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/components/from_.py +0 -0
  55. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/components/headers.py +0 -0
  56. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/components/json.py +0 -0
  57. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/components/null.py +0 -0
  58. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/components/post.py +0 -0
  59. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/components/text.py +0 -0
  60. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/context.py +0 -0
  61. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/data_structures/__init__.py +0 -0
  62. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/data_structures/associative_array.py +0 -0
  63. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/data_structures/json_array.py +0 -0
  64. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/data_structures/key_value_pair.py +0 -0
  65. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/data_structures/list_comprehension.py +0 -0
  66. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/data_structures/lookup.py +0 -0
  67. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/data_structures/range_lookup.py +0 -0
  68. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/expressions/__init__.py +0 -0
  69. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/expressions/binding_reference.py +0 -0
  70. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/expressions/boolean.py +0 -0
  71. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/expressions/collect_subquery.py +0 -0
  72. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/expressions/count_subquery.py +0 -0
  73. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/expressions/exists_subquery.py +0 -0
  74. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/expressions/expression.py +0 -0
  75. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/expressions/expression_map.py +0 -0
  76. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/expressions/f_string.py +0 -0
  77. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/expressions/identifier.py +0 -0
  78. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/expressions/number.py +0 -0
  79. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/expressions/parameter_reference.py +0 -0
  80. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/expressions/reference.py +0 -0
  81. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/expressions/string.py +0 -0
  82. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/expressions/subquery_expression.py +0 -0
  83. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/__init__.py +0 -0
  84. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/aggregate_function.py +0 -0
  85. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/async_function.py +0 -0
  86. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/avg.py +0 -0
  87. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/coalesce.py +0 -0
  88. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/collect.py +0 -0
  89. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/count.py +0 -0
  90. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/date_.py +0 -0
  91. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/datetime_.py +0 -0
  92. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/duration.py +0 -0
  93. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/element_id.py +0 -0
  94. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/function.py +0 -0
  95. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/function_factory.py +0 -0
  96. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/function_metadata.py +0 -0
  97. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/functions.py +0 -0
  98. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/head.py +0 -0
  99. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/id_.py +0 -0
  100. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/join.py +0 -0
  101. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/keys.py +0 -0
  102. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/labels.py +0 -0
  103. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/last.py +0 -0
  104. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/length.py +0 -0
  105. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/localdatetime.py +0 -0
  106. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/localtime.py +0 -0
  107. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/log.py +0 -0
  108. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/log10.py +0 -0
  109. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/max_.py +0 -0
  110. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/min_.py +0 -0
  111. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/nodes.py +0 -0
  112. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/pow.py +0 -0
  113. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/predicate_all.py +0 -0
  114. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/predicate_any.py +0 -0
  115. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/predicate_function.py +0 -0
  116. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/predicate_none.py +0 -0
  117. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/predicate_single.py +0 -0
  118. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/predicate_sum.py +0 -0
  119. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/properties.py +0 -0
  120. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/rand.py +0 -0
  121. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/range_.py +0 -0
  122. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/reducer_element.py +0 -0
  123. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/relationships.py +0 -0
  124. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/replace.py +0 -0
  125. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/round_.py +0 -0
  126. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/schema.py +0 -0
  127. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/size.py +0 -0
  128. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/split.py +0 -0
  129. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/string_distance.py +0 -0
  130. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/stringify.py +0 -0
  131. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/substring.py +0 -0
  132. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/sum.py +0 -0
  133. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/tail.py +0 -0
  134. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/temporal_utils.py +0 -0
  135. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/time_.py +0 -0
  136. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/timestamp.py +0 -0
  137. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/to_float.py +0 -0
  138. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/to_integer.py +0 -0
  139. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/to_json.py +0 -0
  140. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/to_lower.py +0 -0
  141. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/to_string.py +0 -0
  142. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/trim.py +0 -0
  143. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/type_.py +0 -0
  144. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/functions/value_holder.py +0 -0
  145. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/logic/__init__.py +0 -0
  146. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/logic/case.py +0 -0
  147. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/logic/else_.py +0 -0
  148. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/logic/end.py +0 -0
  149. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/logic/then.py +0 -0
  150. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/logic/when.py +0 -0
  151. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/__init__.py +0 -0
  152. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/call.py +0 -0
  153. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/create_node.py +0 -0
  154. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/create_relationship.py +0 -0
  155. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/delete_node.py +0 -0
  156. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/delete_relationship.py +0 -0
  157. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/drop_binding.py +0 -0
  158. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/let.py +0 -0
  159. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/limit.py +0 -0
  160. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/load.py +0 -0
  161. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/match.py +0 -0
  162. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/merge.py +0 -0
  163. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/operation.py +0 -0
  164. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/projection.py +0 -0
  165. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/refresh_binding.py +0 -0
  166. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/refresh_node.py +0 -0
  167. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/refresh_relationship.py +0 -0
  168. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/return_op.py +0 -0
  169. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/union.py +0 -0
  170. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/union_all.py +0 -0
  171. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/unwind.py +0 -0
  172. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/update.py +0 -0
  173. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/update_delete.py +0 -0
  174. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/where.py +0 -0
  175. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/operations/with_op.py +0 -0
  176. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/parser_state.py +0 -0
  177. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/statement_info_crawler.py +0 -0
  178. {flowquery-1.0.61 → flowquery-1.0.63}/src/parsing/token_to_node.py +0 -0
  179. {flowquery-1.0.61 → flowquery-1.0.63}/src/tokenization/__init__.py +0 -0
  180. {flowquery-1.0.61 → flowquery-1.0.63}/src/tokenization/keyword.py +0 -0
  181. {flowquery-1.0.61 → flowquery-1.0.63}/src/tokenization/operator.py +0 -0
  182. {flowquery-1.0.61 → flowquery-1.0.63}/src/tokenization/string_walker.py +0 -0
  183. {flowquery-1.0.61 → flowquery-1.0.63}/src/tokenization/symbol.py +0 -0
  184. {flowquery-1.0.61 → flowquery-1.0.63}/src/tokenization/token.py +0 -0
  185. {flowquery-1.0.61 → flowquery-1.0.63}/src/tokenization/token_mapper.py +0 -0
  186. {flowquery-1.0.61 → flowquery-1.0.63}/src/tokenization/token_type.py +0 -0
  187. {flowquery-1.0.61 → flowquery-1.0.63}/src/tokenization/trie.py +0 -0
  188. {flowquery-1.0.61 → flowquery-1.0.63}/src/utils/__init__.py +0 -0
  189. {flowquery-1.0.61 → flowquery-1.0.63}/src/utils/object_utils.py +0 -0
  190. {flowquery-1.0.61 → flowquery-1.0.63}/src/utils/string_utils.py +0 -0
  191. {flowquery-1.0.61 → flowquery-1.0.63}/tests/test_extensibility.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flowquery
3
- Version: 1.0.61
3
+ Version: 1.0.63
4
4
  Summary: A declarative query language for data processing pipelines
5
- Author: FlowQuery Contributors
5
+ Author-email: Team Microsoft FlowQuery <flowquery@microsoft.com>
6
6
  License: MIT
7
- Project-URL: Homepage, https://github.com/microsoft/FlowQuery/flowquery-py
8
- Project-URL: Repository, https://github.com/microsoft/FlowQuery/flowquery-py
9
- Project-URL: Documentation, https://github.com/microsoft/FlowQuery/flowquery-py#readme
7
+ Project-URL: Homepage, https://github.com/microsoft/FlowQuery
8
+ Project-URL: Repository, https://github.com/microsoft/FlowQuery
9
+ Project-URL: Documentation, https://github.com/microsoft/FlowQuery#readme
10
10
  Project-URL: Issues, https://github.com/microsoft/FlowQuery/issues
11
11
  Keywords: query,data-processing,pipeline,declarative
12
12
  Classifier: Development Status :: 4 - Beta
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flowquery
3
- Version: 1.0.61
3
+ Version: 1.0.63
4
4
  Summary: A declarative query language for data processing pipelines
5
- Author: FlowQuery Contributors
5
+ Author-email: Team Microsoft FlowQuery <flowquery@microsoft.com>
6
6
  License: MIT
7
- Project-URL: Homepage, https://github.com/microsoft/FlowQuery/flowquery-py
8
- Project-URL: Repository, https://github.com/microsoft/FlowQuery/flowquery-py
9
- Project-URL: Documentation, https://github.com/microsoft/FlowQuery/flowquery-py#readme
7
+ Project-URL: Homepage, https://github.com/microsoft/FlowQuery
8
+ Project-URL: Repository, https://github.com/microsoft/FlowQuery
9
+ Project-URL: Documentation, https://github.com/microsoft/FlowQuery#readme
10
10
  Project-URL: Issues, https://github.com/microsoft/FlowQuery/issues
11
11
  Keywords: query,data-processing,pipeline,declarative
12
12
  Classifier: Development Status :: 4 - Beta
@@ -1,12 +1,12 @@
1
1
  [project]
2
2
  name = "flowquery"
3
- version = "1.0.61"
3
+ version = "1.0.63"
4
4
  description = "A declarative query language for data processing pipelines"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
7
7
  license = {text = "MIT"}
8
8
  authors = [
9
- {name = "FlowQuery Contributors"}
9
+ {name = "Team Microsoft FlowQuery", email = "flowquery@microsoft.com"}
10
10
  ]
11
11
  keywords = ["query", "data-processing", "pipeline", "declarative"]
12
12
  classifiers = [
@@ -29,9 +29,9 @@ dependencies = [
29
29
  flowquery = "flowquery.io.command_line:main"
30
30
 
31
31
  [project.urls]
32
- Homepage = "https://github.com/microsoft/FlowQuery/flowquery-py"
33
- Repository = "https://github.com/microsoft/FlowQuery/flowquery-py"
34
- Documentation = "https://github.com/microsoft/FlowQuery/flowquery-py#readme"
32
+ Homepage = "https://github.com/microsoft/FlowQuery"
33
+ Repository = "https://github.com/microsoft/FlowQuery"
34
+ Documentation = "https://github.com/microsoft/FlowQuery#readme"
35
35
  Issues = "https://github.com/microsoft/FlowQuery/issues"
36
36
 
37
37
  [project.optional-dependencies]
@@ -161,6 +161,20 @@ class Not(Operator):
161
161
  return 1 if not self.lhs.value() else 0
162
162
 
163
163
 
164
+ class Negate(Operator):
165
+ """Unary minus. Binds to a single operand, so ``-a + b`` is ``(-a) + b``."""
166
+
167
+ def __init__(self) -> None:
168
+ super().__init__(0, True)
169
+
170
+ def is_operator(self) -> bool:
171
+ return False
172
+
173
+ def value(self) -> Any:
174
+ value = self.lhs.value()
175
+ return None if value is None else -value
176
+
177
+
164
178
  class Is(Operator):
165
179
  def __init__(self) -> None:
166
180
  super().__init__(0, True)
@@ -52,6 +52,9 @@ class AggregatedReturn(Return):
52
52
  # (e.g. ``RETURN count(*)`` over no MATCH). This keeps
53
53
  # ``runner.provenance`` length aligned with ``runner.results``.
54
54
  want_provenance = self._provenance_sink is not None
55
+ # Re-emission re-walks the group tree, so drop stale keys first.
56
+ if self._order_by is not None:
57
+ self._order_by.reset_sort_keys()
55
58
  if want_provenance:
56
59
  record_iter = self._group_by.generate_results()
57
60
  prov_iter = self._group_by.generate_provenance()
@@ -64,10 +67,16 @@ class AggregatedReturn(Return):
64
67
  p = next(prov_iter)
65
68
  except StopIteration:
66
69
  break
70
+ # Evaluated while this group's overrides are live, so ORDER BY
71
+ # supports arbitrary expressions and not just bare aliases.
72
+ if self._order_by is not None:
73
+ self._order_by.capture_sort_keys()
67
74
  results.append(r)
68
75
  provenance.append(p)
69
76
  else:
70
77
  for r in self._group_by.generate_results():
78
+ if self._order_by is not None:
79
+ self._order_by.capture_sort_keys()
71
80
  results.append(r)
72
81
  if self._order_by is not None:
73
82
  indices = self._order_by.sort_indices(results)
@@ -1,4 +1,4 @@
1
- from typing import List, Optional
1
+ from typing import Any, Callable, Dict, List, Optional
2
2
 
3
3
  from ...compute.provenance import ProvenanceSource, RowProvenance, RowSegment
4
4
  from ..ast_node import ASTNode
@@ -48,13 +48,40 @@ class AggregatedWith(Return):
48
48
  async def finish(self) -> None:
49
49
  want_provenance = self._group_by.provenance_enabled
50
50
  prov_iter = self._group_by.generate_provenance() if want_provenance else None
51
- for _ in self._group_by.generate_results():
52
- if prov_iter is not None:
53
- try:
54
- self._current_group_provenance = next(prov_iter)
55
- except StopIteration:
56
- self._current_group_provenance = None
57
- if self.next:
58
- await self.next.run()
51
+ if self._order_by is not None:
52
+ # Re-emission re-walks the group tree, so drop stale keys first.
53
+ self._order_by.reset_sort_keys()
54
+ # Groups must be buffered so ORDER BY can permute them before
55
+ # any downstream operation (notably LIMIT) consumes the stream.
56
+ records: List[Dict[str, Any]] = []
57
+ restores: List[Callable[[], None]] = []
58
+ provenance: List[Optional[RowProvenance]] = []
59
+ for record, restore in self._group_by.generate_groups():
60
+ # Evaluated while this group's overrides are live, so ORDER BY
61
+ # supports arbitrary expressions and not just bare aliases.
62
+ self._order_by.capture_sort_keys()
63
+ records.append(record)
64
+ restores.append(restore)
65
+ if prov_iter is not None:
66
+ try:
67
+ provenance.append(next(prov_iter))
68
+ except StopIteration:
69
+ provenance.append(None)
70
+ for index in self._order_by.sort_indices(records):
71
+ restores[index]()
72
+ self._current_group_provenance = (
73
+ None if prov_iter is None else provenance[index]
74
+ )
75
+ if self.next:
76
+ await self.next.run()
77
+ else:
78
+ for _ in self._group_by.generate_results():
79
+ if prov_iter is not None:
80
+ try:
81
+ self._current_group_provenance = next(prov_iter)
82
+ except StopIteration:
83
+ self._current_group_provenance = None
84
+ if self.next:
85
+ await self.next.run()
59
86
  self._current_group_provenance = None
60
87
  await super().finish()
@@ -1,7 +1,7 @@
1
1
  """GroupBy implementation for aggregate operations."""
2
2
 
3
3
  import json
4
- from typing import Any, Callable, Dict, Generator, List, Optional
4
+ from typing import Any, Callable, Dict, Generator, List, Optional, Tuple
5
5
 
6
6
  from ...compute.provenance import (
7
7
  NodeBinding,
@@ -190,13 +190,22 @@ class GroupBy(Projection):
190
190
  self._reducers.extend(child.reducers())
191
191
  return self._reducers
192
192
 
193
- def generate_results(
193
+ def _generate_leaves(
194
194
  self,
195
195
  mapper_index: int = 0,
196
- node: Optional[GroupByNode] = None
197
- ) -> Generator[Dict[str, Any], None, None]:
196
+ node: Optional[GroupByNode] = None,
197
+ path: Optional[List[Any]] = None,
198
+ ) -> Generator[Tuple[GroupByNode, List[Any]], None, None]:
199
+ """Depth-first walk over the group tree that applies each
200
+ group's mapper and reducer overrides before yielding its leaf,
201
+ together with the mapper values collected along the path.
202
+ Shared by the result, group and provenance generators so they
203
+ always traverse in identical order.
204
+ """
198
205
  if node is None:
199
206
  node = self._root
207
+ if path is None:
208
+ path = []
200
209
 
201
210
  if mapper_index == 0 and len(node.children) == 0 and len(self.mappers) > 0:
202
211
  return
@@ -204,46 +213,60 @@ class GroupBy(Projection):
204
213
  if len(node.children) > 0:
205
214
  for child in node.children.values():
206
215
  self.mappers[mapper_index].overridden = child.value
207
- yield from self.generate_results(mapper_index + 1, child)
216
+ yield from self._generate_leaves(
217
+ mapper_index + 1, child, path + [child.value]
218
+ )
208
219
  else:
209
220
  if node.elements is None:
210
221
  node.elements = [reducer.element() for reducer in self.reducers]
211
222
  if node.elements:
212
223
  for i, element in enumerate(node.elements):
213
224
  self.reducers[i].overridden = element.value
225
+ yield node, path
226
+
227
+ def _make_restore(
228
+ self, path: List[Any], element_values: List[Any]
229
+ ) -> Callable[[], None]:
230
+ # A factory, not an inline closure: consumers hold restorers past
231
+ # the yield, so the captured values must not be late-bound.
232
+ def restore() -> None:
233
+ for i, value in enumerate(path):
234
+ self.mappers[i].overridden = value
235
+ for i, value in enumerate(element_values):
236
+ self.reducers[i].overridden = value
237
+
238
+ return restore
239
+
240
+ def generate_groups(
241
+ self,
242
+ ) -> Generator[Tuple[Dict[str, Any], Callable[[], None]], None, None]:
243
+ """Yields each emitted group together with a callback that
244
+ re-applies that group's mapper and reducer overrides. Lets
245
+ callers replay groups in an order other than tree-traversal
246
+ order (e.g. after ORDER BY) while keeping downstream expression
247
+ evaluation correct.
248
+ """
249
+ for node, path in self._generate_leaves():
214
250
  record: Dict[str, Any] = {}
215
251
  for expression, alias in self.expressions():
216
252
  record[alias] = expression.value()
217
- if self.where_condition:
218
- yield record
253
+ if not self.where_condition:
254
+ continue
255
+ element_values = [element.value for element in (node.elements or [])]
256
+ yield record, self._make_restore(path, element_values)
219
257
 
220
- def generate_provenance(
221
- self,
222
- mapper_index: int = 0,
223
- node: Optional[GroupByNode] = None,
224
- ) -> Generator[RowProvenance, None, None]:
258
+ def generate_results(self) -> Generator[Dict[str, Any], None, None]:
259
+ for record, _ in self.generate_groups():
260
+ yield record
261
+
262
+ def generate_provenance(self) -> Generator[RowProvenance, None, None]:
225
263
  """Walks the group tree in the same traversal order as
226
264
  :meth:`generate_results`, yielding the materialised
227
265
  :class:`RowProvenance` for each emitted group.
228
266
  """
229
- if node is None:
230
- node = self._root
231
-
232
- if mapper_index == 0 and len(node.children) == 0 and len(self.mappers) > 0:
233
- return
234
-
235
- if len(node.children) > 0:
236
- for child in node.children.values():
237
- self.mappers[mapper_index].overridden = child.value
238
- yield from self.generate_provenance(mapper_index + 1, child)
239
- else:
240
- if node.elements is None:
241
- node.elements = [reducer.element() for reducer in self.reducers]
242
- if node.elements:
243
- for i, element in enumerate(node.elements):
244
- self.reducers[i].overridden = element.value
267
+ for node, _ in self._generate_leaves():
245
268
  if not self.where_condition:
246
- return
269
+ continue
247
270
  yield RowProvenance(
248
271
  nodes=list(node.provenance_nodes.values()),
249
272
  relationships=list(node.provenance_relationships.values()),
@@ -48,6 +48,11 @@ class OrderBy(Operation):
48
48
  accumulated row (from ``Return.run()``)."""
49
49
  self._sort_keys.append([f.expression.value() for f in self._fields])
50
50
 
51
+ def reset_sort_keys(self) -> None:
52
+ """Discard keys from a previous emission pass so a re-run starts
53
+ clean."""
54
+ self._sort_keys = []
55
+
51
56
  def sort(self, records: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
52
57
  """Sort records using pre-computed sort keys captured during
53
58
  accumulation. When no keys have been captured (e.g. aggregated
@@ -38,6 +38,7 @@ from .expressions.operator import (
38
38
  In,
39
39
  Is,
40
40
  IsNot,
41
+ Negate,
41
42
  Not,
42
43
  NotContains,
43
44
  NotEndsWith,
@@ -1685,6 +1686,19 @@ class Parser(BaseParser):
1685
1686
  raise ValueError("Expected CASE statement")
1686
1687
  expression.add_node(case)
1687
1688
  return True
1689
+ elif self.token.is_subtract():
1690
+ # Only unary here: a binary minus is consumed as an operator by
1691
+ # _parse_expression, and the tokenizer folds ``-<digit>`` into a
1692
+ # literal.
1693
+ negate = Negate()
1694
+ self.set_next_token()
1695
+ temp_expr = Expression()
1696
+ if not self._parse_operand(temp_expr):
1697
+ raise ValueError("Expected expression after unary minus")
1698
+ temp_expr.finish()
1699
+ negate.add_child(temp_expr)
1700
+ expression.add_node(negate)
1701
+ return True
1688
1702
  elif self.token.is_not():
1689
1703
  not_node = Not()
1690
1704
  self.set_next_token()
@@ -207,8 +207,19 @@ class Tokenizer:
207
207
  return None
208
208
 
209
209
  def _skip_minus(self, last: Optional[Token], current: Token) -> bool:
210
- if last is None:
210
+ if last is None or not current.is_negation():
211
211
  return False
212
- if (last.is_keyword() or last.is_comma() or last.is_colon()) and current.is_negation():
213
- return True
214
- return False
212
+ # A digit must follow immediately, otherwise relationship patterns
213
+ # such as ``<-[r]-`` and ``-->`` would lex their ``-`` as a number lead.
214
+ remaining = self._walker.get_remaining_string()
215
+ if len(remaining) < 2 or remaining[1] not in '0123456789':
216
+ return False
217
+ # Prefix position: no operand can precede the ``-``, so it is a sign.
218
+ return (
219
+ last.is_keyword()
220
+ or last.is_comma()
221
+ or last.is_colon()
222
+ or last.is_left_parenthesis()
223
+ or last.is_opening_bracket()
224
+ or last.is_operator()
225
+ )
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes