flowquery 1.0.65__tar.gz → 1.0.66__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.65 → flowquery-1.0.66}/PKG-INFO +142 -1
- {flowquery-1.0.65 → flowquery-1.0.66}/README.md +141 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/flowquery.egg-info/PKG-INFO +142 -1
- {flowquery-1.0.65 → flowquery-1.0.66}/flowquery.egg-info/SOURCES.txt +5 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/pyproject.toml +1 -1
- {flowquery-1.0.65 → flowquery-1.0.66}/src/__init__.py +40 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/compute/flowquery.py +51 -1
- {flowquery-1.0.65 → flowquery-1.0.66}/src/compute/runner.py +101 -4
- {flowquery-1.0.65 → flowquery-1.0.66}/src/graph/data.py +5 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/graph/data_cache.py +26 -5
- {flowquery-1.0.65 → flowquery-1.0.66}/src/graph/data_resolver.py +58 -2
- {flowquery-1.0.65 → flowquery-1.0.66}/src/graph/database.py +93 -1
- {flowquery-1.0.65 → flowquery-1.0.66}/src/graph/node.py +124 -3
- {flowquery-1.0.65 → flowquery-1.0.66}/src/graph/node_reference.py +3 -3
- flowquery-1.0.66/src/graph/pattern.py +399 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/graph/pattern_expression.py +12 -4
- flowquery-1.0.66/src/graph/patterns.py +41 -0
- flowquery-1.0.66/src/graph/physical_node.py +85 -0
- flowquery-1.0.66/src/graph/physical_relationship.py +81 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/graph/relationship.py +135 -2
- {flowquery-1.0.65 → flowquery-1.0.66}/src/graph/relationship_match_collector.py +34 -4
- flowquery-1.0.66/src/graph/source_access.py +700 -0
- flowquery-1.0.66/src/graph/source_context.py +473 -0
- flowquery-1.0.66/src/graph/source_provider.py +539 -0
- flowquery-1.0.66/src/graph/statement_source.py +70 -0
- flowquery-1.0.66/src/graph/virtual_statement.py +274 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/aggregated_return.py +8 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/aggregated_with.py +8 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/create_node.py +18 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/create_relationship.py +24 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/limit.py +4 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/match.py +115 -7
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/operation.py +7 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/return_op.py +30 -1
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/unwind.py +6 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/where.py +4 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/with_op.py +4 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/parser.py +21 -2
- flowquery-1.0.65/src/graph/pattern.py +0 -116
- flowquery-1.0.65/src/graph/patterns.py +0 -34
- flowquery-1.0.65/src/graph/physical_node.py +0 -105
- flowquery-1.0.65/src/graph/physical_relationship.py +0 -97
- {flowquery-1.0.65 → flowquery-1.0.66}/flowquery.egg-info/dependency_links.txt +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/flowquery.egg-info/entry_points.txt +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/flowquery.egg-info/requires.txt +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/flowquery.egg-info/top_level.txt +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/setup.cfg +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/__main__.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/compute/__init__.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/compute/provenance.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/extensibility.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/graph/__init__.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/graph/bindings.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/graph/hops.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/graph/node_data.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/graph/relationship_data.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/graph/relationship_reference.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/graph/virtual_sources.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/io/__init__.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/io/command_line.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/__init__.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/alias.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/alias_option.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/ast_node.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/base_parser.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/components/__init__.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/components/csv.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/components/from_.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/components/headers.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/components/json.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/components/null.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/components/post.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/components/text.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/context.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/data_structures/__init__.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/data_structures/associative_array.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/data_structures/json_array.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/data_structures/key_value_pair.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/data_structures/list_comprehension.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/data_structures/lookup.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/data_structures/range_lookup.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/expressions/__init__.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/expressions/binding_reference.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/expressions/boolean.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/expressions/collect_subquery.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/expressions/count_subquery.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/expressions/exists_subquery.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/expressions/expression.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/expressions/expression_map.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/expressions/f_string.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/expressions/identifier.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/expressions/number.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/expressions/operator.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/expressions/parameter_reference.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/expressions/reference.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/expressions/string.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/expressions/subquery_expression.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/__init__.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/aggregate_function.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/async_function.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/avg.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/coalesce.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/collect.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/count.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/date_.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/datetime_.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/duration.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/element_id.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/function.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/function_factory.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/function_metadata.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/functions.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/head.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/id_.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/join.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/keys.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/labels.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/last.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/length.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/localdatetime.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/localtime.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/log.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/log10.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/max_.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/min_.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/nodes.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/pow.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/predicate_all.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/predicate_any.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/predicate_function.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/predicate_none.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/predicate_single.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/predicate_sum.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/properties.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/rand.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/range_.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/reducer_element.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/relationships.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/replace.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/round_.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/schema.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/size.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/split.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/string_distance.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/stringify.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/substring.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/sum.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/tail.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/temporal_utils.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/time_.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/timestamp.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/to_float.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/to_integer.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/to_json.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/to_lower.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/to_string.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/trim.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/type_.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/functions/value_holder.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/logic/__init__.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/logic/case.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/logic/else_.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/logic/end.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/logic/then.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/logic/when.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/__init__.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/call.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/delete_node.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/delete_relationship.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/drop_binding.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/group_by.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/let.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/load.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/merge.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/order_by.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/projection.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/refresh_binding.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/refresh_node.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/refresh_relationship.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/union.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/union_all.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/update.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/operations/update_delete.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/parser_state.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/statement_info_crawler.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/parsing/token_to_node.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/tokenization/__init__.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/tokenization/keyword.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/tokenization/operator.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/tokenization/string_walker.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/tokenization/symbol.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/tokenization/token.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/tokenization/token_mapper.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/tokenization/token_type.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/tokenization/tokenizer.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/tokenization/trie.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/utils/__init__.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/utils/object_utils.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/src/utils/string_utils.py +0 -0
- {flowquery-1.0.65 → flowquery-1.0.66}/tests/test_extensibility.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flowquery
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.66
|
|
4
4
|
Summary: A declarative query language for data processing pipelines
|
|
5
5
|
Author-email: Team Microsoft FlowQuery <flowquery@microsoft.com>
|
|
6
6
|
License: MIT
|
|
@@ -307,6 +307,147 @@ The same chain is reflected statically in `info.sources` so callers
|
|
|
307
307
|
can introspect the upstream dependency graph without running the
|
|
308
308
|
query.
|
|
309
309
|
|
|
310
|
+
## Backend-Aware Virtual Sources
|
|
311
|
+
|
|
312
|
+
By default a virtual definition is loaded eagerly: its sub-query runs
|
|
313
|
+
once per query and all rows are held in memory. For large or remote
|
|
314
|
+
backends, a **source provider** lets FlowQuery read only what a pattern
|
|
315
|
+
needs. This is opt-in: existing `CREATE VIRTUAL` definitions behave
|
|
316
|
+
exactly as before.
|
|
317
|
+
|
|
318
|
+
The simplest way to write a provider is to subclass `NodeSource` or
|
|
319
|
+
`RelationshipSource` and implement `scan(request, context)`. Implement
|
|
320
|
+
`lookup` (nodes by id) or `expand` (relationships by `left_id`/`right_id`)
|
|
321
|
+
when the backend can fetch selectively; the base class then declares the
|
|
322
|
+
matching capability for you. `version` defaults to the current contract
|
|
323
|
+
version, and capability keys may be written in snake_case
|
|
324
|
+
(`max_batch_size`, `string_ordering`). A method may return a list of
|
|
325
|
+
rows, an awaitable list, or an (async) iterable of pages.
|
|
326
|
+
|
|
327
|
+
The `request` is a `SourceRequest`: a dict with the same camelCase keys
|
|
328
|
+
as the TypeScript implementation (`request["pageSize"]`) that also
|
|
329
|
+
accepts snake_case keys (`request["page_size"]`) and attribute access
|
|
330
|
+
(`request.ids`, `request.side`, `request.predicate`, `request.page_size`).
|
|
331
|
+
|
|
332
|
+
```python
|
|
333
|
+
from flowquery import FlowQuery, NodeSource, RelationshipSource, cursor_pages
|
|
334
|
+
|
|
335
|
+
class People(NodeSource):
|
|
336
|
+
capabilities = {
|
|
337
|
+
"cacheable": True, # identical reads within one query may be cached
|
|
338
|
+
"max_batch_size": 50, # ids per lookup/expand request
|
|
339
|
+
"predicates": {"operators": ["eq", "in", "gt", "gte", "lt", "lte"]},
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
def scan(self, request, context):
|
|
343
|
+
async def fetch(cursor):
|
|
344
|
+
page = await api.people(filter=request.predicate, cursor=cursor)
|
|
345
|
+
return {"rows": page.rows, "cursor": page.next_cursor}
|
|
346
|
+
return cursor_pages(fetch)
|
|
347
|
+
|
|
348
|
+
async def lookup(self, request, context): # declares "lookup": True
|
|
349
|
+
return await api.people_by_ids(request.ids)
|
|
350
|
+
|
|
351
|
+
class Knows(RelationshipSource):
|
|
352
|
+
async def scan(self, request, context):
|
|
353
|
+
return await api.all_knows()
|
|
354
|
+
|
|
355
|
+
async def expand(self, request, context): # declares both expand sides
|
|
356
|
+
key = "from" if request.side == "left" else "to"
|
|
357
|
+
return await api.knows(**{key: request.ids})
|
|
358
|
+
|
|
359
|
+
FlowQuery.register_node_source("Person", People())
|
|
360
|
+
FlowQuery.register_relationship_source("KNOWS", "Person", "Person", Knows())
|
|
361
|
+
|
|
362
|
+
fq = FlowQuery("MATCH (a:Person {id: 7})-[:KNOWS]->(b:Person) RETURN b.name LIMIT 10")
|
|
363
|
+
await fq.run()
|
|
364
|
+
print(fq.source_stats) # calls, pages, rows, cacheHits, batches, plans...
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Subclassing is optional: any object with a `capabilities` dict and a
|
|
368
|
+
`scan` method works, and `SourceProvider` is a `typing.Protocol`
|
|
369
|
+
describing that shape.
|
|
370
|
+
|
|
371
|
+
The engine then loads correlated rows in batches instead of reading
|
|
372
|
+
every person and relationship:
|
|
373
|
+
|
|
374
|
+
- **Correlated loading and batching.** It issues id lookups, expands per
|
|
375
|
+
page of anchor nodes, and makes batched lookups for target nodes.
|
|
376
|
+
- **Selective start points.** A chain may start from its more selective
|
|
377
|
+
end. The decision is recorded in `source_stats["plans"]`, and it can
|
|
378
|
+
change result order when there is no `ORDER BY`.
|
|
379
|
+
- **Predicate push-down.** Pattern properties and `AND`-ed `WHERE`
|
|
380
|
+
comparisons (`=`, `<`, `<=`, `>`, `>=`, `IN`) against literals or
|
|
381
|
+
parameters are sent as `request["predicate"]`.
|
|
382
|
+
- **Streaming with early stop under `LIMIT`.** Reading stops once the
|
|
383
|
+
limit is reached and the provider's iterator is closed.
|
|
384
|
+
- **Query-scoped caching.**
|
|
385
|
+
- **Cancellation.** Call `runner.cancel()` or pass
|
|
386
|
+
`SourceOptions(signal=...)` (an `asyncio.Event` or `SourceSignal`).
|
|
387
|
+
The query raises `QueryCancelledError`.
|
|
388
|
+
- **Read contexts.** Optional `open_read_context()` and
|
|
389
|
+
`close_read_context(snapshot)` methods are called once per query.
|
|
390
|
+
|
|
391
|
+
Providers may return a **superset**, because FlowQuery re-checks every
|
|
392
|
+
row. `matches_predicate(predicate, row)` is a reference implementation
|
|
393
|
+
of the predicate semantics.
|
|
394
|
+
|
|
395
|
+
Errors are reported through typed exceptions:
|
|
396
|
+
|
|
397
|
+
- `SourceCapabilityError`: an invalid declaration, raised at
|
|
398
|
+
registration.
|
|
399
|
+
- `SourceResponseError`: malformed pages or a repeated cursor.
|
|
400
|
+
- `SourceError`: any other provider failure.
|
|
401
|
+
|
|
402
|
+
A provider may raise `SourceUnsupportedError` from `lookup`/`expand` to
|
|
403
|
+
fall back to a scan.
|
|
404
|
+
|
|
405
|
+
Query-level tuning goes in `RunnerOptions(sources=SourceOptions(...))`:
|
|
406
|
+
`page_size` (256), `max_batch_size` (100), `cache_capacity` (10000),
|
|
407
|
+
`max_prefetch_rows` (1024) and `reorder` (`True`).
|
|
408
|
+
|
|
409
|
+
**In-memory sources.** `in_memory_node_source(rows)` and
|
|
410
|
+
`in_memory_relationship_source(rows)` are ready-made providers over
|
|
411
|
+
lists. They support lookup, expansion from both sides, every predicate
|
|
412
|
+
operator and paging, which makes them handy for tests, examples and
|
|
413
|
+
small reference data. Keyword options are `cacheable` (default `True`)
|
|
414
|
+
and `max_batch_size`.
|
|
415
|
+
|
|
416
|
+
```python
|
|
417
|
+
from flowquery import in_memory_node_source, in_memory_relationship_source
|
|
418
|
+
|
|
419
|
+
FlowQuery.register_node_source(
|
|
420
|
+
"Person", in_memory_node_source([{"id": 1, "name": "Ada"}, {"id": 2, "name": "Bob"}])
|
|
421
|
+
)
|
|
422
|
+
FlowQuery.register_relationship_source(
|
|
423
|
+
"KNOWS", "Person", "Person", in_memory_relationship_source([{"left_id": 1, "right_id": 2}])
|
|
424
|
+
)
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
**Statement-backed sources.** A `CREATE VIRTUAL` definition can opt into
|
|
428
|
+
the same contract declaratively with a `WITH CAPABILITIES { ... }` clause
|
|
429
|
+
between the pattern and `AS`. Each request runs its sub-query with
|
|
430
|
+
`$request` bound to the request (`operation`, `label`/`type`, `ids`,
|
|
431
|
+
`side`, `predicate`, `pageSize`):
|
|
432
|
+
|
|
433
|
+
```python
|
|
434
|
+
await FlowQuery("""
|
|
435
|
+
CREATE VIRTUAL (:Ticket) WITH CAPABILITIES {lookup: true, streaming: true} AS {
|
|
436
|
+
LOAD JSON FROM f"https://api.example.com/tickets?ids={join(coalesce($request.ids, []), ',')}" AS t
|
|
437
|
+
RETURN t.id AS id, t.title AS title
|
|
438
|
+
}
|
|
439
|
+
""").run()
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
The map takes the same (camelCase) keys as the capabilities dict;
|
|
443
|
+
`version` defaults to the current contract version. Invalid capabilities
|
|
444
|
+
reject the whole definition. Redefining the label without
|
|
445
|
+
`WITH CAPABILITIES`, or deleting it, restores eager loading. The same
|
|
446
|
+
opt-in is available programmatically with
|
|
447
|
+
`FlowQuery.set_node_source_capabilities(label, capabilities)` and
|
|
448
|
+
`FlowQuery.set_relationship_source_capabilities(...)`; pass `None` to
|
|
449
|
+
opt out.
|
|
450
|
+
|
|
310
451
|
## Documentation
|
|
311
452
|
|
|
312
453
|
- [Language Reference](https://github.com/microsoft/FlowQuery#language-reference) (clauses, expressions, functions, graph operations, and more)
|
|
@@ -274,6 +274,147 @@ The same chain is reflected statically in `info.sources` so callers
|
|
|
274
274
|
can introspect the upstream dependency graph without running the
|
|
275
275
|
query.
|
|
276
276
|
|
|
277
|
+
## Backend-Aware Virtual Sources
|
|
278
|
+
|
|
279
|
+
By default a virtual definition is loaded eagerly: its sub-query runs
|
|
280
|
+
once per query and all rows are held in memory. For large or remote
|
|
281
|
+
backends, a **source provider** lets FlowQuery read only what a pattern
|
|
282
|
+
needs. This is opt-in: existing `CREATE VIRTUAL` definitions behave
|
|
283
|
+
exactly as before.
|
|
284
|
+
|
|
285
|
+
The simplest way to write a provider is to subclass `NodeSource` or
|
|
286
|
+
`RelationshipSource` and implement `scan(request, context)`. Implement
|
|
287
|
+
`lookup` (nodes by id) or `expand` (relationships by `left_id`/`right_id`)
|
|
288
|
+
when the backend can fetch selectively; the base class then declares the
|
|
289
|
+
matching capability for you. `version` defaults to the current contract
|
|
290
|
+
version, and capability keys may be written in snake_case
|
|
291
|
+
(`max_batch_size`, `string_ordering`). A method may return a list of
|
|
292
|
+
rows, an awaitable list, or an (async) iterable of pages.
|
|
293
|
+
|
|
294
|
+
The `request` is a `SourceRequest`: a dict with the same camelCase keys
|
|
295
|
+
as the TypeScript implementation (`request["pageSize"]`) that also
|
|
296
|
+
accepts snake_case keys (`request["page_size"]`) and attribute access
|
|
297
|
+
(`request.ids`, `request.side`, `request.predicate`, `request.page_size`).
|
|
298
|
+
|
|
299
|
+
```python
|
|
300
|
+
from flowquery import FlowQuery, NodeSource, RelationshipSource, cursor_pages
|
|
301
|
+
|
|
302
|
+
class People(NodeSource):
|
|
303
|
+
capabilities = {
|
|
304
|
+
"cacheable": True, # identical reads within one query may be cached
|
|
305
|
+
"max_batch_size": 50, # ids per lookup/expand request
|
|
306
|
+
"predicates": {"operators": ["eq", "in", "gt", "gte", "lt", "lte"]},
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
def scan(self, request, context):
|
|
310
|
+
async def fetch(cursor):
|
|
311
|
+
page = await api.people(filter=request.predicate, cursor=cursor)
|
|
312
|
+
return {"rows": page.rows, "cursor": page.next_cursor}
|
|
313
|
+
return cursor_pages(fetch)
|
|
314
|
+
|
|
315
|
+
async def lookup(self, request, context): # declares "lookup": True
|
|
316
|
+
return await api.people_by_ids(request.ids)
|
|
317
|
+
|
|
318
|
+
class Knows(RelationshipSource):
|
|
319
|
+
async def scan(self, request, context):
|
|
320
|
+
return await api.all_knows()
|
|
321
|
+
|
|
322
|
+
async def expand(self, request, context): # declares both expand sides
|
|
323
|
+
key = "from" if request.side == "left" else "to"
|
|
324
|
+
return await api.knows(**{key: request.ids})
|
|
325
|
+
|
|
326
|
+
FlowQuery.register_node_source("Person", People())
|
|
327
|
+
FlowQuery.register_relationship_source("KNOWS", "Person", "Person", Knows())
|
|
328
|
+
|
|
329
|
+
fq = FlowQuery("MATCH (a:Person {id: 7})-[:KNOWS]->(b:Person) RETURN b.name LIMIT 10")
|
|
330
|
+
await fq.run()
|
|
331
|
+
print(fq.source_stats) # calls, pages, rows, cacheHits, batches, plans...
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Subclassing is optional: any object with a `capabilities` dict and a
|
|
335
|
+
`scan` method works, and `SourceProvider` is a `typing.Protocol`
|
|
336
|
+
describing that shape.
|
|
337
|
+
|
|
338
|
+
The engine then loads correlated rows in batches instead of reading
|
|
339
|
+
every person and relationship:
|
|
340
|
+
|
|
341
|
+
- **Correlated loading and batching.** It issues id lookups, expands per
|
|
342
|
+
page of anchor nodes, and makes batched lookups for target nodes.
|
|
343
|
+
- **Selective start points.** A chain may start from its more selective
|
|
344
|
+
end. The decision is recorded in `source_stats["plans"]`, and it can
|
|
345
|
+
change result order when there is no `ORDER BY`.
|
|
346
|
+
- **Predicate push-down.** Pattern properties and `AND`-ed `WHERE`
|
|
347
|
+
comparisons (`=`, `<`, `<=`, `>`, `>=`, `IN`) against literals or
|
|
348
|
+
parameters are sent as `request["predicate"]`.
|
|
349
|
+
- **Streaming with early stop under `LIMIT`.** Reading stops once the
|
|
350
|
+
limit is reached and the provider's iterator is closed.
|
|
351
|
+
- **Query-scoped caching.**
|
|
352
|
+
- **Cancellation.** Call `runner.cancel()` or pass
|
|
353
|
+
`SourceOptions(signal=...)` (an `asyncio.Event` or `SourceSignal`).
|
|
354
|
+
The query raises `QueryCancelledError`.
|
|
355
|
+
- **Read contexts.** Optional `open_read_context()` and
|
|
356
|
+
`close_read_context(snapshot)` methods are called once per query.
|
|
357
|
+
|
|
358
|
+
Providers may return a **superset**, because FlowQuery re-checks every
|
|
359
|
+
row. `matches_predicate(predicate, row)` is a reference implementation
|
|
360
|
+
of the predicate semantics.
|
|
361
|
+
|
|
362
|
+
Errors are reported through typed exceptions:
|
|
363
|
+
|
|
364
|
+
- `SourceCapabilityError`: an invalid declaration, raised at
|
|
365
|
+
registration.
|
|
366
|
+
- `SourceResponseError`: malformed pages or a repeated cursor.
|
|
367
|
+
- `SourceError`: any other provider failure.
|
|
368
|
+
|
|
369
|
+
A provider may raise `SourceUnsupportedError` from `lookup`/`expand` to
|
|
370
|
+
fall back to a scan.
|
|
371
|
+
|
|
372
|
+
Query-level tuning goes in `RunnerOptions(sources=SourceOptions(...))`:
|
|
373
|
+
`page_size` (256), `max_batch_size` (100), `cache_capacity` (10000),
|
|
374
|
+
`max_prefetch_rows` (1024) and `reorder` (`True`).
|
|
375
|
+
|
|
376
|
+
**In-memory sources.** `in_memory_node_source(rows)` and
|
|
377
|
+
`in_memory_relationship_source(rows)` are ready-made providers over
|
|
378
|
+
lists. They support lookup, expansion from both sides, every predicate
|
|
379
|
+
operator and paging, which makes them handy for tests, examples and
|
|
380
|
+
small reference data. Keyword options are `cacheable` (default `True`)
|
|
381
|
+
and `max_batch_size`.
|
|
382
|
+
|
|
383
|
+
```python
|
|
384
|
+
from flowquery import in_memory_node_source, in_memory_relationship_source
|
|
385
|
+
|
|
386
|
+
FlowQuery.register_node_source(
|
|
387
|
+
"Person", in_memory_node_source([{"id": 1, "name": "Ada"}, {"id": 2, "name": "Bob"}])
|
|
388
|
+
)
|
|
389
|
+
FlowQuery.register_relationship_source(
|
|
390
|
+
"KNOWS", "Person", "Person", in_memory_relationship_source([{"left_id": 1, "right_id": 2}])
|
|
391
|
+
)
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
**Statement-backed sources.** A `CREATE VIRTUAL` definition can opt into
|
|
395
|
+
the same contract declaratively with a `WITH CAPABILITIES { ... }` clause
|
|
396
|
+
between the pattern and `AS`. Each request runs its sub-query with
|
|
397
|
+
`$request` bound to the request (`operation`, `label`/`type`, `ids`,
|
|
398
|
+
`side`, `predicate`, `pageSize`):
|
|
399
|
+
|
|
400
|
+
```python
|
|
401
|
+
await FlowQuery("""
|
|
402
|
+
CREATE VIRTUAL (:Ticket) WITH CAPABILITIES {lookup: true, streaming: true} AS {
|
|
403
|
+
LOAD JSON FROM f"https://api.example.com/tickets?ids={join(coalesce($request.ids, []), ',')}" AS t
|
|
404
|
+
RETURN t.id AS id, t.title AS title
|
|
405
|
+
}
|
|
406
|
+
""").run()
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
The map takes the same (camelCase) keys as the capabilities dict;
|
|
410
|
+
`version` defaults to the current contract version. Invalid capabilities
|
|
411
|
+
reject the whole definition. Redefining the label without
|
|
412
|
+
`WITH CAPABILITIES`, or deleting it, restores eager loading. The same
|
|
413
|
+
opt-in is available programmatically with
|
|
414
|
+
`FlowQuery.set_node_source_capabilities(label, capabilities)` and
|
|
415
|
+
`FlowQuery.set_relationship_source_capabilities(...)`; pass `None` to
|
|
416
|
+
opt out.
|
|
417
|
+
|
|
277
418
|
## Documentation
|
|
278
419
|
|
|
279
420
|
- [Language Reference](https://github.com/microsoft/FlowQuery#language-reference) (clauses, expressions, functions, graph operations, and more)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flowquery
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.66
|
|
4
4
|
Summary: A declarative query language for data processing pipelines
|
|
5
5
|
Author-email: Team Microsoft FlowQuery <flowquery@microsoft.com>
|
|
6
6
|
License: MIT
|
|
@@ -307,6 +307,147 @@ The same chain is reflected statically in `info.sources` so callers
|
|
|
307
307
|
can introspect the upstream dependency graph without running the
|
|
308
308
|
query.
|
|
309
309
|
|
|
310
|
+
## Backend-Aware Virtual Sources
|
|
311
|
+
|
|
312
|
+
By default a virtual definition is loaded eagerly: its sub-query runs
|
|
313
|
+
once per query and all rows are held in memory. For large or remote
|
|
314
|
+
backends, a **source provider** lets FlowQuery read only what a pattern
|
|
315
|
+
needs. This is opt-in: existing `CREATE VIRTUAL` definitions behave
|
|
316
|
+
exactly as before.
|
|
317
|
+
|
|
318
|
+
The simplest way to write a provider is to subclass `NodeSource` or
|
|
319
|
+
`RelationshipSource` and implement `scan(request, context)`. Implement
|
|
320
|
+
`lookup` (nodes by id) or `expand` (relationships by `left_id`/`right_id`)
|
|
321
|
+
when the backend can fetch selectively; the base class then declares the
|
|
322
|
+
matching capability for you. `version` defaults to the current contract
|
|
323
|
+
version, and capability keys may be written in snake_case
|
|
324
|
+
(`max_batch_size`, `string_ordering`). A method may return a list of
|
|
325
|
+
rows, an awaitable list, or an (async) iterable of pages.
|
|
326
|
+
|
|
327
|
+
The `request` is a `SourceRequest`: a dict with the same camelCase keys
|
|
328
|
+
as the TypeScript implementation (`request["pageSize"]`) that also
|
|
329
|
+
accepts snake_case keys (`request["page_size"]`) and attribute access
|
|
330
|
+
(`request.ids`, `request.side`, `request.predicate`, `request.page_size`).
|
|
331
|
+
|
|
332
|
+
```python
|
|
333
|
+
from flowquery import FlowQuery, NodeSource, RelationshipSource, cursor_pages
|
|
334
|
+
|
|
335
|
+
class People(NodeSource):
|
|
336
|
+
capabilities = {
|
|
337
|
+
"cacheable": True, # identical reads within one query may be cached
|
|
338
|
+
"max_batch_size": 50, # ids per lookup/expand request
|
|
339
|
+
"predicates": {"operators": ["eq", "in", "gt", "gte", "lt", "lte"]},
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
def scan(self, request, context):
|
|
343
|
+
async def fetch(cursor):
|
|
344
|
+
page = await api.people(filter=request.predicate, cursor=cursor)
|
|
345
|
+
return {"rows": page.rows, "cursor": page.next_cursor}
|
|
346
|
+
return cursor_pages(fetch)
|
|
347
|
+
|
|
348
|
+
async def lookup(self, request, context): # declares "lookup": True
|
|
349
|
+
return await api.people_by_ids(request.ids)
|
|
350
|
+
|
|
351
|
+
class Knows(RelationshipSource):
|
|
352
|
+
async def scan(self, request, context):
|
|
353
|
+
return await api.all_knows()
|
|
354
|
+
|
|
355
|
+
async def expand(self, request, context): # declares both expand sides
|
|
356
|
+
key = "from" if request.side == "left" else "to"
|
|
357
|
+
return await api.knows(**{key: request.ids})
|
|
358
|
+
|
|
359
|
+
FlowQuery.register_node_source("Person", People())
|
|
360
|
+
FlowQuery.register_relationship_source("KNOWS", "Person", "Person", Knows())
|
|
361
|
+
|
|
362
|
+
fq = FlowQuery("MATCH (a:Person {id: 7})-[:KNOWS]->(b:Person) RETURN b.name LIMIT 10")
|
|
363
|
+
await fq.run()
|
|
364
|
+
print(fq.source_stats) # calls, pages, rows, cacheHits, batches, plans...
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Subclassing is optional: any object with a `capabilities` dict and a
|
|
368
|
+
`scan` method works, and `SourceProvider` is a `typing.Protocol`
|
|
369
|
+
describing that shape.
|
|
370
|
+
|
|
371
|
+
The engine then loads correlated rows in batches instead of reading
|
|
372
|
+
every person and relationship:
|
|
373
|
+
|
|
374
|
+
- **Correlated loading and batching.** It issues id lookups, expands per
|
|
375
|
+
page of anchor nodes, and makes batched lookups for target nodes.
|
|
376
|
+
- **Selective start points.** A chain may start from its more selective
|
|
377
|
+
end. The decision is recorded in `source_stats["plans"]`, and it can
|
|
378
|
+
change result order when there is no `ORDER BY`.
|
|
379
|
+
- **Predicate push-down.** Pattern properties and `AND`-ed `WHERE`
|
|
380
|
+
comparisons (`=`, `<`, `<=`, `>`, `>=`, `IN`) against literals or
|
|
381
|
+
parameters are sent as `request["predicate"]`.
|
|
382
|
+
- **Streaming with early stop under `LIMIT`.** Reading stops once the
|
|
383
|
+
limit is reached and the provider's iterator is closed.
|
|
384
|
+
- **Query-scoped caching.**
|
|
385
|
+
- **Cancellation.** Call `runner.cancel()` or pass
|
|
386
|
+
`SourceOptions(signal=...)` (an `asyncio.Event` or `SourceSignal`).
|
|
387
|
+
The query raises `QueryCancelledError`.
|
|
388
|
+
- **Read contexts.** Optional `open_read_context()` and
|
|
389
|
+
`close_read_context(snapshot)` methods are called once per query.
|
|
390
|
+
|
|
391
|
+
Providers may return a **superset**, because FlowQuery re-checks every
|
|
392
|
+
row. `matches_predicate(predicate, row)` is a reference implementation
|
|
393
|
+
of the predicate semantics.
|
|
394
|
+
|
|
395
|
+
Errors are reported through typed exceptions:
|
|
396
|
+
|
|
397
|
+
- `SourceCapabilityError`: an invalid declaration, raised at
|
|
398
|
+
registration.
|
|
399
|
+
- `SourceResponseError`: malformed pages or a repeated cursor.
|
|
400
|
+
- `SourceError`: any other provider failure.
|
|
401
|
+
|
|
402
|
+
A provider may raise `SourceUnsupportedError` from `lookup`/`expand` to
|
|
403
|
+
fall back to a scan.
|
|
404
|
+
|
|
405
|
+
Query-level tuning goes in `RunnerOptions(sources=SourceOptions(...))`:
|
|
406
|
+
`page_size` (256), `max_batch_size` (100), `cache_capacity` (10000),
|
|
407
|
+
`max_prefetch_rows` (1024) and `reorder` (`True`).
|
|
408
|
+
|
|
409
|
+
**In-memory sources.** `in_memory_node_source(rows)` and
|
|
410
|
+
`in_memory_relationship_source(rows)` are ready-made providers over
|
|
411
|
+
lists. They support lookup, expansion from both sides, every predicate
|
|
412
|
+
operator and paging, which makes them handy for tests, examples and
|
|
413
|
+
small reference data. Keyword options are `cacheable` (default `True`)
|
|
414
|
+
and `max_batch_size`.
|
|
415
|
+
|
|
416
|
+
```python
|
|
417
|
+
from flowquery import in_memory_node_source, in_memory_relationship_source
|
|
418
|
+
|
|
419
|
+
FlowQuery.register_node_source(
|
|
420
|
+
"Person", in_memory_node_source([{"id": 1, "name": "Ada"}, {"id": 2, "name": "Bob"}])
|
|
421
|
+
)
|
|
422
|
+
FlowQuery.register_relationship_source(
|
|
423
|
+
"KNOWS", "Person", "Person", in_memory_relationship_source([{"left_id": 1, "right_id": 2}])
|
|
424
|
+
)
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
**Statement-backed sources.** A `CREATE VIRTUAL` definition can opt into
|
|
428
|
+
the same contract declaratively with a `WITH CAPABILITIES { ... }` clause
|
|
429
|
+
between the pattern and `AS`. Each request runs its sub-query with
|
|
430
|
+
`$request` bound to the request (`operation`, `label`/`type`, `ids`,
|
|
431
|
+
`side`, `predicate`, `pageSize`):
|
|
432
|
+
|
|
433
|
+
```python
|
|
434
|
+
await FlowQuery("""
|
|
435
|
+
CREATE VIRTUAL (:Ticket) WITH CAPABILITIES {lookup: true, streaming: true} AS {
|
|
436
|
+
LOAD JSON FROM f"https://api.example.com/tickets?ids={join(coalesce($request.ids, []), ',')}" AS t
|
|
437
|
+
RETURN t.id AS id, t.title AS title
|
|
438
|
+
}
|
|
439
|
+
""").run()
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
The map takes the same (camelCase) keys as the capabilities dict;
|
|
443
|
+
`version` defaults to the current contract version. Invalid capabilities
|
|
444
|
+
reject the whole definition. Redefining the label without
|
|
445
|
+
`WITH CAPABILITIES`, or deleting it, restores eager loading. The same
|
|
446
|
+
opt-in is available programmatically with
|
|
447
|
+
`FlowQuery.set_node_source_capabilities(label, capabilities)` and
|
|
448
|
+
`FlowQuery.set_relationship_source_capabilities(...)`; pass `None` to
|
|
449
|
+
opt out.
|
|
450
|
+
|
|
310
451
|
## Documentation
|
|
311
452
|
|
|
312
453
|
- [Language Reference](https://github.com/microsoft/FlowQuery#language-reference) (clauses, expressions, functions, graph operations, and more)
|
|
@@ -32,7 +32,12 @@ src/graph/relationship.py
|
|
|
32
32
|
src/graph/relationship_data.py
|
|
33
33
|
src/graph/relationship_match_collector.py
|
|
34
34
|
src/graph/relationship_reference.py
|
|
35
|
+
src/graph/source_access.py
|
|
36
|
+
src/graph/source_context.py
|
|
37
|
+
src/graph/source_provider.py
|
|
38
|
+
src/graph/statement_source.py
|
|
35
39
|
src/graph/virtual_sources.py
|
|
40
|
+
src/graph/virtual_statement.py
|
|
36
41
|
src/io/__init__.py
|
|
37
42
|
src/io/command_line.py
|
|
38
43
|
src/parsing/__init__.py
|
|
@@ -24,6 +24,27 @@ from .compute.runner import (
|
|
|
24
24
|
RunnerMetadata,
|
|
25
25
|
RunnerOptions,
|
|
26
26
|
)
|
|
27
|
+
from .graph.source_context import SourceOptions
|
|
28
|
+
from .graph.source_provider import (
|
|
29
|
+
PREDICATE_OPERATORS,
|
|
30
|
+
SOURCE_CONTRACT_VERSION,
|
|
31
|
+
NodeSource,
|
|
32
|
+
QueryCancelledError,
|
|
33
|
+
RelationshipSource,
|
|
34
|
+
SourceCapabilityError,
|
|
35
|
+
SourceError,
|
|
36
|
+
SourceProvider,
|
|
37
|
+
SourceReadContext,
|
|
38
|
+
SourceRequest,
|
|
39
|
+
SourceResponseError,
|
|
40
|
+
SourceResult,
|
|
41
|
+
SourceSignal,
|
|
42
|
+
SourceUnsupportedError,
|
|
43
|
+
cursor_pages,
|
|
44
|
+
in_memory_node_source,
|
|
45
|
+
in_memory_relationship_source,
|
|
46
|
+
matches_predicate,
|
|
47
|
+
)
|
|
27
48
|
from .io.command_line import CommandLine
|
|
28
49
|
from .parsing.functions.aggregate_function import AggregateFunction
|
|
29
50
|
from .parsing.functions.async_function import AsyncFunction
|
|
@@ -80,5 +101,24 @@ __all__ = [
|
|
|
80
101
|
"FunctionDef",
|
|
81
102
|
"FunctionMetadata",
|
|
82
103
|
"FunctionCategory",
|
|
104
|
+
"SOURCE_CONTRACT_VERSION",
|
|
105
|
+
"PREDICATE_OPERATORS",
|
|
106
|
+
"SourceOptions",
|
|
107
|
+
"SourceSignal",
|
|
108
|
+
"SourceReadContext",
|
|
109
|
+
"SourceError",
|
|
110
|
+
"SourceCapabilityError",
|
|
111
|
+
"SourceResponseError",
|
|
112
|
+
"SourceUnsupportedError",
|
|
113
|
+
"QueryCancelledError",
|
|
114
|
+
"SourceProvider",
|
|
115
|
+
"SourceRequest",
|
|
116
|
+
"SourceResult",
|
|
117
|
+
"NodeSource",
|
|
118
|
+
"RelationshipSource",
|
|
119
|
+
"cursor_pages",
|
|
120
|
+
"matches_predicate",
|
|
121
|
+
"in_memory_node_source",
|
|
122
|
+
"in_memory_relationship_source",
|
|
83
123
|
]
|
|
84
124
|
|
|
@@ -4,7 +4,7 @@ Extends Runner with extensibility features such as function listing
|
|
|
4
4
|
and plugin registration, keeping the Runner focused on execution.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from typing import List, Optional, Type
|
|
7
|
+
from typing import Any, Dict, List, Optional, Type
|
|
8
8
|
|
|
9
9
|
from ..parsing.functions.function import Function
|
|
10
10
|
from ..parsing.functions.function_factory import FunctionFactory
|
|
@@ -66,3 +66,53 @@ class FlowQuery(Runner):
|
|
|
66
66
|
Function metadata or None
|
|
67
67
|
"""
|
|
68
68
|
return get_function_metadata(name.lower())
|
|
69
|
+
|
|
70
|
+
@staticmethod
|
|
71
|
+
def register_node_source(label: str, provider: Any) -> None:
|
|
72
|
+
"""Register a backend-aware node source for ``label``.
|
|
73
|
+
|
|
74
|
+
Replaces any existing virtual definition for the label. The provider
|
|
75
|
+
exposes ``capabilities`` and ``scan`` (and optionally ``lookup``).
|
|
76
|
+
"""
|
|
77
|
+
from ..graph.database import Database
|
|
78
|
+
|
|
79
|
+
Database.get_instance().register_node_source(label, provider)
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def register_relationship_source(
|
|
83
|
+
rel_type: str,
|
|
84
|
+
source_label: Optional[str],
|
|
85
|
+
target_label: Optional[str],
|
|
86
|
+
provider: Any,
|
|
87
|
+
) -> None:
|
|
88
|
+
"""Register a backend-aware relationship source for
|
|
89
|
+
``(:source_label)-[:rel_type]->(:target_label)``."""
|
|
90
|
+
from ..graph.database import Database
|
|
91
|
+
|
|
92
|
+
Database.get_instance().register_relationship_source(
|
|
93
|
+
rel_type, source_label, target_label, provider
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def set_node_source_capabilities(
|
|
98
|
+
label: str, capabilities: Optional[Dict[str, Any]]
|
|
99
|
+
) -> None:
|
|
100
|
+
"""Opt a ``CREATE VIRTUAL (:label) AS { ... }`` definition into the
|
|
101
|
+
source contract with explicit capabilities, or pass ``None`` to opt out."""
|
|
102
|
+
from ..graph.database import Database
|
|
103
|
+
|
|
104
|
+
Database.get_instance().set_node_source_capabilities(label, capabilities)
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def set_relationship_source_capabilities(
|
|
108
|
+
rel_type: str,
|
|
109
|
+
source_label: Optional[str],
|
|
110
|
+
target_label: Optional[str],
|
|
111
|
+
capabilities: Optional[Dict[str, Any]],
|
|
112
|
+
) -> None:
|
|
113
|
+
"""Relationship counterpart of :meth:`set_node_source_capabilities`."""
|
|
114
|
+
from ..graph.database import Database
|
|
115
|
+
|
|
116
|
+
Database.get_instance().set_relationship_source_capabilities(
|
|
117
|
+
rel_type, source_label, target_label, capabilities
|
|
118
|
+
)
|