dblift 2.0.1__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 (408) hide show
  1. api/__init__.py +12 -0
  2. api/_cli_support.py +27 -0
  3. api/_client_factory.py +387 -0
  4. api/_client_operations.py +258 -0
  5. api/_engine_config.py +70 -0
  6. api/async_client.py +138 -0
  7. api/client.py +974 -0
  8. api/events.py +579 -0
  9. api/migrations.py +5 -0
  10. api/py.typed +0 -0
  11. cli/__init__.py +10 -0
  12. cli/_command_handlers.py +94 -0
  13. cli/_config_helpers.py +599 -0
  14. cli/_constants.py +33 -0
  15. cli/_output.py +179 -0
  16. cli/_parser_setup.py +416 -0
  17. cli/db_utils.py +518 -0
  18. cli/extensions.py +56 -0
  19. cli/handlers/__init__.py +11 -0
  20. cli/handlers/_shared.py +132 -0
  21. cli/handlers/baseline.py +18 -0
  22. cli/handlers/clean.py +17 -0
  23. cli/handlers/import_flyway.py +17 -0
  24. cli/handlers/info.py +147 -0
  25. cli/handlers/migrate.py +46 -0
  26. cli/handlers/repair.py +18 -0
  27. cli/handlers/undo.py +32 -0
  28. cli/handlers/validate.py +29 -0
  29. cli/main.py +442 -0
  30. config/__init__.py +10 -0
  31. config/_credential_masking.py +71 -0
  32. config/_subclasses/__init__.py +12 -0
  33. config/_subclasses/cosmosdb_config.py +94 -0
  34. config/_subclasses/db2_config.py +56 -0
  35. config/_subclasses/dummy_config.py +19 -0
  36. config/_subclasses/mysql_config.py +91 -0
  37. config/_subclasses/oracle_config.py +48 -0
  38. config/_subclasses/postgresql_config.py +56 -0
  39. config/_subclasses/sqlite_config.py +88 -0
  40. config/_subclasses/sqlserver_config.py +116 -0
  41. config/_url_builder_mixin.py +119 -0
  42. config/config_builder.py +335 -0
  43. config/database_config.py +615 -0
  44. config/dblift_config.py +1139 -0
  45. config/errors.py +5 -0
  46. config/secrets/__init__.py +16 -0
  47. config/secrets/_cache.py +37 -0
  48. config/secrets/_provider_base.py +27 -0
  49. config/secrets/_registry.py +117 -0
  50. config/secrets/_resolver.py +83 -0
  51. config/secrets/_secrets_config.py +60 -0
  52. config/validation_config.py +124 -0
  53. core/__init__.py +59 -0
  54. core/constants.py +72 -0
  55. core/dialect_boundary.py +275 -0
  56. core/exceptions.py +55 -0
  57. core/features.py +28 -0
  58. core/introspection/__init__.py +23 -0
  59. core/introspection/_column_enricher.py +279 -0
  60. core/introspection/_partition_enricher.py +184 -0
  61. core/introspection/_schema_orchestrator.py +241 -0
  62. core/introspection/_utils.py +22 -0
  63. core/introspection/_vendor_property_applier.py +70 -0
  64. core/introspection/base_introspector.py +889 -0
  65. core/introspection/capability_matrix.py +276 -0
  66. core/introspection/extractors/__init__.py +25 -0
  67. core/introspection/extractors/base_extractor.py +142 -0
  68. core/introspection/extractors/column_extractor.py +230 -0
  69. core/introspection/extractors/constraint_extractor.py +598 -0
  70. core/introspection/extractors/index_extractor.py +344 -0
  71. core/introspection/extractors/misc_extractor.py +730 -0
  72. core/introspection/extractors/procedure_extractor.py +821 -0
  73. core/introspection/extractors/sequence_extractor.py +138 -0
  74. core/introspection/extractors/table_extractor.py +467 -0
  75. core/introspection/extractors/trigger_extractor.py +170 -0
  76. core/introspection/extractors/view_extractor.py +323 -0
  77. core/introspection/introspector_factory.py +136 -0
  78. core/introspection/result.py +204 -0
  79. core/introspection/schema_introspector.py +16 -0
  80. core/introspection/vendor_queries_base.py +924 -0
  81. core/introspection/vendor_queries_factory.py +153 -0
  82. core/introspection/vendor_queries_protocols.py +225 -0
  83. core/introspection/version_detector.py +279 -0
  84. core/logger/__init__.py +448 -0
  85. core/logger/_base.py +145 -0
  86. core/logger/_factory.py +239 -0
  87. core/logger/_formatters.py +120 -0
  88. core/logger/_levels.py +112 -0
  89. core/logger/_multi.py +135 -0
  90. core/logger/_null.py +66 -0
  91. core/logger/console.py +195 -0
  92. core/logger/formatters/__init__.py +30 -0
  93. core/logger/formatters/_format_diff_meta.py +117 -0
  94. core/logger/formatters/_format_diff_object.py +195 -0
  95. core/logger/formatters/_format_diff_routine.py +208 -0
  96. core/logger/formatters/_format_diff_table.py +250 -0
  97. core/logger/formatters/_formatter_impl.py +567 -0
  98. core/logger/formatters/diff_utils.py +396 -0
  99. core/logger/formatters/factory.py +104 -0
  100. core/logger/formatters/formatter.py +12 -0
  101. core/logger/formatters/htmlformatter.py +876 -0
  102. core/logger/formatters/jsonformatter.py +978 -0
  103. core/logger/log.py +782 -0
  104. core/logger/results.py +904 -0
  105. core/migration/__init__.py +33 -0
  106. core/migration/_type_match.py +111 -0
  107. core/migration/clean_summary.py +86 -0
  108. core/migration/commands/__init__.py +6 -0
  109. core/migration/commands/_script_events.py +18 -0
  110. core/migration/commands/base_command.py +1025 -0
  111. core/migration/commands/baseline_command.py +96 -0
  112. core/migration/commands/clean_command.py +342 -0
  113. core/migration/commands/import_flyway_command.py +190 -0
  114. core/migration/commands/info_command.py +208 -0
  115. core/migration/commands/migrate_command.py +853 -0
  116. core/migration/commands/repair_command.py +769 -0
  117. core/migration/commands/undo_command.py +869 -0
  118. core/migration/commands/validate_command.py +90 -0
  119. core/migration/encoding.py +90 -0
  120. core/migration/executor/__init__.py +12 -0
  121. core/migration/executor/execution_engine.py +1196 -0
  122. core/migration/executor/migration_executor.py +439 -0
  123. core/migration/executor/migration_helpers.py +142 -0
  124. core/migration/executor/placeholder_manager.py +77 -0
  125. core/migration/executor/transaction_policy.py +63 -0
  126. core/migration/executors/__init__.py +20 -0
  127. core/migration/executors/base_executor.py +179 -0
  128. core/migration/executors/executor_factory.py +251 -0
  129. core/migration/executors/python_executor.py +379 -0
  130. core/migration/executors/sql_executor.py +273 -0
  131. core/migration/formats/__init__.py +14 -0
  132. core/migration/formats/format_detector.py +204 -0
  133. core/migration/formats/migration_format.py +67 -0
  134. core/migration/history/__init__.py +1 -0
  135. core/migration/history/migration_history_manager.py +384 -0
  136. core/migration/journals/__init__.py +14 -0
  137. core/migration/journals/migration_journal.py +618 -0
  138. core/migration/migration.py +775 -0
  139. core/migration/placeholders/__init__.py +1 -0
  140. core/migration/placeholders/placeholder_service.py +84 -0
  141. core/migration/rules/__init__.py +1 -0
  142. core/migration/rules/migration_rules.py +145 -0
  143. core/migration/scripting/__init__.py +1 -0
  144. core/migration/scripting/migration_script_manager.py +721 -0
  145. core/migration/scripting/undo_script_generator/__init__.py +24 -0
  146. core/migration/scripting/undo_script_generator/_ddl_reversers.py +485 -0
  147. core/migration/scripting/undo_script_generator/_dml_reversers.py +319 -0
  148. core/migration/scripting/undo_script_generator/_extractors.py +511 -0
  149. core/migration/scripting/undo_script_generator/_generator.py +310 -0
  150. core/migration/scripting/undo_script_generator/_helpers.py +532 -0
  151. core/migration/scripting/undo_script_generator/_models.py +18 -0
  152. core/migration/scripting/undo_script_generator/_reversers.py +864 -0
  153. core/migration/sql/__init__.py +1 -0
  154. core/migration/sql/execution_statement.py +38 -0
  155. core/migration/sql/sql_analyzer.py +806 -0
  156. core/migration/sql/sql_execution_service.py +431 -0
  157. core/migration/sql/statement_splitter.py +74 -0
  158. core/migration/state/__init__.py +5 -0
  159. core/migration/state/migration_data_service.py +603 -0
  160. core/migration/state/migration_display_state.py +36 -0
  161. core/migration/state/migration_formatter.py +215 -0
  162. core/migration/state/migration_state.py +166 -0
  163. core/migration/state/migration_state_manager.py +830 -0
  164. core/migration/state/migration_state_service.py +244 -0
  165. core/migration/ui/__init__.py +15 -0
  166. core/migration/ui/data_collector.py +926 -0
  167. core/migration/ui/display_formatters.py +251 -0
  168. core/migration/ui/migration_analyzer.py +259 -0
  169. core/migration/ui/migration_ui.py +362 -0
  170. core/migration/ui/table_renderer.py +250 -0
  171. core/migration/version_utils.py +94 -0
  172. core/normalization/__init__.py +35 -0
  173. core/normalization/data_type_normalizer.py +5 -0
  174. core/normalization/dependency_resolver.py +435 -0
  175. core/normalization/identifier_normalizer.py +297 -0
  176. core/normalization/object_orderer.py +230 -0
  177. core/normalization/type_constants.py +154 -0
  178. core/normalization/type_mapper.py +220 -0
  179. core/normalization/type_mappings.py +89 -0
  180. core/normalization/type_normalizer.py +245 -0
  181. core/seams/__init__.py +1 -0
  182. core/seams/event_listeners.py +26 -0
  183. core/seams/introspection.py +18 -0
  184. core/sql_model/__init__.py +60 -0
  185. core/sql_model/_base_parse_result.py +491 -0
  186. core/sql_model/_base_sql_column.py +194 -0
  187. core/sql_model/_base_sql_constraint.py +223 -0
  188. core/sql_model/_base_sql_object.py +233 -0
  189. core/sql_model/_base_sql_statement.py +100 -0
  190. core/sql_model/base.py +43 -0
  191. core/sql_model/constraint_validator.py +469 -0
  192. core/sql_model/database_link.py +151 -0
  193. core/sql_model/dialect.py +482 -0
  194. core/sql_model/event.py +155 -0
  195. core/sql_model/extension.py +109 -0
  196. core/sql_model/foreign_data_wrapper.py +113 -0
  197. core/sql_model/foreign_server.py +138 -0
  198. core/sql_model/index.py +279 -0
  199. core/sql_model/linked_server.py +170 -0
  200. core/sql_model/module.py +112 -0
  201. core/sql_model/package.py +134 -0
  202. core/sql_model/partition.py +180 -0
  203. core/sql_model/procedure.py +321 -0
  204. core/sql_model/sequence.py +208 -0
  205. core/sql_model/synonym.py +143 -0
  206. core/sql_model/table.py +1106 -0
  207. core/sql_model/table_canonicalizer.py +45 -0
  208. core/sql_model/table_options.py +73 -0
  209. core/sql_model/trigger.py +310 -0
  210. core/sql_model/user_defined_type.py +151 -0
  211. core/sql_model/view.py +429 -0
  212. core/sql_model/view_options.py +95 -0
  213. core/sql_parser/__init__.py +5 -0
  214. core/sql_parser/_partition_handler.py +113 -0
  215. core/sql_parser/_sqlglot_builders.py +634 -0
  216. core/sql_parser/base_statement_parser.py +356 -0
  217. core/sql_parser/base_tokenizer.py +473 -0
  218. core/sql_parser/common/__init__.py +1 -0
  219. core/sql_parser/common/base_parser.py +598 -0
  220. core/sql_parser/dialects/__init__.py +5 -0
  221. core/sql_parser/dialects/base_config.py +125 -0
  222. core/sql_parser/enhanced_regex_parser.py +453 -0
  223. core/sql_parser/hybrid_parser.py +1106 -0
  224. core/sql_parser/parser_context.py +89 -0
  225. core/sql_parser/parser_factory.py +215 -0
  226. core/sql_parser/parser_interface.py +93 -0
  227. core/sql_parser/sqlglot_parser.py +544 -0
  228. core/sql_parser/tokens.py +48 -0
  229. core/sql_parser/unified_regex_parser.py +644 -0
  230. core/sql_validator/__init__.py +5 -0
  231. core/sql_validator/_checksum_validator.py +341 -0
  232. core/sql_validator/_flyway_compatibility.py +301 -0
  233. core/sql_validator/_migration_filter.py +221 -0
  234. core/sql_validator/_sql_syntax_validator.py +168 -0
  235. core/sql_validator/_strict_mode_validator.py +136 -0
  236. core/sql_validator/migration_validator.py +923 -0
  237. core/state/sql_script_formatter.py +144 -0
  238. core/state/sql_statement.py +41 -0
  239. core/utils/__init__.py +1 -0
  240. core/utils/database_url_parser.py +104 -0
  241. core/utils/metadata_helpers.py +83 -0
  242. core/utils/row_access.py +192 -0
  243. core/utils/string_utils.py +21 -0
  244. core/utils/url_masking.py +24 -0
  245. db/__init__.py +21 -0
  246. db/base_provider.py +266 -0
  247. db/base_quirks.py +1620 -0
  248. db/constants.py +5 -0
  249. db/error.py +344 -0
  250. db/error_handler.py +349 -0
  251. db/exceptions.py +11 -0
  252. db/native_connection_manager.py +70 -0
  253. db/object_naming.py +39 -0
  254. db/plugins/__init__.py +9 -0
  255. db/plugins/base_history_manager.py +616 -0
  256. db/plugins/base_locking_manager.py +154 -0
  257. db/plugins/base_query_executor.py +253 -0
  258. db/plugins/base_schema_operations.py +288 -0
  259. db/plugins/base_snapshot_manager.py +172 -0
  260. db/plugins/base_undo_manager.py +126 -0
  261. db/plugins/cosmosdb/__init__.py +12 -0
  262. db/plugins/cosmosdb/cosmosdb/__init__.py +19 -0
  263. db/plugins/cosmosdb/cosmosdb/_sdk.py +28 -0
  264. db/plugins/cosmosdb/cosmosdb/connection_manager.py +247 -0
  265. db/plugins/cosmosdb/cosmosdb/history_manager.py +427 -0
  266. db/plugins/cosmosdb/cosmosdb/locking_manager.py +465 -0
  267. db/plugins/cosmosdb/cosmosdb/query_executor.py +1518 -0
  268. db/plugins/cosmosdb/cosmosdb/schema_operations.py +416 -0
  269. db/plugins/cosmosdb/introspection/__init__.py +3 -0
  270. db/plugins/cosmosdb/parser/__init__.py +5 -0
  271. db/plugins/cosmosdb/parser/cosmosdb_regex_parser.py +64 -0
  272. db/plugins/cosmosdb/plugin.py +19 -0
  273. db/plugins/cosmosdb/provider.py +364 -0
  274. db/plugins/cosmosdb/quirks.py +240 -0
  275. db/plugins/cosmosdb/sdk_translator/__init__.py +39 -0
  276. db/plugins/cosmosdb/sdk_translator/_executor.py +422 -0
  277. db/plugins/cosmosdb/sdk_translator/_executors.py +356 -0
  278. db/plugins/cosmosdb/sdk_translator/_parsing.py +53 -0
  279. db/plugins/cosmosdb/sdk_translator/_script_generation.py +141 -0
  280. db/plugins/cosmosdb/sdk_translator/_translator.py +129 -0
  281. db/plugins/cosmosdb/sdk_translator/_translators.py +795 -0
  282. db/plugins/db2/__init__.py +11 -0
  283. db/plugins/db2/db2/__init__.py +11 -0
  284. db/plugins/db2/db2/history_manager.py +317 -0
  285. db/plugins/db2/db2/locking_manager.py +558 -0
  286. db/plugins/db2/db2/schema_operations.py +985 -0
  287. db/plugins/db2/introspection/__init__.py +3 -0
  288. db/plugins/db2/parser/__init__.py +9 -0
  289. db/plugins/db2/parser/db2_regex_parser.py +668 -0
  290. db/plugins/db2/parser/parser_config.py +1147 -0
  291. db/plugins/db2/plugin.py +22 -0
  292. db/plugins/db2/provider.py +420 -0
  293. db/plugins/db2/quirks.py +382 -0
  294. db/plugins/db2/sqlalchemy_url.py +62 -0
  295. db/plugins/mariadb/__init__.py +13 -0
  296. db/plugins/mariadb/plugin.py +21 -0
  297. db/plugins/mariadb/provider.py +40 -0
  298. db/plugins/mariadb/quirks.py +50 -0
  299. db/plugins/mysql/__init__.py +11 -0
  300. db/plugins/mysql/introspection/__init__.py +1 -0
  301. db/plugins/mysql/mysql/__init__.py +11 -0
  302. db/plugins/mysql/mysql/history_manager.py +384 -0
  303. db/plugins/mysql/mysql/locking_manager.py +371 -0
  304. db/plugins/mysql/mysql/schema_operations.py +555 -0
  305. db/plugins/mysql/parser/__init__.py +1 -0
  306. db/plugins/mysql/parser/mysql_regex_parser.py +531 -0
  307. db/plugins/mysql/parser/mysql_statement_parser.py +255 -0
  308. db/plugins/mysql/parser/mysql_tokenizer.py +460 -0
  309. db/plugins/mysql/parser/parser_config.py +670 -0
  310. db/plugins/mysql/plugin.py +22 -0
  311. db/plugins/mysql/provider.py +329 -0
  312. db/plugins/mysql/quirks.py +540 -0
  313. db/plugins/mysql/sqlalchemy_url.py +67 -0
  314. db/plugins/oracle/__init__.py +11 -0
  315. db/plugins/oracle/introspection/__init__.py +3 -0
  316. db/plugins/oracle/introspection/oracle_utils.py +86 -0
  317. db/plugins/oracle/oracle/__init__.py +14 -0
  318. db/plugins/oracle/oracle/dbms_output.py +49 -0
  319. db/plugins/oracle/oracle/history_manager.py +444 -0
  320. db/plugins/oracle/oracle/locking_manager.py +436 -0
  321. db/plugins/oracle/oracle/schema_operations.py +1134 -0
  322. db/plugins/oracle/parser/__init__.py +13 -0
  323. db/plugins/oracle/parser/_comments.py +57 -0
  324. db/plugins/oracle/parser/_object_extractor.py +203 -0
  325. db/plugins/oracle/parser/_plsql_block.py +727 -0
  326. db/plugins/oracle/parser/_sqlplus.py +312 -0
  327. db/plugins/oracle/parser/_statement_splitter.py +233 -0
  328. db/plugins/oracle/parser/oracle_parser.py +297 -0
  329. db/plugins/oracle/parser/oracle_statement_parser.py +434 -0
  330. db/plugins/oracle/parser/oracle_tokenizer.py +251 -0
  331. db/plugins/oracle/parser/parser_config.py +330 -0
  332. db/plugins/oracle/parser/sqlplus_context.py +179 -0
  333. db/plugins/oracle/plugin.py +22 -0
  334. db/plugins/oracle/provider.py +736 -0
  335. db/plugins/oracle/quirks.py +743 -0
  336. db/plugins/oracle/sqlalchemy_url.py +65 -0
  337. db/plugins/postgresql/__init__.py +12 -0
  338. db/plugins/postgresql/_provider_query_executor.py +25 -0
  339. db/plugins/postgresql/introspection/__init__.py +3 -0
  340. db/plugins/postgresql/parser/__init__.py +10 -0
  341. db/plugins/postgresql/parser/parser_config.py +876 -0
  342. db/plugins/postgresql/parser/postgresql_regex_parser.py +691 -0
  343. db/plugins/postgresql/parser/postgresql_statement_parser.py +118 -0
  344. db/plugins/postgresql/parser/postgresql_tokenizer.py +325 -0
  345. db/plugins/postgresql/plugin.py +29 -0
  346. db/plugins/postgresql/postgresql/__init__.py +11 -0
  347. db/plugins/postgresql/postgresql/history_manager.py +231 -0
  348. db/plugins/postgresql/postgresql/locking_manager.py +310 -0
  349. db/plugins/postgresql/postgresql/schema_operations.py +690 -0
  350. db/plugins/postgresql/provider.py +248 -0
  351. db/plugins/postgresql/quirks.py +722 -0
  352. db/plugins/postgresql/sqlalchemy_url.py +65 -0
  353. db/plugins/sqlite/__init__.py +12 -0
  354. db/plugins/sqlite/introspection/__init__.py +3 -0
  355. db/plugins/sqlite/parser/__init__.py +9 -0
  356. db/plugins/sqlite/parser/parser_config.py +377 -0
  357. db/plugins/sqlite/parser/sqlite_regex_parser.py +338 -0
  358. db/plugins/sqlite/plugin.py +21 -0
  359. db/plugins/sqlite/provider.py +504 -0
  360. db/plugins/sqlite/quirks.py +96 -0
  361. db/plugins/sqlite/sqlalchemy_url.py +16 -0
  362. db/plugins/sqlite/sqlite/__init__.py +24 -0
  363. db/plugins/sqlite/sqlite/connection_manager.py +162 -0
  364. db/plugins/sqlite/sqlite/history_manager.py +233 -0
  365. db/plugins/sqlite/sqlite/locking_manager.py +216 -0
  366. db/plugins/sqlite/sqlite/query_executor.py +243 -0
  367. db/plugins/sqlite/sqlite/schema_operations.py +303 -0
  368. db/plugins/sqlserver/__init__.py +11 -0
  369. db/plugins/sqlserver/introspection/__init__.py +3 -0
  370. db/plugins/sqlserver/parser/__init__.py +9 -0
  371. db/plugins/sqlserver/parser/parser_config.py +604 -0
  372. db/plugins/sqlserver/parser/sqlserver_regex_parser.py +368 -0
  373. db/plugins/sqlserver/parser/sqlserver_statement_parser.py +173 -0
  374. db/plugins/sqlserver/parser/sqlserver_tokenizer.py +238 -0
  375. db/plugins/sqlserver/parser/tsql_batch_separator.py +14 -0
  376. db/plugins/sqlserver/plugin.py +22 -0
  377. db/plugins/sqlserver/provider.py +615 -0
  378. db/plugins/sqlserver/quirks.py +489 -0
  379. db/plugins/sqlserver/sqlalchemy_url.py +120 -0
  380. db/plugins/sqlserver/sqlserver/__init__.py +11 -0
  381. db/plugins/sqlserver/sqlserver/history_manager.py +382 -0
  382. db/plugins/sqlserver/sqlserver/locking_manager.py +328 -0
  383. db/plugins/sqlserver/sqlserver/schema_operations.py +524 -0
  384. db/provider_capabilities.py +85 -0
  385. db/provider_interfaces.py +347 -0
  386. db/provider_registry.py +623 -0
  387. db/sqlalchemy_provider.py +340 -0
  388. db/value_utils.py +10 -0
  389. dblift-2.0.1.dist-info/METADATA +1075 -0
  390. dblift-2.0.1.dist-info/RECORD +408 -0
  391. dblift-2.0.1.dist-info/WHEEL +5 -0
  392. dblift-2.0.1.dist-info/entry_points.txt +12 -0
  393. dblift-2.0.1.dist-info/licenses/LICENSE +201 -0
  394. dblift-2.0.1.dist-info/top_level.txt +6 -0
  395. integrations/__init__.py +30 -0
  396. integrations/django/__init__.py +1 -0
  397. integrations/django/_client.py +54 -0
  398. integrations/django/_engine.py +53 -0
  399. integrations/django/apps.py +14 -0
  400. integrations/django/checks.py +35 -0
  401. integrations/django/management/__init__.py +1 -0
  402. integrations/django/management/commands/__init__.py +1 -0
  403. integrations/django/management/commands/dblift_info.py +25 -0
  404. integrations/django/management/commands/dblift_migrate.py +24 -0
  405. integrations/django/management/commands/dblift_validate.py +24 -0
  406. integrations/fastapi.py +153 -0
  407. integrations/flask.py +82 -0
  408. integrations/opentelemetry.py +155 -0
@@ -0,0 +1,1147 @@
1
+ """DB2 dialect configuration for regex-based SQL parsing.
2
+
3
+ This module provides DB2-specific patterns and configuration for the regex parser,
4
+ extracted from DB2 grammar files and existing parser implementation.
5
+ """
6
+
7
+ import re
8
+ from typing import Any, Dict, List, Pattern, Set
9
+
10
+ from core.sql_parser.dialects.base_config import DialectConfig
11
+
12
+
13
+ class DB2Config(DialectConfig):
14
+ """DB2 dialect configuration with comprehensive regex patterns."""
15
+
16
+ dialect_name = "db2" # lint: allow-dialect-string: dialect dispatch
17
+
18
+ def __init__(self) -> None:
19
+ """Initialize DB2 dialect configuration."""
20
+ super().__init__() # type: ignore[no-untyped-call]
21
+ self._compile_patterns()
22
+
23
+ def _compile_patterns(self) -> None:
24
+ """Compile all DB2-specific regex patterns."""
25
+ self._ddl_patterns = self._compile_ddl_patterns()
26
+ self._dml_patterns = self._compile_dml_patterns()
27
+ self._query_patterns = self._compile_query_patterns()
28
+ self._object_patterns = self._compile_object_patterns()
29
+ self._comment_patterns = self._compile_comment_patterns()
30
+ self._batch_separators = self._compile_batch_separators()
31
+
32
+ def _compile_ddl_patterns(self) -> Dict[str, Pattern[str]]:
33
+ """Compile DB2 DDL patterns."""
34
+ return {
35
+ # CREATE statements
36
+ # Grammar-based: CREATE [GLOBAL TEMPORARY | AUXILIARY] TABLE
37
+ # Note: DB2 z/OS grammar does not have IF NOT EXISTS for tables
38
+ "create_table": re.compile(
39
+ r"\b(?:CREATE)\s+(?:GLOBAL\s+TEMPORARY\s+|AUXILIARY\s+)?TABLE\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
40
+ re.IGNORECASE,
41
+ ),
42
+ # Grammar-based: CREATE VIEW (no OR REPLACE or IF NOT EXISTS in DB2 z/OS grammar)
43
+ "create_view": re.compile(
44
+ r"\b(?:CREATE)\s+VIEW\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
45
+ re.IGNORECASE,
46
+ ),
47
+ # Grammar-based: CREATE [TYPE n] [UNIQUE [WHERE NOT NULL]] INDEX
48
+ # DB2-specific: TYPE 1/2 (deprecated), UNIQUE WHERE NOT NULL (partial unique indexes)
49
+ "create_index": re.compile(
50
+ r"\b(?:CREATE)\s+(?:TYPE\s+\d+\s+)?(?:UNIQUE(?:\s+WHERE\s+NOT\s+NULL)?\s+)?INDEX\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+)\s+ON\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
51
+ re.IGNORECASE,
52
+ ),
53
+ # Grammar-based: CREATE SEQUENCE (no OR REPLACE or IF NOT EXISTS)
54
+ "create_sequence": re.compile(
55
+ r"\b(?:CREATE)\s+SEQUENCE\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
56
+ re.IGNORECASE,
57
+ ),
58
+ # Grammar-based: CREATE [OR REPLACE] PROCEDURE
59
+ # Supports VERSION option and WRAPPED code
60
+ "create_procedure": re.compile(
61
+ r"\b(?:CREATE)\s+(?:OR\s+REPLACE\s+)?PROCEDURE\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
62
+ re.IGNORECASE,
63
+ ),
64
+ # Grammar-based: CREATE FUNCTION (no OR REPLACE in DB2 z/OS)
65
+ # Note: Functions don't support OR REPLACE per grammar, only procedures/triggers do
66
+ "create_function": re.compile(
67
+ r"\b(?:CREATE)\s+FUNCTION\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
68
+ re.IGNORECASE,
69
+ ),
70
+ # Grammar-based: CREATE [OR REPLACE] TRIGGER
71
+ # Advanced triggers support OR REPLACE
72
+ "create_trigger": re.compile(
73
+ r"\b(?:CREATE)\s+(?:OR\s+REPLACE\s+)?TRIGGER\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
74
+ re.IGNORECASE,
75
+ ),
76
+ "create_database": re.compile(
77
+ r"\b(?:CREATE)\s+DATABASE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+)",
78
+ re.IGNORECASE,
79
+ ),
80
+ "create_tablespace": re.compile(
81
+ r"\b(?:CREATE)\s+(?:LOB\s+)?TABLESPACE\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+)",
82
+ re.IGNORECASE,
83
+ ),
84
+ "create_stogroup": re.compile(
85
+ r"\b(?:CREATE)\s+STOGROUP\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+)", re.IGNORECASE
86
+ ),
87
+ "create_alias": re.compile(
88
+ r"\b(?:CREATE)\s+ALIAS\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
89
+ re.IGNORECASE,
90
+ ),
91
+ "create_role": re.compile(
92
+ r"\b(?:CREATE)\s+ROLE\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+)", re.IGNORECASE
93
+ ),
94
+ "create_mask": re.compile(
95
+ r"\b(?:CREATE)\s+(?:OR\s+REPLACE\s+)?MASK\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
96
+ re.IGNORECASE,
97
+ ),
98
+ "create_permission": re.compile(
99
+ r"\b(?:CREATE)\s+(?:OR\s+REPLACE\s+)?PERMISSION\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
100
+ re.IGNORECASE,
101
+ ),
102
+ "create_trusted_context": re.compile(
103
+ r"\b(?:CREATE)\s+TRUSTED\s+CONTEXT\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+)", re.IGNORECASE
104
+ ),
105
+ "create_type": re.compile(
106
+ r"\b(?:CREATE)\s+(?:OR\s+REPLACE\s+)?TYPE\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
107
+ re.IGNORECASE,
108
+ ),
109
+ "create_variable": re.compile(
110
+ r"\b(?:CREATE)\s+(?:OR\s+REPLACE\s+)?VARIABLE\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
111
+ re.IGNORECASE,
112
+ ),
113
+ "create_synonym": re.compile(
114
+ r"\b(?:CREATE)\s+(?:OR\s+REPLACE\s+)?(?:SYNONYM|ALIAS)\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
115
+ re.IGNORECASE,
116
+ ),
117
+ "create_module": re.compile(
118
+ r"\b(?:CREATE)\s+(?:OR\s+REPLACE\s+)?MODULE\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
119
+ re.IGNORECASE,
120
+ ),
121
+ "drop_module": re.compile(
122
+ r"\b(?:DROP)\s+MODULE\s+(?:IF\s+EXISTS\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
123
+ re.IGNORECASE,
124
+ ),
125
+ # ALTER statements
126
+ "alter_table": re.compile(
127
+ r"\b(?:ALTER)\s+TABLE\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
128
+ re.IGNORECASE,
129
+ ),
130
+ "alter_view": re.compile(
131
+ r"\b(?:ALTER)\s+VIEW\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
132
+ re.IGNORECASE,
133
+ ),
134
+ "alter_index": re.compile(
135
+ r"\b(?:ALTER)\s+INDEX\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
136
+ re.IGNORECASE,
137
+ ),
138
+ "alter_sequence": re.compile(
139
+ r"\b(?:ALTER)\s+SEQUENCE\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
140
+ re.IGNORECASE,
141
+ ),
142
+ "alter_procedure": re.compile(
143
+ r"\b(?:ALTER)\s+PROCEDURE\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
144
+ re.IGNORECASE,
145
+ ),
146
+ "alter_function": re.compile(
147
+ r"\b(?:ALTER)\s+FUNCTION\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
148
+ re.IGNORECASE,
149
+ ),
150
+ "alter_trigger": re.compile(
151
+ r"\b(?:ALTER)\s+TRIGGER\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
152
+ re.IGNORECASE,
153
+ ),
154
+ "alter_database": re.compile(
155
+ r"\b(?:ALTER)\s+DATABASE\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+)", re.IGNORECASE
156
+ ),
157
+ "alter_tablespace": re.compile(
158
+ r"\b(?:ALTER)\s+TABLESPACE\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+)", re.IGNORECASE
159
+ ),
160
+ "alter_stogroup": re.compile(
161
+ r"\b(?:ALTER)\s+STOGROUP\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+)", re.IGNORECASE
162
+ ),
163
+ "alter_mask": re.compile(
164
+ r"\b(?:ALTER)\s+MASK\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
165
+ re.IGNORECASE,
166
+ ),
167
+ "alter_permission": re.compile(
168
+ r"\b(?:ALTER)\s+PERMISSION\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
169
+ re.IGNORECASE,
170
+ ),
171
+ "alter_trusted_context": re.compile(
172
+ r"\b(?:ALTER)\s+TRUSTED\s+CONTEXT\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+)", re.IGNORECASE
173
+ ),
174
+ # DROP statements
175
+ "drop_table": re.compile(
176
+ r"\b(?:DROP)\s+TABLE\s+(?:IF\s+EXISTS\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
177
+ re.IGNORECASE,
178
+ ),
179
+ "drop_view": re.compile(
180
+ r"\b(?:DROP)\s+VIEW\s+(?:IF\s+EXISTS\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
181
+ re.IGNORECASE,
182
+ ),
183
+ "drop_index": re.compile(
184
+ r"\b(?:DROP)\s+INDEX\s+(?:IF\s+EXISTS\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
185
+ re.IGNORECASE,
186
+ ),
187
+ "drop_sequence": re.compile(
188
+ r"\b(?:DROP)\s+SEQUENCE\s+(?:IF\s+EXISTS\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
189
+ re.IGNORECASE,
190
+ ),
191
+ "drop_procedure": re.compile(
192
+ r"\b(?:DROP)\s+PROCEDURE\s+(?:IF\s+EXISTS\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
193
+ re.IGNORECASE,
194
+ ),
195
+ "drop_function": re.compile(
196
+ r"\b(?:DROP)\s+FUNCTION\s+(?:IF\s+EXISTS\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
197
+ re.IGNORECASE,
198
+ ),
199
+ "drop_trigger": re.compile(
200
+ r"\b(?:DROP)\s+TRIGGER\s+(?:IF\s+EXISTS\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
201
+ re.IGNORECASE,
202
+ ),
203
+ "drop_database": re.compile(
204
+ r"\b(?:DROP)\s+DATABASE\s+(?:IF\s+EXISTS\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+)",
205
+ re.IGNORECASE,
206
+ ),
207
+ "drop_tablespace": re.compile(
208
+ r"\b(?:DROP)\s+TABLESPACE\s+(?:IF\s+EXISTS\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+)",
209
+ re.IGNORECASE,
210
+ ),
211
+ "drop_stogroup": re.compile(
212
+ r"\b(?:DROP)\s+STOGROUP\s+(?:IF\s+EXISTS\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+)",
213
+ re.IGNORECASE,
214
+ ),
215
+ "drop_alias": re.compile(
216
+ r"\b(?:DROP)\s+ALIAS\s+(?:IF\s+EXISTS\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
217
+ re.IGNORECASE,
218
+ ),
219
+ "drop_role": re.compile(
220
+ r"\b(?:DROP)\s+ROLE\s+(?:IF\s+EXISTS\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+)",
221
+ re.IGNORECASE,
222
+ ),
223
+ "drop_mask": re.compile(
224
+ r"\b(?:DROP)\s+MASK\s+(?:IF\s+EXISTS\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
225
+ re.IGNORECASE,
226
+ ),
227
+ "drop_permission": re.compile(
228
+ r"\b(?:DROP)\s+PERMISSION\s+(?:IF\s+EXISTS\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
229
+ re.IGNORECASE,
230
+ ),
231
+ "drop_trusted_context": re.compile(
232
+ r"\b(?:DROP)\s+TRUSTED\s+CONTEXT\s+(?:IF\s+EXISTS\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+)",
233
+ re.IGNORECASE,
234
+ ),
235
+ "drop_type": re.compile(
236
+ r"\b(?:DROP)\s+TYPE\s+(?:IF\s+EXISTS\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
237
+ re.IGNORECASE,
238
+ ),
239
+ "drop_variable": re.compile(
240
+ r"\b(?:DROP)\s+VARIABLE\s+(?:IF\s+EXISTS\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
241
+ re.IGNORECASE,
242
+ ),
243
+ "drop_synonym": re.compile(
244
+ r"\b(?:DROP)\s+SYNONYM\s+(?:IF\s+EXISTS\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
245
+ re.IGNORECASE,
246
+ ),
247
+ # Other DDL statements
248
+ "truncate_table": re.compile(
249
+ r"\b(?:TRUNCATE)\s+(?:TABLE\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
250
+ re.IGNORECASE,
251
+ ),
252
+ "comment": re.compile(
253
+ r"\b(?:COMMENT)\s+ON\s+(?:TABLE|VIEW|COLUMN|INDEX|SEQUENCE|PROCEDURE|FUNCTION|TRIGGER)\s+",
254
+ re.IGNORECASE,
255
+ ),
256
+ "grant": re.compile(r"\b(?:GRANT)\s+", re.IGNORECASE),
257
+ "revoke": re.compile(r"\b(?:REVOKE)\s+", re.IGNORECASE),
258
+ }
259
+
260
+ def _compile_dml_patterns(self) -> Dict[str, Pattern[str]]:
261
+ """Compile DB2 DML patterns."""
262
+ return {
263
+ "insert": re.compile(
264
+ r"\b(?:INSERT)\s+(?:INTO\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
265
+ re.IGNORECASE,
266
+ ),
267
+ "update": re.compile(
268
+ r"\b(?:UPDATE)\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
269
+ re.IGNORECASE,
270
+ ),
271
+ "delete": re.compile(
272
+ r"\b(?:DELETE)\s+(?:FROM\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
273
+ re.IGNORECASE,
274
+ ),
275
+ "merge": re.compile(
276
+ r"\b(?:MERGE)\s+(?:INTO\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
277
+ re.IGNORECASE,
278
+ ),
279
+ "call": re.compile(
280
+ r"\b(?:CALL)\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
281
+ re.IGNORECASE,
282
+ ),
283
+ "set": re.compile(r"\b(?:SET)\s+(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+)", re.IGNORECASE),
284
+ "values": re.compile(r"\b(?:VALUES)\s+", re.IGNORECASE),
285
+ }
286
+
287
+ def _compile_query_patterns(self) -> Dict[str, Pattern[str]]:
288
+ """Compile DB2 query patterns."""
289
+ return {
290
+ "select": re.compile(r"\b(?:SELECT)\s+(?:DISTINCT\s+|ALL\s+)?", re.IGNORECASE),
291
+ "with": re.compile(r"\b(?:WITH)\s+(?:RECURSIVE\s+)?", re.IGNORECASE),
292
+ "explain": re.compile(r"\b(?:EXPLAIN)\s+(?:PLAN\s+)?(?:FOR\s+)?", re.IGNORECASE),
293
+ "describe": re.compile(
294
+ r"\b(?:DESCRIBE)\s+(?:TABLE\s+)?(?:\"[^\"]+\"|[a-zA-Z0-9_$#@]+(?:\.[a-zA-Z0-9_$#@]+)?)",
295
+ re.IGNORECASE,
296
+ ),
297
+ "show": re.compile(r"\b(?:SHOW)\s+", re.IGNORECASE),
298
+ }
299
+
300
+ def _compile_object_patterns(self) -> Dict[str, Pattern[str]]:
301
+ """Compile DB2 object extraction patterns."""
302
+ return {
303
+ "table": re.compile(
304
+ r"\b(?:CREATE|DROP|ALTER)\s+(?:GLOBAL\s+TEMPORARY\s+|AUXILIARY\s+)?TABLE\s+(?:IF\s+(?:NOT\s+)?EXISTS\s+)?(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+))(?:\.(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+)))?",
305
+ re.IGNORECASE,
306
+ ),
307
+ "view": re.compile(
308
+ r"\b(?:CREATE|DROP|ALTER)\s+(?:OR\s+REPLACE\s+)?VIEW\s+(?:IF\s+(?:NOT\s+)?EXISTS\s+)?(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+))(?:\.(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+)))?",
309
+ re.IGNORECASE,
310
+ ),
311
+ "index": re.compile(
312
+ r"\b(?:CREATE|DROP|ALTER)\s+(?:UNIQUE\s+)?INDEX\s+(?:IF\s+(?:NOT\s+)?EXISTS\s+)?(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+))(?:\s+ON\s+(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+))(?:\.(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+)))?)?",
313
+ re.IGNORECASE,
314
+ ),
315
+ "sequence": re.compile(
316
+ r"\b(?:CREATE|DROP|ALTER)\s+(?:OR\s+REPLACE\s+)?SEQUENCE\s+(?:IF\s+(?:NOT\s+)?EXISTS\s+)?(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+))(?:\.(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+)))?",
317
+ re.IGNORECASE,
318
+ ),
319
+ "procedure": re.compile(
320
+ r"\b(?:CREATE|DROP|ALTER)\s+(?:OR\s+REPLACE\s+)?PROCEDURE\s+(?:IF\s+(?:NOT\s+)?EXISTS\s+)?(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+))(?:\.(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+)))?",
321
+ re.IGNORECASE,
322
+ ),
323
+ "function": re.compile(
324
+ r"\b(?:CREATE|DROP|ALTER)\s+(?:OR\s+REPLACE\s+)?FUNCTION\s+(?:IF\s+(?:NOT\s+)?EXISTS\s+)?(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+))(?:\.(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+)))?",
325
+ re.IGNORECASE,
326
+ ),
327
+ "trigger": re.compile(
328
+ r"\b(?:CREATE|DROP|ALTER)\s+(?:OR\s+REPLACE\s+)?TRIGGER\s+(?:IF\s+(?:NOT\s+)?EXISTS\s+)?(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+))(?:\.(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+)))?",
329
+ re.IGNORECASE,
330
+ ),
331
+ "database": re.compile(
332
+ r"\b(?:CREATE|DROP|ALTER)\s+DATABASE\s+(?:IF\s+(?:NOT\s+)?EXISTS\s+)?(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+))",
333
+ re.IGNORECASE,
334
+ ),
335
+ "tablespace": re.compile(
336
+ r"\b(?:CREATE|DROP|ALTER)\s+(?:LOB\s+)?TABLESPACE\s+(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+))",
337
+ re.IGNORECASE,
338
+ ),
339
+ "stogroup": re.compile(
340
+ r"\b(?:CREATE|DROP|ALTER)\s+STOGROUP\s+(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+))",
341
+ re.IGNORECASE,
342
+ ),
343
+ "alias": re.compile(
344
+ r"\b(?:CREATE|DROP)\s+ALIAS\s+(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+))(?:\.(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+)))?",
345
+ re.IGNORECASE,
346
+ ),
347
+ "role": re.compile(
348
+ r"\b(?:CREATE|DROP)\s+ROLE\s+(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+))", re.IGNORECASE
349
+ ),
350
+ "mask": re.compile(
351
+ r"\b(?:CREATE|DROP|ALTER)\s+(?:OR\s+REPLACE\s+)?MASK\s+(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+))(?:\.(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+)))?",
352
+ re.IGNORECASE,
353
+ ),
354
+ "permission": re.compile(
355
+ r"\b(?:CREATE|DROP|ALTER)\s+(?:OR\s+REPLACE\s+)?PERMISSION\s+(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+))(?:\.(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+)))?",
356
+ re.IGNORECASE,
357
+ ),
358
+ "trusted_context": re.compile(
359
+ r"\b(?:CREATE|DROP|ALTER)\s+TRUSTED\s+CONTEXT\s+(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+))",
360
+ re.IGNORECASE,
361
+ ),
362
+ "type": re.compile(
363
+ r"\b(?:CREATE|DROP)\s+(?:OR\s+REPLACE\s+)?TYPE\s+(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+))(?:\.(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+)))?",
364
+ re.IGNORECASE,
365
+ ),
366
+ "variable": re.compile(
367
+ r"\b(?:CREATE|DROP)\s+(?:OR\s+REPLACE\s+)?VARIABLE\s+(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+))(?:\.(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+)))?",
368
+ re.IGNORECASE,
369
+ ),
370
+ "synonym": re.compile(
371
+ r"\b(?:CREATE|DROP)\s+(?:OR\s+REPLACE\s+)?SYNONYM\s+(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+))(?:\.(?:(?:\"([^\"]+)\")|([a-zA-Z0-9_$#@]+)))?",
372
+ re.IGNORECASE,
373
+ ),
374
+ }
375
+
376
+ def _compile_comment_patterns(self) -> List[Pattern[str]]:
377
+ """Compile DB2 comment patterns."""
378
+ return [
379
+ # Single-line comments with --
380
+ re.compile(r"--.*$", re.MULTILINE),
381
+ # Multi-line comments /* ... */
382
+ re.compile(r"/\*.*?\*/", re.DOTALL),
383
+ # SPUFI terminator comments
384
+ re.compile(r"--#SET\s+TERMINATOR.*$", re.MULTILINE | re.IGNORECASE),
385
+ ]
386
+
387
+ def _compile_batch_separators(self) -> List[Pattern[str]]:
388
+ """Compile DB2 batch separator patterns."""
389
+ return [
390
+ # SQL statement terminators
391
+ re.compile(r";\s*$", re.MULTILINE),
392
+ # SPUFI terminator customization
393
+ re.compile(r"--#SET\s+TERMINATOR\s+(\S)", re.IGNORECASE),
394
+ # SQL/PL statement terminators
395
+ re.compile(r"SQL_STATEMENT_TERMINATOR", re.IGNORECASE),
396
+ ]
397
+
398
+ @property
399
+ def name(self) -> str:
400
+ """Dialect name."""
401
+ return self.dialect_name
402
+
403
+ @property
404
+ def ddl_patterns(self) -> Dict[str, Pattern[str]]:
405
+ """DDL statement regex patterns."""
406
+ return self._ddl_patterns
407
+
408
+ @property
409
+ def dml_patterns(self) -> Dict[str, Pattern[str]]:
410
+ """DML statement regex patterns."""
411
+ return self._dml_patterns
412
+
413
+ @property
414
+ def query_patterns(self) -> Dict[str, Pattern[str]]:
415
+ """Query statement regex patterns."""
416
+ return self._query_patterns
417
+
418
+ @property
419
+ def object_patterns(self) -> Dict[str, Pattern[str]]:
420
+ """Object extraction regex patterns."""
421
+ return self._object_patterns
422
+
423
+ @property
424
+ def comment_patterns(self) -> List[Pattern[str]]:
425
+ """Regex patterns for comments."""
426
+ return self._comment_patterns
427
+
428
+ @property
429
+ def batch_separators(self) -> List[Pattern[str]]:
430
+ """Regex patterns for batch separators."""
431
+ return self._batch_separators
432
+
433
+ @property
434
+ def quoted_identifiers(self) -> List[Pattern[str]]:
435
+ """Regex patterns for quoted identifiers."""
436
+ return [re.compile(r"\"([^\"]+)\"")]
437
+
438
+ @property
439
+ def block_keywords(self) -> List[str]:
440
+ """Keywords that start block statements (procedures, functions, etc.)."""
441
+ return [
442
+ "BEGIN",
443
+ "END",
444
+ "DECLARE",
445
+ "IF",
446
+ "THEN",
447
+ "ELSE",
448
+ "ELSEIF",
449
+ "ENDIF",
450
+ "WHILE",
451
+ "FOR",
452
+ "LOOP",
453
+ "REPEAT",
454
+ "UNTIL",
455
+ "CASE",
456
+ "WHEN",
457
+ "ATOMIC",
458
+ "NOT ATOMIC",
459
+ "COMPOUND",
460
+ "SIGNAL",
461
+ "RESIGNAL",
462
+ "CONTINUE",
463
+ "EXIT",
464
+ "UNDO",
465
+ "GOTO",
466
+ "ITERATE",
467
+ "LEAVE",
468
+ "SQLPL",
469
+ "LANGUAGE SQL",
470
+ "WRAPPED",
471
+ ]
472
+
473
+ def get_default_schema(self) -> str:
474
+ """Get default schema name for DB2."""
475
+ return "SYSIBM"
476
+
477
+ def normalize_identifier(self, identifier: str, is_quoted: bool = False) -> str:
478
+ """Normalize DB2 identifier according to dialect rules.
479
+
480
+ Args:
481
+ identifier: Raw identifier string
482
+ is_quoted: Whether the identifier was quoted
483
+
484
+ Returns:
485
+ Normalized identifier
486
+ """
487
+ if not identifier:
488
+ return identifier
489
+
490
+ # Remove quotes if present
491
+ if identifier.startswith('"') and identifier.endswith('"'):
492
+ identifier = identifier[1:-1]
493
+ is_quoted = True
494
+
495
+ # DB2 identifiers are case-insensitive unless quoted
496
+ if not is_quoted:
497
+ identifier = identifier.upper()
498
+
499
+ return identifier
500
+
501
+ def extract_sqlpl_blocks(self, sql: str) -> List[Dict[str, Any]]:
502
+ """Extract SQL/PL blocks from SQL content.
503
+
504
+ Args:
505
+ sql: SQL content to parse
506
+
507
+ Returns:
508
+ List of SQL/PL blocks with their content
509
+ """
510
+ blocks = []
511
+
512
+ # Pattern to find the start of SQL/PL procedures and functions
513
+ start_pattern = re.compile(
514
+ r"\b(?:CREATE|ALTER)\s+(?:OR\s+REPLACE\s+)?(?:PROCEDURE|FUNCTION)\s+", re.IGNORECASE
515
+ )
516
+
517
+ for start_match in start_pattern.finditer(sql):
518
+ start_pos = start_match.start()
519
+
520
+ # Find the BEGIN keyword after the start
521
+ begin_match = re.search(r"\bBEGIN\b", sql[start_pos:], re.IGNORECASE)
522
+ if not begin_match:
523
+ continue
524
+
525
+ begin_pos = start_pos + begin_match.start()
526
+
527
+ # Manually count BEGIN/END pairs to find the matching END
528
+ i = begin_pos + 5 # Start after "BEGIN"
529
+ depth = 1
530
+ case_depth = 0 # Track CASE expressions separately
531
+ in_string = False
532
+ in_comment = False
533
+ string_char = None
534
+
535
+ while i < len(sql) and depth > 0:
536
+ # Handle string literals
537
+ if not in_comment:
538
+ if not in_string and sql[i] in ("'", '"'):
539
+ in_string = True
540
+ string_char = sql[i]
541
+ elif in_string and sql[i] == string_char:
542
+ # Check for escaped quotes
543
+ if i + 1 < len(sql) and sql[i + 1] == string_char:
544
+ i += 2
545
+ continue
546
+ in_string = False
547
+ string_char = None
548
+
549
+ # Handle comments
550
+ if not in_string:
551
+ if sql[i : i + 2] == "--":
552
+ # Line comment - skip to end of line
553
+ while i < len(sql) and sql[i] not in ("\n", "\r"):
554
+ i += 1
555
+ continue
556
+ elif sql[i : i + 2] == "/*":
557
+ # Block comment
558
+ in_comment = True
559
+ i += 2
560
+ continue
561
+ elif sql[i : i + 2] == "*/" and in_comment:
562
+ in_comment = False
563
+ i += 2
564
+ continue
565
+
566
+ # Count BEGIN/END and CASE/END pairs outside strings and comments
567
+ if not in_string and not in_comment:
568
+ # Check for CASE keyword (starts a CASE expression)
569
+ if sql[i : i + 4].upper() == "CASE":
570
+ if (i == 0 or not sql[i - 1].isalnum()) and (
571
+ i + 4 >= len(sql) or not sql[i + 4].isalnum()
572
+ ):
573
+ case_depth += 1
574
+ i += 4
575
+ continue
576
+ # Check for BEGIN keyword
577
+ elif sql[i : i + 5].upper() == "BEGIN":
578
+ # Make sure it's a word boundary
579
+ if (i == 0 or not sql[i - 1].isalnum()) and (
580
+ i + 5 >= len(sql) or not sql[i + 5].isalnum()
581
+ ):
582
+ depth += 1
583
+ i += 5
584
+ continue
585
+ elif sql[i : i + 3].upper() == "END":
586
+ # Make sure it's a word boundary before END
587
+ if i == 0 or not sql[i - 1].isalnum():
588
+ # Check what comes after END
589
+ # Skip whitespace after END
590
+ j = i + 3
591
+ while j < len(sql) and sql[j] in (" ", "\t"):
592
+ j += 1
593
+
594
+ # Check if this is "END IF", "END WHILE", "END FOR", "END LOOP", "END CASE", etc.
595
+ # These are control structure endings, not block endings
596
+ is_control_end = False
597
+ if j < len(sql):
598
+ next_word_upper = ""
599
+ k = j
600
+ while k < len(sql) and sql[k].isalpha():
601
+ next_word_upper += sql[k].upper()
602
+ k += 1
603
+
604
+ # Control structure keywords that follow END
605
+ if next_word_upper in (
606
+ "IF",
607
+ "WHILE",
608
+ "FOR",
609
+ "LOOP",
610
+ "CASE",
611
+ "REPEAT",
612
+ ):
613
+ is_control_end = True
614
+ # Special case: END CASE decrements case_depth
615
+ if next_word_upper == "CASE":
616
+ case_depth -= 1
617
+
618
+ # Check if this END matches a CASE expression (not END CASE)
619
+ # CASE expressions have END without a following keyword
620
+ is_case_expression_end = False
621
+ if not is_control_end and case_depth > 0:
622
+ # This END might close a CASE expression
623
+ # Check if the next non-whitespace char is ; or ,
624
+ if j < len(sql) and sql[j] in (";", ",", ")"):
625
+ case_depth -= 1
626
+ is_case_expression_end = True
627
+
628
+ # Only count as block END if it's not a control structure end or CASE expression end
629
+ if not is_control_end and not is_case_expression_end:
630
+ depth -= 1
631
+ if depth == 0:
632
+ # Found the matching END, now look for delimiter (@ or ;)
633
+ j = i + 3
634
+ while j < len(sql) and sql[j] in (" ", "\t", "\n", "\r"):
635
+ j += 1
636
+
637
+ # Accept either @ or ; as delimiter
638
+ if j < len(sql) and sql[j] in ("@", ";"):
639
+ end_pos = j + 1
640
+ content = sql[start_pos:end_pos].rstrip("@;").strip()
641
+ else:
642
+ # No explicit delimiter, use position after END
643
+ end_pos = j
644
+ content = sql[start_pos:end_pos].strip()
645
+
646
+ blocks.append(
647
+ {
648
+ "type": "sqlpl_block",
649
+ "content": content,
650
+ "start": start_pos,
651
+ "end": end_pos,
652
+ }
653
+ )
654
+ break
655
+ i += 3
656
+ continue
657
+
658
+ i += 1
659
+
660
+ return blocks
661
+
662
+ def extract_compound_statements(self, sql: str) -> List[Dict[str, Any]]:
663
+ """Extract compound statements from SQL content.
664
+
665
+ Args:
666
+ sql: SQL content to parse
667
+
668
+ Returns:
669
+ List of compound statements with their content
670
+ """
671
+ blocks = []
672
+
673
+ # Pattern to match compound statements with @ delimiter
674
+ compound_pattern = re.compile(
675
+ r"\bBEGIN\s+(?:ATOMIC|NOT\s+ATOMIC).*?\bEND\s*[@;]", re.IGNORECASE | re.DOTALL
676
+ )
677
+
678
+ matches = compound_pattern.finditer(sql)
679
+ for match in matches:
680
+ content = match.group(0).rstrip(";@").strip() # Remove trailing delimiter
681
+ blocks.append(
682
+ {
683
+ "type": "compound_statement",
684
+ "content": content,
685
+ "start": match.start(),
686
+ "end": match.end(),
687
+ }
688
+ )
689
+
690
+ return blocks
691
+
692
+ def extract_trigger_blocks(self, sql: str) -> List[Dict[str, Any]]:
693
+ """Extract trigger blocks from SQL content.
694
+
695
+ Args:
696
+ sql: SQL content to parse
697
+
698
+ Returns:
699
+ List of trigger blocks with their content
700
+ """
701
+ blocks = []
702
+
703
+ # Pattern to find the start of a trigger statement
704
+ trigger_start_pattern = re.compile(
705
+ r"\b(?:CREATE|ALTER)\s+(?:OR\s+REPLACE\s+)?TRIGGER\s+",
706
+ re.IGNORECASE,
707
+ )
708
+
709
+ # Find all trigger starts
710
+ for match in trigger_start_pattern.finditer(sql):
711
+ start_pos = match.start()
712
+
713
+ # Find the BEGIN ATOMIC keyword after the trigger start
714
+ begin_atomic_match = re.search(r"\bBEGIN\s+ATOMIC\b", sql[start_pos:], re.IGNORECASE)
715
+
716
+ if not begin_atomic_match:
717
+ continue
718
+
719
+ # Start counting BEGIN/END pairs from the BEGIN ATOMIC position
720
+ begin_pos = start_pos + begin_atomic_match.start()
721
+ i = begin_pos
722
+ depth = 0
723
+ in_string = False
724
+ in_comment = False
725
+ string_char = None
726
+
727
+ while i < len(sql):
728
+ char = sql[i]
729
+
730
+ # Handle string literals
731
+ if char in ("'", '"') and not in_comment:
732
+ if not in_string:
733
+ in_string = True
734
+ string_char = char
735
+ elif char == string_char:
736
+ in_string = False
737
+ string_char = None
738
+ i += 1
739
+ continue
740
+
741
+ # Handle comments
742
+ if not in_string:
743
+ # Line comment
744
+ if sql[i : i + 2] == "--":
745
+ in_comment = True
746
+ i += 2
747
+ continue
748
+ # Block comment
749
+ if sql[i : i + 2] == "/*":
750
+ in_comment = True
751
+ i += 2
752
+ continue
753
+ if sql[i : i + 2] == "*/" and in_comment:
754
+ in_comment = False
755
+ i += 2
756
+ continue
757
+ # End of line comment
758
+ if char == "\n" and in_comment:
759
+ in_comment = False
760
+
761
+ # Count BEGIN/END pairs (only outside strings and comments)
762
+ if not in_string and not in_comment:
763
+ # Check for BEGIN keyword
764
+ if re.match(r"\bBEGIN\b", sql[i:], re.IGNORECASE):
765
+ depth += 1
766
+ i += 5 # len("BEGIN")
767
+ continue
768
+
769
+ # Check for END keyword
770
+ if re.match(r"\bEND\b", sql[i:], re.IGNORECASE):
771
+ depth -= 1
772
+ i += 3 # len("END")
773
+
774
+ # If we've closed all BEGIN blocks, look for terminator
775
+ if depth == 0:
776
+ # Skip whitespace
777
+ while i < len(sql) and sql[i] in (" ", "\t", "\n", "\r"):
778
+ i += 1
779
+
780
+ # Check for terminator (semicolon or @)
781
+ if i < len(sql) and sql[i] in (";", "@"):
782
+ end_pos = i + 1
783
+ content = sql[start_pos:end_pos].rstrip(";@").strip()
784
+ blocks.append(
785
+ {
786
+ "type": "trigger_block",
787
+ "content": content,
788
+ "start": start_pos,
789
+ "end": end_pos,
790
+ }
791
+ )
792
+ break
793
+ else:
794
+ # No terminator found, use END position
795
+ end_pos = i
796
+ content = sql[start_pos:end_pos].strip()
797
+ blocks.append(
798
+ {
799
+ "type": "trigger_block",
800
+ "content": content,
801
+ "start": start_pos,
802
+ "end": end_pos,
803
+ }
804
+ )
805
+ break
806
+ continue
807
+
808
+ i += 1
809
+
810
+ return blocks
811
+
812
+ def is_db2_utility_statement(self, sql: str) -> bool:
813
+ """Check if SQL is a DB2 utility statement.
814
+
815
+ Args:
816
+ sql: SQL content to check
817
+
818
+ Returns:
819
+ True if it's a DB2 utility statement
820
+ """
821
+ utility_keywords = [
822
+ "DSNUTILX",
823
+ "DSNUTILU",
824
+ "DSNUTILC",
825
+ "DSNUTILP",
826
+ "REORG",
827
+ "RUNSTATS",
828
+ "BIND",
829
+ "REBIND",
830
+ "COPY",
831
+ "RECOVER",
832
+ "REPAIR",
833
+ "LOAD",
834
+ "UNLOAD",
835
+ "CHECK",
836
+ ]
837
+
838
+ sql_upper = sql.upper()
839
+ return any(keyword in sql_upper for keyword in utility_keywords)
840
+
841
+ def extract_module_blocks(self, sql: str) -> List[Dict[str, Any]]:
842
+ """Extract DB2 module blocks (CREATE MODULE ... END MODULE).
843
+
844
+ DB2 LUW modules are compound statements containing procedures, functions,
845
+ and variables. The entire module must be treated as a single statement.
846
+
847
+ Args:
848
+ sql: SQL content to parse
849
+
850
+ Returns:
851
+ List of module blocks with their content
852
+ """
853
+ blocks = []
854
+
855
+ # Pattern to find CREATE MODULE statements
856
+ # Modules end with "END MODULE" not just "END"
857
+ pattern = re.compile(
858
+ r"\bCREATE\s+(?:OR\s+REPLACE\s+)?MODULE\s+.*?\bEND\s+MODULE\s*;?",
859
+ re.IGNORECASE | re.DOTALL,
860
+ )
861
+
862
+ for match in pattern.finditer(sql):
863
+ content = match.group(0).strip()
864
+ blocks.append(
865
+ {
866
+ "type": "module",
867
+ "content": content,
868
+ "start": match.start(),
869
+ "end": match.end(),
870
+ }
871
+ )
872
+
873
+ return blocks
874
+
875
+ def extract_exec_sql_blocks(self, sql: str) -> List[Dict[str, Any]]:
876
+ """Extract EXEC SQL blocks from SQL content.
877
+
878
+ Args:
879
+ sql: SQL content to parse
880
+
881
+ Returns:
882
+ List of EXEC SQL blocks with their content
883
+ """
884
+ blocks = []
885
+
886
+ # Pattern to match EXEC SQL ... END-EXEC blocks
887
+ exec_sql_pattern = re.compile(r"\bEXEC\s+SQL\s+(.*?)\s+END-EXEC", re.IGNORECASE | re.DOTALL)
888
+
889
+ matches = exec_sql_pattern.finditer(sql)
890
+ for match in matches:
891
+ blocks.append(
892
+ {
893
+ "type": "exec_sql_block",
894
+ "content": match.group(1).strip(),
895
+ "start": match.start(),
896
+ "end": match.end(),
897
+ }
898
+ )
899
+
900
+ return blocks
901
+
902
+ def split_statements(self, sql: str) -> List[str]:
903
+ """Split SQL into individual statements, handling procedures/functions with BEGIN/END blocks.
904
+
905
+ Args:
906
+ sql: SQL script containing multiple statements
907
+
908
+ Returns:
909
+ List of individual SQL statements
910
+ """
911
+ statements = []
912
+
913
+ # Extract SQL/PL blocks (procedures and functions with BEGIN/END)
914
+ sqlpl_blocks = self.extract_sqlpl_blocks(sql)
915
+
916
+ # Extract trigger blocks
917
+ trigger_blocks = self.extract_trigger_blocks(sql)
918
+
919
+ # Extract module blocks (DB2 LUW - CREATE MODULE ... END MODULE)
920
+ module_blocks = self.extract_module_blocks(sql)
921
+
922
+ # Combine all blocks and sort by position
923
+ all_blocks = sqlpl_blocks + trigger_blocks + module_blocks
924
+ all_blocks.sort(key=lambda b: b["start"])
925
+
926
+ # Track which parts of the SQL have been processed
927
+ processed_ranges = []
928
+ for block in all_blocks:
929
+ processed_ranges.append((block["start"], block["end"]))
930
+ statements.append(block["content"])
931
+
932
+ # Now process the remaining SQL (non-block statements)
933
+ remaining_sql = ""
934
+ last_end = 0
935
+
936
+ for start, end in processed_ranges:
937
+ if start > last_end:
938
+ remaining_sql += sql[last_end:start]
939
+ last_end = end
940
+
941
+ # Add any remaining SQL after the last block
942
+ if last_end < len(sql):
943
+ remaining_sql += sql[last_end:]
944
+
945
+ # Split the remaining SQL by semicolons (simple statements)
946
+ if remaining_sql.strip():
947
+ # Split by semicolons, but be careful with strings and comments
948
+ remaining_statements = self._split_simple_statements(remaining_sql)
949
+ statements.extend(remaining_statements)
950
+
951
+ # Filter out empty statements
952
+ statements = [s.strip() for s in statements if s.strip()]
953
+
954
+ return statements
955
+
956
+ def _split_simple_statements(self, sql: str) -> List[str]:
957
+ """Split simple SQL statements by semicolons, handling strings and comments.
958
+
959
+ Args:
960
+ sql: SQL content without complex blocks
961
+
962
+ Returns:
963
+ List of individual statements
964
+ """
965
+ statements = []
966
+ current_statement = []
967
+ i = 0
968
+ in_string = False
969
+ string_char = None
970
+ in_line_comment = False
971
+ in_block_comment = False
972
+
973
+ while i < len(sql):
974
+ char = sql[i]
975
+
976
+ # Handle line comments
977
+ if not in_string and not in_block_comment and sql[i : i + 2] == "--":
978
+ in_line_comment = True
979
+ current_statement.append(char)
980
+ i += 1
981
+ continue
982
+
983
+ if in_line_comment:
984
+ current_statement.append(char)
985
+ if char in ("\n", "\r"):
986
+ in_line_comment = False
987
+ i += 1
988
+ continue
989
+
990
+ # Handle block comments
991
+ if not in_string and not in_line_comment and sql[i : i + 2] == "/*":
992
+ in_block_comment = True
993
+ current_statement.append(char)
994
+ i += 1
995
+ continue
996
+
997
+ if in_block_comment:
998
+ current_statement.append(char)
999
+ if sql[i : i + 2] == "*/":
1000
+ in_block_comment = False
1001
+ current_statement.append(sql[i + 1])
1002
+ i += 2
1003
+ continue
1004
+ i += 1
1005
+ continue
1006
+
1007
+ # Handle strings
1008
+ if not in_line_comment and not in_block_comment:
1009
+ if not in_string and char in ("'", '"'):
1010
+ in_string = True
1011
+ string_char = char
1012
+ current_statement.append(char)
1013
+ i += 1
1014
+ continue
1015
+
1016
+ if in_string:
1017
+ current_statement.append(char)
1018
+ if char == string_char:
1019
+ # Check for escaped quote
1020
+ if i + 1 < len(sql) and sql[i + 1] == string_char:
1021
+ current_statement.append(sql[i + 1])
1022
+ i += 2
1023
+ continue
1024
+ in_string = False
1025
+ string_char = None
1026
+ i += 1
1027
+ continue
1028
+
1029
+ # Handle semicolon as statement separator
1030
+ if char == ";" and not in_string and not in_line_comment and not in_block_comment:
1031
+ # End of statement
1032
+ stmt = "".join(current_statement).strip()
1033
+ if stmt:
1034
+ statements.append(stmt)
1035
+ current_statement = []
1036
+ i += 1
1037
+ continue
1038
+
1039
+ # Regular character
1040
+ current_statement.append(char)
1041
+ i += 1
1042
+
1043
+ # Add the last statement if there's anything left
1044
+ stmt = "".join(current_statement).strip()
1045
+ if stmt:
1046
+ statements.append(stmt)
1047
+
1048
+ return statements
1049
+
1050
+ # Abstract method implementations
1051
+ def get_ddl_keywords(self) -> Set[str]:
1052
+ """Get DDL keywords for DB2."""
1053
+ return {
1054
+ "CREATE",
1055
+ "ALTER",
1056
+ "DROP",
1057
+ "TRUNCATE",
1058
+ "COMMENT",
1059
+ "GRANT",
1060
+ "REVOKE",
1061
+ "TABLE",
1062
+ "VIEW",
1063
+ "INDEX",
1064
+ "SEQUENCE",
1065
+ "PROCEDURE",
1066
+ "FUNCTION",
1067
+ "TRIGGER",
1068
+ "DATABASE",
1069
+ "TABLESPACE",
1070
+ "STOGROUP",
1071
+ "ALIAS",
1072
+ "ROLE",
1073
+ "MASK",
1074
+ "PERMISSION",
1075
+ "TRUSTED",
1076
+ "CONTEXT",
1077
+ "TYPE",
1078
+ "VARIABLE",
1079
+ "SYNONYM",
1080
+ "MODULE",
1081
+ "PACKAGE",
1082
+ }
1083
+
1084
+ def get_dml_keywords(self) -> Set[str]:
1085
+ """Get DML keywords for DB2."""
1086
+ return {"INSERT", "UPDATE", "DELETE", "MERGE", "CALL", "SET", "VALUES"}
1087
+
1088
+ def get_query_keywords(self) -> Set[str]:
1089
+ """Get query keywords for DB2."""
1090
+ return {"SELECT", "WITH", "EXPLAIN", "DESCRIBE", "SHOW"}
1091
+
1092
+ def get_identifier_pattern(self) -> Pattern[str]:
1093
+ """Get regex pattern for DB2 identifiers."""
1094
+ # DB2 supports quoted identifiers with double quotes
1095
+ return re.compile(r'(?:"[^"]+"|[a-zA-Z_][a-zA-Z0-9_$#@]*)', re.IGNORECASE)
1096
+
1097
+ def get_qualified_identifier_pattern(self) -> Pattern[str]:
1098
+ """Get regex pattern for qualified identifiers (schema.table)."""
1099
+ identifier = r'(?:"[^"]+"|[a-zA-Z_][a-zA-Z0-9_$#@]*)'
1100
+ return re.compile(rf"(?:{identifier}\\.)?{identifier}", re.IGNORECASE)
1101
+
1102
+ def get_string_literal_pattern(self) -> Pattern[str]:
1103
+ """Get regex pattern for string literals."""
1104
+ return re.compile(r"'([^']|'')*'", re.IGNORECASE)
1105
+
1106
+ def get_comment_pattern(self) -> Pattern[str]:
1107
+ """Get regex pattern for comments."""
1108
+ return re.compile(r"(?:--.*$|/\*.*?\*/)", re.MULTILINE | re.DOTALL)
1109
+
1110
+ def get_statement_separator_pattern(self) -> Pattern[str]:
1111
+ """Get regex pattern for statement separators."""
1112
+ return re.compile(r";\s*$", re.MULTILINE)
1113
+
1114
+ def is_ddl_statement(self, statement: str) -> bool:
1115
+ """Check if statement is a DDL statement."""
1116
+ statement_upper = statement.strip().upper()
1117
+ ddl_keywords = self.get_ddl_keywords()
1118
+ first_words = statement_upper.split()[:2]
1119
+ return any(word in ddl_keywords for word in first_words if word)
1120
+
1121
+ def is_dml_statement(self, statement: str) -> bool:
1122
+ """Check if statement is a DML statement."""
1123
+ statement_upper = statement.strip().upper()
1124
+ dml_keywords = self.get_dml_keywords()
1125
+ words = statement_upper.split()
1126
+ first_word = words[0] if words else ""
1127
+ return first_word in dml_keywords
1128
+
1129
+ def is_query_statement(self, statement: str) -> bool:
1130
+ """Check if statement is a query statement."""
1131
+ statement_upper = statement.strip().upper()
1132
+ query_keywords = self.get_query_keywords()
1133
+ words = statement_upper.split()
1134
+ first_word = words[0] if words else ""
1135
+ return first_word in query_keywords
1136
+
1137
+ def get_batch_separator(self) -> str:
1138
+ """Get batch separator for DB2."""
1139
+ return ";"
1140
+
1141
+ def supports_block_comments(self) -> bool:
1142
+ """Check if DB2 supports block comments."""
1143
+ return True
1144
+
1145
+ def supports_line_comments(self) -> bool:
1146
+ """Check if DB2 supports line comments."""
1147
+ return True