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,322 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Quantum Database Service - Manages database connections and query execution
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
import requests
|
|
7
|
+
from typing import Dict, Any, List, Optional
|
|
8
|
+
from dataclasses import dataclass, asdict
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class QueryResult:
|
|
13
|
+
"""Container for query results and metadata"""
|
|
14
|
+
data: List[Dict[str, Any]]
|
|
15
|
+
column_list: List[str]
|
|
16
|
+
execution_time: float # in milliseconds
|
|
17
|
+
record_count: int
|
|
18
|
+
sql: Optional[str] = None # Set in debug mode
|
|
19
|
+
page: Optional[int] = None
|
|
20
|
+
total_pages: Optional[int] = None
|
|
21
|
+
has_more: bool = False
|
|
22
|
+
cached: bool = False
|
|
23
|
+
|
|
24
|
+
def to_dict(self):
|
|
25
|
+
"""Convert to dictionary for template access"""
|
|
26
|
+
result = asdict(self)
|
|
27
|
+
# Rename column_list to columnList for JavaScript compatibility
|
|
28
|
+
result['columnList'] = result.pop('column_list')
|
|
29
|
+
result['recordCount'] = result.pop('record_count')
|
|
30
|
+
result['executionTime'] = result.pop('execution_time')
|
|
31
|
+
result['totalPages'] = result.pop('total_pages')
|
|
32
|
+
result['hasMore'] = result.pop('has_more')
|
|
33
|
+
return result
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class DatabaseConnectionError(Exception):
|
|
37
|
+
"""Raised when database connection fails"""
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class QueryExecutionError(Exception):
|
|
42
|
+
"""Raised when query execution fails"""
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class DatabaseService:
|
|
47
|
+
"""Manages database connections and query execution"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, admin_api_url: str = "http://localhost:8000"):
|
|
50
|
+
self.admin_api_url = admin_api_url
|
|
51
|
+
self.connection_pool: Dict[str, Any] = {} # Cache connections by datasource name
|
|
52
|
+
|
|
53
|
+
def get_datasource_config(self, datasource_name: str) -> Dict[str, Any]:
|
|
54
|
+
"""
|
|
55
|
+
Fetch datasource configuration from Quantum Admin API
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
datasource_name: Name of the datasource
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Dict with datasource configuration
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
DatabaseConnectionError: If datasource not found or not ready
|
|
65
|
+
"""
|
|
66
|
+
try:
|
|
67
|
+
url = f"{self.admin_api_url}/api/datasources/by-name/{datasource_name}"
|
|
68
|
+
response = requests.get(url, timeout=5)
|
|
69
|
+
|
|
70
|
+
if response.status_code == 404:
|
|
71
|
+
raise DatabaseConnectionError(f"Datasource '{datasource_name}' not found")
|
|
72
|
+
elif response.status_code == 503:
|
|
73
|
+
raise DatabaseConnectionError(
|
|
74
|
+
f"Datasource '{datasource_name}' is not ready. Please check Quantum Admin."
|
|
75
|
+
)
|
|
76
|
+
elif response.status_code != 200:
|
|
77
|
+
raise DatabaseConnectionError(
|
|
78
|
+
f"Failed to fetch datasource '{datasource_name}': {response.status_code}"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return response.json()
|
|
82
|
+
|
|
83
|
+
except requests.exceptions.ConnectionError:
|
|
84
|
+
raise DatabaseConnectionError(
|
|
85
|
+
f"Cannot connect to Quantum Admin API at {self.admin_api_url}. "
|
|
86
|
+
"Please ensure Quantum Admin is running."
|
|
87
|
+
)
|
|
88
|
+
except requests.exceptions.Timeout:
|
|
89
|
+
raise DatabaseConnectionError(
|
|
90
|
+
f"Timeout connecting to Quantum Admin API at {self.admin_api_url}"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def get_connection(self, datasource_config: Dict[str, Any]):
|
|
94
|
+
"""
|
|
95
|
+
Get or create database connection
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
datasource_config: Datasource configuration from Admin API
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Database connection object
|
|
102
|
+
|
|
103
|
+
Raises:
|
|
104
|
+
DatabaseConnectionError: If connection fails
|
|
105
|
+
"""
|
|
106
|
+
datasource_name = datasource_config['name']
|
|
107
|
+
db_type = datasource_config['type']
|
|
108
|
+
|
|
109
|
+
# Check if connection already exists in pool
|
|
110
|
+
if datasource_name in self.connection_pool:
|
|
111
|
+
conn = self.connection_pool[datasource_name]
|
|
112
|
+
# Test connection is still alive
|
|
113
|
+
if self._test_connection(conn, db_type):
|
|
114
|
+
return conn
|
|
115
|
+
else:
|
|
116
|
+
# Connection dead, remove from pool
|
|
117
|
+
del self.connection_pool[datasource_name]
|
|
118
|
+
|
|
119
|
+
# Create new connection
|
|
120
|
+
try:
|
|
121
|
+
if db_type == 'postgresql':
|
|
122
|
+
conn = self._create_postgres_connection(datasource_config)
|
|
123
|
+
elif db_type in ['mysql', 'mariadb']:
|
|
124
|
+
conn = self._create_mysql_connection(datasource_config)
|
|
125
|
+
else:
|
|
126
|
+
raise DatabaseConnectionError(f"Unsupported database type: {db_type}")
|
|
127
|
+
|
|
128
|
+
# Store in pool
|
|
129
|
+
self.connection_pool[datasource_name] = conn
|
|
130
|
+
return conn
|
|
131
|
+
|
|
132
|
+
except Exception as e:
|
|
133
|
+
raise DatabaseConnectionError(f"Failed to connect to database: {e}")
|
|
134
|
+
|
|
135
|
+
def _create_postgres_connection(self, config: Dict[str, Any]):
|
|
136
|
+
"""Create PostgreSQL connection"""
|
|
137
|
+
try:
|
|
138
|
+
import psycopg2
|
|
139
|
+
return psycopg2.connect(
|
|
140
|
+
host=config['host'],
|
|
141
|
+
port=config['port'],
|
|
142
|
+
database=config['database_name'],
|
|
143
|
+
user=config['username'],
|
|
144
|
+
password=config['password']
|
|
145
|
+
)
|
|
146
|
+
except ImportError:
|
|
147
|
+
raise DatabaseConnectionError(
|
|
148
|
+
"psycopg2 not installed. Install with: pip install psycopg2-binary"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def _create_mysql_connection(self, config: Dict[str, Any]):
|
|
152
|
+
"""Create MySQL/MariaDB connection"""
|
|
153
|
+
try:
|
|
154
|
+
import pymysql
|
|
155
|
+
return pymysql.connect(
|
|
156
|
+
host=config['host'],
|
|
157
|
+
port=config['port'],
|
|
158
|
+
database=config['database_name'],
|
|
159
|
+
user=config['username'],
|
|
160
|
+
password=config['password'],
|
|
161
|
+
cursorclass=pymysql.cursors.DictCursor # Return results as dictionaries
|
|
162
|
+
)
|
|
163
|
+
except ImportError:
|
|
164
|
+
raise DatabaseConnectionError(
|
|
165
|
+
"pymysql not installed. Install with: pip install pymysql"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def _test_connection(self, conn, db_type: str) -> bool:
|
|
169
|
+
"""Test if connection is still alive"""
|
|
170
|
+
try:
|
|
171
|
+
cursor = conn.cursor()
|
|
172
|
+
if db_type == 'postgresql':
|
|
173
|
+
cursor.execute("SELECT 1")
|
|
174
|
+
elif db_type in ['mysql', 'mariadb']:
|
|
175
|
+
cursor.execute("SELECT 1")
|
|
176
|
+
cursor.close()
|
|
177
|
+
return True
|
|
178
|
+
except:
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
def execute_query(self, datasource_name: str, sql: str, params: Dict[str, Any] = None) -> QueryResult:
|
|
182
|
+
"""
|
|
183
|
+
Execute SQL with parameter binding
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
datasource_name: Name of the datasource
|
|
187
|
+
sql: SQL query with :param placeholders
|
|
188
|
+
params: Dictionary of parameter values
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
QueryResult with data and metadata
|
|
192
|
+
|
|
193
|
+
Raises:
|
|
194
|
+
QueryExecutionError: If query execution fails
|
|
195
|
+
"""
|
|
196
|
+
if params is None:
|
|
197
|
+
params = {}
|
|
198
|
+
|
|
199
|
+
# Get datasource config
|
|
200
|
+
config = self.get_datasource_config(datasource_name)
|
|
201
|
+
db_type = config['type']
|
|
202
|
+
|
|
203
|
+
# Get connection
|
|
204
|
+
conn = self.get_connection(config)
|
|
205
|
+
|
|
206
|
+
# Start timing
|
|
207
|
+
start_time = time.time()
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
cursor = conn.cursor()
|
|
211
|
+
|
|
212
|
+
# Convert :param syntax to database-specific placeholder
|
|
213
|
+
prepared_sql, prepared_params = self._prepare_query(sql, params, db_type)
|
|
214
|
+
|
|
215
|
+
# Execute query
|
|
216
|
+
cursor.execute(prepared_sql, prepared_params)
|
|
217
|
+
|
|
218
|
+
# Check if it's a SELECT query (returns data)
|
|
219
|
+
if cursor.description:
|
|
220
|
+
# Fetch results
|
|
221
|
+
rows = cursor.fetchall()
|
|
222
|
+
|
|
223
|
+
# Get column names
|
|
224
|
+
if db_type == 'postgresql':
|
|
225
|
+
column_names = [desc[0] for desc in cursor.description]
|
|
226
|
+
# Convert rows to dictionaries for PostgreSQL
|
|
227
|
+
data = [dict(zip(column_names, row)) for row in rows]
|
|
228
|
+
elif db_type in ['mysql', 'mariadb']:
|
|
229
|
+
# pymysql with DictCursor already returns dictionaries
|
|
230
|
+
column_names = list(rows[0].keys()) if rows else []
|
|
231
|
+
data = rows
|
|
232
|
+
else:
|
|
233
|
+
column_names = []
|
|
234
|
+
data = []
|
|
235
|
+
|
|
236
|
+
record_count = len(data)
|
|
237
|
+
else:
|
|
238
|
+
# Non-SELECT query (INSERT, UPDATE, DELETE)
|
|
239
|
+
conn.commit()
|
|
240
|
+
data = []
|
|
241
|
+
column_names = []
|
|
242
|
+
record_count = cursor.rowcount
|
|
243
|
+
|
|
244
|
+
# For INSERT with RETURNING (PostgreSQL)
|
|
245
|
+
if db_type == 'postgresql' and cursor.description:
|
|
246
|
+
rows = cursor.fetchall()
|
|
247
|
+
column_names = [desc[0] for desc in cursor.description]
|
|
248
|
+
data = [dict(zip(column_names, row)) for row in rows]
|
|
249
|
+
record_count = len(data)
|
|
250
|
+
|
|
251
|
+
cursor.close()
|
|
252
|
+
|
|
253
|
+
# Calculate execution time
|
|
254
|
+
execution_time = (time.time() - start_time) * 1000 # Convert to ms
|
|
255
|
+
|
|
256
|
+
return QueryResult(
|
|
257
|
+
data=data,
|
|
258
|
+
column_list=column_names,
|
|
259
|
+
execution_time=execution_time,
|
|
260
|
+
record_count=record_count
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
except Exception as e:
|
|
264
|
+
# Rollback on error
|
|
265
|
+
conn.rollback()
|
|
266
|
+
raise QueryExecutionError(f"Query execution failed: {e}")
|
|
267
|
+
|
|
268
|
+
def _prepare_query(self, sql: str, params: Dict[str, Any], db_type: str) -> tuple:
|
|
269
|
+
"""
|
|
270
|
+
Convert :param syntax to database-specific placeholders
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
sql: SQL with :param placeholders
|
|
274
|
+
params: Dictionary of parameters
|
|
275
|
+
db_type: Database type (postgresql, mysql, mariadb)
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
Tuple of (prepared_sql, prepared_params)
|
|
279
|
+
"""
|
|
280
|
+
import re
|
|
281
|
+
|
|
282
|
+
# Find all :param placeholders
|
|
283
|
+
param_pattern = r':(\w+)'
|
|
284
|
+
param_names = re.findall(param_pattern, sql)
|
|
285
|
+
|
|
286
|
+
if db_type == 'postgresql':
|
|
287
|
+
# PostgreSQL uses %s placeholders
|
|
288
|
+
prepared_sql = sql
|
|
289
|
+
for i, param_name in enumerate(param_names, 1):
|
|
290
|
+
# Replace :param with %s
|
|
291
|
+
prepared_sql = prepared_sql.replace(f':{param_name}', '%s', 1)
|
|
292
|
+
|
|
293
|
+
# Build parameter list in correct order
|
|
294
|
+
prepared_params = [params.get(name) for name in param_names]
|
|
295
|
+
|
|
296
|
+
elif db_type in ['mysql', 'mariadb']:
|
|
297
|
+
# MySQL uses %s placeholders
|
|
298
|
+
prepared_sql = sql
|
|
299
|
+
for param_name in param_names:
|
|
300
|
+
prepared_sql = prepared_sql.replace(f':{param_name}', '%s', 1)
|
|
301
|
+
|
|
302
|
+
# Build parameter list in correct order
|
|
303
|
+
prepared_params = [params.get(name) for name in param_names]
|
|
304
|
+
|
|
305
|
+
else:
|
|
306
|
+
raise QueryExecutionError(f"Unsupported database type: {db_type}")
|
|
307
|
+
|
|
308
|
+
return prepared_sql, prepared_params
|
|
309
|
+
|
|
310
|
+
def close_connection(self, datasource_name: str):
|
|
311
|
+
"""Close database connection"""
|
|
312
|
+
if datasource_name in self.connection_pool:
|
|
313
|
+
try:
|
|
314
|
+
self.connection_pool[datasource_name].close()
|
|
315
|
+
except:
|
|
316
|
+
pass
|
|
317
|
+
del self.connection_pool[datasource_name]
|
|
318
|
+
|
|
319
|
+
def close_all_connections(self):
|
|
320
|
+
"""Close all database connections"""
|
|
321
|
+
for datasource_name in list(self.connection_pool.keys()):
|
|
322
|
+
self.close_connection(datasource_name)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Quantum Query Validators — re-export of the canonical implementation.
|
|
3
|
+
|
|
4
|
+
This module used to hold a full 290-line COPY of QueryValidator. Nothing
|
|
5
|
+
imported it, so it quietly drifted out of step with the module the runtime
|
|
6
|
+
actually uses (quantum/runtime/query_validators.py) — and by the time the
|
|
7
|
+
audit looked, the two copies of a SECURITY validator disagreed with each
|
|
8
|
+
other. A stale duplicate of a validator is worse than no duplicate: whoever
|
|
9
|
+
reads this one believes they are reading what runs.
|
|
10
|
+
|
|
11
|
+
It re-exports instead of copying, so the two cannot diverge again. Import
|
|
12
|
+
from quantum.runtime.query_validators directly in new code.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from quantum.runtime.query_validators import ( # noqa: F401
|
|
16
|
+
QueryValidationError,
|
|
17
|
+
QueryValidator,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = ["QueryValidationError", "QueryValidator"]
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AST Node for State Management (q:set)
|
|
3
|
+
|
|
4
|
+
This module contains the SetNode class, which represents the <q:set> tag
|
|
5
|
+
for state management in Quantum Language.
|
|
6
|
+
|
|
7
|
+
State Persistence Support:
|
|
8
|
+
- persist="local" - localStorage (browser) or file (desktop)
|
|
9
|
+
- persist="session" - sessionStorage (browser) or memory (desktop)
|
|
10
|
+
- persist="sync" - synchronized across tabs/windows (browser) or network (desktop)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import Dict, Any, List, Optional
|
|
14
|
+
|
|
15
|
+
# Import base class from parent AST nodes
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from quantum.core.ast_nodes import QuantumNode
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SetNode(QuantumNode):
|
|
22
|
+
"""Represents a <q:set> - State Management"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, name: str):
|
|
25
|
+
# Obrigatórios
|
|
26
|
+
self.name = name
|
|
27
|
+
|
|
28
|
+
# Tipo e valor
|
|
29
|
+
self.type = "string"
|
|
30
|
+
self.value = None
|
|
31
|
+
self.default = None
|
|
32
|
+
|
|
33
|
+
# Validação
|
|
34
|
+
self.required = False
|
|
35
|
+
self.nullable = True
|
|
36
|
+
self.validate_rule = None # Renomeado de 'validate' para não conflitar com o método
|
|
37
|
+
self.pattern = None
|
|
38
|
+
self.mask = None
|
|
39
|
+
self.range = None
|
|
40
|
+
self.enum = None
|
|
41
|
+
self.unique = None
|
|
42
|
+
self.min = None
|
|
43
|
+
self.max = None
|
|
44
|
+
self.minlength = None
|
|
45
|
+
self.maxlength = None
|
|
46
|
+
|
|
47
|
+
# Comportamento
|
|
48
|
+
self.scope = "local"
|
|
49
|
+
self.operation = "assign"
|
|
50
|
+
self.step = 1
|
|
51
|
+
|
|
52
|
+
# Para operações em collections
|
|
53
|
+
self.index = None
|
|
54
|
+
self.key = None
|
|
55
|
+
self.source = None
|
|
56
|
+
|
|
57
|
+
# State Persistence
|
|
58
|
+
self.persist: Optional[str] = None # None, "local", "session", "sync"
|
|
59
|
+
self.persist_key: Optional[str] = None # Custom storage key (defaults to name)
|
|
60
|
+
self.persist_encrypt: bool = False # Encrypt persisted data
|
|
61
|
+
self.persist_ttl: Optional[int] = None # TTL in seconds for cached persistence
|
|
62
|
+
|
|
63
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
64
|
+
result = {
|
|
65
|
+
"type": "set",
|
|
66
|
+
"name": self.name,
|
|
67
|
+
"value_type": self.type,
|
|
68
|
+
"value": self.value,
|
|
69
|
+
"default": self.default,
|
|
70
|
+
"scope": self.scope,
|
|
71
|
+
"operation": self.operation,
|
|
72
|
+
"required": self.required,
|
|
73
|
+
"nullable": self.nullable,
|
|
74
|
+
"validation": {
|
|
75
|
+
"validate": self.validate_rule,
|
|
76
|
+
"pattern": self.pattern,
|
|
77
|
+
"mask": self.mask,
|
|
78
|
+
"range": self.range,
|
|
79
|
+
"enum": self.enum,
|
|
80
|
+
"unique": self.unique,
|
|
81
|
+
"min": self.min,
|
|
82
|
+
"max": self.max,
|
|
83
|
+
"minlength": self.minlength,
|
|
84
|
+
"maxlength": self.maxlength
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
# Add persistence info if enabled
|
|
88
|
+
if self.persist:
|
|
89
|
+
result["persistence"] = {
|
|
90
|
+
"scope": self.persist,
|
|
91
|
+
"key": self.persist_key or self.name,
|
|
92
|
+
"encrypt": self.persist_encrypt,
|
|
93
|
+
"ttl": self.persist_ttl
|
|
94
|
+
}
|
|
95
|
+
return result
|
|
96
|
+
|
|
97
|
+
def validate(self) -> List[str]:
|
|
98
|
+
errors = []
|
|
99
|
+
|
|
100
|
+
if not self.name:
|
|
101
|
+
errors.append("Set variable name is required")
|
|
102
|
+
|
|
103
|
+
# Validar tipo
|
|
104
|
+
# 'integer' was missing here even though SetExecutor._convert_to_type()
|
|
105
|
+
# already handles it (target_type in ("integer", "number")), and other
|
|
106
|
+
# AST nodes like AgentToolParamNode accept it — this rejected any
|
|
107
|
+
# q:set/q:param using type="integer" (test-query-loop.q,
|
|
108
|
+
# test-query-params.q, and every qg: sprite param in game_engine_2d).
|
|
109
|
+
valid_types = ['string', 'number', 'integer', 'decimal', 'boolean', 'date', 'datetime',
|
|
110
|
+
'array', 'object', 'json', 'binary', 'null']
|
|
111
|
+
if self.type not in valid_types:
|
|
112
|
+
errors.append(f"Invalid type: {self.type}. Must be one of {valid_types}")
|
|
113
|
+
|
|
114
|
+
# Validar operação
|
|
115
|
+
valid_operations = ['assign', 'increment', 'decrement', 'add', 'multiply',
|
|
116
|
+
'append', 'prepend', 'remove', 'removeAt', 'clear', 'sort',
|
|
117
|
+
'reverse', 'unique', 'merge', 'setProperty', 'deleteProperty',
|
|
118
|
+
'clone', 'uppercase', 'lowercase', 'trim', 'format']
|
|
119
|
+
if self.operation not in valid_operations:
|
|
120
|
+
errors.append(f"Invalid operation: {self.operation}")
|
|
121
|
+
|
|
122
|
+
# Validar valor obrigatório para assign
|
|
123
|
+
if self.operation == "assign" and not self.value and not self.default and self.required:
|
|
124
|
+
errors.append("Set value or default is required when required=true")
|
|
125
|
+
|
|
126
|
+
# Validar enum
|
|
127
|
+
if self.enum and self.value:
|
|
128
|
+
enum_values = [v.strip() for v in self.enum.split(',')]
|
|
129
|
+
if self.value not in enum_values:
|
|
130
|
+
errors.append(f"Value '{self.value}' not in enum: {enum_values}")
|
|
131
|
+
|
|
132
|
+
# Validar minlength/maxlength
|
|
133
|
+
if self.type == "string" and self.value:
|
|
134
|
+
if self.minlength and len(str(self.value)) < int(self.minlength):
|
|
135
|
+
errors.append(f"Value length {len(str(self.value))} is less than minlength {self.minlength}")
|
|
136
|
+
if self.maxlength and len(str(self.value)) > int(self.maxlength):
|
|
137
|
+
errors.append(f"Value length {len(str(self.value))} exceeds maxlength {self.maxlength}")
|
|
138
|
+
|
|
139
|
+
# Validar persist scope
|
|
140
|
+
if self.persist:
|
|
141
|
+
valid_persist_scopes = ['local', 'session', 'sync']
|
|
142
|
+
if self.persist not in valid_persist_scopes:
|
|
143
|
+
errors.append(f"Invalid persist scope: {self.persist}. Must be one of {valid_persist_scopes}")
|
|
144
|
+
|
|
145
|
+
return errors
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class PersistNode(QuantumNode):
|
|
149
|
+
"""
|
|
150
|
+
Represents a <q:persist> - Explicit persistence configuration.
|
|
151
|
+
|
|
152
|
+
Allows fine-grained control over state persistence beyond the persist attribute on q:set.
|
|
153
|
+
|
|
154
|
+
Examples:
|
|
155
|
+
<q:persist scope="local" prefix="myapp_">
|
|
156
|
+
<q:var name="theme" />
|
|
157
|
+
<q:var name="locale" />
|
|
158
|
+
</q:persist>
|
|
159
|
+
|
|
160
|
+
<q:persist scope="sync" key="user_preferences" encrypt="true">
|
|
161
|
+
<q:var name="darkMode" />
|
|
162
|
+
<q:var name="fontSize" />
|
|
163
|
+
</q:persist>
|
|
164
|
+
|
|
165
|
+
Attributes:
|
|
166
|
+
scope: "local" (localStorage), "session" (sessionStorage), "sync" (cross-tab/network)
|
|
167
|
+
prefix: Optional prefix for all storage keys
|
|
168
|
+
key: If set, all vars are stored under this single key as an object
|
|
169
|
+
encrypt: Whether to encrypt persisted data
|
|
170
|
+
ttl: Time-to-live in seconds (for cache invalidation)
|
|
171
|
+
storage: Override storage backend ("localStorage", "indexedDB", "file", "db")
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
def __init__(
|
|
175
|
+
self,
|
|
176
|
+
scope: str = "local",
|
|
177
|
+
prefix: Optional[str] = None,
|
|
178
|
+
key: Optional[str] = None,
|
|
179
|
+
encrypt: bool = False,
|
|
180
|
+
ttl: Optional[int] = None,
|
|
181
|
+
storage: Optional[str] = None
|
|
182
|
+
):
|
|
183
|
+
self.scope = scope
|
|
184
|
+
self.prefix = prefix
|
|
185
|
+
self.key = key
|
|
186
|
+
self.encrypt = encrypt
|
|
187
|
+
self.ttl = ttl
|
|
188
|
+
self.storage = storage
|
|
189
|
+
self.variables: List[str] = [] # List of variable names to persist
|
|
190
|
+
|
|
191
|
+
def add_variable(self, name: str):
|
|
192
|
+
"""Add a variable name to persist."""
|
|
193
|
+
self.variables.append(name)
|
|
194
|
+
|
|
195
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
196
|
+
return {
|
|
197
|
+
"type": "persist",
|
|
198
|
+
"scope": self.scope,
|
|
199
|
+
"prefix": self.prefix,
|
|
200
|
+
"key": self.key,
|
|
201
|
+
"encrypt": self.encrypt,
|
|
202
|
+
"ttl": self.ttl,
|
|
203
|
+
"storage": self.storage,
|
|
204
|
+
"variables": self.variables
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
def validate(self) -> List[str]:
|
|
208
|
+
errors = []
|
|
209
|
+
|
|
210
|
+
# Validate scope
|
|
211
|
+
valid_scopes = ['local', 'session', 'sync']
|
|
212
|
+
if self.scope not in valid_scopes:
|
|
213
|
+
errors.append(f"Invalid persist scope: {self.scope}. Must be one of {valid_scopes}")
|
|
214
|
+
|
|
215
|
+
# Validate storage backend if specified
|
|
216
|
+
if self.storage:
|
|
217
|
+
valid_storage = ['localStorage', 'sessionStorage', 'indexedDB', 'file', 'db']
|
|
218
|
+
if self.storage not in valid_storage:
|
|
219
|
+
errors.append(f"Invalid storage backend: {self.storage}. Must be one of {valid_storage}")
|
|
220
|
+
|
|
221
|
+
# Validate variables
|
|
222
|
+
if not self.variables:
|
|
223
|
+
errors.append("q:persist must contain at least one q:var element")
|
|
224
|
+
|
|
225
|
+
return errors
|
|
226
|
+
|
|
227
|
+
def __repr__(self):
|
|
228
|
+
return f'<PersistNode scope={self.scope} vars={len(self.variables)}>'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Terminal Engine - AST nodes and parser for the qt: namespace.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .ast_nodes import (
|
|
6
|
+
ScreenNode, PanelNode, LayoutNode, TableNode, ColumnNode,
|
|
7
|
+
TerminalInputNode, ButtonNode, MenuNode, OptionNode,
|
|
8
|
+
TextNode_Terminal, ProgressNode, TreeNode, TabsNode, TabNode,
|
|
9
|
+
LogNode_Terminal, HeaderNode_Terminal, FooterNode, StatusNode,
|
|
10
|
+
KeybindingNode, TimerNode_Terminal, ServiceNode, CssNode,
|
|
11
|
+
OnEventNode_Terminal, RawCodeNode_Terminal,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
'ScreenNode', 'PanelNode', 'LayoutNode', 'TableNode', 'ColumnNode',
|
|
16
|
+
'TerminalInputNode', 'ButtonNode', 'MenuNode', 'OptionNode',
|
|
17
|
+
'TextNode_Terminal', 'ProgressNode', 'TreeNode', 'TabsNode', 'TabNode',
|
|
18
|
+
'LogNode_Terminal', 'HeaderNode_Terminal', 'FooterNode', 'StatusNode',
|
|
19
|
+
'KeybindingNode', 'TimerNode_Terminal', 'ServiceNode', 'CssNode',
|
|
20
|
+
'OnEventNode_Terminal', 'RawCodeNode_Terminal',
|
|
21
|
+
]
|