quantum-framework 0.9.0__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.
- quantum/__init__.py +1 -0
- quantum/cli/__init__.py +2 -0
- quantum/cli/commands/__init__.py +15 -0
- quantum/cli/commands/build.py +417 -0
- quantum/cli/commands/dev.py +185 -0
- quantum/cli/commands/docs.py +329 -0
- quantum/cli/commands/lint.py +523 -0
- quantum/cli/commands/migrate.py +527 -0
- quantum/cli/commands/new.py +622 -0
- quantum/cli/commands/serve.py +190 -0
- quantum/cli/commands/test.py +193 -0
- quantum/cli/deploy.py +810 -0
- quantum/cli/hot_reload.py +951 -0
- quantum/cli/jobs.py +356 -0
- quantum/cli/mq.py +582 -0
- quantum/cli/pkg.py +390 -0
- quantum/cli/runner.py +547 -0
- quantum/cli/server_process.py +159 -0
- quantum/cli/utils.py +334 -0
- quantum/compiler/__init__.py +30 -0
- quantum/compiler/base_generator.py +367 -0
- quantum/compiler/cli.py +295 -0
- quantum/compiler/expression_transformer.py +444 -0
- quantum/compiler/javascript/__init__.py +10 -0
- quantum/compiler/javascript/generator.py +659 -0
- quantum/compiler/optimizer.py +270 -0
- quantum/compiler/python/__init__.py +10 -0
- quantum/compiler/python/generator.py +883 -0
- quantum/compiler/python/runtime.py +863 -0
- quantum/compiler/transpiler.py +330 -0
- quantum/core/__init__.py +3 -0
- quantum/core/ast_nodes.py +2611 -0
- quantum/core/expression_diagnostics.py +87 -0
- quantum/core/expression_stdlib.py +148 -0
- quantum/core/expressions.py +591 -0
- quantum/core/features/agents/src/__init__.py +30 -0
- quantum/core/features/agents/src/ast_node.py +540 -0
- quantum/core/features/conditionals/src/__init__.py +8 -0
- quantum/core/features/conditionals/src/ast_node.py +68 -0
- quantum/core/features/data_fetching/src/__init__.py +21 -0
- quantum/core/features/data_fetching/src/ast_node.py +312 -0
- quantum/core/features/data_fetching/src/desktop_adapter.py +351 -0
- quantum/core/features/data_fetching/src/html_adapter.py +474 -0
- quantum/core/features/data_fetching/src/parser.py +225 -0
- quantum/core/features/data_import/src/__init__.py +0 -0
- quantum/core/features/data_import/src/ast_node.py +291 -0
- quantum/core/features/data_import/src/runtime.py +538 -0
- quantum/core/features/dump/src/__init__.py +12 -0
- quantum/core/features/dump/src/ast_node.py +106 -0
- quantum/core/features/dump/src/parser.py +61 -0
- quantum/core/features/dump/src/runtime.py +246 -0
- quantum/core/features/functions/src/__init__.py +8 -0
- quantum/core/features/functions/src/ast_node.py +149 -0
- quantum/core/features/game_engine_2d/src/__init__.py +19 -0
- quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
- quantum/core/features/game_engine_2d/src/parser.py +983 -0
- quantum/core/features/invocation/src/__init__.py +0 -0
- quantum/core/features/invocation/src/ast_node.py +146 -0
- quantum/core/features/invocation/src/runtime.py +327 -0
- quantum/core/features/knowledge_base/src/__init__.py +6 -0
- quantum/core/features/knowledge_base/src/ast_node.py +113 -0
- quantum/core/features/knowledge_base/src/parser.py +82 -0
- quantum/core/features/logging/src/__init__.py +12 -0
- quantum/core/features/logging/src/ast_node.py +111 -0
- quantum/core/features/logging/src/parser.py +50 -0
- quantum/core/features/logging/src/runtime.py +190 -0
- quantum/core/features/loops/src/__init__.py +8 -0
- quantum/core/features/loops/src/ast_node.py +60 -0
- quantum/core/features/query/src/__init__.py +0 -0
- quantum/core/features/query/src/database_service.py +322 -0
- quantum/core/features/query/src/query_validators.py +20 -0
- quantum/core/features/state_management/src/__init__.py +11 -0
- quantum/core/features/state_management/src/ast_node.py +228 -0
- quantum/core/features/terminal_engine/src/__init__.py +21 -0
- quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
- quantum/core/features/terminal_engine/src/parser.py +361 -0
- quantum/core/features/testing_engine/src/__init__.py +41 -0
- quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
- quantum/core/features/testing_engine/src/parser.py +604 -0
- quantum/core/features/theming/src/__init__.py +48 -0
- quantum/core/features/theming/src/ast_node.py +137 -0
- quantum/core/features/theming/src/presets.py +405 -0
- quantum/core/features/ui_engine/src/__init__.py +20 -0
- quantum/core/features/ui_engine/src/ast_nodes.py +1854 -0
- quantum/core/features/ui_engine/src/parser.py +1106 -0
- quantum/core/features/websocket/src/__init__.py +24 -0
- quantum/core/features/websocket/src/ast_node.py +247 -0
- quantum/core/html_compat.py +299 -0
- quantum/core/parser.py +1235 -0
- quantum/core/parser_registry.py +213 -0
- quantum/core/parsers/__init__.py +76 -0
- quantum/core/parsers/ai/__init__.py +12 -0
- quantum/core/parsers/ai/agent_parser.py +106 -0
- quantum/core/parsers/ai/knowledge_parser.py +86 -0
- quantum/core/parsers/ai/llm_parser.py +78 -0
- quantum/core/parsers/ai/team_parser.py +88 -0
- quantum/core/parsers/base.py +322 -0
- quantum/core/parsers/composition/__init__.py +10 -0
- quantum/core/parsers/composition/import_parser.py +50 -0
- quantum/core/parsers/composition/slot_parser.py +48 -0
- quantum/core/parsers/control_flow/__init__.py +11 -0
- quantum/core/parsers/control_flow/if_parser.py +68 -0
- quantum/core/parsers/control_flow/loop_parser.py +105 -0
- quantum/core/parsers/control_flow/set_parser.py +89 -0
- quantum/core/parsers/data/__init__.py +12 -0
- quantum/core/parsers/data/data_parser.py +217 -0
- quantum/core/parsers/data/invoke_parser.py +116 -0
- quantum/core/parsers/data/query_parser.py +188 -0
- quantum/core/parsers/data/transaction_parser.py +77 -0
- quantum/core/parsers/events/__init__.py +9 -0
- quantum/core/parsers/events/dispatch_event_parser.py +44 -0
- quantum/core/parsers/forms/__init__.py +11 -0
- quantum/core/parsers/forms/action_parser.py +54 -0
- quantum/core/parsers/forms/flash_parser.py +34 -0
- quantum/core/parsers/forms/redirect_parser.py +33 -0
- quantum/core/parsers/functions/__init__.py +11 -0
- quantum/core/parsers/functions/function_parser.py +137 -0
- quantum/core/parsers/functions/param_parser.py +66 -0
- quantum/core/parsers/functions/return_parser.py +31 -0
- quantum/core/parsers/html/__init__.py +10 -0
- quantum/core/parsers/html/component_call_parser.py +130 -0
- quantum/core/parsers/html/html_parser.py +115 -0
- quantum/core/parsers/jobs/__init__.py +11 -0
- quantum/core/parsers/jobs/job_parser.py +71 -0
- quantum/core/parsers/jobs/schedule_parser.py +62 -0
- quantum/core/parsers/jobs/thread_parser.py +57 -0
- quantum/core/parsers/messaging/__init__.py +17 -0
- quantum/core/parsers/messaging/message_ack_parser.py +30 -0
- quantum/core/parsers/messaging/message_nack_parser.py +30 -0
- quantum/core/parsers/messaging/message_parser.py +114 -0
- quantum/core/parsers/messaging/queue_parser.py +61 -0
- quantum/core/parsers/messaging/websocket_parser.py +121 -0
- quantum/core/parsers/persistence/__init__.py +9 -0
- quantum/core/parsers/persistence/persist_parser.py +64 -0
- quantum/core/parsers/routing/__init__.py +9 -0
- quantum/core/parsers/routing/route_parser.py +41 -0
- quantum/core/parsers/scripting/__init__.py +12 -0
- quantum/core/parsers/scripting/pyclass_parser.py +62 -0
- quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
- quantum/core/parsers/scripting/pyimport_parser.py +52 -0
- quantum/core/parsers/scripting/python_parser.py +49 -0
- quantum/core/parsers/services/__init__.py +12 -0
- quantum/core/parsers/services/dump_parser.py +52 -0
- quantum/core/parsers/services/file_parser.py +48 -0
- quantum/core/parsers/services/log_parser.py +46 -0
- quantum/core/parsers/services/mail_parser.py +65 -0
- quantum/core/tiers.py +82 -0
- quantum/packages/__init__.py +28 -0
- quantum/packages/manager.py +413 -0
- quantum/packages/manifest.py +351 -0
- quantum/packages/registry.py +399 -0
- quantum/packages/resolver.py +336 -0
- quantum/plugins/__init__.py +33 -0
- quantum/plugins/hooks.py +329 -0
- quantum/plugins/loader.py +479 -0
- quantum/plugins/manifest.py +336 -0
- quantum/plugins/registry.py +371 -0
- quantum/runtime/__init__.py +28 -0
- quantum/runtime/action_handler.py +443 -0
- quantum/runtime/adapters/__init__.py +88 -0
- quantum/runtime/adapters/memory_adapter.py +690 -0
- quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
- quantum/runtime/adapters/redis_adapter.py +582 -0
- quantum/runtime/adapters/sqlite_adapter.py +414 -0
- quantum/runtime/agent_service.py +1133 -0
- quantum/runtime/api_server.py +86 -0
- quantum/runtime/ast_cache.py +506 -0
- quantum/runtime/auth_service.py +267 -0
- quantum/runtime/component.py +990 -0
- quantum/runtime/component_composer.py +319 -0
- quantum/runtime/component_resolver.py +174 -0
- quantum/runtime/database_service.py +598 -0
- quantum/runtime/email_service.py +162 -0
- quantum/runtime/error_handler.py +295 -0
- quantum/runtime/execution_context.py +286 -0
- quantum/runtime/executor_registry.py +171 -0
- quantum/runtime/executors/__init__.py +71 -0
- quantum/runtime/executors/ai/__init__.py +12 -0
- quantum/runtime/executors/ai/agent_executor.py +217 -0
- quantum/runtime/executors/ai/knowledge_executor.py +114 -0
- quantum/runtime/executors/ai/llm_executor.py +153 -0
- quantum/runtime/executors/ai/team_executor.py +171 -0
- quantum/runtime/executors/base.py +262 -0
- quantum/runtime/executors/control_flow/__init__.py +11 -0
- quantum/runtime/executors/control_flow/if_executor.py +93 -0
- quantum/runtime/executors/control_flow/loop_executor.py +307 -0
- quantum/runtime/executors/control_flow/set_executor.py +412 -0
- quantum/runtime/executors/data/__init__.py +12 -0
- quantum/runtime/executors/data/data_executor.py +145 -0
- quantum/runtime/executors/data/invoke_executor.py +176 -0
- quantum/runtime/executors/data/query_executor.py +256 -0
- quantum/runtime/executors/data/transaction_executor.py +91 -0
- quantum/runtime/executors/jobs/__init__.py +11 -0
- quantum/runtime/executors/jobs/job_executor.py +190 -0
- quantum/runtime/executors/jobs/schedule_executor.py +132 -0
- quantum/runtime/executors/jobs/thread_executor.py +127 -0
- quantum/runtime/executors/messaging/__init__.py +17 -0
- quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
- quantum/runtime/executors/messaging/message_executor.py +174 -0
- quantum/runtime/executors/messaging/queue_executor.py +103 -0
- quantum/runtime/executors/messaging/websocket_executor.py +197 -0
- quantum/runtime/executors/scripting/__init__.py +11 -0
- quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
- quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
- quantum/runtime/executors/scripting/python_executor.py +249 -0
- quantum/runtime/executors/services/__init__.py +12 -0
- quantum/runtime/executors/services/dump_executor.py +72 -0
- quantum/runtime/executors/services/file_executor.py +89 -0
- quantum/runtime/executors/services/log_executor.py +77 -0
- quantum/runtime/executors/services/mail_executor.py +81 -0
- quantum/runtime/expression_cache.py +498 -0
- quantum/runtime/file_upload_service.py +326 -0
- quantum/runtime/function_registry.py +118 -0
- quantum/runtime/game_builder.py +166 -0
- quantum/runtime/game_code_generator.py +2371 -0
- quantum/runtime/game_templates.py +2006 -0
- quantum/runtime/godot_code_generator.py +4681 -0
- quantum/runtime/godot_templates.py +1449 -0
- quantum/runtime/job_executor.py +1599 -0
- quantum/runtime/knowledge_service.py +500 -0
- quantum/runtime/llm_cache.py +100 -0
- quantum/runtime/llm_providers.py +704 -0
- quantum/runtime/llm_service.py +287 -0
- quantum/runtime/logging_setup.py +140 -0
- quantum/runtime/message_broker.py +364 -0
- quantum/runtime/message_queue_service.py +571 -0
- quantum/runtime/param_validation.py +184 -0
- quantum/runtime/pypy_compat.py +315 -0
- quantum/runtime/python_bridge.py +698 -0
- quantum/runtime/query_validators.py +304 -0
- quantum/runtime/renderer.py +733 -0
- quantum/runtime/service_container.py +444 -0
- quantum/runtime/terminal_builder.py +76 -0
- quantum/runtime/terminal_code_generator.py +607 -0
- quantum/runtime/terminal_templates.py +243 -0
- quantum/runtime/testing_builder.py +77 -0
- quantum/runtime/testing_code_generator.py +833 -0
- quantum/runtime/testing_templates.py +85 -0
- quantum/runtime/ui_builder.py +188 -0
- quantum/runtime/ui_desktop_adapter.py +1730 -0
- quantum/runtime/ui_desktop_templates.py +307 -0
- quantum/runtime/ui_html_adapter.py +2691 -0
- quantum/runtime/ui_html_templates.py +2297 -0
- quantum/runtime/ui_mobile_adapter.py +1832 -0
- quantum/runtime/ui_mobile_templates.py +1003 -0
- quantum/runtime/ui_textual_adapter.py +1866 -0
- quantum/runtime/ui_textual_templates.py +45 -0
- quantum/runtime/ui_tokens.py +465 -0
- quantum/runtime/ui_validator.py +365 -0
- quantum/runtime/validators.py +256 -0
- quantum/runtime/web_server.py +1766 -0
- quantum/runtime/websocket_adapter.py +501 -0
- quantum/runtime/websocket_service.py +585 -0
- quantum/runtime/websocket_transport.py +289 -0
- quantum/runtime/wsgi.py +101 -0
- quantum/utils/__init__.py +1 -0
- quantum_framework-0.9.0.dist-info/METADATA +244 -0
- quantum_framework-0.9.0.dist-info/RECORD +262 -0
- quantum_framework-0.9.0.dist-info/WHEEL +5 -0
- quantum_framework-0.9.0.dist-info/entry_points.txt +2 -0
- quantum_framework-0.9.0.dist-info/licenses/LICENSE +21 -0
- quantum_framework-0.9.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Invoke Executor - Execute q:invoke statements
|
|
3
|
+
|
|
4
|
+
Handles function, component, and HTTP invocations with caching.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, List, Dict, Type
|
|
8
|
+
import json
|
|
9
|
+
from quantum.runtime.executors.base import BaseExecutor, ExecutorError
|
|
10
|
+
from quantum.core.features.invocation.src.ast_node import InvokeNode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class InvokeExecutor(BaseExecutor):
|
|
14
|
+
"""
|
|
15
|
+
Executor for q:invoke statements.
|
|
16
|
+
|
|
17
|
+
Supports:
|
|
18
|
+
- Function invocation (local functions)
|
|
19
|
+
- Component invocation (other .q components)
|
|
20
|
+
- HTTP invocation (REST APIs)
|
|
21
|
+
- Response caching with TTL
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def handles(self) -> List[Type]:
|
|
26
|
+
return [InvokeNode]
|
|
27
|
+
|
|
28
|
+
def execute(self, node: InvokeNode, exec_context) -> Any:
|
|
29
|
+
"""
|
|
30
|
+
Execute invocation.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
node: InvokeNode with invocation configuration
|
|
34
|
+
exec_context: Execution context
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
None (stores result in context)
|
|
38
|
+
"""
|
|
39
|
+
try:
|
|
40
|
+
context = exec_context.get_all_variables()
|
|
41
|
+
|
|
42
|
+
invocation_type = node.get_invocation_type()
|
|
43
|
+
|
|
44
|
+
if invocation_type == "unknown":
|
|
45
|
+
raise ExecutorError(
|
|
46
|
+
f"Invoke '{node.name}' requires one of: function, component, url, endpoint, or service"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Build parameters based on type
|
|
50
|
+
if invocation_type == "function":
|
|
51
|
+
params = self._build_function_params(node, context)
|
|
52
|
+
elif invocation_type == "component":
|
|
53
|
+
params = self._build_component_params(node, context)
|
|
54
|
+
elif invocation_type == "http":
|
|
55
|
+
params = self._build_http_params(node, context)
|
|
56
|
+
else:
|
|
57
|
+
raise ExecutorError(f"Unsupported invocation type: {invocation_type}")
|
|
58
|
+
|
|
59
|
+
# Check cache
|
|
60
|
+
if node.cache:
|
|
61
|
+
cache_key = f"invoke_{node.name}_{hash(str(params))}"
|
|
62
|
+
cached = self.services.invocation.get_from_cache(cache_key)
|
|
63
|
+
if cached is not None:
|
|
64
|
+
self._store_result(node, cached, exec_context)
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
# Execute invocation
|
|
68
|
+
result = self.services.invocation.invoke(
|
|
69
|
+
invocation_type,
|
|
70
|
+
params,
|
|
71
|
+
context=self.runtime
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Cache result
|
|
75
|
+
if node.cache and result.success:
|
|
76
|
+
cache_key = f"invoke_{node.name}_{hash(str(params))}"
|
|
77
|
+
self.services.invocation.put_in_cache(cache_key, result, node.ttl)
|
|
78
|
+
|
|
79
|
+
# Store result
|
|
80
|
+
self._store_result(node, result, exec_context)
|
|
81
|
+
|
|
82
|
+
except Exception as e:
|
|
83
|
+
raise ExecutorError(f"Invoke execution error in '{node.name}': {e}")
|
|
84
|
+
|
|
85
|
+
def _param_args(self, node: InvokeNode, context: Dict[str, Any]) -> Dict[str, Any]:
|
|
86
|
+
"""Resolve <q:param> children into call arguments.
|
|
87
|
+
|
|
88
|
+
This used to read `param.default`, but the invoke parser stores the
|
|
89
|
+
value= attribute on `param.value` — `default` is only the fallback.
|
|
90
|
+
So every argument arrived as the empty string and
|
|
91
|
+
<q:param name="n" value="21" /> passed ''.
|
|
92
|
+
"""
|
|
93
|
+
args = {}
|
|
94
|
+
for param in node.params:
|
|
95
|
+
raw = getattr(param, 'value', None)
|
|
96
|
+
if raw is None:
|
|
97
|
+
raw = param.default
|
|
98
|
+
args[param.name] = self.apply_databinding(raw if raw is not None else "", context)
|
|
99
|
+
return args
|
|
100
|
+
|
|
101
|
+
def _build_function_params(self, node: InvokeNode, context: Dict[str, Any]) -> Dict[str, Any]:
|
|
102
|
+
"""Build parameters for function invocation"""
|
|
103
|
+
args = self._param_args(node, context)
|
|
104
|
+
return {
|
|
105
|
+
'function': node.function,
|
|
106
|
+
'args': args
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
def _build_component_params(self, node: InvokeNode, context: Dict[str, Any]) -> Dict[str, Any]:
|
|
110
|
+
"""Build parameters for component invocation"""
|
|
111
|
+
args = self._param_args(node, context)
|
|
112
|
+
return {
|
|
113
|
+
'component': node.component,
|
|
114
|
+
'args': args
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
def _build_http_params(self, node: InvokeNode, context: Dict[str, Any]) -> Dict[str, Any]:
|
|
118
|
+
"""Build parameters for HTTP invocation"""
|
|
119
|
+
url = self.apply_databinding(node.url, context)
|
|
120
|
+
|
|
121
|
+
headers = {}
|
|
122
|
+
for header_node in node.headers:
|
|
123
|
+
header_value = self.apply_databinding(header_node.value, context)
|
|
124
|
+
headers[header_node.name] = header_value
|
|
125
|
+
|
|
126
|
+
if 'Content-Type' not in headers and 'content-type' not in headers:
|
|
127
|
+
headers['Content-Type'] = node.content_type
|
|
128
|
+
|
|
129
|
+
query_params = {}
|
|
130
|
+
for param in node.params:
|
|
131
|
+
param_value = self.apply_databinding(param.default if param.default else "", context)
|
|
132
|
+
query_params[param.name] = param_value
|
|
133
|
+
|
|
134
|
+
body = None
|
|
135
|
+
if node.body:
|
|
136
|
+
body = self.apply_databinding(node.body, context)
|
|
137
|
+
if isinstance(body, str) and 'json' in node.content_type.lower():
|
|
138
|
+
try:
|
|
139
|
+
body = json.loads(body)
|
|
140
|
+
except:
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
'url': url,
|
|
145
|
+
'method': node.method,
|
|
146
|
+
'headers': headers,
|
|
147
|
+
'params': query_params,
|
|
148
|
+
'body': body,
|
|
149
|
+
'auth_type': node.auth_type,
|
|
150
|
+
'auth_token': self.apply_databinding(node.auth_token, context) if node.auth_token else None,
|
|
151
|
+
'auth_header': node.auth_header,
|
|
152
|
+
'auth_username': self.apply_databinding(node.auth_username, context) if node.auth_username else None,
|
|
153
|
+
'auth_password': self.apply_databinding(node.auth_password, context) if node.auth_password else None,
|
|
154
|
+
'timeout': node.timeout,
|
|
155
|
+
'retry': node.retry,
|
|
156
|
+
'retry_delay': node.retry_delay,
|
|
157
|
+
'response_format': node.response_format
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
def _store_result(self, node: InvokeNode, result, exec_context):
|
|
161
|
+
"""Store invocation result in context"""
|
|
162
|
+
exec_context.set_variable(node.name, result.data, scope="component")
|
|
163
|
+
|
|
164
|
+
result_dict = {
|
|
165
|
+
'success': result.success,
|
|
166
|
+
'data': result.data,
|
|
167
|
+
'error': result.error,
|
|
168
|
+
'executionTime': result.execution_time,
|
|
169
|
+
'invocationType': result.invocation_type,
|
|
170
|
+
'metadata': result.metadata
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
exec_context.set_variable(f"{node.name}_result", result_dict, scope="component")
|
|
174
|
+
|
|
175
|
+
if node.result:
|
|
176
|
+
exec_context.set_variable(node.result, result_dict, scope="component")
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Query Executor - Execute q:query statements
|
|
3
|
+
|
|
4
|
+
Handles database queries with parameter validation, pagination, and Query of Queries.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, List, Dict, Type
|
|
8
|
+
import sqlite3
|
|
9
|
+
import time
|
|
10
|
+
import re
|
|
11
|
+
from quantum.runtime.executors.base import BaseExecutor, ExecutorError
|
|
12
|
+
from quantum.core.ast_nodes import QueryNode
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class QueryExecutor(BaseExecutor):
|
|
16
|
+
"""
|
|
17
|
+
Executor for q:query statements.
|
|
18
|
+
|
|
19
|
+
Supports:
|
|
20
|
+
- Database queries with parameterized SQL
|
|
21
|
+
- Pagination with automatic COUNT
|
|
22
|
+
- Query of Queries (in-memory SQL on result sets)
|
|
23
|
+
- Knowledge base queries (virtual datasource)
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def handles(self) -> List[Type]:
|
|
28
|
+
return [QueryNode]
|
|
29
|
+
|
|
30
|
+
def execute(self, node: QueryNode, exec_context) -> Any:
|
|
31
|
+
"""
|
|
32
|
+
Execute database query.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
node: QueryNode with query configuration
|
|
36
|
+
exec_context: Execution context
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
QueryResult with data and metadata
|
|
40
|
+
"""
|
|
41
|
+
try:
|
|
42
|
+
context = exec_context.get_all_variables()
|
|
43
|
+
|
|
44
|
+
# Resolve and validate parameters
|
|
45
|
+
resolved_params = self._resolve_params(node, context)
|
|
46
|
+
|
|
47
|
+
# Check for knowledge base query
|
|
48
|
+
if node.datasource and node.datasource.startswith('knowledge:'):
|
|
49
|
+
return self._execute_knowledge_query(node, resolved_params, exec_context)
|
|
50
|
+
|
|
51
|
+
# Check for Query of Queries
|
|
52
|
+
if node.source:
|
|
53
|
+
return self._execute_query_of_queries(node, context, resolved_params, exec_context)
|
|
54
|
+
|
|
55
|
+
# Execute standard database query
|
|
56
|
+
return self._execute_database_query(node, resolved_params, exec_context)
|
|
57
|
+
|
|
58
|
+
except Exception as e:
|
|
59
|
+
raise ExecutorError(f"Query execution error in '{node.name}': {e}")
|
|
60
|
+
|
|
61
|
+
def _resolve_params(self, node: QueryNode, context: Dict[str, Any]) -> Dict[str, Any]:
|
|
62
|
+
"""Resolve and validate query parameters"""
|
|
63
|
+
from quantum.runtime.query_validators import QueryValidator, QueryValidationError
|
|
64
|
+
|
|
65
|
+
resolved_params = {}
|
|
66
|
+
for param_node in node.params:
|
|
67
|
+
param_value = self.apply_databinding(param_node.value, context)
|
|
68
|
+
|
|
69
|
+
attributes = {
|
|
70
|
+
'null': param_node.null,
|
|
71
|
+
'max_length': param_node.max_length,
|
|
72
|
+
'scale': param_node.scale
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
validated_value = QueryValidator.validate_param(
|
|
77
|
+
param_value,
|
|
78
|
+
param_node.param_type,
|
|
79
|
+
attributes
|
|
80
|
+
)
|
|
81
|
+
resolved_params[param_node.name] = validated_value
|
|
82
|
+
except QueryValidationError as e:
|
|
83
|
+
raise ExecutorError(f"Parameter '{param_node.name}' validation failed: {e}")
|
|
84
|
+
|
|
85
|
+
return resolved_params
|
|
86
|
+
|
|
87
|
+
def _execute_database_query(self, node: QueryNode, params: Dict[str, Any], exec_context) -> Any:
|
|
88
|
+
"""Execute standard database query"""
|
|
89
|
+
from quantum.runtime.query_validators import QueryValidator
|
|
90
|
+
|
|
91
|
+
# Sanitize SQL
|
|
92
|
+
QueryValidator.sanitize_sql(node.sql)
|
|
93
|
+
|
|
94
|
+
# Handle pagination
|
|
95
|
+
pagination_metadata = None
|
|
96
|
+
sql_to_execute = node.sql
|
|
97
|
+
|
|
98
|
+
if node.paginate:
|
|
99
|
+
pagination_metadata, sql_to_execute = self._handle_pagination(node, params)
|
|
100
|
+
|
|
101
|
+
# Execute query
|
|
102
|
+
result = self.services.database.execute_query(
|
|
103
|
+
node.datasource,
|
|
104
|
+
sql_to_execute,
|
|
105
|
+
params
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Store results
|
|
109
|
+
self._store_query_result(node, result, pagination_metadata, exec_context)
|
|
110
|
+
|
|
111
|
+
return result
|
|
112
|
+
|
|
113
|
+
def _handle_pagination(self, node: QueryNode, params: Dict[str, Any]):
|
|
114
|
+
"""Handle query pagination"""
|
|
115
|
+
count_sql = self._generate_count_query(node.sql)
|
|
116
|
+
count_result = self.services.database.execute_query(
|
|
117
|
+
node.datasource,
|
|
118
|
+
count_sql,
|
|
119
|
+
params
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
total_records = count_result.data[0]['count'] if count_result.data else 0
|
|
123
|
+
|
|
124
|
+
page = node.page if node.page is not None else 1
|
|
125
|
+
# paginate="true" without a page size is a config error, not a
|
|
126
|
+
# reason to crash on None arithmetic further down.
|
|
127
|
+
page_size = node.page_size or 10
|
|
128
|
+
total_pages = (total_records + page_size - 1) // page_size
|
|
129
|
+
|
|
130
|
+
offset = (page - 1) * page_size
|
|
131
|
+
sql_to_execute = f"{node.sql}\nLIMIT {page_size} OFFSET {offset}"
|
|
132
|
+
|
|
133
|
+
pagination_metadata = {
|
|
134
|
+
'totalRecords': total_records,
|
|
135
|
+
'totalPages': total_pages,
|
|
136
|
+
'currentPage': page,
|
|
137
|
+
'pageSize': page_size,
|
|
138
|
+
'hasNextPage': page < total_pages,
|
|
139
|
+
'hasPreviousPage': page > 1,
|
|
140
|
+
'startRecord': offset + 1 if total_records > 0 else 0,
|
|
141
|
+
'endRecord': min(offset + page_size, total_records)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return pagination_metadata, sql_to_execute
|
|
145
|
+
|
|
146
|
+
def _generate_count_query(self, original_sql: str) -> str:
|
|
147
|
+
"""Generate COUNT(*) query from original SQL"""
|
|
148
|
+
sql = ' '.join(original_sql.split())
|
|
149
|
+
sql = re.sub(r'\s+ORDER\s+BY\s+[^;]+?(?=\s+(?:LIMIT|OFFSET|FOR\s+UPDATE)|$)', '', sql, flags=re.IGNORECASE)
|
|
150
|
+
sql = re.sub(r'\s+LIMIT\s+\d+', '', sql, flags=re.IGNORECASE)
|
|
151
|
+
sql = re.sub(r'\s+OFFSET\s+\d+', '', sql, flags=re.IGNORECASE)
|
|
152
|
+
return f"SELECT COUNT(*) as count FROM ({sql}) AS count_query"
|
|
153
|
+
|
|
154
|
+
def _execute_query_of_queries(self, node: QueryNode, context: Dict[str, Any],
|
|
155
|
+
params: Dict[str, Any], exec_context) -> Any:
|
|
156
|
+
"""Execute Query of Queries - SQL on in-memory result sets"""
|
|
157
|
+
from quantum.runtime.database_service import QueryResult
|
|
158
|
+
|
|
159
|
+
source_name = node.source
|
|
160
|
+
source_data = context.get(source_name) or exec_context.get_variable(source_name)
|
|
161
|
+
|
|
162
|
+
if source_data is None:
|
|
163
|
+
raise ExecutorError(f"Source query '{source_name}' not found")
|
|
164
|
+
|
|
165
|
+
if not isinstance(source_data, list):
|
|
166
|
+
raise ExecutorError(f"Source '{source_name}' is not a query result")
|
|
167
|
+
|
|
168
|
+
if not source_data:
|
|
169
|
+
result = QueryResult(
|
|
170
|
+
success=True, data=[], record_count=0,
|
|
171
|
+
column_list=[], execution_time=0, sql=node.sql
|
|
172
|
+
)
|
|
173
|
+
self._store_query_result(node, result, None, exec_context)
|
|
174
|
+
return result
|
|
175
|
+
|
|
176
|
+
# Create in-memory SQLite database
|
|
177
|
+
conn = sqlite3.connect(':memory:')
|
|
178
|
+
conn.row_factory = sqlite3.Row
|
|
179
|
+
cursor = conn.cursor()
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
first_row = source_data[0]
|
|
183
|
+
if not isinstance(first_row, dict):
|
|
184
|
+
raise ExecutorError("Source data must be list of dictionaries")
|
|
185
|
+
|
|
186
|
+
columns = list(first_row.keys())
|
|
187
|
+
column_defs = ', '.join([f'"{col}" TEXT' for col in columns])
|
|
188
|
+
cursor.execute(f'CREATE TABLE source_table ({column_defs})')
|
|
189
|
+
|
|
190
|
+
placeholders = ', '.join(['?' for _ in columns])
|
|
191
|
+
for row in source_data:
|
|
192
|
+
values = [row.get(col) for col in columns]
|
|
193
|
+
cursor.execute(f'INSERT INTO source_table VALUES ({placeholders})', values)
|
|
194
|
+
|
|
195
|
+
sql = node.sql.replace(f'FROM {source_name}', 'FROM source_table')
|
|
196
|
+
sql = sql.replace(f'FROM {{{source_name}}}', 'FROM source_table')
|
|
197
|
+
|
|
198
|
+
for param_name in params:
|
|
199
|
+
sql = sql.replace(f':{param_name}', '?')
|
|
200
|
+
|
|
201
|
+
start_time = time.time()
|
|
202
|
+
cursor.execute(sql, list(params.values()) if params else [])
|
|
203
|
+
result_rows = cursor.fetchall()
|
|
204
|
+
execution_time = (time.time() - start_time) * 1000
|
|
205
|
+
|
|
206
|
+
result_data = []
|
|
207
|
+
column_names = []
|
|
208
|
+
if result_rows:
|
|
209
|
+
column_names = [desc[0] for desc in cursor.description]
|
|
210
|
+
for row in result_rows:
|
|
211
|
+
result_data.append(dict(zip(column_names, row)))
|
|
212
|
+
|
|
213
|
+
conn.close()
|
|
214
|
+
|
|
215
|
+
result = QueryResult(
|
|
216
|
+
success=True,
|
|
217
|
+
data=result_data,
|
|
218
|
+
record_count=len(result_data),
|
|
219
|
+
column_list=column_names,
|
|
220
|
+
execution_time=int(execution_time),
|
|
221
|
+
sql=node.sql
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
self._store_query_result(node, result, None, exec_context)
|
|
225
|
+
return result
|
|
226
|
+
|
|
227
|
+
except sqlite3.Error as e:
|
|
228
|
+
conn.close()
|
|
229
|
+
raise ExecutorError(f"Query of Queries SQL error: {e}")
|
|
230
|
+
|
|
231
|
+
def _execute_knowledge_query(self, node: QueryNode, params: Dict[str, Any], exec_context) -> Any:
|
|
232
|
+
"""Execute knowledge base query (RAG)"""
|
|
233
|
+
# Delegate to runtime's knowledge query handler
|
|
234
|
+
return self.runtime._execute_knowledge_query(node, params, exec_context)
|
|
235
|
+
|
|
236
|
+
def _store_query_result(self, node: QueryNode, result, pagination_metadata, exec_context):
|
|
237
|
+
"""Store query result in context"""
|
|
238
|
+
result_dict = result.to_dict()
|
|
239
|
+
|
|
240
|
+
if pagination_metadata:
|
|
241
|
+
result_dict['pagination'] = pagination_metadata
|
|
242
|
+
|
|
243
|
+
# Store data as array
|
|
244
|
+
exec_context.set_variable(node.name, result.data, scope="component")
|
|
245
|
+
|
|
246
|
+
# Store full result with metadata
|
|
247
|
+
exec_context.set_variable(f"{node.name}_result", result_dict, scope="component")
|
|
248
|
+
|
|
249
|
+
# Single-row field exposure
|
|
250
|
+
if result.data and len(result.data) == 1 and isinstance(result.data[0], dict):
|
|
251
|
+
for field_name, field_value in result.data[0].items():
|
|
252
|
+
dotted_key = f"{node.name}.{field_name}"
|
|
253
|
+
exec_context.set_variable(dotted_key, field_value, scope="component")
|
|
254
|
+
|
|
255
|
+
if node.result:
|
|
256
|
+
exec_context.set_variable(node.result, result_dict, scope="component")
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Transaction Executor - Execute q:transaction statements
|
|
3
|
+
|
|
4
|
+
Handles database transactions with atomic commit/rollback.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, List, Dict, Type
|
|
8
|
+
from quantum.runtime.executors.base import BaseExecutor, ExecutorError
|
|
9
|
+
from quantum.core.ast_nodes import TransactionNode, QueryNode
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TransactionExecutor(BaseExecutor):
|
|
13
|
+
"""
|
|
14
|
+
Executor for q:transaction statements.
|
|
15
|
+
|
|
16
|
+
Supports:
|
|
17
|
+
- Atomic transaction execution
|
|
18
|
+
- Automatic rollback on failure
|
|
19
|
+
- Multiple isolation levels
|
|
20
|
+
- Nested statement execution
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def handles(self) -> List[Type]:
|
|
25
|
+
return [TransactionNode]
|
|
26
|
+
|
|
27
|
+
def execute(self, node: TransactionNode, exec_context) -> Any:
|
|
28
|
+
"""
|
|
29
|
+
Execute database transaction.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
node: TransactionNode with statements to execute
|
|
33
|
+
exec_context: Execution context
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Transaction result dict with success status
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
# Determine datasource
|
|
40
|
+
datasource_name = self._get_datasource(node)
|
|
41
|
+
|
|
42
|
+
# Begin transaction
|
|
43
|
+
transaction_context = self.services.database.begin_transaction(datasource_name)
|
|
44
|
+
|
|
45
|
+
results = []
|
|
46
|
+
try:
|
|
47
|
+
# Execute all statements
|
|
48
|
+
for statement in node.statements:
|
|
49
|
+
result = self.execute_child(statement, exec_context)
|
|
50
|
+
results.append(result)
|
|
51
|
+
|
|
52
|
+
# Commit transaction
|
|
53
|
+
success = self.services.database.commit_transaction(transaction_context)
|
|
54
|
+
|
|
55
|
+
transaction_result = {
|
|
56
|
+
'success': success,
|
|
57
|
+
'committed': True,
|
|
58
|
+
'isolation_level': node.isolation_level,
|
|
59
|
+
'statement_count': len(node.statements),
|
|
60
|
+
'results': results
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
except Exception as stmt_error:
|
|
64
|
+
# Rollback on failure
|
|
65
|
+
self.services.database.rollback_transaction(transaction_context)
|
|
66
|
+
|
|
67
|
+
transaction_result = {
|
|
68
|
+
'success': False,
|
|
69
|
+
'rolled_back': True,
|
|
70
|
+
'error': str(stmt_error),
|
|
71
|
+
'isolation_level': node.isolation_level
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
raise ExecutorError(f"Transaction failed and was rolled back: {stmt_error}")
|
|
75
|
+
|
|
76
|
+
# Store result
|
|
77
|
+
exec_context.set_variable('_transaction_result', transaction_result, scope="component")
|
|
78
|
+
|
|
79
|
+
return transaction_result
|
|
80
|
+
|
|
81
|
+
except ExecutorError:
|
|
82
|
+
raise
|
|
83
|
+
except Exception as e:
|
|
84
|
+
raise ExecutorError(f"Transaction execution error: {e}")
|
|
85
|
+
|
|
86
|
+
def _get_datasource(self, node: TransactionNode) -> str:
|
|
87
|
+
"""Get datasource from first query in transaction"""
|
|
88
|
+
for stmt in node.statements:
|
|
89
|
+
if isinstance(stmt, QueryNode) and stmt.datasource:
|
|
90
|
+
return stmt.datasource
|
|
91
|
+
return "default"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Jobs Executors
|
|
3
|
+
|
|
4
|
+
Executors for job/scheduling operations: schedule, thread, job.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .schedule_executor import ScheduleExecutor
|
|
8
|
+
from .thread_executor import ThreadExecutor
|
|
9
|
+
from .job_executor import JobExecutor
|
|
10
|
+
|
|
11
|
+
__all__ = ['ScheduleExecutor', 'ThreadExecutor', 'JobExecutor']
|