springbootAI 1.8.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.
- spring/__init__.py +66 -0
- spring/ai/__init__.py +78 -0
- spring/ai/advisors.py +139 -0
- spring/ai/annotations.py +74 -0
- spring/ai/autoconfig.py +481 -0
- spring/ai/core.py +391 -0
- spring/ai/etl.py +188 -0
- spring/ai/memory.py +109 -0
- spring/ai/observability.py +129 -0
- spring/ai/providers.py +789 -0
- spring/ai/resilience.py +258 -0
- spring/ai/tools.py +106 -0
- spring/ai/vectorstore.py +303 -0
- spring/annotations/__init__.py +188 -0
- spring/annotations/cache.py +126 -0
- spring/annotations/cloud.py +207 -0
- spring/annotations/conditional.py +272 -0
- spring/annotations/core.py +864 -0
- spring/annotations/messaging.py +107 -0
- spring/aop/__init__.py +4 -0
- spring/aop/cloud_aop.py +404 -0
- spring/aop/comprehensive_aop.py +1015 -0
- spring/aop/method_interceptor.py +19 -0
- spring/aop/proxy_factory.py +55 -0
- spring/cloud/__init__.py +76 -0
- spring/cloud/discovery.py +364 -0
- spring/cloud/feign.py +469 -0
- spring/cloud/gateway.py +452 -0
- spring/cloud/load_balancer.py +149 -0
- spring/cloud/seata.py +557 -0
- spring/cloud/sentinel.py +525 -0
- spring/cloud/tracer.py +337 -0
- spring/config/__init__.py +21 -0
- spring/config/binding.py +206 -0
- spring/config/config_loader.py +405 -0
- spring/context/__init__.py +13 -0
- spring/context/application_context.py +589 -0
- spring/context/bean_definition.py +70 -0
- spring/context/bean_factory.py +1052 -0
- spring/context/registry.py +58 -0
- spring/context/scanner.py +106 -0
- spring/core/__init__.py +3 -0
- spring/core/graceful_shutdown.py +196 -0
- spring/core/typing_utils.py +50 -0
- spring/csv/__init__.py +52 -0
- spring/csv/annotations.py +402 -0
- spring/csv/converters.py +69 -0
- spring/csv/easy_csv.py +95 -0
- spring/csv/exceptions.py +27 -0
- spring/csv/reader.py +195 -0
- spring/csv/writer.py +155 -0
- spring/data/__init__.py +54 -0
- spring/data/page.py +181 -0
- spring/data/repository.py +274 -0
- spring/data/specification.py +228 -0
- spring/datasource/__init__.py +66 -0
- spring/datasource/annotations.py +133 -0
- spring/datasource/context.py +69 -0
- spring/datasource/dynamic.py +148 -0
- spring/event/__init__.py +7 -0
- spring/event/publisher.py +69 -0
- spring/excel/__init__.py +51 -0
- spring/excel/annotations.py +405 -0
- spring/excel/converters.py +231 -0
- spring/excel/easy_excel.py +94 -0
- spring/excel/exceptions.py +31 -0
- spring/excel/reader.py +254 -0
- spring/excel/style.py +95 -0
- spring/excel/writer.py +197 -0
- spring/i18n/__init__.py +97 -0
- spring/i18n/accessor.py +94 -0
- spring/i18n/auto_config.py +177 -0
- spring/i18n/holder.py +106 -0
- spring/i18n/locale.py +152 -0
- spring/i18n/locale_resolver.py +367 -0
- spring/i18n/message_source.py +250 -0
- spring/i18n/middleware.py +79 -0
- spring/i18n/properties.py +168 -0
- spring/i18n/sources.py +255 -0
- spring/logging/__init__.py +1 -0
- spring/logging/loguru_logger.py +228 -0
- spring/main.py +378 -0
- spring/messaging/__init__.py +1 -0
- spring/messaging/rabbitmq.py +302 -0
- spring/monitoring/__init__.py +1 -0
- spring/monitoring/prometheus.py +199 -0
- spring/orm/__init__.py +258 -0
- spring/orm/database.py +222 -0
- spring/orm/ddl_auto.py +1217 -0
- spring/orm/migration.py +419 -0
- spring/orm/mybatis_integration.py +400 -0
- spring/orm/pymybatis/__init__.py +86 -0
- spring/orm/pymybatis/annotations/__init__.py +30 -0
- spring/orm/pymybatis/annotations/annotations.py +332 -0
- spring/orm/pymybatis/cache/__init__.py +47 -0
- spring/orm/pymybatis/cache/cache.py +371 -0
- spring/orm/pymybatis/cache/redis_cache.py +434 -0
- spring/orm/pymybatis/circuit_breaker/__init__.py +21 -0
- spring/orm/pymybatis/circuit_breaker/circuit_breaker.py +424 -0
- spring/orm/pymybatis/configuration.py +525 -0
- spring/orm/pymybatis/core/__init__.py +10 -0
- spring/orm/pymybatis/core/sql_session.py +1382 -0
- spring/orm/pymybatis/core/sql_session_factory.py +76 -0
- spring/orm/pymybatis/dialect/__init__.py +9 -0
- spring/orm/pymybatis/dialect/dialect.py +445 -0
- spring/orm/pymybatis/dynamic_sql/__init__.py +9 -0
- spring/orm/pymybatis/dynamic_sql/dynamic_sql.py +900 -0
- spring/orm/pymybatis/interceptor/__init__.py +31 -0
- spring/orm/pymybatis/interceptor/interceptor.py +427 -0
- spring/orm/pymybatis/mapper/__init__.py +9 -0
- spring/orm/pymybatis/mapper/mapper.py +540 -0
- spring/orm/pymybatis/metrics/__init__.py +41 -0
- spring/orm/pymybatis/metrics/metrics.py +595 -0
- spring/orm/pymybatis/pool/__init__.py +9 -0
- spring/orm/pymybatis/pool/connection_pool.py +711 -0
- spring/orm/pymybatis/security/__init__.py +19 -0
- spring/orm/pymybatis/security/access_control.py +415 -0
- spring/orm/pymybatis/security/password_encoder.py +293 -0
- spring/orm/pymybatis/security/sensitive_data_masker.py +326 -0
- spring/orm/pymybatis/security/sql_injection_detector.py +675 -0
- spring/orm/pymybatis/transaction/__init__.py +9 -0
- spring/orm/pymybatis/transaction/transaction.py +288 -0
- spring/orm/pymybatis/type_handler/__init__.py +37 -0
- spring/orm/pymybatis/type_handler/type_handler.py +473 -0
- spring/orm/pymybatis/version.py +9 -0
- spring/orm/pymybatis/xml_parser/__init__.py +9 -0
- spring/orm/pymybatis/xml_parser/xml_parser.py +761 -0
- spring/retry/__init__.py +12 -0
- spring/retry/retry_annotations.py +71 -0
- spring/retry/retry_decorator.py +155 -0
- spring/scheduling/__init__.py +3 -0
- spring/scheduling/scheduler.py +389 -0
- spring/security/__init__.py +39 -0
- spring/security/jwt_utils.py +281 -0
- spring/security/replay_protection.py +206 -0
- spring/security/secret_manager.py +226 -0
- spring/security/security_aop.py +248 -0
- spring/security/security_context.py +172 -0
- spring/test/__init__.py +45 -0
- spring/test/slicing.py +341 -0
- spring/tracing/__init__.py +11 -0
- spring/tracing/skywalking.py +229 -0
- spring/tx/__init__.py +52 -0
- spring/tx/events.py +172 -0
- spring/tx/synchronization.py +143 -0
- spring/utils/__init__.py +5 -0
- spring/utils/banner.py +32 -0
- spring/utils/logger.py +73 -0
- spring/utils/redis_client.py +526 -0
- spring/validation/__init__.py +55 -0
- spring/validation/aop.py +141 -0
- spring/validation/constraints.py +357 -0
- spring/validation/exceptions.py +55 -0
- spring/validation/validator.py +139 -0
- spring/web/__init__.py +12 -0
- spring/web/actuator.py +319 -0
- spring/web/exception_handler.py +61 -0
- spring/web/health.py +399 -0
- spring/web/interceptor.py +91 -0
- spring/web/result.py +44 -0
- spring/web/swagger.py +601 -0
- spring/web/web_context.py +755 -0
- spring/websocket/__init__.py +86 -0
- spring/websocket/annotations.py +169 -0
- spring/websocket/broker.py +238 -0
- spring/websocket/exceptions.py +26 -0
- spring/websocket/handler.py +243 -0
- spring/websocket/router.py +526 -0
- spring/websocket/session.py +216 -0
- springbootai-1.8.0.dist-info/METADATA +2796 -0
- springbootai-1.8.0.dist-info/RECORD +175 -0
- springbootai-1.8.0.dist-info/WHEEL +5 -0
- springbootai-1.8.0.dist-info/entry_points.txt +2 -0
- springbootai-1.8.0.dist-info/licenses/LICENSE +7 -0
- springbootai-1.8.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyMyBatis XML映射文件解析器
|
|
3
|
+
|
|
4
|
+
解析MyBatis风格的XML映射文件,支持:
|
|
5
|
+
- select/insert/update/delete标签
|
|
6
|
+
- resultMap标签
|
|
7
|
+
- sql片段
|
|
8
|
+
- 动态SQL标签
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from xml.etree import ElementTree as ET
|
|
15
|
+
from typing import Any, Dict, List, Optional
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class NestedResultMapping:
|
|
20
|
+
"""Metadata for MyBatis ``association``/``collection`` mappings."""
|
|
21
|
+
|
|
22
|
+
property: str
|
|
23
|
+
result_map: Optional[str] = None
|
|
24
|
+
result_type: Optional[str] = None
|
|
25
|
+
select: Optional[str] = None
|
|
26
|
+
column: Optional[str] = None
|
|
27
|
+
java_type: Optional[str] = None
|
|
28
|
+
of_type: Optional[str] = None
|
|
29
|
+
mapping: Optional['ResultMap'] = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class DiscriminatorMapping:
|
|
34
|
+
column: str
|
|
35
|
+
cases: Dict[str, str] = field(default_factory=dict)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class MappedStatement:
|
|
39
|
+
"""
|
|
40
|
+
映射语句
|
|
41
|
+
|
|
42
|
+
封装XML中定义的SQL语句及其配置
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, id: str, sql_type: str, sql: str, result_map: Optional[str] = None,
|
|
46
|
+
parameter_type: Optional[str] = None, result_type: Optional[str] = None,
|
|
47
|
+
fetch_size: Optional[int] = None, timeout: Optional[int] = None,
|
|
48
|
+
use_cache: bool = True, flush_cache: bool = False,
|
|
49
|
+
use_generated_keys: bool = False,
|
|
50
|
+
key_property: Optional[str] = None,
|
|
51
|
+
key_column: Optional[str] = None,
|
|
52
|
+
database_id: Optional[str] = None,
|
|
53
|
+
select_key_sql: Optional[str] = None,
|
|
54
|
+
select_key_order: str = 'AFTER',
|
|
55
|
+
select_key_result_type: Optional[str] = None,
|
|
56
|
+
select_key_key_property: Optional[str] = None,
|
|
57
|
+
select_key_key_column: Optional[str] = None):
|
|
58
|
+
"""
|
|
59
|
+
初始化映射语句
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
id: 语句ID
|
|
63
|
+
sql_type: SQL类型(SELECT/INSERT/UPDATE/DELETE)
|
|
64
|
+
sql: SQL语句
|
|
65
|
+
result_map: 结果映射ID
|
|
66
|
+
parameter_type: 参数类型
|
|
67
|
+
result_type: 结果类型
|
|
68
|
+
fetch_size: 抓取大小
|
|
69
|
+
timeout: 超时时间
|
|
70
|
+
"""
|
|
71
|
+
self.id = id
|
|
72
|
+
self.sql_type = sql_type.upper()
|
|
73
|
+
self.sql = sql
|
|
74
|
+
self.result_map = result_map
|
|
75
|
+
self.parameter_type = parameter_type
|
|
76
|
+
self.result_type = result_type
|
|
77
|
+
self.fetch_size = fetch_size
|
|
78
|
+
self.timeout = timeout
|
|
79
|
+
self.use_cache = use_cache
|
|
80
|
+
self.flush_cache = flush_cache
|
|
81
|
+
self.use_generated_keys = use_generated_keys
|
|
82
|
+
self.key_property = key_property
|
|
83
|
+
self.key_column = key_column
|
|
84
|
+
self.database_id = database_id
|
|
85
|
+
self.select_key_sql = select_key_sql
|
|
86
|
+
self.select_key_order = (select_key_order or 'AFTER').upper()
|
|
87
|
+
self.select_key_result_type = select_key_result_type
|
|
88
|
+
self.select_key_key_property = select_key_key_property
|
|
89
|
+
self.select_key_key_column = select_key_key_column
|
|
90
|
+
|
|
91
|
+
def __repr__(self) -> str:
|
|
92
|
+
return f"<MappedStatement id={self.id}, type={self.sql_type}>"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class ResultMap:
|
|
96
|
+
"""
|
|
97
|
+
结果映射
|
|
98
|
+
|
|
99
|
+
定义数据库列到Java对象属性的映射关系
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
def __init__(self, id: str, type: str):
|
|
103
|
+
"""
|
|
104
|
+
初始化结果映射
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
id: 结果映射ID
|
|
108
|
+
type: 目标类型
|
|
109
|
+
"""
|
|
110
|
+
self.id = id
|
|
111
|
+
self.type = type
|
|
112
|
+
self.mappings: Dict[str, str] = {} # column -> property
|
|
113
|
+
self.id_columns: List[str] = []
|
|
114
|
+
self.associations: List[NestedResultMapping] = []
|
|
115
|
+
self.collections: List[NestedResultMapping] = []
|
|
116
|
+
self.discriminator: Optional[DiscriminatorMapping] = None
|
|
117
|
+
self.extends: Optional[str] = None
|
|
118
|
+
|
|
119
|
+
def add_mapping(self, column: str, property: str) -> None:
|
|
120
|
+
"""
|
|
121
|
+
添加列映射
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
column: 数据库列名
|
|
125
|
+
property: 对象属性名
|
|
126
|
+
"""
|
|
127
|
+
self.mappings[column] = property
|
|
128
|
+
|
|
129
|
+
def add_id_mapping(self, column: str, property: str) -> None:
|
|
130
|
+
self.add_mapping(column, property)
|
|
131
|
+
if column and column not in self.id_columns:
|
|
132
|
+
self.id_columns.append(column)
|
|
133
|
+
|
|
134
|
+
def get_property(self, column: str) -> Optional[str]:
|
|
135
|
+
"""
|
|
136
|
+
获取列对应的属性名
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
column: 数据库列名
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
对象属性名,未找到返回None
|
|
143
|
+
"""
|
|
144
|
+
return self.mappings.get(column)
|
|
145
|
+
|
|
146
|
+
def add_nested(self, nested: NestedResultMapping, collection: bool = False) -> None:
|
|
147
|
+
(self.collections if collection else self.associations).append(nested)
|
|
148
|
+
|
|
149
|
+
def __repr__(self) -> str:
|
|
150
|
+
return f"<ResultMap id={self.id}, type={self.type}, mappings={self.mappings}>"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class SqlFragment:
|
|
154
|
+
"""
|
|
155
|
+
SQL片段
|
|
156
|
+
|
|
157
|
+
可复用的SQL片段
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
def __init__(self, id: str, sql: str):
|
|
161
|
+
"""
|
|
162
|
+
初始化SQL片段
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
id: 片段ID
|
|
166
|
+
sql: SQL内容
|
|
167
|
+
"""
|
|
168
|
+
self.id = id
|
|
169
|
+
self.sql = sql
|
|
170
|
+
|
|
171
|
+
def __repr__(self) -> str:
|
|
172
|
+
return f"<SqlFragment id={self.id}>"
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class XmlParser:
|
|
176
|
+
"""
|
|
177
|
+
XML映射文件解析器
|
|
178
|
+
|
|
179
|
+
解析MyBatis风格的XML映射文件
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
def __init__(self):
|
|
183
|
+
"""初始化XML解析器"""
|
|
184
|
+
self.mapped_statements: Dict[str, MappedStatement] = {}
|
|
185
|
+
self._statement_variants: Dict[str, List[MappedStatement]] = {}
|
|
186
|
+
self.result_maps: Dict[str, ResultMap] = {}
|
|
187
|
+
self.sql_fragments: Dict[str, SqlFragment] = {}
|
|
188
|
+
self.namespace: Optional[str] = None
|
|
189
|
+
|
|
190
|
+
@staticmethod
|
|
191
|
+
def _normalize_comparison_operators(xml_content: str) -> str:
|
|
192
|
+
"""Make raw SQL comparison operators safe for XML parsing.
|
|
193
|
+
|
|
194
|
+
Mapper SQL often contains ``<=`` and ``>=``. The former is invalid
|
|
195
|
+
XML unless escaped. Normalize both operators outside comments and
|
|
196
|
+
CDATA blocks; ElementTree decodes the entities back to SQL text.
|
|
197
|
+
"""
|
|
198
|
+
protected_pattern = re.compile(
|
|
199
|
+
r'(<!\[CDATA\[.*?\]\]>|<!--.*?-->)',
|
|
200
|
+
flags=re.DOTALL,
|
|
201
|
+
)
|
|
202
|
+
parts = protected_pattern.split(xml_content)
|
|
203
|
+
for index in range(0, len(parts), 2):
|
|
204
|
+
parts[index] = parts[index].replace('<=', '<=')
|
|
205
|
+
parts[index] = parts[index].replace('>=', '>=')
|
|
206
|
+
return ''.join(parts)
|
|
207
|
+
|
|
208
|
+
def parse(self, file_path: str) -> Dict[str, MappedStatement]:
|
|
209
|
+
"""
|
|
210
|
+
解析XML文件(兼容接口)
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
file_path: XML文件路径
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
映射语句字典
|
|
217
|
+
|
|
218
|
+
Raises:
|
|
219
|
+
FileNotFoundError: 文件不存在
|
|
220
|
+
ValueError: XML格式错误
|
|
221
|
+
"""
|
|
222
|
+
self.parse_file(file_path)
|
|
223
|
+
return self.mapped_statements
|
|
224
|
+
|
|
225
|
+
def parse_file(self, file_path: str) -> None:
|
|
226
|
+
"""
|
|
227
|
+
解析XML文件
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
file_path: XML文件路径
|
|
231
|
+
|
|
232
|
+
Raises:
|
|
233
|
+
FileNotFoundError: 文件不存在
|
|
234
|
+
ValueError: XML格式错误
|
|
235
|
+
"""
|
|
236
|
+
if not os.path.exists(file_path):
|
|
237
|
+
raise FileNotFoundError(f"XML文件不存在: {file_path}")
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
with open(file_path, 'r', encoding='utf-8-sig') as xml_file:
|
|
241
|
+
xml_content = xml_file.read()
|
|
242
|
+
root = ET.fromstring(
|
|
243
|
+
self._normalize_comparison_operators(xml_content)
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
# 解析namespace
|
|
247
|
+
self.namespace = root.get('namespace', '')
|
|
248
|
+
|
|
249
|
+
# 解析各个标签
|
|
250
|
+
for child in root:
|
|
251
|
+
self._parse_element(child)
|
|
252
|
+
|
|
253
|
+
self._resolve_result_map_extends()
|
|
254
|
+
# 替换SQL片段引用
|
|
255
|
+
self._replace_sql_fragments()
|
|
256
|
+
|
|
257
|
+
except ET.ParseError as e:
|
|
258
|
+
raise ValueError(f"XML解析错误: {e}")
|
|
259
|
+
|
|
260
|
+
def parse_string(self, xml_string: str) -> None:
|
|
261
|
+
"""
|
|
262
|
+
解析XML字符串
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
xml_string: XML字符串
|
|
266
|
+
"""
|
|
267
|
+
root = ET.fromstring(
|
|
268
|
+
self._normalize_comparison_operators(xml_string)
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# 解析namespace
|
|
272
|
+
self.namespace = root.get('namespace', '')
|
|
273
|
+
|
|
274
|
+
# 解析各个标签
|
|
275
|
+
for child in root:
|
|
276
|
+
self._parse_element(child)
|
|
277
|
+
|
|
278
|
+
self._resolve_result_map_extends()
|
|
279
|
+
# 替换SQL片段引用
|
|
280
|
+
self._replace_sql_fragments()
|
|
281
|
+
|
|
282
|
+
def _parse_element(self, element: ET.Element) -> None:
|
|
283
|
+
"""
|
|
284
|
+
解析单个元素
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
element: XML元素
|
|
288
|
+
"""
|
|
289
|
+
tag_name = element.tag
|
|
290
|
+
|
|
291
|
+
if tag_name == 'select':
|
|
292
|
+
self._parse_select(element)
|
|
293
|
+
elif tag_name == 'insert':
|
|
294
|
+
self._parse_insert(element)
|
|
295
|
+
elif tag_name == 'update':
|
|
296
|
+
self._parse_update(element)
|
|
297
|
+
elif tag_name == 'delete':
|
|
298
|
+
self._parse_delete(element)
|
|
299
|
+
elif tag_name == 'resultMap':
|
|
300
|
+
self._parse_result_map(element)
|
|
301
|
+
elif tag_name == 'sql':
|
|
302
|
+
self._parse_sql(element)
|
|
303
|
+
|
|
304
|
+
def _parse_select(self, element: ET.Element) -> None:
|
|
305
|
+
"""
|
|
306
|
+
解析<select>标签
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
element: select元素
|
|
310
|
+
"""
|
|
311
|
+
statement_id = element.get('id', '')
|
|
312
|
+
result_map = element.get('resultMap')
|
|
313
|
+
result_type = element.get('resultType')
|
|
314
|
+
parameter_type = element.get('parameterType')
|
|
315
|
+
fetch_size = element.get('fetchSize')
|
|
316
|
+
timeout = element.get('timeout')
|
|
317
|
+
|
|
318
|
+
sql = self._get_element_text(element)
|
|
319
|
+
|
|
320
|
+
mapped_statement = MappedStatement(
|
|
321
|
+
id=statement_id,
|
|
322
|
+
sql_type='SELECT',
|
|
323
|
+
sql=sql,
|
|
324
|
+
result_map=result_map,
|
|
325
|
+
parameter_type=parameter_type,
|
|
326
|
+
result_type=result_type,
|
|
327
|
+
fetch_size=int(fetch_size) if fetch_size else None,
|
|
328
|
+
timeout=int(timeout) if timeout else None,
|
|
329
|
+
use_cache=self._parse_bool(element.get('useCache'), default=True),
|
|
330
|
+
flush_cache=self._parse_bool(element.get('flushCache'), default=False),
|
|
331
|
+
database_id=element.get('databaseId'),
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
self._add_mapped_statement(mapped_statement)
|
|
335
|
+
|
|
336
|
+
def _parse_insert(self, element: ET.Element) -> None:
|
|
337
|
+
"""
|
|
338
|
+
解析<insert>标签
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
element: insert元素
|
|
342
|
+
"""
|
|
343
|
+
statement_id = element.get('id', '')
|
|
344
|
+
parameter_type = element.get('parameterType')
|
|
345
|
+
|
|
346
|
+
sql = self._get_statement_text(element, excluded_tags={'selectKey'})
|
|
347
|
+
select_key = self._parse_select_key(element)
|
|
348
|
+
|
|
349
|
+
mapped_statement = MappedStatement(
|
|
350
|
+
id=statement_id,
|
|
351
|
+
sql_type='INSERT',
|
|
352
|
+
sql=sql,
|
|
353
|
+
parameter_type=parameter_type,
|
|
354
|
+
timeout=self._parse_optional_int(element.get('timeout')),
|
|
355
|
+
flush_cache=self._parse_bool(element.get('flushCache'), default=True),
|
|
356
|
+
use_generated_keys=self._parse_bool(
|
|
357
|
+
element.get('useGeneratedKeys'), default=False
|
|
358
|
+
),
|
|
359
|
+
key_property=element.get('keyProperty'),
|
|
360
|
+
key_column=element.get('keyColumn'),
|
|
361
|
+
database_id=element.get('databaseId'),
|
|
362
|
+
select_key_sql=select_key[0] if select_key else None,
|
|
363
|
+
select_key_order=select_key[1] if select_key else 'AFTER',
|
|
364
|
+
select_key_result_type=select_key[2] if select_key else None,
|
|
365
|
+
select_key_key_property=select_key[3] if select_key else None,
|
|
366
|
+
select_key_key_column=select_key[4] if select_key else None,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
self._add_mapped_statement(mapped_statement)
|
|
370
|
+
|
|
371
|
+
def _parse_update(self, element: ET.Element) -> None:
|
|
372
|
+
"""
|
|
373
|
+
解析<update>标签
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
element: update元素
|
|
377
|
+
"""
|
|
378
|
+
statement_id = element.get('id', '')
|
|
379
|
+
parameter_type = element.get('parameterType')
|
|
380
|
+
|
|
381
|
+
sql = self._get_element_text(element)
|
|
382
|
+
|
|
383
|
+
mapped_statement = MappedStatement(
|
|
384
|
+
id=statement_id,
|
|
385
|
+
sql_type='UPDATE',
|
|
386
|
+
sql=sql,
|
|
387
|
+
parameter_type=parameter_type,
|
|
388
|
+
timeout=self._parse_optional_int(element.get('timeout')),
|
|
389
|
+
flush_cache=self._parse_bool(element.get('flushCache'), default=True),
|
|
390
|
+
database_id=element.get('databaseId'),
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
self._add_mapped_statement(mapped_statement)
|
|
394
|
+
|
|
395
|
+
def _parse_delete(self, element: ET.Element) -> None:
|
|
396
|
+
"""
|
|
397
|
+
解析<delete>标签
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
element: delete元素
|
|
401
|
+
"""
|
|
402
|
+
statement_id = element.get('id', '')
|
|
403
|
+
parameter_type = element.get('parameterType')
|
|
404
|
+
|
|
405
|
+
sql = self._get_element_text(element)
|
|
406
|
+
|
|
407
|
+
mapped_statement = MappedStatement(
|
|
408
|
+
id=statement_id,
|
|
409
|
+
sql_type='DELETE',
|
|
410
|
+
sql=sql,
|
|
411
|
+
parameter_type=parameter_type,
|
|
412
|
+
timeout=self._parse_optional_int(element.get('timeout')),
|
|
413
|
+
flush_cache=self._parse_bool(element.get('flushCache'), default=True),
|
|
414
|
+
database_id=element.get('databaseId'),
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
self._add_mapped_statement(mapped_statement)
|
|
418
|
+
|
|
419
|
+
def _parse_result_map(self, element: ET.Element) -> None:
|
|
420
|
+
"""
|
|
421
|
+
解析<resultMap>标签
|
|
422
|
+
|
|
423
|
+
Args:
|
|
424
|
+
element: resultMap元素
|
|
425
|
+
"""
|
|
426
|
+
result_map_id = element.get('id', '')
|
|
427
|
+
result_type = element.get('type', '')
|
|
428
|
+
|
|
429
|
+
result_map = ResultMap(id=result_map_id, type=result_type)
|
|
430
|
+
|
|
431
|
+
# 解析子标签
|
|
432
|
+
result_map.extends = element.get('extends')
|
|
433
|
+
for child in element:
|
|
434
|
+
if child.tag == 'id':
|
|
435
|
+
column = child.get('column', '')
|
|
436
|
+
property = child.get('property', '')
|
|
437
|
+
result_map.add_id_mapping(column, property)
|
|
438
|
+
elif child.tag == 'result':
|
|
439
|
+
column = child.get('column', '')
|
|
440
|
+
property = child.get('property', '')
|
|
441
|
+
result_map.add_mapping(column, property)
|
|
442
|
+
elif child.tag in {'association', 'collection'}:
|
|
443
|
+
nested = self._parse_nested_mapping(child)
|
|
444
|
+
result_map.add_nested(nested, collection=child.tag == 'collection')
|
|
445
|
+
elif child.tag == 'discriminator':
|
|
446
|
+
discriminator = DiscriminatorMapping(
|
|
447
|
+
column=child.get('column', '')
|
|
448
|
+
)
|
|
449
|
+
for case in child:
|
|
450
|
+
if case.tag != 'case':
|
|
451
|
+
continue
|
|
452
|
+
case_result_map = case.get('resultMap')
|
|
453
|
+
if not case_result_map:
|
|
454
|
+
# Inline case result maps are represented by a
|
|
455
|
+
# synthetic map and filled recursively below.
|
|
456
|
+
inline_id = f"{result_map_id}.__case_{case.get('value', '')}"
|
|
457
|
+
inline = ResultMap(inline_id, case.get('type', result_type))
|
|
458
|
+
self._parse_result_map_children(case, inline)
|
|
459
|
+
self.result_maps[inline_id] = inline
|
|
460
|
+
case_result_map = inline_id
|
|
461
|
+
discriminator.cases[str(case.get('value'))] = case_result_map
|
|
462
|
+
result_map.discriminator = discriminator
|
|
463
|
+
|
|
464
|
+
self.result_maps[result_map_id] = result_map
|
|
465
|
+
# 同时存储带namespace的key(但只存一份实例)
|
|
466
|
+
if self.namespace:
|
|
467
|
+
namespaced_id = f"{self.namespace}.{result_map_id}"
|
|
468
|
+
self.result_maps[namespaced_id] = result_map
|
|
469
|
+
|
|
470
|
+
def _parse_result_map_children(self, element: ET.Element, result_map: ResultMap) -> None:
|
|
471
|
+
"""Parse mapping children shared by normal maps and discriminator cases."""
|
|
472
|
+
for child in element:
|
|
473
|
+
if child.tag == 'id':
|
|
474
|
+
result_map.add_id_mapping(child.get('column', ''), child.get('property', ''))
|
|
475
|
+
elif child.tag == 'result':
|
|
476
|
+
result_map.add_mapping(child.get('column', ''), child.get('property', ''))
|
|
477
|
+
elif child.tag in {'association', 'collection'}:
|
|
478
|
+
result_map.add_nested(
|
|
479
|
+
self._parse_nested_mapping(child),
|
|
480
|
+
collection=child.tag == 'collection',
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
def _parse_nested_mapping(self, element: ET.Element) -> NestedResultMapping:
|
|
484
|
+
nested = NestedResultMapping(
|
|
485
|
+
property=element.get('property', ''),
|
|
486
|
+
result_map=element.get('resultMap'),
|
|
487
|
+
result_type=element.get('resultType'),
|
|
488
|
+
select=element.get('select'),
|
|
489
|
+
column=element.get('column'),
|
|
490
|
+
java_type=element.get('javaType'),
|
|
491
|
+
of_type=element.get('ofType'),
|
|
492
|
+
)
|
|
493
|
+
if nested.result_map is None and list(element):
|
|
494
|
+
nested_id = f"__nested_{nested.property}_{id(element)}"
|
|
495
|
+
nested.mapping = ResultMap(nested_id, nested.result_type or nested.of_type or '')
|
|
496
|
+
self._parse_result_map_children(element, nested.mapping)
|
|
497
|
+
nested.result_map = nested_id
|
|
498
|
+
self.result_maps[nested_id] = nested.mapping
|
|
499
|
+
return nested
|
|
500
|
+
|
|
501
|
+
def _resolve_result_map_extends(self) -> None:
|
|
502
|
+
"""Merge inherited result-map fields after the document is parsed."""
|
|
503
|
+
visiting = set()
|
|
504
|
+
|
|
505
|
+
def resolve(result_map: ResultMap) -> None:
|
|
506
|
+
if not result_map.extends:
|
|
507
|
+
return
|
|
508
|
+
if id(result_map) in visiting:
|
|
509
|
+
raise ValueError(f"resultMap extends 存在循环: {result_map.id}")
|
|
510
|
+
visiting.add(id(result_map))
|
|
511
|
+
parent_id = result_map.extends
|
|
512
|
+
parent = self.result_maps.get(parent_id)
|
|
513
|
+
if parent is None and self.namespace:
|
|
514
|
+
parent = self.result_maps.get(f"{self.namespace}.{parent_id}")
|
|
515
|
+
if parent is None:
|
|
516
|
+
raise ValueError(f"未找到 resultMap extends: {parent_id}")
|
|
517
|
+
resolve(parent)
|
|
518
|
+
inherited = dict(parent.mappings)
|
|
519
|
+
inherited.update(result_map.mappings)
|
|
520
|
+
result_map.mappings = inherited
|
|
521
|
+
result_map.id_columns = list(dict.fromkeys(parent.id_columns + result_map.id_columns))
|
|
522
|
+
result_map.associations = list(parent.associations) + list(result_map.associations)
|
|
523
|
+
result_map.collections = list(parent.collections) + list(result_map.collections)
|
|
524
|
+
if result_map.discriminator is None:
|
|
525
|
+
result_map.discriminator = parent.discriminator
|
|
526
|
+
visiting.remove(id(result_map))
|
|
527
|
+
|
|
528
|
+
seen = set()
|
|
529
|
+
for result_map in self.result_maps.values():
|
|
530
|
+
if id(result_map) in seen:
|
|
531
|
+
continue
|
|
532
|
+
seen.add(id(result_map))
|
|
533
|
+
resolve(result_map)
|
|
534
|
+
|
|
535
|
+
def _parse_select_key(self, element: ET.Element):
|
|
536
|
+
for child in element:
|
|
537
|
+
if child.tag != 'selectKey':
|
|
538
|
+
continue
|
|
539
|
+
return (
|
|
540
|
+
self._get_element_text(child),
|
|
541
|
+
child.get('order', 'AFTER').upper(),
|
|
542
|
+
child.get('resultType'),
|
|
543
|
+
child.get('keyProperty'),
|
|
544
|
+
child.get('keyColumn'),
|
|
545
|
+
)
|
|
546
|
+
return None
|
|
547
|
+
|
|
548
|
+
def _get_statement_text(self, element: ET.Element, excluded_tags: set) -> str:
|
|
549
|
+
"""Get statement text while dropping non-SQL child controls."""
|
|
550
|
+
parts = []
|
|
551
|
+
if element.text:
|
|
552
|
+
parts.append(element.text)
|
|
553
|
+
for child in element:
|
|
554
|
+
if child.tag not in excluded_tags:
|
|
555
|
+
parts.append(ET.tostring(child, encoding='unicode'))
|
|
556
|
+
if child.tail:
|
|
557
|
+
parts.append(child.tail)
|
|
558
|
+
text = ''.join(parts)
|
|
559
|
+
for entity, value in {
|
|
560
|
+
'>': '>', '<': '<', '&': '&',
|
|
561
|
+
'"': '"', ''': "'",
|
|
562
|
+
}.items():
|
|
563
|
+
text = text.replace(entity, value)
|
|
564
|
+
return text.strip()
|
|
565
|
+
|
|
566
|
+
def _parse_sql(self, element: ET.Element) -> None:
|
|
567
|
+
"""
|
|
568
|
+
解析<sql>标签
|
|
569
|
+
|
|
570
|
+
Args:
|
|
571
|
+
element: sql元素
|
|
572
|
+
"""
|
|
573
|
+
sql_id = element.get('id', '')
|
|
574
|
+
sql = self._get_element_text(element)
|
|
575
|
+
|
|
576
|
+
sql_fragment = SqlFragment(id=sql_id, sql=sql)
|
|
577
|
+
key = f"{self.namespace}.{sql_id}" if self.namespace else sql_id
|
|
578
|
+
self.sql_fragments[key] = sql_fragment
|
|
579
|
+
|
|
580
|
+
@staticmethod
|
|
581
|
+
def _parse_bool(value: Optional[str], default: bool) -> bool:
|
|
582
|
+
if value is None:
|
|
583
|
+
return default
|
|
584
|
+
normalized = value.strip().lower()
|
|
585
|
+
if normalized in {'true', '1', 'yes', 'on'}:
|
|
586
|
+
return True
|
|
587
|
+
if normalized in {'false', '0', 'no', 'off'}:
|
|
588
|
+
return False
|
|
589
|
+
raise ValueError(f"布尔属性值无效: {value}")
|
|
590
|
+
|
|
591
|
+
@staticmethod
|
|
592
|
+
def _parse_optional_int(value: Optional[str]) -> Optional[int]:
|
|
593
|
+
return int(value) if value is not None else None
|
|
594
|
+
|
|
595
|
+
def _get_element_text(self, element: ET.Element) -> str:
|
|
596
|
+
"""
|
|
597
|
+
获取元素的文本内容(包括子元素)
|
|
598
|
+
|
|
599
|
+
Args:
|
|
600
|
+
element: XML元素
|
|
601
|
+
|
|
602
|
+
Returns:
|
|
603
|
+
文本内容(保留原始XML标签)
|
|
604
|
+
"""
|
|
605
|
+
# 使用ET.tostring获取完整内容,然后提取标签内部的部分
|
|
606
|
+
full_str = ET.tostring(element, encoding='unicode')
|
|
607
|
+
# 移除开始和结束标签,只保留内部内容
|
|
608
|
+
# 匹配 <tag ...> 开始标签
|
|
609
|
+
start_tag_end = full_str.find('>') + 1
|
|
610
|
+
# 匹配 </tag> 结束标签
|
|
611
|
+
end_tag_start = full_str.rfind('</')
|
|
612
|
+
if end_tag_start == -1:
|
|
613
|
+
# 自闭合标签
|
|
614
|
+
return ''
|
|
615
|
+
inner_content = full_str[start_tag_end:end_tag_start]
|
|
616
|
+
|
|
617
|
+
# 将XML实体编码转换回原始字符
|
|
618
|
+
inner_content = inner_content.replace('>', '>')
|
|
619
|
+
inner_content = inner_content.replace('<', '<')
|
|
620
|
+
inner_content = inner_content.replace('&', '&')
|
|
621
|
+
inner_content = inner_content.replace('"', '"')
|
|
622
|
+
inner_content = inner_content.replace(''', "'")
|
|
623
|
+
|
|
624
|
+
return inner_content.strip()
|
|
625
|
+
|
|
626
|
+
def _add_mapped_statement(self, statement: MappedStatement) -> None:
|
|
627
|
+
"""
|
|
628
|
+
添加映射语句
|
|
629
|
+
|
|
630
|
+
Args:
|
|
631
|
+
statement: 映射语句
|
|
632
|
+
"""
|
|
633
|
+
key = f"{self.namespace}.{statement.id}" if self.namespace else statement.id
|
|
634
|
+
if not statement.id:
|
|
635
|
+
raise ValueError("Mapper statement 必须设置 id")
|
|
636
|
+
variants = self._statement_variants.setdefault(key, [])
|
|
637
|
+
if any(item.database_id == statement.database_id for item in variants):
|
|
638
|
+
raise ValueError(f"重复的 Mapper statement id/databaseId: {key}/{statement.database_id}")
|
|
639
|
+
variants.append(statement)
|
|
640
|
+
# Keep the generic statement as the compatibility lookup. If there
|
|
641
|
+
# is no generic variant, expose the first database-specific statement.
|
|
642
|
+
if key not in self.mapped_statements or statement.database_id is None:
|
|
643
|
+
self.mapped_statements[key] = statement
|
|
644
|
+
|
|
645
|
+
def _replace_sql_fragments(self) -> None:
|
|
646
|
+
"""
|
|
647
|
+
替换SQL片段引用
|
|
648
|
+
|
|
649
|
+
将<include refid="xxx"/>替换为对应的SQL片段
|
|
650
|
+
"""
|
|
651
|
+
include_pattern = re.compile(
|
|
652
|
+
r'<include\s+([^>]*)>(.*?)</include>|<include\s+([^>]*)/\s*>',
|
|
653
|
+
flags=re.DOTALL,
|
|
654
|
+
)
|
|
655
|
+
|
|
656
|
+
def replace_include(match: re.Match) -> str:
|
|
657
|
+
attributes = match.group(1) or match.group(3) or ''
|
|
658
|
+
content = match.group(2) or ''
|
|
659
|
+
attrs = dict(re.findall(r'(\w+)\s*=\s*["\']([^"\']*)["\']', attributes))
|
|
660
|
+
refid = attrs.get('refid')
|
|
661
|
+
if not refid:
|
|
662
|
+
raise ValueError("<include> 必须设置 refid")
|
|
663
|
+
|
|
664
|
+
fragment = self.sql_fragments.get(refid)
|
|
665
|
+
if fragment is None and self.namespace:
|
|
666
|
+
fragment = self.sql_fragments.get(f"{self.namespace}.{refid}")
|
|
667
|
+
if fragment is None:
|
|
668
|
+
raise ValueError(f"未找到 SQL fragment: {refid}")
|
|
669
|
+
|
|
670
|
+
properties = {}
|
|
671
|
+
for property_attrs in re.findall(
|
|
672
|
+
r'<property\s+([^>]*)/\s*>', content, flags=re.DOTALL
|
|
673
|
+
):
|
|
674
|
+
property_values = dict(re.findall(
|
|
675
|
+
r'(\w+)\s*=\s*["\']([^"\']*)["\']', property_attrs
|
|
676
|
+
))
|
|
677
|
+
name = property_values.get('name')
|
|
678
|
+
if name is not None and 'value' in property_values:
|
|
679
|
+
properties[name] = property_values['value']
|
|
680
|
+
sql = fragment.sql
|
|
681
|
+
for name, value in properties.items():
|
|
682
|
+
sql = sql.replace('${' + name + '}', value)
|
|
683
|
+
return sql
|
|
684
|
+
|
|
685
|
+
for statement in self.get_all_mapped_statements():
|
|
686
|
+
previous = None
|
|
687
|
+
while previous != statement.sql:
|
|
688
|
+
previous = statement.sql
|
|
689
|
+
statement.sql = include_pattern.sub(replace_include, statement.sql)
|
|
690
|
+
if statement.select_key_sql:
|
|
691
|
+
previous = None
|
|
692
|
+
while previous != statement.select_key_sql:
|
|
693
|
+
previous = statement.select_key_sql
|
|
694
|
+
statement.select_key_sql = include_pattern.sub(
|
|
695
|
+
replace_include, statement.select_key_sql
|
|
696
|
+
)
|
|
697
|
+
|
|
698
|
+
def get_mapped_statement(self, id: str) -> Optional[MappedStatement]:
|
|
699
|
+
"""
|
|
700
|
+
获取映射语句
|
|
701
|
+
|
|
702
|
+
Args:
|
|
703
|
+
id: 语句ID
|
|
704
|
+
|
|
705
|
+
Returns:
|
|
706
|
+
映射语句,未找到返回None
|
|
707
|
+
"""
|
|
708
|
+
return self.mapped_statements.get(id)
|
|
709
|
+
|
|
710
|
+
def get_result_map(self, id: str) -> Optional[ResultMap]:
|
|
711
|
+
"""
|
|
712
|
+
获取结果映射
|
|
713
|
+
|
|
714
|
+
Args:
|
|
715
|
+
id: 结果映射ID
|
|
716
|
+
|
|
717
|
+
Returns:
|
|
718
|
+
结果映射,未找到返回None
|
|
719
|
+
"""
|
|
720
|
+
return self.result_maps.get(id)
|
|
721
|
+
|
|
722
|
+
def get_all_mapped_statements(self) -> List[MappedStatement]:
|
|
723
|
+
"""
|
|
724
|
+
获取所有映射语句
|
|
725
|
+
|
|
726
|
+
Returns:
|
|
727
|
+
映射语句列表
|
|
728
|
+
"""
|
|
729
|
+
statements = []
|
|
730
|
+
seen = set()
|
|
731
|
+
for variants in self._statement_variants.values():
|
|
732
|
+
for statement in variants:
|
|
733
|
+
if id(statement) not in seen:
|
|
734
|
+
seen.add(id(statement))
|
|
735
|
+
statements.append(statement)
|
|
736
|
+
return statements
|
|
737
|
+
|
|
738
|
+
def get_all_result_maps(self) -> List[ResultMap]:
|
|
739
|
+
"""
|
|
740
|
+
获取所有结果映射
|
|
741
|
+
|
|
742
|
+
Returns:
|
|
743
|
+
结果映射列表
|
|
744
|
+
"""
|
|
745
|
+
# 使用id()去重,因为同一个对象可能有多个key
|
|
746
|
+
seen = set()
|
|
747
|
+
unique_maps = []
|
|
748
|
+
for rm in self.result_maps.values():
|
|
749
|
+
if id(rm) not in seen:
|
|
750
|
+
seen.add(id(rm))
|
|
751
|
+
unique_maps.append(rm)
|
|
752
|
+
return unique_maps
|
|
753
|
+
|
|
754
|
+
def get_namespace(self) -> Optional[str]:
|
|
755
|
+
"""
|
|
756
|
+
获取命名空间
|
|
757
|
+
|
|
758
|
+
Returns:
|
|
759
|
+
命名空间
|
|
760
|
+
"""
|
|
761
|
+
return self.namespace
|