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.
- api/__init__.py +12 -0
- api/_cli_support.py +27 -0
- api/_client_factory.py +387 -0
- api/_client_operations.py +258 -0
- api/_engine_config.py +70 -0
- api/async_client.py +138 -0
- api/client.py +974 -0
- api/events.py +579 -0
- api/migrations.py +5 -0
- api/py.typed +0 -0
- cli/__init__.py +10 -0
- cli/_command_handlers.py +94 -0
- cli/_config_helpers.py +599 -0
- cli/_constants.py +33 -0
- cli/_output.py +179 -0
- cli/_parser_setup.py +416 -0
- cli/db_utils.py +518 -0
- cli/extensions.py +56 -0
- cli/handlers/__init__.py +11 -0
- cli/handlers/_shared.py +132 -0
- cli/handlers/baseline.py +18 -0
- cli/handlers/clean.py +17 -0
- cli/handlers/import_flyway.py +17 -0
- cli/handlers/info.py +147 -0
- cli/handlers/migrate.py +46 -0
- cli/handlers/repair.py +18 -0
- cli/handlers/undo.py +32 -0
- cli/handlers/validate.py +29 -0
- cli/main.py +442 -0
- config/__init__.py +10 -0
- config/_credential_masking.py +71 -0
- config/_subclasses/__init__.py +12 -0
- config/_subclasses/cosmosdb_config.py +94 -0
- config/_subclasses/db2_config.py +56 -0
- config/_subclasses/dummy_config.py +19 -0
- config/_subclasses/mysql_config.py +91 -0
- config/_subclasses/oracle_config.py +48 -0
- config/_subclasses/postgresql_config.py +56 -0
- config/_subclasses/sqlite_config.py +88 -0
- config/_subclasses/sqlserver_config.py +116 -0
- config/_url_builder_mixin.py +119 -0
- config/config_builder.py +335 -0
- config/database_config.py +615 -0
- config/dblift_config.py +1139 -0
- config/errors.py +5 -0
- config/secrets/__init__.py +16 -0
- config/secrets/_cache.py +37 -0
- config/secrets/_provider_base.py +27 -0
- config/secrets/_registry.py +117 -0
- config/secrets/_resolver.py +83 -0
- config/secrets/_secrets_config.py +60 -0
- config/validation_config.py +124 -0
- core/__init__.py +59 -0
- core/constants.py +72 -0
- core/dialect_boundary.py +275 -0
- core/exceptions.py +55 -0
- core/features.py +28 -0
- core/introspection/__init__.py +23 -0
- core/introspection/_column_enricher.py +279 -0
- core/introspection/_partition_enricher.py +184 -0
- core/introspection/_schema_orchestrator.py +241 -0
- core/introspection/_utils.py +22 -0
- core/introspection/_vendor_property_applier.py +70 -0
- core/introspection/base_introspector.py +889 -0
- core/introspection/capability_matrix.py +276 -0
- core/introspection/extractors/__init__.py +25 -0
- core/introspection/extractors/base_extractor.py +142 -0
- core/introspection/extractors/column_extractor.py +230 -0
- core/introspection/extractors/constraint_extractor.py +598 -0
- core/introspection/extractors/index_extractor.py +344 -0
- core/introspection/extractors/misc_extractor.py +730 -0
- core/introspection/extractors/procedure_extractor.py +821 -0
- core/introspection/extractors/sequence_extractor.py +138 -0
- core/introspection/extractors/table_extractor.py +467 -0
- core/introspection/extractors/trigger_extractor.py +170 -0
- core/introspection/extractors/view_extractor.py +323 -0
- core/introspection/introspector_factory.py +136 -0
- core/introspection/result.py +204 -0
- core/introspection/schema_introspector.py +16 -0
- core/introspection/vendor_queries_base.py +924 -0
- core/introspection/vendor_queries_factory.py +153 -0
- core/introspection/vendor_queries_protocols.py +225 -0
- core/introspection/version_detector.py +279 -0
- core/logger/__init__.py +448 -0
- core/logger/_base.py +145 -0
- core/logger/_factory.py +239 -0
- core/logger/_formatters.py +120 -0
- core/logger/_levels.py +112 -0
- core/logger/_multi.py +135 -0
- core/logger/_null.py +66 -0
- core/logger/console.py +195 -0
- core/logger/formatters/__init__.py +30 -0
- core/logger/formatters/_format_diff_meta.py +117 -0
- core/logger/formatters/_format_diff_object.py +195 -0
- core/logger/formatters/_format_diff_routine.py +208 -0
- core/logger/formatters/_format_diff_table.py +250 -0
- core/logger/formatters/_formatter_impl.py +567 -0
- core/logger/formatters/diff_utils.py +396 -0
- core/logger/formatters/factory.py +104 -0
- core/logger/formatters/formatter.py +12 -0
- core/logger/formatters/htmlformatter.py +876 -0
- core/logger/formatters/jsonformatter.py +978 -0
- core/logger/log.py +782 -0
- core/logger/results.py +904 -0
- core/migration/__init__.py +33 -0
- core/migration/_type_match.py +111 -0
- core/migration/clean_summary.py +86 -0
- core/migration/commands/__init__.py +6 -0
- core/migration/commands/_script_events.py +18 -0
- core/migration/commands/base_command.py +1025 -0
- core/migration/commands/baseline_command.py +96 -0
- core/migration/commands/clean_command.py +342 -0
- core/migration/commands/import_flyway_command.py +190 -0
- core/migration/commands/info_command.py +208 -0
- core/migration/commands/migrate_command.py +853 -0
- core/migration/commands/repair_command.py +769 -0
- core/migration/commands/undo_command.py +869 -0
- core/migration/commands/validate_command.py +90 -0
- core/migration/encoding.py +90 -0
- core/migration/executor/__init__.py +12 -0
- core/migration/executor/execution_engine.py +1196 -0
- core/migration/executor/migration_executor.py +439 -0
- core/migration/executor/migration_helpers.py +142 -0
- core/migration/executor/placeholder_manager.py +77 -0
- core/migration/executor/transaction_policy.py +63 -0
- core/migration/executors/__init__.py +20 -0
- core/migration/executors/base_executor.py +179 -0
- core/migration/executors/executor_factory.py +251 -0
- core/migration/executors/python_executor.py +379 -0
- core/migration/executors/sql_executor.py +273 -0
- core/migration/formats/__init__.py +14 -0
- core/migration/formats/format_detector.py +204 -0
- core/migration/formats/migration_format.py +67 -0
- core/migration/history/__init__.py +1 -0
- core/migration/history/migration_history_manager.py +384 -0
- core/migration/journals/__init__.py +14 -0
- core/migration/journals/migration_journal.py +618 -0
- core/migration/migration.py +775 -0
- core/migration/placeholders/__init__.py +1 -0
- core/migration/placeholders/placeholder_service.py +84 -0
- core/migration/rules/__init__.py +1 -0
- core/migration/rules/migration_rules.py +145 -0
- core/migration/scripting/__init__.py +1 -0
- core/migration/scripting/migration_script_manager.py +721 -0
- core/migration/scripting/undo_script_generator/__init__.py +24 -0
- core/migration/scripting/undo_script_generator/_ddl_reversers.py +485 -0
- core/migration/scripting/undo_script_generator/_dml_reversers.py +319 -0
- core/migration/scripting/undo_script_generator/_extractors.py +511 -0
- core/migration/scripting/undo_script_generator/_generator.py +310 -0
- core/migration/scripting/undo_script_generator/_helpers.py +532 -0
- core/migration/scripting/undo_script_generator/_models.py +18 -0
- core/migration/scripting/undo_script_generator/_reversers.py +864 -0
- core/migration/sql/__init__.py +1 -0
- core/migration/sql/execution_statement.py +38 -0
- core/migration/sql/sql_analyzer.py +806 -0
- core/migration/sql/sql_execution_service.py +431 -0
- core/migration/sql/statement_splitter.py +74 -0
- core/migration/state/__init__.py +5 -0
- core/migration/state/migration_data_service.py +603 -0
- core/migration/state/migration_display_state.py +36 -0
- core/migration/state/migration_formatter.py +215 -0
- core/migration/state/migration_state.py +166 -0
- core/migration/state/migration_state_manager.py +830 -0
- core/migration/state/migration_state_service.py +244 -0
- core/migration/ui/__init__.py +15 -0
- core/migration/ui/data_collector.py +926 -0
- core/migration/ui/display_formatters.py +251 -0
- core/migration/ui/migration_analyzer.py +259 -0
- core/migration/ui/migration_ui.py +362 -0
- core/migration/ui/table_renderer.py +250 -0
- core/migration/version_utils.py +94 -0
- core/normalization/__init__.py +35 -0
- core/normalization/data_type_normalizer.py +5 -0
- core/normalization/dependency_resolver.py +435 -0
- core/normalization/identifier_normalizer.py +297 -0
- core/normalization/object_orderer.py +230 -0
- core/normalization/type_constants.py +154 -0
- core/normalization/type_mapper.py +220 -0
- core/normalization/type_mappings.py +89 -0
- core/normalization/type_normalizer.py +245 -0
- core/seams/__init__.py +1 -0
- core/seams/event_listeners.py +26 -0
- core/seams/introspection.py +18 -0
- core/sql_model/__init__.py +60 -0
- core/sql_model/_base_parse_result.py +491 -0
- core/sql_model/_base_sql_column.py +194 -0
- core/sql_model/_base_sql_constraint.py +223 -0
- core/sql_model/_base_sql_object.py +233 -0
- core/sql_model/_base_sql_statement.py +100 -0
- core/sql_model/base.py +43 -0
- core/sql_model/constraint_validator.py +469 -0
- core/sql_model/database_link.py +151 -0
- core/sql_model/dialect.py +482 -0
- core/sql_model/event.py +155 -0
- core/sql_model/extension.py +109 -0
- core/sql_model/foreign_data_wrapper.py +113 -0
- core/sql_model/foreign_server.py +138 -0
- core/sql_model/index.py +279 -0
- core/sql_model/linked_server.py +170 -0
- core/sql_model/module.py +112 -0
- core/sql_model/package.py +134 -0
- core/sql_model/partition.py +180 -0
- core/sql_model/procedure.py +321 -0
- core/sql_model/sequence.py +208 -0
- core/sql_model/synonym.py +143 -0
- core/sql_model/table.py +1106 -0
- core/sql_model/table_canonicalizer.py +45 -0
- core/sql_model/table_options.py +73 -0
- core/sql_model/trigger.py +310 -0
- core/sql_model/user_defined_type.py +151 -0
- core/sql_model/view.py +429 -0
- core/sql_model/view_options.py +95 -0
- core/sql_parser/__init__.py +5 -0
- core/sql_parser/_partition_handler.py +113 -0
- core/sql_parser/_sqlglot_builders.py +634 -0
- core/sql_parser/base_statement_parser.py +356 -0
- core/sql_parser/base_tokenizer.py +473 -0
- core/sql_parser/common/__init__.py +1 -0
- core/sql_parser/common/base_parser.py +598 -0
- core/sql_parser/dialects/__init__.py +5 -0
- core/sql_parser/dialects/base_config.py +125 -0
- core/sql_parser/enhanced_regex_parser.py +453 -0
- core/sql_parser/hybrid_parser.py +1106 -0
- core/sql_parser/parser_context.py +89 -0
- core/sql_parser/parser_factory.py +215 -0
- core/sql_parser/parser_interface.py +93 -0
- core/sql_parser/sqlglot_parser.py +544 -0
- core/sql_parser/tokens.py +48 -0
- core/sql_parser/unified_regex_parser.py +644 -0
- core/sql_validator/__init__.py +5 -0
- core/sql_validator/_checksum_validator.py +341 -0
- core/sql_validator/_flyway_compatibility.py +301 -0
- core/sql_validator/_migration_filter.py +221 -0
- core/sql_validator/_sql_syntax_validator.py +168 -0
- core/sql_validator/_strict_mode_validator.py +136 -0
- core/sql_validator/migration_validator.py +923 -0
- core/state/sql_script_formatter.py +144 -0
- core/state/sql_statement.py +41 -0
- core/utils/__init__.py +1 -0
- core/utils/database_url_parser.py +104 -0
- core/utils/metadata_helpers.py +83 -0
- core/utils/row_access.py +192 -0
- core/utils/string_utils.py +21 -0
- core/utils/url_masking.py +24 -0
- db/__init__.py +21 -0
- db/base_provider.py +266 -0
- db/base_quirks.py +1620 -0
- db/constants.py +5 -0
- db/error.py +344 -0
- db/error_handler.py +349 -0
- db/exceptions.py +11 -0
- db/native_connection_manager.py +70 -0
- db/object_naming.py +39 -0
- db/plugins/__init__.py +9 -0
- db/plugins/base_history_manager.py +616 -0
- db/plugins/base_locking_manager.py +154 -0
- db/plugins/base_query_executor.py +253 -0
- db/plugins/base_schema_operations.py +288 -0
- db/plugins/base_snapshot_manager.py +172 -0
- db/plugins/base_undo_manager.py +126 -0
- db/plugins/cosmosdb/__init__.py +12 -0
- db/plugins/cosmosdb/cosmosdb/__init__.py +19 -0
- db/plugins/cosmosdb/cosmosdb/_sdk.py +28 -0
- db/plugins/cosmosdb/cosmosdb/connection_manager.py +247 -0
- db/plugins/cosmosdb/cosmosdb/history_manager.py +427 -0
- db/plugins/cosmosdb/cosmosdb/locking_manager.py +465 -0
- db/plugins/cosmosdb/cosmosdb/query_executor.py +1518 -0
- db/plugins/cosmosdb/cosmosdb/schema_operations.py +416 -0
- db/plugins/cosmosdb/introspection/__init__.py +3 -0
- db/plugins/cosmosdb/parser/__init__.py +5 -0
- db/plugins/cosmosdb/parser/cosmosdb_regex_parser.py +64 -0
- db/plugins/cosmosdb/plugin.py +19 -0
- db/plugins/cosmosdb/provider.py +364 -0
- db/plugins/cosmosdb/quirks.py +240 -0
- db/plugins/cosmosdb/sdk_translator/__init__.py +39 -0
- db/plugins/cosmosdb/sdk_translator/_executor.py +422 -0
- db/plugins/cosmosdb/sdk_translator/_executors.py +356 -0
- db/plugins/cosmosdb/sdk_translator/_parsing.py +53 -0
- db/plugins/cosmosdb/sdk_translator/_script_generation.py +141 -0
- db/plugins/cosmosdb/sdk_translator/_translator.py +129 -0
- db/plugins/cosmosdb/sdk_translator/_translators.py +795 -0
- db/plugins/db2/__init__.py +11 -0
- db/plugins/db2/db2/__init__.py +11 -0
- db/plugins/db2/db2/history_manager.py +317 -0
- db/plugins/db2/db2/locking_manager.py +558 -0
- db/plugins/db2/db2/schema_operations.py +985 -0
- db/plugins/db2/introspection/__init__.py +3 -0
- db/plugins/db2/parser/__init__.py +9 -0
- db/plugins/db2/parser/db2_regex_parser.py +668 -0
- db/plugins/db2/parser/parser_config.py +1147 -0
- db/plugins/db2/plugin.py +22 -0
- db/plugins/db2/provider.py +420 -0
- db/plugins/db2/quirks.py +382 -0
- db/plugins/db2/sqlalchemy_url.py +62 -0
- db/plugins/mariadb/__init__.py +13 -0
- db/plugins/mariadb/plugin.py +21 -0
- db/plugins/mariadb/provider.py +40 -0
- db/plugins/mariadb/quirks.py +50 -0
- db/plugins/mysql/__init__.py +11 -0
- db/plugins/mysql/introspection/__init__.py +1 -0
- db/plugins/mysql/mysql/__init__.py +11 -0
- db/plugins/mysql/mysql/history_manager.py +384 -0
- db/plugins/mysql/mysql/locking_manager.py +371 -0
- db/plugins/mysql/mysql/schema_operations.py +555 -0
- db/plugins/mysql/parser/__init__.py +1 -0
- db/plugins/mysql/parser/mysql_regex_parser.py +531 -0
- db/plugins/mysql/parser/mysql_statement_parser.py +255 -0
- db/plugins/mysql/parser/mysql_tokenizer.py +460 -0
- db/plugins/mysql/parser/parser_config.py +670 -0
- db/plugins/mysql/plugin.py +22 -0
- db/plugins/mysql/provider.py +329 -0
- db/plugins/mysql/quirks.py +540 -0
- db/plugins/mysql/sqlalchemy_url.py +67 -0
- db/plugins/oracle/__init__.py +11 -0
- db/plugins/oracle/introspection/__init__.py +3 -0
- db/plugins/oracle/introspection/oracle_utils.py +86 -0
- db/plugins/oracle/oracle/__init__.py +14 -0
- db/plugins/oracle/oracle/dbms_output.py +49 -0
- db/plugins/oracle/oracle/history_manager.py +444 -0
- db/plugins/oracle/oracle/locking_manager.py +436 -0
- db/plugins/oracle/oracle/schema_operations.py +1134 -0
- db/plugins/oracle/parser/__init__.py +13 -0
- db/plugins/oracle/parser/_comments.py +57 -0
- db/plugins/oracle/parser/_object_extractor.py +203 -0
- db/plugins/oracle/parser/_plsql_block.py +727 -0
- db/plugins/oracle/parser/_sqlplus.py +312 -0
- db/plugins/oracle/parser/_statement_splitter.py +233 -0
- db/plugins/oracle/parser/oracle_parser.py +297 -0
- db/plugins/oracle/parser/oracle_statement_parser.py +434 -0
- db/plugins/oracle/parser/oracle_tokenizer.py +251 -0
- db/plugins/oracle/parser/parser_config.py +330 -0
- db/plugins/oracle/parser/sqlplus_context.py +179 -0
- db/plugins/oracle/plugin.py +22 -0
- db/plugins/oracle/provider.py +736 -0
- db/plugins/oracle/quirks.py +743 -0
- db/plugins/oracle/sqlalchemy_url.py +65 -0
- db/plugins/postgresql/__init__.py +12 -0
- db/plugins/postgresql/_provider_query_executor.py +25 -0
- db/plugins/postgresql/introspection/__init__.py +3 -0
- db/plugins/postgresql/parser/__init__.py +10 -0
- db/plugins/postgresql/parser/parser_config.py +876 -0
- db/plugins/postgresql/parser/postgresql_regex_parser.py +691 -0
- db/plugins/postgresql/parser/postgresql_statement_parser.py +118 -0
- db/plugins/postgresql/parser/postgresql_tokenizer.py +325 -0
- db/plugins/postgresql/plugin.py +29 -0
- db/plugins/postgresql/postgresql/__init__.py +11 -0
- db/plugins/postgresql/postgresql/history_manager.py +231 -0
- db/plugins/postgresql/postgresql/locking_manager.py +310 -0
- db/plugins/postgresql/postgresql/schema_operations.py +690 -0
- db/plugins/postgresql/provider.py +248 -0
- db/plugins/postgresql/quirks.py +722 -0
- db/plugins/postgresql/sqlalchemy_url.py +65 -0
- db/plugins/sqlite/__init__.py +12 -0
- db/plugins/sqlite/introspection/__init__.py +3 -0
- db/plugins/sqlite/parser/__init__.py +9 -0
- db/plugins/sqlite/parser/parser_config.py +377 -0
- db/plugins/sqlite/parser/sqlite_regex_parser.py +338 -0
- db/plugins/sqlite/plugin.py +21 -0
- db/plugins/sqlite/provider.py +504 -0
- db/plugins/sqlite/quirks.py +96 -0
- db/plugins/sqlite/sqlalchemy_url.py +16 -0
- db/plugins/sqlite/sqlite/__init__.py +24 -0
- db/plugins/sqlite/sqlite/connection_manager.py +162 -0
- db/plugins/sqlite/sqlite/history_manager.py +233 -0
- db/plugins/sqlite/sqlite/locking_manager.py +216 -0
- db/plugins/sqlite/sqlite/query_executor.py +243 -0
- db/plugins/sqlite/sqlite/schema_operations.py +303 -0
- db/plugins/sqlserver/__init__.py +11 -0
- db/plugins/sqlserver/introspection/__init__.py +3 -0
- db/plugins/sqlserver/parser/__init__.py +9 -0
- db/plugins/sqlserver/parser/parser_config.py +604 -0
- db/plugins/sqlserver/parser/sqlserver_regex_parser.py +368 -0
- db/plugins/sqlserver/parser/sqlserver_statement_parser.py +173 -0
- db/plugins/sqlserver/parser/sqlserver_tokenizer.py +238 -0
- db/plugins/sqlserver/parser/tsql_batch_separator.py +14 -0
- db/plugins/sqlserver/plugin.py +22 -0
- db/plugins/sqlserver/provider.py +615 -0
- db/plugins/sqlserver/quirks.py +489 -0
- db/plugins/sqlserver/sqlalchemy_url.py +120 -0
- db/plugins/sqlserver/sqlserver/__init__.py +11 -0
- db/plugins/sqlserver/sqlserver/history_manager.py +382 -0
- db/plugins/sqlserver/sqlserver/locking_manager.py +328 -0
- db/plugins/sqlserver/sqlserver/schema_operations.py +524 -0
- db/provider_capabilities.py +85 -0
- db/provider_interfaces.py +347 -0
- db/provider_registry.py +623 -0
- db/sqlalchemy_provider.py +340 -0
- db/value_utils.py +10 -0
- dblift-2.0.1.dist-info/METADATA +1075 -0
- dblift-2.0.1.dist-info/RECORD +408 -0
- dblift-2.0.1.dist-info/WHEEL +5 -0
- dblift-2.0.1.dist-info/entry_points.txt +12 -0
- dblift-2.0.1.dist-info/licenses/LICENSE +201 -0
- dblift-2.0.1.dist-info/top_level.txt +6 -0
- integrations/__init__.py +30 -0
- integrations/django/__init__.py +1 -0
- integrations/django/_client.py +54 -0
- integrations/django/_engine.py +53 -0
- integrations/django/apps.py +14 -0
- integrations/django/checks.py +35 -0
- integrations/django/management/__init__.py +1 -0
- integrations/django/management/commands/__init__.py +1 -0
- integrations/django/management/commands/dblift_info.py +25 -0
- integrations/django/management/commands/dblift_migrate.py +24 -0
- integrations/django/management/commands/dblift_validate.py +24 -0
- integrations/fastapi.py +153 -0
- integrations/flask.py +82 -0
- 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
|