flowquery 1.0.62__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.
- {flowquery-1.0.62 → flowquery-1.0.63}/PKG-INFO +1 -1
- {flowquery-1.0.62 → flowquery-1.0.63}/flowquery.egg-info/PKG-INFO +1 -1
- {flowquery-1.0.62 → flowquery-1.0.63}/pyproject.toml +1 -1
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/expressions/operator.py +14 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/aggregated_return.py +9 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/aggregated_with.py +36 -9
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/group_by.py +52 -29
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/order_by.py +5 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/parser.py +14 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/tokenization/tokenizer.py +15 -4
- {flowquery-1.0.62 → flowquery-1.0.63}/README.md +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/flowquery.egg-info/SOURCES.txt +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/flowquery.egg-info/dependency_links.txt +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/flowquery.egg-info/entry_points.txt +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/flowquery.egg-info/requires.txt +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/flowquery.egg-info/top_level.txt +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/setup.cfg +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/__init__.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/__main__.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/compute/__init__.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/compute/flowquery.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/compute/provenance.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/compute/runner.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/extensibility.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/graph/__init__.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/graph/bindings.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/graph/data.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/graph/data_cache.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/graph/data_resolver.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/graph/database.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/graph/hops.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/graph/node.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/graph/node_data.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/graph/node_reference.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/graph/pattern.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/graph/pattern_expression.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/graph/patterns.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/graph/physical_node.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/graph/physical_relationship.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/graph/relationship.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/graph/relationship_data.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/graph/relationship_match_collector.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/graph/relationship_reference.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/graph/virtual_sources.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/io/__init__.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/io/command_line.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/__init__.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/alias.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/alias_option.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/ast_node.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/base_parser.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/components/__init__.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/components/csv.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/components/from_.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/components/headers.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/components/json.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/components/null.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/components/post.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/components/text.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/context.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/data_structures/__init__.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/data_structures/associative_array.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/data_structures/json_array.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/data_structures/key_value_pair.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/data_structures/list_comprehension.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/data_structures/lookup.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/data_structures/range_lookup.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/expressions/__init__.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/expressions/binding_reference.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/expressions/boolean.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/expressions/collect_subquery.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/expressions/count_subquery.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/expressions/exists_subquery.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/expressions/expression.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/expressions/expression_map.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/expressions/f_string.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/expressions/identifier.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/expressions/number.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/expressions/parameter_reference.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/expressions/reference.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/expressions/string.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/expressions/subquery_expression.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/__init__.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/aggregate_function.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/async_function.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/avg.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/coalesce.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/collect.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/count.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/date_.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/datetime_.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/duration.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/element_id.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/function.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/function_factory.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/function_metadata.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/functions.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/head.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/id_.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/join.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/keys.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/labels.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/last.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/length.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/localdatetime.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/localtime.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/log.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/log10.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/max_.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/min_.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/nodes.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/pow.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/predicate_all.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/predicate_any.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/predicate_function.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/predicate_none.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/predicate_single.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/predicate_sum.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/properties.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/rand.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/range_.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/reducer_element.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/relationships.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/replace.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/round_.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/schema.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/size.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/split.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/string_distance.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/stringify.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/substring.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/sum.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/tail.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/temporal_utils.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/time_.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/timestamp.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/to_float.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/to_integer.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/to_json.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/to_lower.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/to_string.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/trim.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/type_.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/functions/value_holder.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/logic/__init__.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/logic/case.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/logic/else_.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/logic/end.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/logic/then.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/logic/when.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/__init__.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/call.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/create_node.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/create_relationship.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/delete_node.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/delete_relationship.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/drop_binding.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/let.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/limit.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/load.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/match.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/merge.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/operation.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/projection.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/refresh_binding.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/refresh_node.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/refresh_relationship.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/return_op.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/union.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/union_all.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/unwind.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/update.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/update_delete.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/where.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/operations/with_op.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/parser_state.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/statement_info_crawler.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/parsing/token_to_node.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/tokenization/__init__.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/tokenization/keyword.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/tokenization/operator.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/tokenization/string_walker.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/tokenization/symbol.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/tokenization/token.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/tokenization/token_mapper.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/tokenization/token_type.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/tokenization/trie.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/utils/__init__.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/utils/object_utils.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/src/utils/string_utils.py +0 -0
- {flowquery-1.0.62 → flowquery-1.0.63}/tests/test_extensibility.py +0 -0
|
@@ -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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
193
|
+
def _generate_leaves(
|
|
194
194
|
self,
|
|
195
195
|
mapper_index: int = 0,
|
|
196
|
-
node: Optional[GroupByNode] = None
|
|
197
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
221
|
-
self
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|