exonware-xwnode 0.0.1.13__py3-none-any.whl → 0.0.1.15__py3-none-any.whl
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.
- exonware/__init__.py +1 -1
- exonware/xwnode/__init__.py +1 -1
- exonware/xwnode/base.py +1 -1
- exonware/xwnode/common/__init__.py +20 -0
- exonware/xwnode/common/management/__init__.py +26 -0
- exonware/xwnode/{strategies → common/management}/manager.py +2 -2
- exonware/xwnode/common/monitoring/__init__.py +26 -0
- exonware/xwnode/{strategies → common/monitoring}/metrics.py +2 -2
- exonware/xwnode/{strategies → common/monitoring}/pattern_detector.py +2 -2
- exonware/xwnode/{strategies → common/monitoring}/performance_monitor.py +2 -2
- exonware/xwnode/common/patterns/__init__.py +26 -0
- exonware/xwnode/{strategies → common/patterns}/advisor.py +1 -1
- exonware/xwnode/{strategies → common/patterns}/flyweight.py +4 -4
- exonware/xwnode/{strategies → common/patterns}/registry.py +109 -112
- exonware/xwnode/common/utils/__init__.py +26 -0
- exonware/xwnode/{strategies/edges → edges/strategies}/__init__.py +1 -1
- exonware/xwnode/{strategies/edges → edges/strategies}/base.py +3 -3
- exonware/xwnode/facade.py +4 -3
- exonware/xwnode/{strategies/nodes → nodes/strategies}/__init__.py +1 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/adjacency_list.py +7 -2
- exonware/xwnode/{strategies/nodes → nodes/strategies}/aho_corasick.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/array_list.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/base.py +24 -4
- exonware/xwnode/nodes/strategies/contracts.py +116 -0
- exonware/xwnode/{strategies/nodes → nodes/strategies}/deque.py +7 -2
- exonware/xwnode/{strategies/nodes → nodes/strategies}/hash_map.py +4 -0
- exonware/xwnode/{strategies/nodes → nodes/strategies}/heap.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/linked_list.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_aho_corasick.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_array_list.py +4 -0
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_avl_tree.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_b_plus_tree.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_bitmap.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_bitset_dynamic.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_bloom_filter.py +4 -0
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_btree.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_count_min_sketch.py +4 -0
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_cow_tree.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_fenwick_tree.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_hash_map.py +4 -0
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_heap.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_hyperloglog.py +4 -0
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_linked_list.py +4 -0
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_lsm_tree.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_ordered_map.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_ordered_map_balanced.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_patricia.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_persistent_tree.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_radix_trie.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_red_black_tree.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_roaring_bitmap.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_segment_tree.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_set_hash.py +4 -0
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_set_tree.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_skip_list.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_splay_tree.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_suffix_array.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_treap.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_tree_graph_hybrid.py +4 -0
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_trie.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_union_find.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/node_xdata_optimized.py +4 -0
- exonware/xwnode/{strategies/nodes → nodes/strategies}/priority_queue.py +7 -2
- exonware/xwnode/{strategies/nodes → nodes/strategies}/queue.py +7 -2
- exonware/xwnode/{strategies/nodes → nodes/strategies}/sparse_matrix.py +7 -2
- exonware/xwnode/{strategies/nodes → nodes/strategies}/stack.py +7 -2
- exonware/xwnode/{strategies/nodes → nodes/strategies}/trie.py +6 -1
- exonware/xwnode/{strategies/nodes → nodes/strategies}/union_find.py +6 -1
- exonware/xwnode/queries/executors/__init__.py +47 -0
- exonware/xwnode/queries/executors/advanced/__init__.py +37 -0
- exonware/xwnode/queries/executors/advanced/aggregate_executor.py +50 -0
- exonware/xwnode/queries/executors/advanced/ask_executor.py +50 -0
- exonware/xwnode/queries/executors/advanced/construct_executor.py +50 -0
- exonware/xwnode/queries/executors/advanced/describe_executor.py +50 -0
- exonware/xwnode/queries/executors/advanced/for_loop_executor.py +50 -0
- exonware/xwnode/queries/executors/advanced/foreach_executor.py +50 -0
- exonware/xwnode/queries/executors/advanced/join_executor.py +50 -0
- exonware/xwnode/queries/executors/advanced/let_executor.py +50 -0
- exonware/xwnode/queries/executors/advanced/mutation_executor.py +50 -0
- exonware/xwnode/queries/executors/advanced/options_executor.py +50 -0
- exonware/xwnode/queries/executors/advanced/pipe_executor.py +50 -0
- exonware/xwnode/queries/executors/advanced/subscribe_executor.py +50 -0
- exonware/xwnode/queries/executors/advanced/subscription_executor.py +50 -0
- exonware/xwnode/queries/executors/advanced/union_executor.py +50 -0
- exonware/xwnode/queries/executors/advanced/window_executor.py +51 -0
- exonware/xwnode/queries/executors/advanced/with_cte_executor.py +50 -0
- exonware/xwnode/queries/executors/aggregation/__init__.py +21 -0
- exonware/xwnode/queries/executors/aggregation/avg_executor.py +50 -0
- exonware/xwnode/queries/executors/aggregation/count_executor.py +38 -0
- exonware/xwnode/queries/executors/aggregation/distinct_executor.py +50 -0
- exonware/xwnode/queries/executors/aggregation/group_executor.py +50 -0
- exonware/xwnode/queries/executors/aggregation/having_executor.py +50 -0
- exonware/xwnode/queries/executors/aggregation/max_executor.py +50 -0
- exonware/xwnode/queries/executors/aggregation/min_executor.py +50 -0
- exonware/xwnode/queries/executors/aggregation/sum_executor.py +50 -0
- exonware/xwnode/queries/executors/aggregation/summarize_executor.py +50 -0
- exonware/xwnode/queries/executors/array/__init__.py +9 -0
- exonware/xwnode/queries/executors/array/indexing_executor.py +51 -0
- exonware/xwnode/queries/executors/array/slicing_executor.py +51 -0
- exonware/xwnode/queries/executors/base.py +257 -0
- exonware/xwnode/queries/executors/capability_checker.py +204 -0
- exonware/xwnode/queries/executors/contracts.py +166 -0
- exonware/xwnode/queries/executors/core/__init__.py +17 -0
- exonware/xwnode/queries/executors/core/create_executor.py +96 -0
- exonware/xwnode/queries/executors/core/delete_executor.py +99 -0
- exonware/xwnode/queries/executors/core/drop_executor.py +100 -0
- exonware/xwnode/queries/executors/core/insert_executor.py +39 -0
- exonware/xwnode/queries/executors/core/select_executor.py +152 -0
- exonware/xwnode/queries/executors/core/update_executor.py +102 -0
- exonware/xwnode/queries/executors/data/__init__.py +13 -0
- exonware/xwnode/queries/executors/data/alter_executor.py +50 -0
- exonware/xwnode/queries/executors/data/load_executor.py +50 -0
- exonware/xwnode/queries/executors/data/merge_executor.py +50 -0
- exonware/xwnode/queries/executors/data/store_executor.py +50 -0
- exonware/xwnode/queries/executors/engine.py +221 -0
- exonware/xwnode/queries/executors/errors.py +68 -0
- exonware/xwnode/queries/executors/filtering/__init__.py +25 -0
- exonware/xwnode/queries/executors/filtering/between_executor.py +80 -0
- exonware/xwnode/queries/executors/filtering/filter_executor.py +79 -0
- exonware/xwnode/queries/executors/filtering/has_executor.py +70 -0
- exonware/xwnode/queries/executors/filtering/in_executor.py +70 -0
- exonware/xwnode/queries/executors/filtering/like_executor.py +76 -0
- exonware/xwnode/queries/executors/filtering/optional_executor.py +76 -0
- exonware/xwnode/queries/executors/filtering/range_executor.py +80 -0
- exonware/xwnode/queries/executors/filtering/term_executor.py +77 -0
- exonware/xwnode/queries/executors/filtering/values_executor.py +71 -0
- exonware/xwnode/queries/executors/filtering/where_executor.py +44 -0
- exonware/xwnode/queries/executors/graph/__init__.py +15 -0
- exonware/xwnode/queries/executors/graph/in_traverse_executor.py +51 -0
- exonware/xwnode/queries/executors/graph/match_executor.py +51 -0
- exonware/xwnode/queries/executors/graph/out_executor.py +51 -0
- exonware/xwnode/queries/executors/graph/path_executor.py +51 -0
- exonware/xwnode/queries/executors/graph/return_executor.py +51 -0
- exonware/xwnode/queries/executors/ordering/__init__.py +9 -0
- exonware/xwnode/queries/executors/ordering/by_executor.py +50 -0
- exonware/xwnode/queries/executors/ordering/order_executor.py +51 -0
- exonware/xwnode/queries/executors/projection/__init__.py +9 -0
- exonware/xwnode/queries/executors/projection/extend_executor.py +50 -0
- exonware/xwnode/queries/executors/projection/project_executor.py +50 -0
- exonware/xwnode/queries/executors/registry.py +173 -0
- exonware/xwnode/queries/executors/types.py +93 -0
- exonware/xwnode/queries/parsers/__init__.py +26 -0
- exonware/xwnode/queries/parsers/base.py +86 -0
- exonware/xwnode/queries/parsers/contracts.py +46 -0
- exonware/xwnode/queries/parsers/errors.py +53 -0
- exonware/xwnode/queries/parsers/sql_param_extractor.py +318 -0
- exonware/xwnode/{strategies/queries → queries/strategies}/__init__.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/base.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/cql.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/cypher.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/datalog.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/elastic_dsl.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/eql.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/flux.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/gql.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/graphql.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/gremlin.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/hiveql.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/hql.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/jmespath.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/jq.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/json_query.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/jsoniq.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/kql.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/linq.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/logql.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/mql.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/n1ql.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/partiql.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/pig.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/promql.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/sparql.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/sql.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/xml_query.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/xpath.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/xquery.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/xwnode_executor.py +1 -1
- exonware/xwnode/{strategies/queries → queries/strategies}/xwquery.py +43 -11
- exonware/xwnode/strategies/__init__.py +8 -8
- exonware/xwnode/version.py +3 -3
- {exonware_xwnode-0.0.1.13.dist-info → exonware_xwnode-0.0.1.15.dist-info}/METADATA +2 -3
- exonware_xwnode-0.0.1.15.dist-info/RECORD +214 -0
- exonware/xwnode/strategies/impls/__init__.py +0 -13
- exonware/xwnode/strategies/nodes/_base_node.py +0 -307
- exonware_xwnode-0.0.1.13.dist-info/RECORD +0 -132
- /exonware/xwnode/{strategies → common/management}/migration.py +0 -0
- /exonware/xwnode/{strategies → common/utils}/simple.py +0 -0
- /exonware/xwnode/{strategies → common/utils}/utils.py +0 -0
- /exonware/xwnode/{strategies/impls → edges/strategies}/_base_edge.py +0 -0
- /exonware/xwnode/{strategies/edges → edges/strategies}/adj_list.py +0 -0
- /exonware/xwnode/{strategies/edges → edges/strategies}/adj_matrix.py +0 -0
- /exonware/xwnode/{strategies/impls → edges/strategies}/edge_adj_list.py +0 -0
- /exonware/xwnode/{strategies/impls → edges/strategies}/edge_adj_matrix.py +0 -0
- /exonware/xwnode/{strategies/impls → edges/strategies}/edge_bidir_wrapper.py +0 -0
- /exonware/xwnode/{strategies/impls → edges/strategies}/edge_block_adj_matrix.py +0 -0
- /exonware/xwnode/{strategies/impls → edges/strategies}/edge_coo.py +0 -0
- /exonware/xwnode/{strategies/impls → edges/strategies}/edge_csc.py +0 -0
- /exonware/xwnode/{strategies/impls → edges/strategies}/edge_csr.py +0 -0
- /exonware/xwnode/{strategies/impls → edges/strategies}/edge_dynamic_adj_list.py +0 -0
- /exonware/xwnode/{strategies/impls → edges/strategies}/edge_flow_network.py +0 -0
- /exonware/xwnode/{strategies/impls → edges/strategies}/edge_hyperedge_set.py +0 -0
- /exonware/xwnode/{strategies/impls → edges/strategies}/edge_neural_graph.py +0 -0
- /exonware/xwnode/{strategies/impls → edges/strategies}/edge_octree.py +0 -0
- /exonware/xwnode/{strategies/impls → edges/strategies}/edge_property_store.py +0 -0
- /exonware/xwnode/{strategies/impls → edges/strategies}/edge_quadtree.py +0 -0
- /exonware/xwnode/{strategies/impls → edges/strategies}/edge_rtree.py +0 -0
- /exonware/xwnode/{strategies/impls → edges/strategies}/edge_temporal_edgeset.py +0 -0
- /exonware/xwnode/{strategies/impls → edges/strategies}/edge_tree_graph_basic.py +0 -0
- /exonware/xwnode/{strategies/impls → edges/strategies}/edge_weighted_graph.py +0 -0
- /exonware/xwnode/{strategies/impls → nodes/strategies}/_base_node.py +0 -0
- /exonware/xwnode/{strategies/nodes → nodes/strategies}/node_cuckoo_hash.py +0 -0
- {exonware_xwnode-0.0.1.13.dist-info → exonware_xwnode-0.0.1.15.dist-info}/WHEEL +0 -0
- {exonware_xwnode-0.0.1.13.dist-info → exonware_xwnode-0.0.1.15.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,86 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
#exonware/xwnode/src/exonware/xwnode/queries/parsers/base.py
|
4
|
+
|
5
|
+
Parser Base Classes
|
6
|
+
|
7
|
+
Abstract base class for parameter extractors.
|
8
|
+
Follows DEV_GUIDELINES.md: base.py extends contracts.py interfaces.
|
9
|
+
|
10
|
+
Company: eXonware.com
|
11
|
+
Author: Eng. Muhammad AlShehri
|
12
|
+
Email: connect@exonware.com
|
13
|
+
Version: 0.0.1.15
|
14
|
+
Generation Date: 09-Oct-2025
|
15
|
+
"""
|
16
|
+
|
17
|
+
from abc import ABC
|
18
|
+
from typing import Dict, Any, Union
|
19
|
+
|
20
|
+
from .contracts import IParamExtractor
|
21
|
+
from .errors import ParseError
|
22
|
+
|
23
|
+
|
24
|
+
class AParamExtractor(IParamExtractor, ABC):
|
25
|
+
"""
|
26
|
+
Abstract base class for parameter extractors.
|
27
|
+
|
28
|
+
Extends IParamExtractor interface per DEV_GUIDELINES.md.
|
29
|
+
"""
|
30
|
+
|
31
|
+
def _parse_value(self, value_str: str) -> Union[str, int, float, bool, None]:
|
32
|
+
"""
|
33
|
+
Parse value from string to appropriate type.
|
34
|
+
|
35
|
+
Args:
|
36
|
+
value_str: String representation of value
|
37
|
+
|
38
|
+
Returns:
|
39
|
+
Parsed value with correct type
|
40
|
+
"""
|
41
|
+
value_str = value_str.strip().strip('"').strip("'")
|
42
|
+
|
43
|
+
# Try boolean
|
44
|
+
if value_str.lower() == 'true':
|
45
|
+
return True
|
46
|
+
if value_str.lower() == 'false':
|
47
|
+
return False
|
48
|
+
if value_str.lower() == 'null' or value_str.lower() == 'none':
|
49
|
+
return None
|
50
|
+
|
51
|
+
# Try number
|
52
|
+
try:
|
53
|
+
if '.' in value_str:
|
54
|
+
return float(value_str)
|
55
|
+
return int(value_str)
|
56
|
+
except ValueError:
|
57
|
+
pass
|
58
|
+
|
59
|
+
# Return as string
|
60
|
+
return value_str
|
61
|
+
|
62
|
+
def _split_fields(self, fields_str: str) -> list:
|
63
|
+
"""Split comma-separated fields, handling nested expressions."""
|
64
|
+
if fields_str.strip() == '*':
|
65
|
+
return ['*']
|
66
|
+
|
67
|
+
fields = []
|
68
|
+
current = []
|
69
|
+
paren_depth = 0
|
70
|
+
|
71
|
+
for char in fields_str:
|
72
|
+
if char == '(':
|
73
|
+
paren_depth += 1
|
74
|
+
elif char == ')':
|
75
|
+
paren_depth -= 1
|
76
|
+
elif char == ',' and paren_depth == 0:
|
77
|
+
fields.append(''.join(current).strip())
|
78
|
+
current = []
|
79
|
+
continue
|
80
|
+
current.append(char)
|
81
|
+
|
82
|
+
if current:
|
83
|
+
fields.append(''.join(current).strip())
|
84
|
+
|
85
|
+
return fields
|
86
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
#exonware/xwnode/src/exonware/xwnode/queries/parsers/contracts.py
|
4
|
+
|
5
|
+
Parser Contracts
|
6
|
+
|
7
|
+
Interfaces for query parameter extractors.
|
8
|
+
Follows DEV_GUIDELINES.md: contracts.py for all interfaces.
|
9
|
+
|
10
|
+
Company: eXonware.com
|
11
|
+
Author: Eng. Muhammad AlShehri
|
12
|
+
Email: connect@exonware.com
|
13
|
+
Version: 0.0.1.15
|
14
|
+
Generation Date: 09-Oct-2025
|
15
|
+
"""
|
16
|
+
|
17
|
+
from abc import ABC, abstractmethod
|
18
|
+
from typing import Dict, Any
|
19
|
+
|
20
|
+
|
21
|
+
class IParamExtractor(ABC):
|
22
|
+
"""
|
23
|
+
Interface for parameter extractors.
|
24
|
+
|
25
|
+
Extracts structured parameters from query strings.
|
26
|
+
"""
|
27
|
+
|
28
|
+
@abstractmethod
|
29
|
+
def extract_params(self, query: str, action_type: str) -> Dict[str, Any]:
|
30
|
+
"""
|
31
|
+
Extract structured parameters from query.
|
32
|
+
|
33
|
+
Args:
|
34
|
+
query: Raw query string
|
35
|
+
action_type: Type of action (SELECT, INSERT, etc.)
|
36
|
+
|
37
|
+
Returns:
|
38
|
+
Structured parameters dictionary
|
39
|
+
"""
|
40
|
+
pass
|
41
|
+
|
42
|
+
@abstractmethod
|
43
|
+
def can_parse(self, query: str) -> bool:
|
44
|
+
"""Check if this extractor can parse the query."""
|
45
|
+
pass
|
46
|
+
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
#exonware/xwnode/src/exonware/xwnode/queries/parsers/errors.py
|
4
|
+
|
5
|
+
Parser Errors
|
6
|
+
|
7
|
+
Module-specific errors for query parsers.
|
8
|
+
Extends root error classes per DEV_GUIDELINES.md - no redundancy.
|
9
|
+
|
10
|
+
Company: eXonware.com
|
11
|
+
Author: Eng. Muhammad AlShehri
|
12
|
+
Email: connect@exonware.com
|
13
|
+
Version: 0.0.1.15
|
14
|
+
Generation Date: 09-Oct-2025
|
15
|
+
"""
|
16
|
+
|
17
|
+
# Import and REUSE root error classes per DEV_GUIDELINES
|
18
|
+
from ...errors import XWNodeError, XWNodeValueError
|
19
|
+
|
20
|
+
|
21
|
+
class ParserError(XWNodeError):
|
22
|
+
"""
|
23
|
+
Base error for parser operations.
|
24
|
+
|
25
|
+
Extends XWNodeError from root - follows DEV_GUIDELINES principle.
|
26
|
+
"""
|
27
|
+
pass
|
28
|
+
|
29
|
+
|
30
|
+
class ParseError(ParserError):
|
31
|
+
"""Raised when query parsing fails."""
|
32
|
+
|
33
|
+
def __init__(self, query: str, reason: str, position: int = None):
|
34
|
+
message = f"Failed to parse query: {reason}"
|
35
|
+
if position is not None:
|
36
|
+
message += f" at position {position}"
|
37
|
+
super().__init__(message)
|
38
|
+
self.query = query
|
39
|
+
self.reason = reason
|
40
|
+
self.position = position
|
41
|
+
|
42
|
+
|
43
|
+
class UnsupportedSyntaxError(ParserError):
|
44
|
+
"""Raised when syntax is not supported."""
|
45
|
+
pass
|
46
|
+
|
47
|
+
|
48
|
+
__all__ = [
|
49
|
+
'ParserError',
|
50
|
+
'ParseError',
|
51
|
+
'UnsupportedSyntaxError',
|
52
|
+
]
|
53
|
+
|
@@ -0,0 +1,318 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
#exonware/xwnode/src/exonware/xwnode/queries/parsers/sql_param_extractor.py
|
4
|
+
|
5
|
+
SQL Parameter Extractor
|
6
|
+
|
7
|
+
Extracts structured parameters from SQL-style queries.
|
8
|
+
Uses regex for simplicity - follows DEV_GUIDELINES.md.
|
9
|
+
|
10
|
+
Company: eXonware.com
|
11
|
+
Author: Eng. Muhammad AlShehri
|
12
|
+
Email: connect@exonware.com
|
13
|
+
Version: 0.0.1.15
|
14
|
+
Generation Date: 09-Oct-2025
|
15
|
+
"""
|
16
|
+
|
17
|
+
import re
|
18
|
+
from typing import Dict, Any, List, Union
|
19
|
+
|
20
|
+
from .base import AParamExtractor
|
21
|
+
from .errors import ParseError
|
22
|
+
|
23
|
+
|
24
|
+
class SQLParamExtractor(AParamExtractor):
|
25
|
+
"""
|
26
|
+
SQL parameter extractor using regex.
|
27
|
+
|
28
|
+
Extracts structured parameters from SQL queries for executor consumption.
|
29
|
+
Implements IParamExtractor interface per DEV_GUIDELINES.md.
|
30
|
+
"""
|
31
|
+
|
32
|
+
def extract_params(self, query: str, action_type: str) -> Dict[str, Any]:
|
33
|
+
"""
|
34
|
+
Extract parameters based on action type.
|
35
|
+
|
36
|
+
Args:
|
37
|
+
query: SQL query string
|
38
|
+
action_type: Type of action (SELECT, INSERT, etc.)
|
39
|
+
|
40
|
+
Returns:
|
41
|
+
Structured parameters dictionary
|
42
|
+
"""
|
43
|
+
# Route to appropriate extractor
|
44
|
+
extractors = {
|
45
|
+
'SELECT': self.extract_select_params,
|
46
|
+
'INSERT': self.extract_insert_params,
|
47
|
+
'UPDATE': self.extract_update_params,
|
48
|
+
'DELETE': self.extract_delete_params,
|
49
|
+
'WHERE': self.extract_where_params,
|
50
|
+
'COUNT': self.extract_count_params,
|
51
|
+
'GROUP': self.extract_group_by_params,
|
52
|
+
'ORDER': self.extract_order_by_params,
|
53
|
+
}
|
54
|
+
|
55
|
+
extractor = extractors.get(action_type)
|
56
|
+
if extractor:
|
57
|
+
return extractor(query)
|
58
|
+
|
59
|
+
# Fallback: return raw query
|
60
|
+
return {'raw': query}
|
61
|
+
|
62
|
+
def can_parse(self, query: str) -> bool:
|
63
|
+
"""Check if query looks like SQL."""
|
64
|
+
query_upper = query.upper()
|
65
|
+
sql_keywords = ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'FROM', 'WHERE']
|
66
|
+
return any(kw in query_upper for kw in sql_keywords)
|
67
|
+
|
68
|
+
def extract_select_params(self, sql: str) -> Dict[str, Any]:
|
69
|
+
"""Extract SELECT statement parameters."""
|
70
|
+
params = {}
|
71
|
+
|
72
|
+
# Extract SELECT fields
|
73
|
+
select_match = re.search(r'SELECT\s+(.*?)\s+FROM', sql, re.IGNORECASE | re.DOTALL)
|
74
|
+
if select_match:
|
75
|
+
fields_str = select_match.group(1).strip()
|
76
|
+
params['fields'] = self._split_fields(fields_str)
|
77
|
+
else:
|
78
|
+
params['fields'] = ['*']
|
79
|
+
|
80
|
+
# Extract FROM table
|
81
|
+
from_match = re.search(r'FROM\s+(\w+)', sql, re.IGNORECASE)
|
82
|
+
if from_match:
|
83
|
+
params['from'] = from_match.group(1)
|
84
|
+
params['path'] = from_match.group(1) # Alias for compatibility
|
85
|
+
|
86
|
+
# Extract WHERE conditions
|
87
|
+
where_match = re.search(r'WHERE\s+(.+?)(?:ORDER|GROUP|LIMIT|$)', sql, re.IGNORECASE | re.DOTALL)
|
88
|
+
if where_match:
|
89
|
+
params['where'] = self._parse_where_condition(where_match.group(1).strip())
|
90
|
+
|
91
|
+
# Extract ORDER BY
|
92
|
+
order_match = re.search(r'ORDER\s+BY\s+(.*?)(?:LIMIT|$)', sql, re.IGNORECASE | re.DOTALL)
|
93
|
+
if order_match:
|
94
|
+
params['order_by'] = order_match.group(1).strip()
|
95
|
+
|
96
|
+
# Extract GROUP BY
|
97
|
+
group_match = re.search(r'GROUP\s+BY\s+(.*?)(?:HAVING|ORDER|LIMIT|$)', sql, re.IGNORECASE | re.DOTALL)
|
98
|
+
if group_match:
|
99
|
+
params['group_by'] = [f.strip() for f in group_match.group(1).split(',')]
|
100
|
+
|
101
|
+
# Extract LIMIT
|
102
|
+
limit_match = re.search(r'LIMIT\s+(\d+)', sql, re.IGNORECASE)
|
103
|
+
if limit_match:
|
104
|
+
params['limit'] = int(limit_match.group(1))
|
105
|
+
|
106
|
+
return params
|
107
|
+
|
108
|
+
def extract_insert_params(self, sql: str) -> Dict[str, Any]:
|
109
|
+
"""Extract INSERT statement parameters."""
|
110
|
+
params = {}
|
111
|
+
|
112
|
+
# INSERT INTO table VALUES {...}
|
113
|
+
into_match = re.search(r'INSERT\s+INTO\s+(\w+)', sql, re.IGNORECASE)
|
114
|
+
if into_match:
|
115
|
+
params['target'] = into_match.group(1)
|
116
|
+
|
117
|
+
# Extract VALUES
|
118
|
+
values_match = re.search(r'VALUES\s+(.+)', sql, re.IGNORECASE | re.DOTALL)
|
119
|
+
if values_match:
|
120
|
+
values_str = values_match.group(1).strip()
|
121
|
+
# Try to parse as JSON-like dict/list
|
122
|
+
try:
|
123
|
+
# Remove outer braces and parse key:value pairs
|
124
|
+
if values_str.startswith('{'):
|
125
|
+
params['values'] = self._parse_dict_literal(values_str)
|
126
|
+
elif values_str.startswith('('):
|
127
|
+
params['values'] = self._parse_tuple_literal(values_str)
|
128
|
+
except:
|
129
|
+
params['values'] = values_str
|
130
|
+
|
131
|
+
return params
|
132
|
+
|
133
|
+
def extract_update_params(self, sql: str) -> Dict[str, Any]:
|
134
|
+
"""Extract UPDATE statement parameters."""
|
135
|
+
params = {}
|
136
|
+
|
137
|
+
# UPDATE table SET ...
|
138
|
+
table_match = re.search(r'UPDATE\s+(\w+)', sql, re.IGNORECASE)
|
139
|
+
if table_match:
|
140
|
+
params['target'] = table_match.group(1)
|
141
|
+
|
142
|
+
# Extract SET clause
|
143
|
+
set_match = re.search(r'SET\s+(.+?)(?:WHERE|$)', sql, re.IGNORECASE | re.DOTALL)
|
144
|
+
if set_match:
|
145
|
+
params['values'] = self._parse_set_clause(set_match.group(1).strip())
|
146
|
+
|
147
|
+
# Extract WHERE
|
148
|
+
where_match = re.search(r'WHERE\s+(.+?)$', sql, re.IGNORECASE | re.DOTALL)
|
149
|
+
if where_match:
|
150
|
+
params['where'] = self._parse_where_condition(where_match.group(1).strip())
|
151
|
+
|
152
|
+
return params
|
153
|
+
|
154
|
+
def extract_delete_params(self, sql: str) -> Dict[str, Any]:
|
155
|
+
"""Extract DELETE statement parameters."""
|
156
|
+
params = {}
|
157
|
+
|
158
|
+
# DELETE FROM table
|
159
|
+
from_match = re.search(r'DELETE\s+FROM\s+(\w+)', sql, re.IGNORECASE)
|
160
|
+
if from_match:
|
161
|
+
params['target'] = from_match.group(1)
|
162
|
+
|
163
|
+
# Extract WHERE
|
164
|
+
where_match = re.search(r'WHERE\s+(.+?)$', sql, re.IGNORECASE | re.DOTALL)
|
165
|
+
if where_match:
|
166
|
+
params['where'] = self._parse_where_condition(where_match.group(1).strip())
|
167
|
+
|
168
|
+
return params
|
169
|
+
|
170
|
+
def extract_where_params(self, sql: str) -> Dict[str, Any]:
|
171
|
+
"""Extract WHERE clause parameters."""
|
172
|
+
# Extract just the condition part
|
173
|
+
where_match = re.search(r'WHERE\s+(.+?)(?:ORDER|GROUP|LIMIT|$)', sql, re.IGNORECASE | re.DOTALL)
|
174
|
+
if where_match:
|
175
|
+
return self._parse_where_condition(where_match.group(1).strip())
|
176
|
+
return {}
|
177
|
+
|
178
|
+
def extract_count_params(self, sql: str) -> Dict[str, Any]:
|
179
|
+
"""Extract COUNT parameters."""
|
180
|
+
params = {}
|
181
|
+
|
182
|
+
# COUNT(*) or COUNT(field)
|
183
|
+
count_match = re.search(r'COUNT\s*\(\s*([^)]+)\s*\)', sql, re.IGNORECASE)
|
184
|
+
if count_match:
|
185
|
+
field = count_match.group(1).strip()
|
186
|
+
params['field'] = field if field != '*' else None
|
187
|
+
|
188
|
+
# Extract FROM
|
189
|
+
from_match = re.search(r'FROM\s+(\w+)', sql, re.IGNORECASE)
|
190
|
+
if from_match:
|
191
|
+
params['from'] = from_match.group(1)
|
192
|
+
params['path'] = from_match.group(1)
|
193
|
+
|
194
|
+
# Extract WHERE
|
195
|
+
where_match = re.search(r'WHERE\s+(.+?)(?:ORDER|GROUP|LIMIT|$)', sql, re.IGNORECASE | re.DOTALL)
|
196
|
+
if where_match:
|
197
|
+
params['where'] = self._parse_where_condition(where_match.group(1).strip())
|
198
|
+
|
199
|
+
return params
|
200
|
+
|
201
|
+
def extract_group_by_params(self, sql: str) -> Dict[str, Any]:
|
202
|
+
"""Extract GROUP BY parameters."""
|
203
|
+
params = {}
|
204
|
+
|
205
|
+
# GROUP BY fields
|
206
|
+
group_match = re.search(r'GROUP\s+BY\s+(.*?)(?:HAVING|ORDER|LIMIT|$)', sql, re.IGNORECASE | re.DOTALL)
|
207
|
+
if group_match:
|
208
|
+
params['fields'] = [f.strip() for f in group_match.group(1).split(',')]
|
209
|
+
|
210
|
+
# Extract HAVING
|
211
|
+
having_match = re.search(r'HAVING\s+(.+?)(?:ORDER|LIMIT|$)', sql, re.IGNORECASE | re.DOTALL)
|
212
|
+
if having_match:
|
213
|
+
params['having'] = self._parse_where_condition(having_match.group(1).strip())
|
214
|
+
|
215
|
+
return params
|
216
|
+
|
217
|
+
def extract_order_by_params(self, sql: str) -> Dict[str, Any]:
|
218
|
+
"""Extract ORDER BY parameters."""
|
219
|
+
params = {}
|
220
|
+
|
221
|
+
# ORDER BY field ASC/DESC
|
222
|
+
order_match = re.search(r'ORDER\s+BY\s+(.*?)(?:LIMIT|$)', sql, re.IGNORECASE | re.DOTALL)
|
223
|
+
if order_match:
|
224
|
+
order_clause = order_match.group(1).strip()
|
225
|
+
|
226
|
+
# Parse: field ASC, field2 DESC
|
227
|
+
order_fields = []
|
228
|
+
for field_spec in order_clause.split(','):
|
229
|
+
field_spec = field_spec.strip()
|
230
|
+
if ' DESC' in field_spec.upper():
|
231
|
+
field = field_spec.upper().replace(' DESC', '').strip()
|
232
|
+
order_fields.append({'field': field, 'direction': 'DESC'})
|
233
|
+
elif ' ASC' in field_spec.upper():
|
234
|
+
field = field_spec.upper().replace(' ASC', '').strip()
|
235
|
+
order_fields.append({'field': field, 'direction': 'ASC'})
|
236
|
+
else:
|
237
|
+
order_fields.append({'field': field_spec, 'direction': 'ASC'})
|
238
|
+
|
239
|
+
params['fields'] = order_fields
|
240
|
+
|
241
|
+
return params
|
242
|
+
|
243
|
+
def _parse_where_condition(self, condition: str) -> Dict[str, Any]:
|
244
|
+
"""
|
245
|
+
Parse WHERE condition into structured format.
|
246
|
+
|
247
|
+
Supports: field operator value
|
248
|
+
Examples: age > 50, name = 'John', price >= 100
|
249
|
+
"""
|
250
|
+
condition = condition.strip()
|
251
|
+
|
252
|
+
# Check for operators in order of precedence
|
253
|
+
operators = ['>=', '<=', '!=', '<>', '>', '<', '=', 'LIKE', 'IN']
|
254
|
+
|
255
|
+
for op in operators:
|
256
|
+
# Case-insensitive for word operators
|
257
|
+
if op.isalpha():
|
258
|
+
pattern = rf'\s+{op}\s+'
|
259
|
+
match = re.search(pattern, condition, re.IGNORECASE)
|
260
|
+
if match:
|
261
|
+
field = condition[:match.start()].strip()
|
262
|
+
value = condition[match.end():].strip()
|
263
|
+
return {
|
264
|
+
'field': field,
|
265
|
+
'operator': op.upper(),
|
266
|
+
'value': self._parse_value(value) if op.upper() != 'IN' else self._parse_in_values(value)
|
267
|
+
}
|
268
|
+
else:
|
269
|
+
if op in condition:
|
270
|
+
parts = condition.split(op, 1)
|
271
|
+
if len(parts) == 2:
|
272
|
+
return {
|
273
|
+
'field': parts[0].strip(),
|
274
|
+
'operator': op,
|
275
|
+
'value': self._parse_value(parts[1].strip())
|
276
|
+
}
|
277
|
+
|
278
|
+
# Can't parse - return as expression
|
279
|
+
return {'expression': condition}
|
280
|
+
|
281
|
+
def _parse_in_values(self, values_str: str) -> List:
|
282
|
+
"""Parse IN clause values."""
|
283
|
+
# IN ['value1', 'value2'] or IN ('value1', 'value2')
|
284
|
+
values_str = values_str.strip().strip('[]()').strip()
|
285
|
+
values = [self._parse_value(v.strip()) for v in values_str.split(',')]
|
286
|
+
return values
|
287
|
+
|
288
|
+
def _parse_set_clause(self, set_str: str) -> Dict[str, Any]:
|
289
|
+
"""Parse SET clause in UPDATE."""
|
290
|
+
assignments = {}
|
291
|
+
|
292
|
+
# Split by comma
|
293
|
+
for assignment in set_str.split(','):
|
294
|
+
if '=' in assignment:
|
295
|
+
field, value = assignment.split('=', 1)
|
296
|
+
assignments[field.strip()] = self._parse_value(value.strip())
|
297
|
+
|
298
|
+
return assignments
|
299
|
+
|
300
|
+
def _parse_dict_literal(self, dict_str: str) -> Dict[str, Any]:
|
301
|
+
"""Parse dictionary literal from string."""
|
302
|
+
# Simple parser for {key: value, key2: value2}
|
303
|
+
dict_str = dict_str.strip('{}').strip()
|
304
|
+
result = {}
|
305
|
+
|
306
|
+
for pair in dict_str.split(','):
|
307
|
+
if ':' in pair:
|
308
|
+
key, value = pair.split(':', 1)
|
309
|
+
result[key.strip()] = self._parse_value(value.strip())
|
310
|
+
|
311
|
+
return result
|
312
|
+
|
313
|
+
def _parse_tuple_literal(self, tuple_str: str) -> List[Any]:
|
314
|
+
"""Parse tuple literal from string."""
|
315
|
+
# Simple parser for (value1, value2, value3)
|
316
|
+
tuple_str = tuple_str.strip('()').strip()
|
317
|
+
return [self._parse_value(v.strip()) for v in tuple_str.split(',')]
|
318
|
+
|