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.
Files changed (262) hide show
  1. quantum/__init__.py +1 -0
  2. quantum/cli/__init__.py +2 -0
  3. quantum/cli/commands/__init__.py +15 -0
  4. quantum/cli/commands/build.py +417 -0
  5. quantum/cli/commands/dev.py +185 -0
  6. quantum/cli/commands/docs.py +329 -0
  7. quantum/cli/commands/lint.py +523 -0
  8. quantum/cli/commands/migrate.py +527 -0
  9. quantum/cli/commands/new.py +622 -0
  10. quantum/cli/commands/serve.py +190 -0
  11. quantum/cli/commands/test.py +193 -0
  12. quantum/cli/deploy.py +810 -0
  13. quantum/cli/hot_reload.py +951 -0
  14. quantum/cli/jobs.py +356 -0
  15. quantum/cli/mq.py +582 -0
  16. quantum/cli/pkg.py +390 -0
  17. quantum/cli/runner.py +547 -0
  18. quantum/cli/server_process.py +159 -0
  19. quantum/cli/utils.py +334 -0
  20. quantum/compiler/__init__.py +30 -0
  21. quantum/compiler/base_generator.py +367 -0
  22. quantum/compiler/cli.py +295 -0
  23. quantum/compiler/expression_transformer.py +444 -0
  24. quantum/compiler/javascript/__init__.py +10 -0
  25. quantum/compiler/javascript/generator.py +659 -0
  26. quantum/compiler/optimizer.py +270 -0
  27. quantum/compiler/python/__init__.py +10 -0
  28. quantum/compiler/python/generator.py +883 -0
  29. quantum/compiler/python/runtime.py +863 -0
  30. quantum/compiler/transpiler.py +330 -0
  31. quantum/core/__init__.py +3 -0
  32. quantum/core/ast_nodes.py +2611 -0
  33. quantum/core/expression_diagnostics.py +87 -0
  34. quantum/core/expression_stdlib.py +148 -0
  35. quantum/core/expressions.py +591 -0
  36. quantum/core/features/agents/src/__init__.py +30 -0
  37. quantum/core/features/agents/src/ast_node.py +540 -0
  38. quantum/core/features/conditionals/src/__init__.py +8 -0
  39. quantum/core/features/conditionals/src/ast_node.py +68 -0
  40. quantum/core/features/data_fetching/src/__init__.py +21 -0
  41. quantum/core/features/data_fetching/src/ast_node.py +312 -0
  42. quantum/core/features/data_fetching/src/desktop_adapter.py +351 -0
  43. quantum/core/features/data_fetching/src/html_adapter.py +474 -0
  44. quantum/core/features/data_fetching/src/parser.py +225 -0
  45. quantum/core/features/data_import/src/__init__.py +0 -0
  46. quantum/core/features/data_import/src/ast_node.py +291 -0
  47. quantum/core/features/data_import/src/runtime.py +538 -0
  48. quantum/core/features/dump/src/__init__.py +12 -0
  49. quantum/core/features/dump/src/ast_node.py +106 -0
  50. quantum/core/features/dump/src/parser.py +61 -0
  51. quantum/core/features/dump/src/runtime.py +246 -0
  52. quantum/core/features/functions/src/__init__.py +8 -0
  53. quantum/core/features/functions/src/ast_node.py +149 -0
  54. quantum/core/features/game_engine_2d/src/__init__.py +19 -0
  55. quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
  56. quantum/core/features/game_engine_2d/src/parser.py +983 -0
  57. quantum/core/features/invocation/src/__init__.py +0 -0
  58. quantum/core/features/invocation/src/ast_node.py +146 -0
  59. quantum/core/features/invocation/src/runtime.py +327 -0
  60. quantum/core/features/knowledge_base/src/__init__.py +6 -0
  61. quantum/core/features/knowledge_base/src/ast_node.py +113 -0
  62. quantum/core/features/knowledge_base/src/parser.py +82 -0
  63. quantum/core/features/logging/src/__init__.py +12 -0
  64. quantum/core/features/logging/src/ast_node.py +111 -0
  65. quantum/core/features/logging/src/parser.py +50 -0
  66. quantum/core/features/logging/src/runtime.py +190 -0
  67. quantum/core/features/loops/src/__init__.py +8 -0
  68. quantum/core/features/loops/src/ast_node.py +60 -0
  69. quantum/core/features/query/src/__init__.py +0 -0
  70. quantum/core/features/query/src/database_service.py +322 -0
  71. quantum/core/features/query/src/query_validators.py +20 -0
  72. quantum/core/features/state_management/src/__init__.py +11 -0
  73. quantum/core/features/state_management/src/ast_node.py +228 -0
  74. quantum/core/features/terminal_engine/src/__init__.py +21 -0
  75. quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
  76. quantum/core/features/terminal_engine/src/parser.py +361 -0
  77. quantum/core/features/testing_engine/src/__init__.py +41 -0
  78. quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
  79. quantum/core/features/testing_engine/src/parser.py +604 -0
  80. quantum/core/features/theming/src/__init__.py +48 -0
  81. quantum/core/features/theming/src/ast_node.py +137 -0
  82. quantum/core/features/theming/src/presets.py +405 -0
  83. quantum/core/features/ui_engine/src/__init__.py +20 -0
  84. quantum/core/features/ui_engine/src/ast_nodes.py +1854 -0
  85. quantum/core/features/ui_engine/src/parser.py +1106 -0
  86. quantum/core/features/websocket/src/__init__.py +24 -0
  87. quantum/core/features/websocket/src/ast_node.py +247 -0
  88. quantum/core/html_compat.py +299 -0
  89. quantum/core/parser.py +1235 -0
  90. quantum/core/parser_registry.py +213 -0
  91. quantum/core/parsers/__init__.py +76 -0
  92. quantum/core/parsers/ai/__init__.py +12 -0
  93. quantum/core/parsers/ai/agent_parser.py +106 -0
  94. quantum/core/parsers/ai/knowledge_parser.py +86 -0
  95. quantum/core/parsers/ai/llm_parser.py +78 -0
  96. quantum/core/parsers/ai/team_parser.py +88 -0
  97. quantum/core/parsers/base.py +322 -0
  98. quantum/core/parsers/composition/__init__.py +10 -0
  99. quantum/core/parsers/composition/import_parser.py +50 -0
  100. quantum/core/parsers/composition/slot_parser.py +48 -0
  101. quantum/core/parsers/control_flow/__init__.py +11 -0
  102. quantum/core/parsers/control_flow/if_parser.py +68 -0
  103. quantum/core/parsers/control_flow/loop_parser.py +105 -0
  104. quantum/core/parsers/control_flow/set_parser.py +89 -0
  105. quantum/core/parsers/data/__init__.py +12 -0
  106. quantum/core/parsers/data/data_parser.py +217 -0
  107. quantum/core/parsers/data/invoke_parser.py +116 -0
  108. quantum/core/parsers/data/query_parser.py +188 -0
  109. quantum/core/parsers/data/transaction_parser.py +77 -0
  110. quantum/core/parsers/events/__init__.py +9 -0
  111. quantum/core/parsers/events/dispatch_event_parser.py +44 -0
  112. quantum/core/parsers/forms/__init__.py +11 -0
  113. quantum/core/parsers/forms/action_parser.py +54 -0
  114. quantum/core/parsers/forms/flash_parser.py +34 -0
  115. quantum/core/parsers/forms/redirect_parser.py +33 -0
  116. quantum/core/parsers/functions/__init__.py +11 -0
  117. quantum/core/parsers/functions/function_parser.py +137 -0
  118. quantum/core/parsers/functions/param_parser.py +66 -0
  119. quantum/core/parsers/functions/return_parser.py +31 -0
  120. quantum/core/parsers/html/__init__.py +10 -0
  121. quantum/core/parsers/html/component_call_parser.py +130 -0
  122. quantum/core/parsers/html/html_parser.py +115 -0
  123. quantum/core/parsers/jobs/__init__.py +11 -0
  124. quantum/core/parsers/jobs/job_parser.py +71 -0
  125. quantum/core/parsers/jobs/schedule_parser.py +62 -0
  126. quantum/core/parsers/jobs/thread_parser.py +57 -0
  127. quantum/core/parsers/messaging/__init__.py +17 -0
  128. quantum/core/parsers/messaging/message_ack_parser.py +30 -0
  129. quantum/core/parsers/messaging/message_nack_parser.py +30 -0
  130. quantum/core/parsers/messaging/message_parser.py +114 -0
  131. quantum/core/parsers/messaging/queue_parser.py +61 -0
  132. quantum/core/parsers/messaging/websocket_parser.py +121 -0
  133. quantum/core/parsers/persistence/__init__.py +9 -0
  134. quantum/core/parsers/persistence/persist_parser.py +64 -0
  135. quantum/core/parsers/routing/__init__.py +9 -0
  136. quantum/core/parsers/routing/route_parser.py +41 -0
  137. quantum/core/parsers/scripting/__init__.py +12 -0
  138. quantum/core/parsers/scripting/pyclass_parser.py +62 -0
  139. quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
  140. quantum/core/parsers/scripting/pyimport_parser.py +52 -0
  141. quantum/core/parsers/scripting/python_parser.py +49 -0
  142. quantum/core/parsers/services/__init__.py +12 -0
  143. quantum/core/parsers/services/dump_parser.py +52 -0
  144. quantum/core/parsers/services/file_parser.py +48 -0
  145. quantum/core/parsers/services/log_parser.py +46 -0
  146. quantum/core/parsers/services/mail_parser.py +65 -0
  147. quantum/core/tiers.py +82 -0
  148. quantum/packages/__init__.py +28 -0
  149. quantum/packages/manager.py +413 -0
  150. quantum/packages/manifest.py +351 -0
  151. quantum/packages/registry.py +399 -0
  152. quantum/packages/resolver.py +336 -0
  153. quantum/plugins/__init__.py +33 -0
  154. quantum/plugins/hooks.py +329 -0
  155. quantum/plugins/loader.py +479 -0
  156. quantum/plugins/manifest.py +336 -0
  157. quantum/plugins/registry.py +371 -0
  158. quantum/runtime/__init__.py +28 -0
  159. quantum/runtime/action_handler.py +443 -0
  160. quantum/runtime/adapters/__init__.py +88 -0
  161. quantum/runtime/adapters/memory_adapter.py +690 -0
  162. quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
  163. quantum/runtime/adapters/redis_adapter.py +582 -0
  164. quantum/runtime/adapters/sqlite_adapter.py +414 -0
  165. quantum/runtime/agent_service.py +1133 -0
  166. quantum/runtime/api_server.py +86 -0
  167. quantum/runtime/ast_cache.py +506 -0
  168. quantum/runtime/auth_service.py +267 -0
  169. quantum/runtime/component.py +990 -0
  170. quantum/runtime/component_composer.py +319 -0
  171. quantum/runtime/component_resolver.py +174 -0
  172. quantum/runtime/database_service.py +598 -0
  173. quantum/runtime/email_service.py +162 -0
  174. quantum/runtime/error_handler.py +295 -0
  175. quantum/runtime/execution_context.py +286 -0
  176. quantum/runtime/executor_registry.py +171 -0
  177. quantum/runtime/executors/__init__.py +71 -0
  178. quantum/runtime/executors/ai/__init__.py +12 -0
  179. quantum/runtime/executors/ai/agent_executor.py +217 -0
  180. quantum/runtime/executors/ai/knowledge_executor.py +114 -0
  181. quantum/runtime/executors/ai/llm_executor.py +153 -0
  182. quantum/runtime/executors/ai/team_executor.py +171 -0
  183. quantum/runtime/executors/base.py +262 -0
  184. quantum/runtime/executors/control_flow/__init__.py +11 -0
  185. quantum/runtime/executors/control_flow/if_executor.py +93 -0
  186. quantum/runtime/executors/control_flow/loop_executor.py +307 -0
  187. quantum/runtime/executors/control_flow/set_executor.py +412 -0
  188. quantum/runtime/executors/data/__init__.py +12 -0
  189. quantum/runtime/executors/data/data_executor.py +145 -0
  190. quantum/runtime/executors/data/invoke_executor.py +176 -0
  191. quantum/runtime/executors/data/query_executor.py +256 -0
  192. quantum/runtime/executors/data/transaction_executor.py +91 -0
  193. quantum/runtime/executors/jobs/__init__.py +11 -0
  194. quantum/runtime/executors/jobs/job_executor.py +190 -0
  195. quantum/runtime/executors/jobs/schedule_executor.py +132 -0
  196. quantum/runtime/executors/jobs/thread_executor.py +127 -0
  197. quantum/runtime/executors/messaging/__init__.py +17 -0
  198. quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
  199. quantum/runtime/executors/messaging/message_executor.py +174 -0
  200. quantum/runtime/executors/messaging/queue_executor.py +103 -0
  201. quantum/runtime/executors/messaging/websocket_executor.py +197 -0
  202. quantum/runtime/executors/scripting/__init__.py +11 -0
  203. quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
  204. quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
  205. quantum/runtime/executors/scripting/python_executor.py +249 -0
  206. quantum/runtime/executors/services/__init__.py +12 -0
  207. quantum/runtime/executors/services/dump_executor.py +72 -0
  208. quantum/runtime/executors/services/file_executor.py +89 -0
  209. quantum/runtime/executors/services/log_executor.py +77 -0
  210. quantum/runtime/executors/services/mail_executor.py +81 -0
  211. quantum/runtime/expression_cache.py +498 -0
  212. quantum/runtime/file_upload_service.py +326 -0
  213. quantum/runtime/function_registry.py +118 -0
  214. quantum/runtime/game_builder.py +166 -0
  215. quantum/runtime/game_code_generator.py +2371 -0
  216. quantum/runtime/game_templates.py +2006 -0
  217. quantum/runtime/godot_code_generator.py +4681 -0
  218. quantum/runtime/godot_templates.py +1449 -0
  219. quantum/runtime/job_executor.py +1599 -0
  220. quantum/runtime/knowledge_service.py +500 -0
  221. quantum/runtime/llm_cache.py +100 -0
  222. quantum/runtime/llm_providers.py +704 -0
  223. quantum/runtime/llm_service.py +287 -0
  224. quantum/runtime/logging_setup.py +140 -0
  225. quantum/runtime/message_broker.py +364 -0
  226. quantum/runtime/message_queue_service.py +571 -0
  227. quantum/runtime/param_validation.py +184 -0
  228. quantum/runtime/pypy_compat.py +315 -0
  229. quantum/runtime/python_bridge.py +698 -0
  230. quantum/runtime/query_validators.py +304 -0
  231. quantum/runtime/renderer.py +733 -0
  232. quantum/runtime/service_container.py +444 -0
  233. quantum/runtime/terminal_builder.py +76 -0
  234. quantum/runtime/terminal_code_generator.py +607 -0
  235. quantum/runtime/terminal_templates.py +243 -0
  236. quantum/runtime/testing_builder.py +77 -0
  237. quantum/runtime/testing_code_generator.py +833 -0
  238. quantum/runtime/testing_templates.py +85 -0
  239. quantum/runtime/ui_builder.py +188 -0
  240. quantum/runtime/ui_desktop_adapter.py +1730 -0
  241. quantum/runtime/ui_desktop_templates.py +307 -0
  242. quantum/runtime/ui_html_adapter.py +2691 -0
  243. quantum/runtime/ui_html_templates.py +2297 -0
  244. quantum/runtime/ui_mobile_adapter.py +1832 -0
  245. quantum/runtime/ui_mobile_templates.py +1003 -0
  246. quantum/runtime/ui_textual_adapter.py +1866 -0
  247. quantum/runtime/ui_textual_templates.py +45 -0
  248. quantum/runtime/ui_tokens.py +465 -0
  249. quantum/runtime/ui_validator.py +365 -0
  250. quantum/runtime/validators.py +256 -0
  251. quantum/runtime/web_server.py +1766 -0
  252. quantum/runtime/websocket_adapter.py +501 -0
  253. quantum/runtime/websocket_service.py +585 -0
  254. quantum/runtime/websocket_transport.py +289 -0
  255. quantum/runtime/wsgi.py +101 -0
  256. quantum/utils/__init__.py +1 -0
  257. quantum_framework-0.9.0.dist-info/METADATA +244 -0
  258. quantum_framework-0.9.0.dist-info/RECORD +262 -0
  259. quantum_framework-0.9.0.dist-info/WHEEL +5 -0
  260. quantum_framework-0.9.0.dist-info/entry_points.txt +2 -0
  261. quantum_framework-0.9.0.dist-info/licenses/LICENSE +21 -0
  262. 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']