{"id":"68cca4cd-b328-468c-9e7f-7372d30d0a4b","entityType":"agent","slug":"clawhub-fish1981bimmer-sql-splitter","name":"Sql Splitter","canonicalUrl":"https://www.xpersona.co/agent/clawhub-fish1981bimmer-sql-splitter","canonicalPath":"/agent/clawhub-fish1981bimmer-sql-splitter","generatedAt":"2026-10-10T04:37:58.986Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T15:58:56.983Z","emptyReason":null},"description":"拆分 SQL 文件为独立文件（存储过程、函数、视图、触发器、表结构、索引、约束），自动分析依赖并生成合并脚本","descriptionLabel":"Source description","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 2.4K downloads reported by the source. Last updated 10/9/2026.","installCommand":"clawhub skill install s17dzz5rp1x5wrb7s33vv04cb984wjf8:sql-splitter","sourceUrl":"https://clawhub.ai/fish1981bimmer/sql-splitter","homepage":"https://clawhub.ai/fish1981bimmer/skills/sql-splitter","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/fish1981bimmer/sql-splitter","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/fish1981bimmer/skills/sql-splitter","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":67,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"Sql Splitter technical dossier on Xpersona with agent coverage, OPENCLEW support, and live trust metadata."},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-09T15:58:56.983Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T15:58:56.983Z","emptyReason":null},"stars":null,"forks":null,"downloads":2353,"packageName":null,"latestVersion":"3.6.3","tractionLabel":"2.4K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T15:58:56.983Z","emptyReason":null},"lastUpdatedAt":"2026-10-09T15:58:56.983Z","lastCrawledAt":"2026-10-09T15:58:56.983Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-10T15:58:56.983Z","lastVerifiedAt":null,"highlights":[{"version":"3.6.3","createdAt":"2026-07-04T01:58:03.027Z","changelog":"sql-splitter v3.6.3 - Updated SKILL.md documentation, including detailed usage guides, transformation rules, and version history up to v3.6.2. - No code-level changes listed; this release focuses on improved documentation for users. - Describes key features such as SQL object splitting, dependency analysis, and conversion rules for different SQL dialects.","fileCount":38,"zipByteSize":145382},{"version":"3.6.2","createdAt":"2026-07-04T01:37:10.408Z","changelog":"sql-splitter v3.6.2 - Updated documentation in SKILL.md for easier understanding of features, behaviors, and historical changes. - No code or functional changes included in this version. - All feature and bug fix details remain current as of the previous release (v3.6.1). - This version improves project transparency by clarifying changelog and usage scenarios in the documentation.","fileCount":38,"zipByteSize":145478},{"version":"3.6.1","createdAt":"2026-07-04T01:17:41.700Z","changelog":"- Improved SQL file splitting into individual .sql files with enhanced dependency analysis and merge script generation. - Refined dbo prefix replacement: now accurately distinguishes between three-part and two-part names, with schema_prefix correctly passed through the entire call chain. - Enhanced bracket identifier conversion: bracket-to-double-quote logic now executes before token restoration, eliminating issues with comments. - Strengthened statement termination: all DDL, DML, and variable assignment statements within procedures now end with semicolons, fully passing 312 procedure conversion cases. - Added CLI parameters --schema-prefix and --dm for flexible schema prefixing and dialect selection. - Fixed SQL type mapping bugs, including correct handling of DATETIME and DATETIME2, and improved regular expression ordering for type matching.","fileCount":38,"zipByteSize":145488},{"version":"3.6.0","createdAt":"2026-07-04T01:12:39.579Z","changelog":"sql-splitter v3.6.0 - Enhanced: Correctly distinguishes and replaces dbo schema prefixes (two-part/three-part) with improved regular expression matching. - Added: CLI parameters --schema-prefix and --dm for flexible schema handling and dm conversion. - Fixed: Ensures semicolons are appended to all DDL, DML, and variable assignment statements inside procedure bodies (no more missing semicolons). - Changed: Bracket identifier replacement now runs before token restoration, preventing interference with comments. - Improved: Type keyword matching now uses length-descending order in regex alternations to avoid mis-matching types like DATETIME/DATETIME2. - All 312 procedure conversions pass; user rules fully verified.","fileCount":37,"zipByteSize":143907},{"version":"3.5.7","createdAt":"2026-06-27T09:44:11.425Z","changelog":"sql-splitter v3.5.7 - 修复方括号替换、dbo前缀规则、类型映射等多个达梦数据库兼容性问题。 - 新增存储过程/函数/DML/DDL语句结尾自动加分号，支持达梦语法要求。 - 支持`SELECT INTO #临时表`自动转换为达梦CTAS语法建临时表。 - 支持方括号标识符智能转双引号，区分类型名和普通标识符。 - schema_prefix替换及相关参数调用链补全，修复参数未生效问题。 - 新增CLI参数：`--schema-prefix`与`--dm`，自动提取及设定schema前缀。 - 各项SQL Server到达梦转换规则通过实战、用户验证，存储过程/函数兼容性大幅提升。","fileCount":40,"zipByteSize":151310},{"version":"3.5.6","createdAt":"2026-06-27T08:24:47.596Z","changelog":"sql-splitter v3.5.6 - Improved accuracy and safety of SQL splitting and conversion, especially in handling schema, object identifiers, and datatypes. - Enhanced conversion for SQL Server to 达梦 (DM), ensuring correct bracket-to-quote translation, intelligent dbo prefix removal, and data type mapping. - Added or fixed CLI options for schema prefix and DM compatibility. - Strengthened rules: ending semicolons for DDL in procedures, SELECT INTO conversion to CTAS for GTT, and careful avoidance of conflicting regex replacement. - Extensive real-world and rule-based testing demonstrate reliable conversion for large SQL codebases.","fileCount":39,"zipByteSize":146759},{"version":"3.5.5","createdAt":"2026-06-27T08:15:12.975Z","changelog":"v3.5.5 更新概要： - 新增方括号标识符加双引号（如 `[aa]`→`\"aa\"`），类型名仅去除方括号不加引号（如 `[int]`→`int`）。 - 修复 `dbo.` 前缀无 schema_prefix 时未正确删除的问题。 - 解决 DATETIME2 被错误识别为 DATETIME 的正则优先级问题（正则表达式按长度降序排列）。 - 方括号替换改为在 token 保护后执行，避免注释内容被误处理。 - 新增将 `SELECT INTO #临时表` 自动转换为 CTAS 创建全局临时表的规则。 - 存储过程体内 DDL 语句自动补全分号，达梦兼容性更强。 - CLI 增加 `--schema-prefix` 和 `--dm` 参数，schema 前缀自动从文件名提取。 - 全链路 schema_prefix 传递，保证","fileCount":39,"zipByteSize":146282},{"version":"3.5.4","createdAt":"2026-06-27T07:14:47.692Z","changelog":"## v3.5.4 Changelog - 新增修复说明文档 `references/dm-converter-v353-fixes.md` - 升级和修正：方括号标识符处理逻辑（参见3.5.3更新内容与修订记录） - 更新测试脚本和转换器逻辑，增强达梦SQL兼容性 - 移除遗留文档 `skill-card.md` - 文档调整与补充，详细列出变更点和规则更新","fileCount":39,"zipByteSize":146092}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s17dzz5rp1x5wrb7s33vv04cb984wjf8:sql-splitter","setupComplexity":"low","setupSteps":["Install using `clawhub skill install s17dzz5rp1x5wrb7s33vv04cb984wjf8:sql-splitter` in an isolated environment before connecting it to live workloads.","No published capability contract is available yet, so validate auth and request/response behavior manually.","Review the upstream CLAWHUB listing at https://clawhub.ai/fish1981bimmer/sql-splitter before using production credentials."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-fish1981bimmer-sql-splitter/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-fish1981bimmer-sql-splitter/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-fish1981bimmer-sql-splitter/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-fish1981bimmer-sql-splitter/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-fish1981bimmer-sql-splitter/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-fish1981bimmer-sql-splitter/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-10T04:37:58.983Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-fish1981bimmer-sql-splitter/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-fish1981bimmer-sql-splitter/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-fish1981bimmer-sql-splitter/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-fish1981bimmer-sql-splitter/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T15:58:56.983Z","emptyReason":null},"readme":"Skill: Sql Splitter\n\nOwner: fish1981bimmer\n\nSummary: 拆分 SQL 文件为独立文件（存储过程、函数、视图、触发器、表结构、索引、约束），自动分析依赖并生成合并脚本\n\nTags: database:1.1.0, latest:3.6.3, sql:1.1.0, utility:1.1.0\n\nVersion history:\n\nv3.6.3 | 2026-07-04T01:58:03.027Z | auto\n\nsql-splitter v3.6.3\n\n- Updated SKILL.md documentation, including detailed usage guides, transformation rules, and version history up to v3.6.2.\n- No code-level changes listed; this release focuses on improved documentation for users.\n- Describes key features such as SQL object splitting, dependency analysis, and conversion rules for different SQL dialects.\n\nv3.6.2 | 2026-07-04T01:37:10.408Z | auto\n\nsql-splitter v3.6.2\n\n- Updated documentation in SKILL.md for easier understanding of features, behaviors, and historical changes.\n- No code or functional changes included in this version.\n- All feature and bug fix details remain current as of the previous release (v3.6.1).\n- This version improves project transparency by clarifying changelog and usage scenarios in the documentation.\n\nv3.6.1 | 2026-07-04T01:17:41.700Z | auto\n\n- Improved SQL file splitting into individual .sql files with enhanced dependency analysis and merge script generation.\n- Refined dbo prefix replacement: now accurately distinguishes between three-part and two-part names, with schema_prefix correctly passed through the entire call chain.\n- Enhanced bracket identifier conversion: bracket-to-double-quote logic now executes before token restoration, eliminating issues with comments.\n- Strengthened statement termination: all DDL, DML, and variable assignment statements within procedures now end with semicolons, fully passing 312 procedure conversion cases.\n- Added CLI parameters --schema-prefix and --dm for flexible schema prefixing and dialect selection.\n- Fixed SQL type mapping bugs, including correct handling of DATETIME and DATETIME2, and improved regular expression ordering for type matching.\n\nv3.6.0 | 2026-07-04T01:12:39.579Z | auto\n\nsql-splitter v3.6.0\n\n- Enhanced: Correctly distinguishes and replaces dbo schema prefixes (two-part/three-part) with improved regular expression matching.\n- Added: CLI parameters --schema-prefix and --dm for flexible schema handling and dm conversion.\n- Fixed: Ensures semicolons are appended to all DDL, DML, and variable assignment statements inside procedure bodies (no more missing semicolons).\n- Changed: Bracket identifier replacement now runs before token restoration, preventing interference with comments.\n- Improved: Type keyword matching now uses length-descending order in regex alternations to avoid mis-matching types like DATETIME/DATETIME2.\n- All 312 procedure conversions pass; user rules fully verified.\n\nv3.5.7 | 2026-06-27T09:44:11.425Z | auto\n\nsql-splitter v3.5.7\n\n- 修复方括号替换、dbo前缀规则、类型映射等多个达梦数据库兼容性问题。\n- 新增存储过程/函数/DML/DDL语句结尾自动加分号，支持达梦语法要求。\n- 支持`SELECT INTO #临时表`自动转换为达梦CTAS语法建临时表。\n- 支持方括号标识符智能转双引号，区分类型名和普通标识符。\n- schema_prefix替换及相关参数调用链补全，修复参数未生效问题。\n- 新增CLI参数：`--schema-prefix`与`--dm`，自动提取及设定schema前缀。\n- 各项SQL Server到达梦转换规则通过实战、用户验证，存储过程/函数兼容性大幅提升。\n\nv3.5.6 | 2026-06-27T08:24:47.596Z | auto\n\nsql-splitter v3.5.6\n\n- Improved accuracy and safety of SQL splitting and conversion, especially in handling schema, object identifiers, and datatypes.\n- Enhanced conversion for SQL Server to 达梦 (DM), ensuring correct bracket-to-quote translation, intelligent dbo prefix removal, and data type mapping.\n- Added or fixed CLI options for schema prefix and DM compatibility.\n- Strengthened rules: ending semicolons for DDL in procedures, SELECT INTO conversion to CTAS for GTT, and careful avoidance of conflicting regex replacement.\n- Extensive real-world and rule-based testing demonstrate reliable conversion for large SQL codebases.\n\nv3.5.5 | 2026-06-27T08:15:12.975Z | auto\n\nv3.5.5 更新概要：\n\n- 新增方括号标识符加双引号（如 `[aa]`→`\"aa\"`），类型名仅去除方括号不加引号（如 `[int]`→`int`）。\n- 修复 `dbo.` 前缀无 schema_prefix 时未正确删除的问题。\n- 解决 DATETIME2 被错误识别为 DATETIME 的正则优先级问题（正则表达式按长度降序排列）。\n- 方括号替换改为在 token 保护后执行，避免注释内容被误处理。\n- 新增将 `SELECT INTO #临时表` 自动转换为 CTAS 创建全局临时表的规则。\n- 存储过程体内 DDL 语句自动补全分号，达梦兼容性更强。\n- CLI 增加 `--schema-prefix` 和 `--dm` 参数，schema 前缀自动从文件名提取。\n- 全链路 schema_prefix 传递，保证\n\nv3.5.4 | 2026-06-27T07:14:47.692Z | auto\n\n## v3.5.4 Changelog\n\n- 新增修复说明文档 `references/dm-converter-v353-fixes.md`\n- 升级和修正：方括号标识符处理逻辑（参见3.5.3更新内容与修订记录）\n- 更新测试脚本和转换器逻辑，增强达梦SQL兼容性\n- 移除遗留文档 `skill-card.md`\n- 文档调整与补充，详细列出变更点和规则更新\n\nv3.5.3 | 2026-06-27T03:49:45.106Z | auto\n\nv3.5.3 (patch release; minor fix)\n\n- Fixed: Corrected the reported version number in SKILL.md from v3.5.1 to v3.5.2.\n- No functional or API changes to core logic.\n- Documentation update only; no impact on behavior.\n\nv3.5.2 | 2026-06-27T02:42:28.602Z | auto\n\n## v3.5.2 Changelog\n\n- Updated documentation (SKILL.md) to accurately reflect current feature behavior and supported examples.\n- Revised object conversion rule descriptions for more precise and consistent rule statements.\n- Clarified sample transformation results and user-validation details.\n- No core logic or code changes; this release only modifies documentation for clarity and accuracy.\n\nv3.5.1 | 2026-06-27T01:25:16.005Z | auto\n\nv3.5.1 is a documentation-only update:\n\n- 更新 SKILL.md 文档，无实际功能或代码更改\n- 无破坏性或行为变更，纯内容修订\n- 适用于需要查看或引用最新文档的用户\n\nv3.5.0 | 2026-06-27T00:20:07.860Z | auto\n\n**v3.5.0 — Productization foundation & key rule fixes**\n\n- Added productization modules: report_generator (quality reports, scoring) and license_checker (community/pro/enterprise licensing).\n- Rewrote GUI with full tkinter interface; added pip packaging and CLI (`sql-splitter`) support.\n- Community Edition restriction enforcement: ≤20 objects, ≤1MB file, advanced features require license.\n- Major SQL conversion fixes:\n  - Fixed dbo prefix replacement logic in all cases.\n  - Fixed DATETIME2 mapped incorrectly due to regex alternation order.\n  - Moved bracket identifier replacement to token-protected phase, preventing accidental changes in comments.\n  - Implemented automated SELECT INTO #temp → CTAS/gtt table conversion and DDL semicolons in procedures.\n- CLI: added `--schema-prefix` and `--dm` options; ensured schema_prefix is consistently passed.\n- 312+ stored procedures and multiple user rule scenarios validated; test coverage expanded.\n\nv3.4.5 | 2026-06-21T11:26:59.758Z | user\n\nv3.4.5: 社区版限制提示优化 - 显示真实文件大小和对象数量\n\nv3.4.4 | 2026-06-21T10:02:01.827Z | user\n\nv3.4.4: 社区版加限制(20对象+1MB文件), 批量/GUI/报告等需专业版, license_checker管理授权\n\nv3.4.3 | 2026-06-21T08:19:51.630Z | user\n\nv3.4.3: 社区版, 清理付费功能代码, GUI社区版, 移除license/report模块\n\nv3.4.2 | 2026-06-20T09:22:45.620Z | user\n\n补发完整文件(references+scripts)，修正SKILL.md标题版本号v3.3.0→v3.4.0，删除重复章节，changelog排序最新在前\n\nv3.4.1 | 2026-06-20T09:22:12.299Z | user\n\n修正SKILL.md: 标题版本号v3.3.0→v3.4.0, 删除重复的v3.4.0功能子章节, 版本changelog排序最新在前\n\nv3.4.0 | 2026-06-20T00:45:04.446Z | auto\n\n# sql-splitter v3.4.0\n\n- 支持 TRUNCATE TABLE 自动转为 DELETE FROM，兼容达梦数据库存储过程场景\n- 拆分后的 TABLE/VIEW/INDEX/CONSTRAINT/SEQUENCE 等对象结尾统一加分号，直接可用\n- 所有方言的拆分输出均加分号，去除 Oracle/DM 特殊逻辑\n- 新增 9 个单元测试，全部通过，总计 53 个\n\nv3.3.0 | 2026-06-19T11:45:36.192Z | auto\n\nsql-splitter v3.3.0\n\n- 修复存储过程名被多层双引号包裹（\"\"sp_test\"\"）的问题，统一用单个正则与分支替换处理。\n- 修正表定义中 IDENTITY 自增声明插入位置，确保紧跟表定义，而非表结构内部。\n- 修复临时表（#tmp、##tmp）正则匹配，避免列定义截断和类型声明不全。\n- 避免 token_map 占位符 key 碰撞，保证字符串和标识符还原准确。\n- 44个单元测试全通过，转换更严密稳定。\n\nv3.2.4 | 2026-06-14T09:16:10.068Z | auto\n\nsql-splitter v3.2.4\n\n- 新增文档：达梦转换修复说明（references/dm-converter-v323-fixes.md）\n- 移除 skill-card.md 文件\n- 更新 SKILL.md 文档内容，未更改主功能代码\n\nv3.2.3 | 2026-06-14T08:13:04.089Z | auto\n\nv3.2.3 adjusts conversion of VARCHAR(n) to VARCHAR(n CHAR) for stored procedures and functions.\n\n- 修正：存储过程和函数中的 VARCHAR(n) 现统一转换为 VARCHAR(n CHAR)\n- 相关的“只在表/视图做 CHAR 语义转换”判断被移除，防止遗漏\n- scripts/dm_converter.py 逻辑修正，unit test 标准同步\n- 新增 conversion 具体说明文档 references/dm-converter-v322-fixes.md\n- 移除 skill-card.md，文档简化\n\nv3.2.2 | 2026-06-14T05:56:26.677Z | auto\n\n**新增批量 SQL Server→达梦转换脚本，改进兼容性和安全性。**\n\n- 新增 scripts/batch_convert.py，支持批量将拆分后的 SQL Server 文件自动转换为达梦语法。\n- 推荐用脚本文件批量转换，避免安全扫描拦截内联 Python 代码。\n- 文档示例与说明更新（SKILL.md），给出实际批处理脚本用法。\n- 移除 skill-card.md，简化冗余文档。\n- dm_converter 测试用例（test_dm_converter.py）同步更新。\n\nv3.2.0 | 2026-06-14T00:23:03.588Z | user\n\nv3.2.0: SKILL.md文档整理(去重合并+版本号统一); v3.1.0: PROCEDURE参数加括号, OR REPLACE兼容, AS→IS修复, GO;兼容, DATE/NVARCHAR(max)映射优化, VARCHAR2支持; v3.0.0: 修复3个dm_converter核心BUG(捕获组偏移/suffix贪婪/方括号替换), 双点号..替换对全部对象类型生效, UTF-16编码支持, 462对象大文件测试通过\n\nv2.4.5 | 2026-06-07T11:56:43.545Z | user\n\nv2.4.5: 方括号→双引号 + dbo前缀智能处理(三段式删dbo/两段式替换) + 精确拆分增强(无终止符时CREATE兜底) + VARCHAR CHAR语义后处理 + schema_prefix自动传递\n\nv2.4.4 | 2026-06-07T06:42:29.489Z | user\n\nv2.4.4: 数据类型映射调整\n- VARCHAR(n) -> VARCHAR(n CHAR) 达梦字符语义（与NVARCHAR统一）\n- UNIQUEIDENTIFIER -> CHAR(36)（原VARCHAR(36)）\n- 40/40 测试通过\n\nv2.4.3 | 2026-06-06T06:37:57.399Z | user\n\nv2.4.3 达梦转换器BUG修复：BIT→BOOLEAN, NVARCHAR(n)→VARCHAR(n CHAR), DATEADD参数重排, SELECT赋值区分有/无FROM, IF→THEN/END IF, WHILE→LOOP/END LOOP, PRINT→DBMS_OUTPUT.PUT_LINE, 全部40个测试通过\n\nv2.4.2 | 2026-05-24T00:44:29.627Z | user\n\nv2.4.2: 调整功能描述顺序，最新版本(v2.4.0)达梦转换器重写功能提到最前面，v2.2.1功能降为次要位置，删除重复的v2.3章节\n\nv2.4.1 | 2026-05-23T12:57:57.420Z | user\n\nv2.4.1 更新版本描述: 新增达梦数据库转换功能 - 支持8种对象类型独立转换SQL Server->达梦, 40+数据类型映射, 30+函数映射, 全局变量转换, TRY CATCH->EXCEPTION, 变量语法@var->var, 触发器伪表inserted/deleted->NEW/OLD, GO->/, 上下文感知替换保护字符串注释, 路径遍历安全修复, 29个单元测试\n\nv2.4.0 | 2026-05-23T12:32:42.695Z | user\n\nv2.4.0 - 达梦数据库转换功能: 拆分后按对象类型独立转换SQL Server->达梦, 路径遍历安全校验, 29个单元测试\n\nv2.2.3 | 2026-05-23T02:54:32.179Z | user\n\nv2.2.3 修复版本内容不一致问题：修正版本号从v2.2.1更新为v2.2.2，添加完整的v2.2.2更新日志，确保内容与版本号一致\n\nv2.2.2 | 2026-05-01T10:31:10.168Z | user\n\nv2.2.1->v2.2.2: 安全修复和文档优化\n\nv2.2.1 | 2026-05-01T07:26:37.535Z | user\n\nv2.2.1 - 安全修复：修复 pickle 反序列化漏洞\n\n- 修复意外代码执行问题（Medium）：将 pickle 替换为 JSON\n- 修复供应链信息缺失（Info）：添加 requirements.txt\n- 确认内存/上下文污染安全（Low）：无全局变量污染\n- 新增安全文档：SECURITY.md、SECURITY_FIX_SUMMARY.md、SECURITY_VERIFICATION_REPORT.md\n- 新增 JSON 序列化测试：scripts/test_json_security.py\n- 所有 37 个单元测试通过\n- 所有功能测试通过\n- 安全验证通过\n\nv2.2.0 | 2026-04-27T13:23:51.074Z | user\n\nv2.2: GUI界面、断点续传、批量并行处理、结果预览对比、配置文件管理\n\nv2.1.1 | 2026-04-25T14:10:35.019Z | user\n\n更新版本信息：SKILL.md 和 split_sql_v21.py 中的版本号从 v2.0 更新为 v2.1\n\nv2.1.0 | 2026-04-25T13:52:00.646Z | user\n\nv2.1 新功能：进度条显示、详细错误处理、Dry-run 预览模式\n\nv2.0.2 | 2026-04-24T13:29:25.112Z | user\n\n修复重复文件问题：添加去重逻辑，避免同一对象被多个正则表达式重复匹配。去重标准：相同起始位置、对象类型、对象名称。解决 SQL Server 存储过程产生重复文件的问题。新增去重功能测试用例。\n\nv2.0.0 | 2026-04-19T11:04:36.466Z | user\n\nv2.0: 边界检测重写(BEGIN/END深度匹配)、依赖分析改进、提取common.py、方言适配合并脚本、新增37个单元测试\n\nv1.1.0 | 2026-04-15T13:40:03.116Z | auto\n\nsql-splitter v1.1.0\n\n- 新增索引支持：CREATE INDEX, CREATE UNIQUE INDEX\n- 新增约束支持：ALTER TABLE ADD CONSTRAINT\n- 现已支持所有六种方言中的索引与约束识别\n- 兼容 SQL Server 的 CLUSTERED/NONCLUSTERED 选项\n- 支持 Oracle/达梦的 BITMAP 索引\n\nArchive index:\n\nArchive v3.6.3: 38 files, 145382 bytes\n\nFiles: references/dm_conversion.py (225b), references/dm-converter-design.md (9174b), references/dm-converter-v243-fixes.md (5550b), references/dm-converter-v245-bracket-dbo-split.md (4853b), references/dm-converter-v246-fixes.md (3357b), references/dm-converter-v251-fixes.md (3634b), references/dm-converter-v30-fixes.md (1512b), references/dm-converter-v322-fixes.md (2691b), references/dm-converter-v323-fixes.md (1693b), references/dm-converter-v330-fixes.md (2547b), references/dm-converter-v340-fixes.md (1459b), references/dm-converter-v345-fixes.md (6488b), references/dm-converter-v353-fixes.md (1889b), references/document-maintenance.md (1660b), references/hrbi-stage-real-world-test.md (2277b), references/python-indentation-debugging.md (2451b), references/split-convert-workflow-20260627.md (2682b), scripts/batch_convert.py (2149b), scripts/batch_processor.py (9333b), scripts/checkpoint.py (9666b), scripts/common.py (8993b), scripts/config_manager.py (9779b), scripts/dependency_analyzer.py (10518b), scripts/dm_converter.py (117494b), scripts/error_handler.py (6806b), scripts/gui.py (1007b), scripts/result_previewer.py (8423b), scripts/split_sql_v21.py (41974b), scripts/split_sql_v22.py (11134b), scripts/split_sql.py (29311b), scripts/test_dm_converter.py (25861b), scripts/test_sql_splitter.py (14305b), scripts/test_v21_features.py (6344b), scripts/test_v22_features.py (9177b), scripts/unlimited_split.py (3755b), skill-card.md (2653b), SKILL.md (68608b), _meta.json (131b)\n\nFile v3.6.3:SKILL.md\n\n---\n\nname: sql-splitter\n\ndescription: 拆分 SQL 文件为独立文件（存储过程、函数、视图、触发器、表结构、索引、约束），自动分析依赖并生成合并脚本\n\n---\n\n\n\n# SQL 文件拆分工具 v3.6.2\n\n\n\n将包含多个 SQL 对象的单一文件或目录拆分为独立的 .sql 文件，\n\n并自动分析对象间依赖关系，生成按依赖排序的合并脚本。\n\n\n\n## v3.4.5 修复 — dbo两段式替换 + DATETIME2排序 + 方括号替换架构优化 + SELECT INTO转换 + DDL分号\n\n\n\n- **v3.5.4 新增: 过程体内所有语句(DML+变量赋值)补分号** — 新增`_ensure_statement_semicolons()`方法(Step 8.6)，处理DML和变量赋值的分号补全，同时正确处理跨行INSERT INTO\n\n- **v3.5.3 行为变更: 非类型名方括号→加双引号** — `[aa]`→`\"aa\"`(v3.5.3起加双引号; v3.5.2及以前去[]不加双引号→`aa`)，类型名`[int]`→`int`仍只去[]不加双引号。修改位置：`_convert_bracket_identifiers()`新增`_replace_bracket`回调函数，用TYPE_MAPPINGS键集合区分类型名和普通标识符\n\n\n\n- **dbo替换规则(v3.5.6最终版)** — 三段式和两段式区别处理：\n\n  - **三段式**（已有其他schema名，如`HRBI_Stage.[dbo].[xxx]`）: dbo是SQL Server默认schema，直接删除\n\n  - **两段式**（只有dbo，如`[dbo].[xxx]`或`dbo.xxx`）: dbo替换为schema_prefix\n\n  - 关键区分：输入中若有其他schema名存在，dbo是冗余层→删除；若只有dbo，dbo是唯一schema标识→替换为prefix\n\n  - **正则替换顺序（不可颠倒）**: 1)双点号`(\\\\w+)\\\\.\\\\.(\\\\w+)`→`\\\\1.\\\\2` 2)三段式引号`\"\\\\w+\"\\\\.dbo\\\\.\\\"(\\\\w+)\\\"`→`\"$1\"` 3)三段式裸名`(\\\\w+)\\\\.dbo\\\\.(\\\\w+)`→`\"$1\\\".\\\"$2\\\"` 4)三段式混合`\"\\\\w+\\\"\\\\.\\\\w+\\\\.(\\\\w+)`→保留 5)两段式引号`\"dbo\\\"\\\\.\\\"(\\\\w+)\\\"`→`prefix.\\\"$1\\\"` 6)两段式裸名`dbo\\\\.(\\\\w+)`→`prefix.$1`\n\n  - 实现位置：`_replace_dbo_prefix()`\n\n  - ⚠️ 改完dbo规则后必须跑全量312过程验证\n\n  - 详见 [dbo演进史](references/dm-converter-v345-fixes.md)\n\n- **DATETIME2→TIMESTAMP2而非TIMESTAMP(BUG)** — TYPE_MAPPINGS的key构建正则alternation时无排序，`DATETIME`排在`DATETIME2`前面抢先匹配，`DATETIME2`被拆成`DATETIME`+`2`变成`TIMESTAMP2`。修复：`sorted(keys, key=len, reverse=True)`按长度降序排列，`DATETIME2`(9字符)排在`DATETIME`(8字符)前面。影响范围：全局`_TYPE_NAMES_PATTERN`、`_FULL_TYPE_NAMES_PATTERN`、以及`_post_convert_table_types`和`_post_convert_generic_types`中的两个`_bare_type_pattern`\n\n- **方括号替换在token还原后执行导致注释内容被误改(架构BUG)** — `_post_convert_table_types`/`_post_convert_generic_types`中的`\\\\[([^\\\\]]+)\\\\]`在Step 6.7(token还原后)执行，注释已还原为原始文本，注释中含方括号(如`---一次性解决率 是[处理人响应次数`)会被误匹配截断。修复：把方括号替换从Step 6.7移到Step 4`_convert_bracket_identifiers()`中执行——此时注释和字符串已被tokenize保护，方括号只出现在真实SQL代码中，不会被注释内容干扰\n\n- **SELECT INTO #临时表→CTAS创建GTT(新规则)** — 达梦不支持`SELECT ... INTO #新表 FROM ...`建表语法。转换规则：临时表(`INTO #xxx`/`INTO tmp_xxx`)→`CREATE GLOBAL TEMPORARY TABLE \"tmp_xxx\" AS SELECT ... FROM ...`(CTAS方式)；普通表暂不自动转换(与达梦变量赋值语法`SELECT expr INTO var FROM`形式相同，无法区分)。修改位置：`_convert_temp_tables()`新增SELECT INTO处理逻辑\n\n- **过程体内DDL语句结尾加分号(新规则)** — 达梦存储过程体内每条DDL(CREATE/ALTER/DROP TABLE/INDEX等)必须以`;`结尾。新增`_ensure_ddl_semicolons()`方法(Step 8.5)，用状态机扫描过程体：单行DDL如`DROP TABLE xxx`直接补`;`，跨行DDL如`CREATE GLOBAL TEMPORARY TABLE \"tmp_xxx\"\\n(id INT)\\n`在`)`行补`;`，CTAS跨行子查询在遇到下一个语句开头时给上一行补`;`，同时处理`ON PRIMARY`文件组语法去掉。312个存储过程中DDL块缺分号数从447降为0\n\n- **过程体内所有语句(DML+变量赋值)结尾加分号(v3.5.4起)** — 新增`_ensure_statement_semicolons()`方法(Step 8.6)。达梦存储过程体内DML(INSERT/DELETE/UPDATE/SELECT)和变量赋值(`:=`)也必须以分号结尾。关键：跨行INSERT INTO(table_name后无括号，列定义在下一行)不加分号；单行DELETE FROM...WHERE...补分号；v_xxx := expr补分号；跨行赋值((/+,/结尾)不补分号。312个存储过程全部验证通过。\n\n- **过程体内DML语句和变量赋值结尾加分号(v3.5.6新增)** — 达梦存储过程体内每条DML(INSERT/DELETE/UPDATE/SELECT)和变量赋值(`v_xxx := expr`)必须以`;`结尾。新增`_ensure_statement_semicolons()`方法(Step 8.6)，逐行扫描过程体：单行DML如`delete FROM xxx WHERE y=1`直接补`;`，跨行INSERT INTO(table_name\\n(columns)\\nSELECT ...)不加分号，列定义结束后自动补`;`；变量赋值`v_xxx := expr`直接补`;`，跨行赋值(`v_xxx := ('`或`v_xxx := expr +`)不加分号。312个存储过程中DML/变量赋值缺分号数从数百降为0\n\n- **schema_prefix传参链路断裂** — `split_sql_v21.py`的`_convert_split_output()`函数签名有schema_prefix参数，但调用处(第886行)没传，convert调用(第977行)也没传，导致dbo替换永远不生效。修复：调用链全程传递schema_prefix\n\n- **新增CLI参数`--schema-prefix`和`--dm`** — `--schema-prefix`手动指定dbo替换前缀；`--dm`是`--convert-to dm`快捷方式。默认从输入文件名自动提取schema_prefix\n\n- **6条用户规则验证通过**：1) []替换规则：`[aa]`→`\"aa\"`(去[]加双引号)、`[hrbi].[xxx]`→`\"hrbi\".\"xxx\"`(schema/表名加双引号)、`[int]`→`int`/`[nvarchar]`→`nvarchar`(类型名去[]不加双引号，后续做类型映射) 2) dbo替换规则：三段式`[HRBI].[dbo].[xxx]`→`\"HRBI\".\"xxx\"`(删dbo)/两段式`[dbo].[xxx]`→`\"hrbi_stage\".\"xxx\"`(替换为prefix)/裸名`dbo.xxx`→`hrbi_stage.xxx`(替换为prefix) 3) VARCHAR(n)加CHAR定义 4) DATETIME/DATETIME2→TIMESTAMP 5) NCHAR→CHAR 6) SELECT INTO #临时表→CTAS创建GTT\n\n- **7条用户规则验证通过**：1) []替换规则：`[aa]`→`\"aa\"`(去[]加双引号)、`[hrbi].[xxx]`→`\"hrbi\".\"xxx\"`(schema/表名加双引号)、`[int]`→`int`/`[nvarchar]`→`nvarchar`(类型名去[]不加双引号，后续做类型映射) 2) dbo替换规则：三段式`[HRBI].[dbo].[xxx]`→`\"HRBI\".\"xxx\"`(删dbo)/两段式`[dbo].[xxx]`→`\"hrbi_stage\".\"xxx\"`(替换为prefix)/裸名`dbo.xxx`→`hrbi_stage.xxx`(替换为prefix) 3) VARCHAR(n)加CHAR定义 4) DATETIME/DATETIME2→TIMESTAMP 5) NCHAR→CHAR 6) SELECT INTO #临时表→CTAS创建GTT 7) 过程体内DDL语句结尾加分号\n\n- **312个存储过程转换全部成功**\n\n\n\n当多个模式有公共前缀时（如`DATETIME`和`DATETIME2`），正则alternation`|`从左到右匹配，公共前缀排在前面会抢先匹配，留下后缀字符。**必须按长度降序排列alternation中的模式**。同理适用于`SMALLDATETIME`(13字符)>`DATETIMEOFFSET`(15字符)>`DATETIME2`(9字符)>`DATETIME`(8字符)等所有有前缀关系的类型名。这不仅是dm_converter的问题——任何用`|`连接多个可变长度keyword的正则都需注意此陷阱。\n\n\n\n### ⚠️ 方括号替换必须在token保护下执行\n\n\n\n`_convert_bracket_identifiers()`（Step 4，token化后/还原前）是方括号替换的唯一正确位置。此时注释和字符串已被替换为`__TOKEN_N__`占位符，方括号只出现在真实SQL代码中。如果在token还原后（Step 6.7）做方括号替换，注释中的`[`和`]`也会被误处理——即使`[^\\]\\n]+`限制不跨行，注释内不含换行时仍会跨标识符贪婪匹配。\n\n\n\n### ⚠️ schema_prefix必须贯穿调用链\n\n\n\n`split_sql_file()` → `_convert_split_output()` → `convert_sqlserver_to_dm_with_result()` → `DMConverter.convert()` → `_replace_dbo_prefix()`。任何一环漏传schema_prefix，dbo替换就不生效。新增`schema_prefix`参数到`split_sql_file()`签名，CLI默认从输入文件名提取。\n\n\n\n## v3.4.0 新功能 — TRUNCATE→DELETE FROM + TABLE/VIEW结尾加分号 + 所有方言拆分加分号\n\n\n\n- **TRUNCATE TABLE → DELETE FROM** — 达梦不支持TRUNCATE在存储过程内，自动将 `TRUNCATE TABLE xxx` 转换为 `DELETE FROM xxx`\n\n- **TABLE/VIEW/INDEX/CONSTRAINT/SEQUENCE结尾加分号** — 拆分后的表、视图等DDL对象结尾自动加 `;`，确保达梦可直接执行（新增 `_add_ending_semicolon` 方法）\n\n- **所有方言拆分后均加分号** — 之前Oracle/DM方言拆分后不加分号（用`/`代替），现在统一加分号（split_sql_v21.py已去掉Oracle/DM特殊逻辑）\n\n- **53个单元测试全部通过**（新增9个：4个TRUNCATE转换+5个结尾分号）\n\n- **⚠️ TRUNCATE→DELETE语义差异**：TRUNCATE是DDL（不可回滚、重置自增），DELETE是DML（可回滚、不重置自增）。自动转换后行为不完全等价，但这是达梦过程体内的唯一可行方案。如果需要重置自增列，需在DELETE后手动调用序列重置。\n\n\n\n## v3.3.0 新功能 — PROCEDURE双重引号修复 + IDENTITY位置修正 + 临时表正则修复 + token碰撞修复\n\n\n\n- **存储过程PROCEDURE双重引号bug修复** — 之前三个正则顺序执行，第一个替换`PROCEDURE sp_test(...)` 后输出 `\"sp_test\"`，第三个又匹配到已替换的结果再包引号变成 `\"\"sp_test\"\"` 。修复：合并为一个正则+分支回调，确保每个存储过程声明只被匹配和替换一次\n\n- **IDENTITY自增列位置修正** — 之前 `IDENTITY(\"id\",1,1)` 被插在表定义的 `)` 前面，达梦语法要求紧跟 `)` 后面。修复：优先匹配独占一行的 `)` ，也兼容单行写法 `(id INT IDENTITY(1,1) NOT NULL)`\n\n- **#临时表正则修复** — 之前 `[^]]*` 匹配到 `NVARCHAR(100)` 的 `)` 就截断，导致临时表列定义不完整。修复：改用贪婪 `(.+)` + `re.DOTALL` 匹配到最后一个 `)` 。同时修复 `##` 全局临时表先被替换为 `#tmp_` 的bug\n\n- **token_map碰撞修复** — Step3重新tokenize时counter从0开始，`\"v_users\"` 新占位符覆盖了 `'N/A'` 原占位符，导致字符串被替换为标识符。修复：新增 `start_counter` 参数，Step3从原最大key+1开始编号\n\n- **44个单元测试全部通过**\n\n\n\n## v3.2.x 功能 — PROCEDURE用AS + VARCHAR(n CHAR)对PROC生效 + CAST中nvarchar映射\n\n\n\n- **存储过程PROCEDURE用AS而非IS(v3.2.3)** — 达梦存储过程声明用`AS`，函数用`IS`，之前PROCEDURE错误用了Oracle风格的`IS`。**关键区分：PROCEDURE→AS，FUNCTION→IS**\n\n- **存储过程VARCHAR(n)加CHAR语义(v3.2.2)** — DECLARE变量和参数中的`VARCHAR(n)` → `VARCHAR(n CHAR)`，与TABLE转换一致\n\n- **CAST中nvarchar→VARCHAR(n CHAR)(v3.2.2)** — `cast(x as nvarchar(50))` → `CAST(x AS VARCHAR(50 CHAR))`，之前nvarchar在CAST中未被映射\n\n- **_post_convert_generic_types增强(v3.2.2)** — 新增`_bare_type_pattern`映射裸类型名（如CAST中nvarchar），之前只映射方括号包裹的类型\n\n- **SET NOCOUNT ON/OFF直接删除(v3.2.1)** — 达梦不需要，之前注释保留，现在直接整行删除\n\n- **462个真实SQL对象端到端测试通过**(HRBI_Stage.sql, 7万行)\n\n\n\n### 旧版功能(v3.0/v2.4.x)\n\n\n\n- **存储过程/函数方括号替换(v3.0)** — `_post_convert_generic_types`方法，所有对象类型统一做方括号→双引号+dbo替换\n\n- **双点号`..`替换(v3.0)** — SQL Server的`database..object`（省略dbo）→达梦`database.object`\n\n- **数据类型正则bug修复(v3.0)** — 捕获组改为非捕获组`(?:...)`修复双重映射；suffix `[^]]`→`[^)]`修复贪婪匹配\n\n- **UTF-16自动转换** — SQL Server导出文件常为UTF-16编码，需先用Python转UTF-8再拆分\n\n\n\n## v2.4.5 功能 — 方括号转双引号 + dbo前缀智能处理 + 精确拆分\n\n\n\n- **方括号→双引号(v3.5.3起)** - `[schema].[table]` → `\"schema\".\"table\"`\n\n - 普通标识符/列名 `[aa]` → `\"aa\"` (v3.5.3起加双引号; v3.5.2及以前去[]不加双引号→`aa`)\n\n - SQL类型名 `[nvarchar]` → `nvarchar`（去掉方括号+类型映射，不加双引号）\n\n - 支持30+种SQL Server类型名识别\n\n- **dbo替换规则(v3.5.6最终版)** — 三段式和两段式区别处理:\n\n  - **三段式**（已有schema）: dbo直接删除。`[HRBI_Stage].[dbo].[Users]` → `\"HRBI_Stage\".\"Users\"` / `HRBI_Stage.[dbo].[Users]` → `HRBI_Stage.\"Users\"`\n\n  - **两段式**（无schema）: dbo替换为schema_prefix。`[dbo].[Users]` → `\"hrbi_stage\".\"Users\"` / `dbo.Users` → `hrbi_stage.Users`\n\n  - **双点号** `xxx..yyy` → `xxx.yyy`（SQL Server省略dbo的写法）\n\n  - **正则顺序（不可颠倒）**: 1)双点号→2)三段式引号→3)三段式裸名→4)三段式混合→5)两段式引号→6)两段式裸名\n\n  - ⚠️ 改完dbo规则后必须跑全量312过程验证\n\n - 支持双引号包裹格式：`\"dbo\".\"Users\"` 和裸名格式：`dbo.Users` 均可正确匹配\n\n- **精确拆分增强** - 无明确终止符时的兜底逻辑\n\n - 新增`_find_next_create`函数：当找不到`;`或`GO`终止符时，用下一个`CREATE`关键字作为对象边界上界\n\n - 跳过字符串和注释内的`CREATE`，只匹配真正的CREATE语句开头\n\n - 所有对象类型（table/view/procedure/function/trigger/index/constraint）均有兜底\n\n- **VARCHAR CHAR语义后处理** - `VARCHAR(n)` → `VARCHAR(n CHAR)`\n\n - 修复detokenize中类型名映射绕过CHAR语义的问题\n\n\n\n- **重写达梦数据库转换器** - 完全重写 dm_converter.py\n\n  - token化保护: 字符串/注释替换为占位符后再做正则替换，避免误改字符串内容\n\n  - 按对象类型独立转换: procedure/function/view/trigger/table/index/constraint\n\n  - 40+种数据类型映射, 30+种函数映射\n\n  - 变量语法转换: @var -> var, DECLARE @var -> var, SET @var= -> var:=\n\n  - TRY-CATCH -> EXCEPTION WHEN OTHERS THEN\n\n  - 全局变量转换: @@ROWCOUNT -> SQL%ROWCOUNT\n\n  - 触发器伪表: inserted/deleted -> NEW/OLD\n\n  - 转换结果输出到子目录: output_split_dm/\n\n- **拆分后转换集成** - split_sql_v21.py 新增 convert_to 参数\n\n  - 拆分完成后自动调用转换器，按对象类型独立转换\n\n  - 生成达梦版合并脚本 merge_all.sql\n\n- **CLI参数** - split_sql_v22.py 新增 --convert-to dm\n\n- **29个转换单元测试** - test_dm_converter.py 全部通过\n\n- **修复已知bug**:\n\n  - INSERT INTO 不再被误替换为 INTEGERO\n\n  - token_map 合并避免占位符还原丢失\n\n  - content = new_content 遗漏导致变量@替换无效\n\n  - 嵌套括号 VARCHAR(100) 导致参数列表正则截断\n\n  - 终止符 / 不再重复添加\n\n\n\n### 过滤特定对象类型（只提取存储过程等）\n\n\n\n拆分默认输出所有对象类型。如果只需要某类对象（如只要存储过程），有两种方式：\n\n\n\n**方式1：拆分后拷贝（推荐，最简单）**\n\n```bash\n\n# 先完整拆分\n\npython3 ~/.hermes/skills/sql-splitter/scripts/split_sql_v21.py input.sql output_dir --dialect sqlserver\n\n\n\n# 再只拷贝目标类型到独立目录\n\ncp output_dir/proc_*.sql /path/to/output_proc/\n\n```\n\n\n\n**方式2：拆分后删除不需要的文件**\n\n```bash\n\n# 保留存储过程，删除其他\n\ncd output_dir && ls -1 | grep -v '^proc_' | xargs rm\n\n```\n\n\n\n- **⚠️ 拆分→转换→过滤的完整工作流(v3.5.4)** — 正确顺序：(1)拆分 `split_sql_v21.py input.sql output_split` (2)转换 `batch_convert.py output_split output_split_dm schema_prefix` (3)过滤 `cp output_split_dm/proc_*.sql output_final/`。**关键坑**：如果先过滤再转换，`batch_convert.py` 只能转换过滤后的文件，schema_prefix可能不对（文件名变了）；如果转换后新旧文件混在同一目录，`cp` 可能把旧文件也拷进去。**必须先转换再过滤，过滤目录必须清空**。转换后验证：`grep -rl '\\bdbo\\.' output_split_dm/` 应为0\n\n\n\n### 达梦转换使用方法\n\n\n\n```bash\n\n# 拆分SQL Server文件并转换为达梦数据库语法\n\npython3 ~/.hermes/skills/sql-splitter/scripts/split_sql_v22.py input.sql output_dir --dialect sqlserver --convert-to dm\n\n\n\n# 仅转换(不拆分)\n\npython3 -c \"from dm_converter import convert_sqlserver_to_dm; print(convert_sqlserver_to_dm('SELECT GETDATE()', 'generic'))\"\n\n```\n\n\n\n### 质量报告使用方法\n\n\n\n```python\n\n# 单对象报告\n\nfrom dm_converter import DMConverter\n\nfrom report_generator import ConversionReportGenerator\n\n\n\nconverter = DMConverter()\n\nresult = converter.convert(sql_content, 'procedure', schema_prefix='hrbi')\n\nreport = ConversionReportGenerator.generate_single(result, 'sp_test', 'procedure')\n\nprint(report.to_markdown())\n\nprint(f'兼容性评分: {report.score}/100')\n\n\n\n# 批量报告\n\nbatch = ConversionReportGenerator.generate_batch(results, schema_prefix='hrbi')\n\nbatch.save_html('report.html')    # 暗色主题HTML\n\nbatch.save_json('report.json')    # 结构化JSON\n\nbatch.save_markdown('report.md')  # Markdown\n\n\n\n# 快速评分(不生成报告)\n\nscore = ConversionReportGenerator.quick_score(result)\n\n```\n\n\n\n### 批量转换(推荐，用脚本文件)\n\n\n\nv21不支持`--convert-to`参数，需分两步：**拆分→转换→过滤**。详见 [拆分+转换完整工作流](references/split-convert-workflow-20260627.md)。\n\n\n\n⚠️ **拆分≠转换**: 拆分产出的是原始SQL Server语法文件，必须经过`batch_convert.py`转换才能得到达梦语法。只拆分不转换是常见错误。\n\n\n\n```bash\n\n# 1) 拆分\n\npython3 ~/.hermes/skills/sql-splitter/scripts/split_sql_v21.py input.sql output_dir --dialect sqlserver\n\n\n\n# 2) 批量转换(写脚本文件方式)\n\ncat > /tmp/batch_convert.py << 'PYEOF'\n\n#!/usr/bin/env python3\n\nimport os, sys\n\nsys.path.insert(0, '/Users/a1234/.hermes/skills/sql-splitter/scripts')\n\nfrom dm_converter import convert_sqlserver_to_dm\n\n\n\nsrc_dir = sys.argv[1] if len(sys.argv) > 1 else 'output_dir'\n\ndm_dir = sys.argv[2] if len(sys.argv) > 2 else src_dir + '_dm'\n\nschema_prefix = sys.argv[3] if len(sys.argv) > 3 else os.path.basename(src_dir).replace('_split','')\n\nos.makedirs(dm_dir, exist_ok=True)\n\n\n\nok = err = 0\n\nerr_list = []\n\nfor f in sorted(os.listdir(src_dir)):\n\n    if not f.endswith('.sql') or f == 'merge_all.sql': continue\n\n    obj_type = f.split('_')[0]\n\n    type_map = {'proc':'procedure','func':'function','trig':'trigger',\n\n                'view':'view','table':'table','idx':'index','uidx':'index',\n\n                'con':'constraint','seq':'sequence'}\n\n    mapped_type = type_map.get(obj_type, 'generic')\n\n    with open(os.path.join(src_dir, f)) as fh: c = fh.read()\n\n    try:\n\n        converted = convert_sqlserver_to_dm(c, mapped_type, schema_prefix=schema_prefix)\n\n        with open(os.path.join(dm_dir, f), 'w') as fh: fh.write(converted)\n\n        ok += 1\n\n    except Exception as e:\n\n        err += 1; err_list.append(f'{f}: {str(e)[:120]}')\n\n\n\nprint(f'转换完成: {ok} 成功, {err} 失败')\n\nif err_list:\n\n    for e in err_list[:15]: print(f'  - {e}')\n\nPYEOF\n\n\n\npython3 /tmp/batch_convert.py /path/to/output_dir /path/to/output_dir_dm schema_prefix\n\n```\n\n\n\n### 转换规则\n\n\n\n| 类别 | SQL Server | 达梦 |\n\n|------|-----------|------|\n\n| 标识符 | `[aa]`(列名) → `\"aa\"`(去[]加双引号)/`[hrbi].[xxx]`(schema/表名) → `\"hrbi\".\"xxx\"`(去[]加双引号)/`[nvarchar]`(类型名) → `nvarchar`(去[]不加双引号，后续做类型映射) |\n\n| 声明 | CREATE PROCEDURE ... AS | CREATE OR REPLACE PROCEDURE ...(p1 INT) **AS** |\n\n| 数据类型 | INT/BIT/DATETIME/MONEY/NVARCHAR/VARCHAR/UNIQUEIDENTIFIER | INTEGER/BOOLEAN/TIMESTAMP/DECIMAL(19,4)/VARCHAR(n CHAR)/CHAR(36) |\n\n| 函数 | GETDATE()/ISNULL()/LEN()/CONVERT() | CURRENT_TIMESTAMP/NVL()/LENGTH()/CAST() |\n\n| 变量 | @var / DECLARE @var / SET @var= | var / var / var:= |\n\n| 异常 | BEGIN TRY...END TRY BEGIN CATCH...END CATCH | BEGIN...EXCEPTION WHEN OTHERS THEN...END; |\n\n| 事务 | COMMIT TRANSACTION / ROLLBACK TRANSACTION | COMMIT / ROLLBACK |\n\n| 全局变量 | @@ROWCOUNT / @@ERROR | SQL%ROWCOUNT / SQL%ERROR_CODE |\n\n| 触发器 | inserted/deleted | NEW/OLD |\n\n| 终止符 | GO | / |\n\n| 清表 | TRUNCATE TABLE xxx | DELETE FROM xxx |\n\n| 建临时表 | SELECT * INTO #tmp FROM src | CREATE GLOBAL TEMPORARY TABLE \"tmp_xxx\" AS SELECT * FROM src |\n\n| DDL结尾 | 过程体DDL无分号 | 自动补分号(单行/跨行/CTAS/ON PRIMARY均处理, 仅PROC/FUNC/TRIG) |\n\n\n\n### 输出目录结构\n\n\n\n```\n\ninput_split/ ← 原始拆分结果\n\n├── proc_sp_test.sql\n\n├── table_users.sql\n\n├── view_v_users.sql\n\n└── merge_all.sql\n\n\n\ninput_split_dm/ ← 达梦转换版本\n\n├── proc_sp_test.sql\n\n├── table_users.sql\n\n├── view_v_users.sql\n\n└── merge_all.sql\n\n```\n\n\n\n### 支持的对象类型转换\n\n\n\n| 对象类型 | 转换策略 |\n\n|---------|---------|\n\n| 存储过程 | CREATE OR REPLACE + **AS** + 参数@去除 + 参数加括号 + 终止符/ |\n\n| 函数 | CREATE OR REPLACE + RETURN + IS + 终止符/ |\n\n| 视图 | CREATE OR REPLACE + SCHEMABINDING去除 |\n\n| 触发器 | CREATE OR REPLACE + inserted/deleted->NEW/OLD + 终止符/ |\n\n| 表 | IDENTITY保留 + 表选项去除 + 类型映射 |\n\n| 索引 | CLUSTERED/NONCLUSTERED去除 + INCLUDE去除 |\n\n| 约束 | WITH NOCHECK去除 |\n\n\n\n### 转换器核心设计要点（开发调试血泪史）\n\n- **token化保护**: 字符串/注释替换为占位符后再做正则替换，避免误改字符串内容\n\n- **token_map合并**: Step2对象类型转换后重新tokenize时，必须合并旧token_map，否则`__TOKEN_0__`等占位符还原丢失\n\n- **变量@前缀**: 在token还原后再做，且用`_tokenize_strings_only`只保护字符串(不保护注释，注释里@变量也要转)\n\n- **`content = new_content` 不可省略**: re.sub后必须更新content变量，否则后续替换基于旧文本\n\n- **嵌套括号**: `VARCHAR(100)`中的`)`会截断`[^)]*`，参数列表匹配需用`(\\([^)]*(?:\\([^)]*\\)[^)]*)*\\))`匹配嵌套\n\n- **数据类型上下文**: 前缀需包含`DECLARE\\s+`，否则`DECLARE @v DATETIME`中的DATETIME不会被转换\n\n- **INSERT INTO误匹配**: `INSERT INT`被匹配为前缀`\\n`+列名`INSERT`+类型`INT`，需在数据类型替换中排除SQL关键字作为列名\n\n- **⚠️ 多正则顺序执行重复匹配陷阱(v3.3.0修复)**: `_convert_procedure`中三个re.sub顺序处理同类模式(有括号参数/无括号参数/无参数存储过程)，第一个替换后的结果被后续正则再次匹配，导致引号叠加`\"\"sp_test\"\"`。**绝不可用多个re.sub顺序处理同一token的不同形式**——必须用单一正则+分支回调，确保每个模式只被匹配一次。详见 [v3.3.0修复记录](references/dm-converter-v330-fixes.md)\n\n- **⚠️ 添加新转换规则的流程**: 在dm_converter中添加新规则（如TRUNCATE→DELETE）的标准流程：(1)在`_convert_statements`或新增专用方法中实现逻辑 (2)在convert() Step4方法链中调用 (3)在test_dm_converter.py添加测试(至少3个:基本转换+大小写+上下文) (4)跑全部测试确认无回归 (5)更新SKILL.md转换规则表和更新日志 (6)更新wiki页面\n\n- **⚠️ 不用delegate_task做代码审查**: dm_converter.py有2265行，delegate_task子任务逐行审查会超时(600s)。正确做法：主agent直接用search_files+read_file定位关键函数，逐条验证规则覆盖，手动补缺失。delegate_task适合独立可并行的任务，不适合需要大量file I/O的逐行审查\n\n- **⚠️ IDENTITY插入位置陷阱(v3.3.0修复)**: SQL中`)`出现在很多上下文(列类型`VARCHAR(100)`、函数调用、表定义结束)。匹配表结束的`)`必须用上下文锚定(独占一行`^(\\s*)\\)(\\s*$)`或紧跟`;`/换行)，不能简单匹配第一个`)后行尾`——会匹配到列定义中的嵌套`)`。详见 [v3.3.0修复记录](references/dm-converter-v330-fixes.md)\n\n- **⚠️ token_map占位符key碰撞(v3.3.0修复)**: Step3重新tokenize时counter从0开始，新占位符`__TOKEN_0__`覆盖了Step1中同key的原始内容，导致字符串`'N/A'`被还原为标识符`\"v_users\"`。修复：`_tokenize`新增`start_counter`参数，Step3传入`max(已存在key)+1`。**任何生成占位符的系统重新运行时，必须从已存在key的最大值+1开始**。详见 [v3.3.0修复记录](references/dm-converter-v330-fixes.md)\n\n- **⚠️ 方括号替换规则变更(v3.5.3)**: `_convert_bracket_identifiers()`从v3.5.3起区分类型名和非类型名：非类型名`[aa]`→`\"aa\"`(加双引号)，类型名`[int]`→`int`(不加双引号，后续做类型映射)。判断逻辑：取方括号内首段token，与TYPE_MAPPINGS键集合(小写)匹配。**注意**：`[int identity]`这种含后缀的方括号内容，只有首段`int`参与类型名判断，整个内容会被保留为裸名(`int identity`不加双引号)——这是正确行为，因为这种写法只出现在列定义中，后续类型映射会处理。v3.5.2及以前所有方括号内容都不加双引号(→裸名)，需要额外`_post_convert_generic_types`步骤加双引号\n\n- **⚠️ _quote_name先split再去方括号(v3.3.0修复)**: `_quote_name`先检查整体`[...body...]`格式，但`[dbo].[PROC_xxx]`以`[`开头`]`结尾被误当成单个方括号标识符，去首尾后变成`dbo].[PROC_xxx`。**当输入可能是schema.name格式时，必须先split('.')再逐段去方括号/引号**，绝不能先对整体做去除外层处理。详见 [v3.3.0修复记录](references/dm-converter-v330-fixes.md)\n\n- **⚠️ `##`全局临时表替换顺序(v3.3.0修复)**: `re.sub(r'#(\\w+)',...)`对`##GlobalTemp`只替换第二个`#`变成`#tmp_`。修复：先替换`##`→`gtmp_`再替换`#`→`tmp_`。详见 [v3.3.0修复记录](references/dm-converter-v330-fixes.md)\n\n- **⚠️ TRUNCATE TABLE在达梦存储过程内不支持(v3.4.0)**: 达梦不支持在存储过程内使用TRUNCATE TABLE，`_convert_truncate`自动将`TRUNCATE TABLE xxx` → `DELETE FROM xxx`。注意：DELETE FROM没有TRUNCATE的重置IDENTITY/不写日志等语义差异，但达梦存储过程内只能用DELETE\n\n- **⚠️ _ensure_ddl_semicolons状态机实现要点(v3.4.5)**: 达梦存储过程体内DDL必须以`;`结尾。用状态机扫描过程体：(1)单行DDL如`DROP TABLE xxx`直接补`;` (2)跨行建表用括号深度跟踪，在`)`行补`;` (3)CTAS跨行子查询遇到下一个语句开头时回补`;` (4)ON PRIMARY文件组语法去掉后补`;`。**关键：此步骤仅对PROCEDURE/FUNCTION/TRIGGER执行**，TABLE/VIEW等独立对象由Step9处理。312个存储过程DDL缺分号从447降为0\n\n- **⚠️ SELECT INTO #临时表达梦不支持(v3.4.5)**: 达梦不支持`SELECT ... INTO #新表 FROM ...`建表语法。`_convert_temp_tables`自动将临时表的SELECT INTO转为CTAS：`CREATE GLOBAL TEMPORARY TABLE \"tmp_xxx\" AS SELECT ... FROM ...`。**非临时表的SELECT INTO暂不自动转换**——因为与达梦变量赋值语法`SELECT expr INTO var FROM ...`形式相同，无法程序化区分。用户需手动处理非临时表的SELECT INTO（改为先CREATE TABLE再INSERT INTO ... SELECT）\n\n- **⚠️ delegate_task不适合逐行代码审查(v3.4.5)**: 2265行dm_converter.py用delegate_task子任务逐行审查600s超时。正确做法：主agent直接用search_files+read_file定位关键函数，逐条验证规则覆盖，手动补缺失。delegate_task适合独立可并行的任务，不适合大量file I/O的审查\n\n- **⚠️ TABLE/VIEW/INDEX/CONSTRAINT/SEQUENCE结尾必须加分号(v3.4.0)**: 之前只有PROCEDURE/FUNCTION/TRIGGER有`_add_terminator`加`/`终止符，TABLE/VIEW等DDL对象结尾没有统一加分号。新增`_add_ending_semicolon`确保这些对象以`;`结尾。拆分阶段(split_sql_v21.py)也去掉了Oracle/DM不加分号的特殊逻辑，所有方言统一加分号\n\n- **⚠️ 函数参数链路断裂是隐蔽BUG(v3.4.5)**: `_convert_split_output()`签名有schema_prefix参数但调用处没传，`convert_sqlserver_to_dm_with_result()`也没传——函数链路每环漏传，整个功能静默失效。**教训：新增参数到函数签名时，必须grep所有调用点确保传参**，否则功能\"存在但不生效\"的BUG极难发现\n\n- **⚠️ \"完全拆分出X\"≠\"拆分所有\"**：当用户说\"完全拆分出存储过程\"时，意思是**只提取存储过程**，不是拆分全部对象类型。应使用过滤方式（见上方\"过滤特定对象类型\"章节），先拆分再按前缀筛选，而非默认全量输出后让用户自己找。\n\n\n\n- **⚠️ patch工具缩进陷阱（严重，已反复触发）**: patch工具修改Python缩进时极易出错：(1) else块内代码被放到块外 (2) if子块和if本身同缩进 (3) 修复脚本的缩进也可能不对（17空格vs16空格的1位偏差导致整个if块变成else子块）。**终极方案：涉及Python方法体修改时，不要用patch，用Python脚本替换整个方法（find方法定义起始→find return content结束→拼接新方法体）。每次修改后必须用`python3 -m py_compile file.py`验证。仅靠lint不够——py_compile才能发现缩进导致的SyntaxError/IndentationError**。详见 [v2.4.5设计记录](references/dm-converter-v245-bracket-dbo-split.md)\n\n- **⚠️ Python缓存陷阱**: 修改.py后pytest可能运行旧的`__pycache__/*.pyc`。修改后必须`find . -name '*.pyc' -delete`或`PYTHONDONTWRITEBYTECODE=1 python3 -m pytest ...`。否则改了代码但测试结果不变，误导调试方向\n\n- **⚠️ write_file不能写代码文件**: Hermes的write_file工具会给内容添加`NNN|`行号前缀，导致Python文件损坏。**代码文件只能用patch工具或terminal的python脚本修改**。详见 [v2.4.3修复记录](references/dm-converter-v243-fixes.md)\n\n- **⚠️ Python脚本嵌套字符串修改dm_converter**: execute_code中用字符串拼接修改dm_converter.py会因缩进/引号嵌套报IndentationError。**正确做法**：写独立.py脚本文件到/tmp/再用terminal执行。步骤：(1)write_file写patch脚本到/tmp (2)terminal运行`python3 /tmp/patch_xxx.py` (3)py_compile验证 (4)跑测试。这是patch工具和terminal python脚本的补充方案——当patch工具做复杂多位置修改时，脚本文件更可控\n\n- **⚠️ detokenize类型名映射陷阱**: 方括号包裹的类型名`[nvarchar]`在token保护下不会被Step4类型映射匹配，必须在detokenize还原时同时做映射+去掉方括号，否则变成`nvarchar(100)`但已过Step4不再映射。详见 [v2.4.5设计记录](references/dm-converter-v245-bracket-dbo-split.md)\n\n- **⚠️ _convert_data_types捕获组偏移陷阱(v3.0修复)**: 正则`(\\[?(TYPE_PATTERN)\\]?)`中TYPE_PATTERN本身是`(INT|VARCHAR|...)`捕获组，导致type_name是group(3)而内部type是group(4)，suffix本应在group(4)却变成了group(5)。症状：`INT`映射成`INTEGERINT`、`VARCHAR`映射成`VARCHARVARCHAR`。修复：改`(TYPE_PATTERN)`为`(?:TYPE_PATTERN)`非捕获组\n\n- **⚠️ _convert_data_types suffix贪婪匹配陷阱(v3.0修复)**: suffix正则`(\\([^]]*...) used `[^]]*` (match non-`]`) instead of `[^)]*` (match non-`)`)。`[^]]*` matches everything up to a `]` which rarely appears in SQL, so `(100)` 后的所有内容全被吞进suffix。症状：第一个类型后面所有列定义都被当作suffix，后续列的类型映射全部失效。修复：`[^]]` → `[^)]`\n\n- **⚠️ procedure/function方括号不替换(v3.0修复)**: `_post_convert_table_types`只对TABLE/VIEW做方括号→双引号+类型映射+dbo替换，PROCEDURE/FUNCTION走的是`_replace_dbo_prefix`只处理双引号格式的dbo。但procedure原始SQL是`[dbo].[xxx]`方括号格式，dbo替换匹配不到。修复：新增`_post_convert_generic_types`对所有非TABLE/VIEW类型做方括号→双引号+类型映射+dbo替换\n\n- **⚠️ 存储过程VARCHAR(n)缺少CHAR语义(v3.2.2修复)**: 之前`_post_convert_generic_types`注释写\"不做 VARCHAR(n) -> VARCHAR(n CHAR) (过程体内变量声明不需要)\"，但用户要求存储过程中的VARCHAR也必须加CHAR语义，与TABLE一致。修复：(1)在`_post_convert_generic_types`中新增`VARCHAR(n) → VARCHAR(n CHAR)`替换正则 (2)DECLARE变量/参数也会被加CHAR语义\n\n- **⚠️ CAST中nvarchar未映射(v3.2.2修复)**: `_post_convert_generic_types`原来只有`_bracket_type_pattern`(匹配方括号包裹的类型如`[nvarchar]`)，但SQL Server过程体中`cast(x as nvarchar(50))`的`nvarchar`是裸名无方括号，不匹配。修复：新增`_bare_type_pattern`用`(?<=\\s)`前缀匹配裸类型名。注意：`_bare_type_pattern`必须用lookbehind `(?<=\\s)`避免匹配列名(列名在逗号/括号后不会有空格前缀)\n\n- **⚠️ PROCEDURE三正则顺序执行导致双重引号(v3.3.0修复)**: `_convert_procedure`中三个`re.sub`顺序执行，第一个`_format_bracket_params`替换后输出`PROCEDURE \"sp_test\" (...)`，第三个`_fmt_no_param_proc`的正则`PROC\\s+(.+?)\\s+AS`又匹配到了这个结果，把`\"sp_test\" (...)`当成name再包引号变成`\"\"sp_test\"\"`。修复：合并为单一正则+分支回调`_format_proc`，确保每个存储过程声明只被匹配和替换一次。**教训：多个正则顺序替换同一类语法时，后面的正则会匹配前面替换的结果——必须用单一正则或标记已替换区域**\n\n- **⚠️ SET NOCOUNT ON/OFF带分号不匹配(v3.2.1修复)**: `_convert_statements`正则`^\\s*SET NOCOUNT ON\\s*$`不匹配`SET NOCOUNT ON;`（行末带分号），导致过程体内部的SET NOCOUNT ON未被转换。修复：正则加`\\s*;?`兼容分号。同时用户要求**直接删除**而非注释保留，所以`SET NOCOUNT ON`/`SET NOCOUNT OFF`映射为空字符串，正则加`\\n?`吃掉换行不留空行\n\n- **⚠️ PROCEDURE三正则顺序执行导致双重引号(v3.3.0修复)**: `_convert_procedure`中三个`re.sub`顺序执行，第一个替换后输出`PROCEDURE \"sp_test\" (...)`，第三个正则又匹配到把`\"sp_test\"(...)`当name再包引号变成`\"\"sp_test\"\"`。修复：合并为单一正则+分支回调。**教训：多个正则顺序替换同一类语法时，后面的会匹配前面替换的结果——必须用单一正则或标记已替换区域**\n\n- **⚠️ IDENTITY子句插入位置(v3.3.0修复)**: 达梦语法`CREATE TABLE \"name\" (...) IDENTITY(\"col\", 1, 1)`。之前正则匹配到`VARCHAR(100 CHAR)`行末的`)`把IDENTITY插在了`)`前面。修复：优先匹配独占一行的`^(\\s*)\\)(\\s*$)`，fallback到行尾`)`。**教训：表定义中列类型括号里的`)`和表结束`)`在正则中难以区分——要求结束括号独占一行或用锚点精确匹配**\n\n- **⚠️ 临时表正则嵌套括号截断(v3.3.0修复)**: `[^)]*`遇`NVARCHAR(100)`的`)`就截断。修复：改用贪婪`(.+)`+`re.DOTALL`匹配到最后一个`)`。**教训：匹配\"最后一个右括号\"时，排除式模式不可靠，改用贪婪+DOTALL**\n\n- **⚠️ `##`全局临时表替换顺序(v3.3.0修复)**: `re.sub(r'#(\\w+)',...)`对`##GlobalTemp`只替换第二个`#`变成`#tmp_`。修复：先替换`##`→`gtmp_`再替换`#`→`tmp_`。**教训：替换含`##`的标识符时必须先处理双#再处理单#**\n\n- **⚠️ token_map碰撞(v3.3.0修复)**: Step1 tokenize`'N/A'`→`__TOKEN_0__`，Step3重新tokenize`\"v_users\"`又分配`__TOKEN_0__`覆盖原值。修复：`_tokenize`新增`start_counter`参数，Step3从原最大key+1开始。**教训：pipeline中多次tokenize必须保证key不碰撞——传起始偏移量**\n\n- **⚠️ _quote_name处理[dbo].[xxx](v3.3.0修复)**: `[dbo].[PROC_xxx]`整体被误当单个方括号标识符，剥首尾括号变成`dbo].[PROC_xxx`。修复：先按`.`拆分再逐段去方括号/引号。**教训：处理含`.`的标识符时，必须先拆分再清理每段括号**\n\n- **⚠️ SET NOCOUNT ON 在过程体内部不转换**: 转换器只处理紧跟 `AS` 后的 `SET NOCOUNT ON`（转为注释）。如果 `SET NOCOUNT ON` 出现在过程体中间（如第7行），不会被转换，残留到输出中。达梦不支持该语句，需手动注释或删除。实测462对象中2个存此问题（0.43%），属已知边界case\n\n- **⚠️ git push分支对齐**: 本地git可能在`master`分支提交，但GitHub仓库HEAD分支可能是`main`。push到`master`不更新GitHub默认展示的`main`分支，导致网页看不到最新代码。修正：`git remote show origin`确认HEAD分支 → `git checkout main && git merge master && git push origin main`\n\n- **⚠️ GitHub API上传大文件超时**: dm_converter.py(88KB+)通过GitHub Contents API上传时，base64后请求体巨大，curl经常超时(300s+)。**推荐方式**：直接`git add && git commit && git push`，比API逐文件PUT快得多且更可靠。之前memory记录\"api.github.com可达但github.com被墙\"已过时——2026-06-14实测git push可正常工作。仅在git push完全不通时才fallback到API上传\n\n- **⚠️ UTF-16编码SQL文件**: SSMS导出的SQL脚本常为UTF-16编码(带BOM)，拆分前必须先转UTF-8，否则内容被当成二进制乱码。转换命令: `python3 -c \"open('out.sql','w',encoding='utf-8').write(open('in.sql',encoding='utf-16').read())\"` 详见 [v3.0修复与UTF-16转换记录](references/dm-converter-v30-fixes.md)\n\n- **⚠️ DATE类型映射重复陷阱**: 当存储过程参数类型为`DATE`时（无方括号包裹），detokenize的类型映射可能在Step4已经替换过一次`DATE→DATE`（因为DATE在达梦也是合法类型名），但如果正则边界不够精确，会把`DATE`后面的换行/空白也吃进去，导致相邻关键字拼接，如`DATE\\nAS`变成`DATEDATE\\nAS`或`DATEDATEAS`。根因：类型映射正则的后缀锚点需用`\\b`或`(?=\\s|,|\\)|$)`精确截断，不能贪婪吃进换行符。**每次修改类型映射正则后，必须跑`test_dm_converter.py`验证**\n\n- **⚠️ dbo前缀正则陷阱**: detokenize后方括号变成双引号，正则必须同时匹配`\"dbo\".`和`dbo.`两种格式。三段式必须在两段式之前处理，否则`schema.dbo.object`中的`dbo.object`先被两段式误匹配。详见 [v2.4.5设计记录](references/dm-converter-v245-bracket-dbo-split.md)\n\n\n\n### 运行转换测试\n\n\n\n```bash\n\ncd ~/.openclaw/skills/sql-splitter/scripts\n\npython3 -m pytest test_dm_converter.py -v\n\n```\n\n\n\n### 发布到 clawhub.ai\n\n\n\n```bash\n\n# ⚠️ 必须用绝对路径，不能用相对路径`.`\n\nclawhub publish /absolute/path/to/skill-dir --slug sql-splitter --version X.Y.Z\n\n# 错误: clawhub publish .  → \"Error: SKILL.md required\" (即使SKILL.md明明存在)\n\n# 正确: clawhub publish /absolute/path/to/skill-dir\n\n# ⚠️ 版本号冲突：clawhub不允许覆盖已发布版本，必须升版本号(如3.2.2→3.2.3)重新发布\n\n# ⚠️ 如果publish成功但随后改了SKILL.md，再次publish同一版本号会报\"already exists\"，必须升版\n\n### ⚠️ GitHub 推送可能失败\n\nmacOS环境下github.com经常网络不通（\"Failed to connect to github.com port 443\"）。clawhub发布不依赖GitHub，可以独立使用。如果需要同步GitHub，稍后重试或检查网络。\n\n### ⚠️ 商业化路径已废弃\n\nv3.6.0起移除了所有License/产品化相关代码（keygen.py、license_verifier.py、RSA密钥对）。sql-splitter现在是纯开源免费工具，无功能限制。references/下的产品化规划文档也已删除。\n\n```\n\n\n\n**注意：clawhub上可能存在同slug不同owner的技能**（如`@fish1981bimmer/sql-splitter`和`@kingaiwork/sql-splitter`），`clawhub inspect`可能报`AMBIGUOUS_SKILL_SLUG`错误。此时用`clawhub install @fish1981bimmer/sql-splitter`指定owner。\n\n\n\n### 发布到 GitHub\n\n\n\n```bash\n\ncd /Users/a1234/.hermes/skills/sql-splitter\n\ngit add -A && git commit -m \"vX.Y.Z: 变更说明\"\n\n# ⚠️ 确认远程主分支名！git remote show origin 查看HEAD branch\n\n# 如果远程HEAD是main但本地在master上提交，push到master不会更新GitHub默认展示的main\n\n# 修正: git checkout main && git merge master && git push origin main\n\ngit push origin main\n\n```\n\n\n\n## 支持的 SQL 方言\n\n\n\n- MySQL\n\n- PostgreSQL\n\n- Oracle\n\n- SQL Server\n\n- 达梦 (DM)\n\n- 通用 (Generic)\n\n\n\n## v2.2.1 功能\n\n\n\n- **GUI 界面** - 提供图形化界面进行 SQL 文件拆分操作\n\n- **断点续传** - 支持记录处理进度，中断后可以继续处理\n\n- **批量并行处理** - 支持同时处理多个 SQL 文件，提升处理速度\n\n- **结果预览和对比** - 可视化查看拆分结果，支持与原始文件对比\n\n- **配置文件管理** - 保存和加载常用配置，支持导入导出\n\n- **详细错误处理** - 结构化错误信息，包含错误类型、上下文和修复建议\n\n- **Dry-run 预览模式** - 预览拆分结果而不实际创建文件\n\n- **安全修复** - pickle反序列化漏洞修复，检查点改用JSON序列化\n\n\n\n## 支持的 SQL 对象类型\n\n\n\n| 类型 | 前缀 | 说明 |\n\n|------|------|------|\n\n| 存储过程 | `proc_` | CREATE PROCEDURE |\n\n| 函数 | `func_` | CREATE FUNCTION |\n\n| 视图 | `view_` | CREATE VIEW |\n\n| 触发器 | `trig_` | CREATE TRIGGER |\n\n| 表结构 | `table_` | CREATE TABLE |\n\n| 包 | `pkg_` | CREATE PACKAGE |\n\n| 索引 | `idx_` | CREATE INDEX |\n\n| 唯一索引 | `uidx_` | CREATE UNIQUE INDEX |\n\n| 约束 | `con_` | ALTER TABLE ADD CONSTRAINT |\n\n| 序列 | `seq_` | CREATE SEQUENCE |\n\n| 同义词 | `syn_` | CREATE SYNONYM (Oracle) |\n\n| 事件 | `evt_` | CREATE EVENT (MySQL) |\n\n| 物化视图 | `mv_` | CREATE MATERIALIZED VIEW (PostgreSQL) |\n\n| 类型 | `type_` | CREATE TYPE |\n\n\n\n## v2.0 核心改进\n\n\n\n### 边界检测重写\n\n- 使用 **BEGIN...END 深度匹配**确定存储过程/函数/触发器边界\n\n- 支持 IF...THEN...END IF、CASE...END CASE、LOOP...END LOOP 嵌套\n\n- 不再依赖\"下一个 CREATE 位置\"做上界，**正确处理过程体内的嵌套 CREATE 语句**\n\n- Oracle/DM: 通过 `/` 终止符定位；SQL Server: 通过 `GO` 定位\n\n- PostgreSQL: 支持 `$$...$$` 包裹语法\n\n- 字符串和注释内的分号/关键字不会干扰边界检测\n\n\n\n### 依赖分析改进\n\n- 函数调用检测改为**限定上下文模式**（:= 赋值、WHERE/HAVING 子句等），大幅减少误报\n\n- SQL 关键字过滤表扩展到 150+ 个，涵盖内置函数、控制流、聚合等\n\n- 自引用自动排除\n\n- 循环依赖不再报错，按类型优先级追加\n\n\n\n### 合并脚本方言适配\n\n- Oracle/DM: `@@filename` + `SET DEFINE OFF`\n\n- SQL Server: `:r filename` + `GO`\n\n- PostgreSQL: `\\i filename` + `ON_ERROR_STOP`\n\n- MySQL: `source filename`\n\n- 通用: 注释方式\n\n\n\n### 架构优化\n\n- 提取 `common.py` 共享模块：SQLDialect 枚举、对象前缀、类型优先级、关键字表\n\n- `dependency_analyzer.py` 不再重复定义枚举，直接引用 common\n\n- 拆分后自动调用依赖分析，生成 `merge_all.sql`\n\n- 新增 37 个单元测试\n\n\n\n## 使用方法\n\n\n\n### GUI 模式（推荐）\n\n```bash\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/gui.py\n\n```\n\n\n\n### 单文件拆分\n\n```bash\n\n# 推荐: 用 v21 (CLI稳定, 支持所有拆分功能)\n\npython3 ~/.hermes/skills/sql-splitter/scripts/split_sql_v21.py <input.sql> [output_dir] --dialect sqlserver\n\n\n\n# v22 目前在无GUI环境会 ImportError (SQLSplitterGUI 依赖 tkinter)\n\n# 如需使用, 确保系统有 tkinter: apt install python3-tk / brew install python-tk\n\npython3 ~/.hermes/skills/sql-splitter/scripts/split_sql_v22.py <input.sql> [output_dir] 2>/dev/null || \\\n\n  python3 ~/.hermes/skills/sql-splitter/scripts/split_sql_v21.py <input.sql> [output_dir]\n\n```\n\n### 拆分后转达梦（两步法）\n\n\n\n```bash\n\n# v21 不支持 --convert-to 参数, 需分两步:\n\n# 1) 拆分\n\npython3 ~/.hermes/skills/sql-splitter/scripts/split_sql_v21.py input.sql output_dir --dialect sqlserver\n\n# 2) 批量转换（用 dm_converter 直接调用）\n\n# ⚠️ 注意: 不要用 python3 -c \"复杂多行脚本\"，安全扫描会拦截\n\n# 推荐写临时脚本文件再运行:\n\npython3 /tmp/batch_convert.py  # 脚本内容见 scripts/batch_convert.py\n\n```\n\n\n\n**批量转换脚本**：`scripts/batch_convert.py` — 用法: `python3 scripts/batch_convert.py [src_dir] [dm_dir] [schema_prefix]`\n\n- 自动按文件名前缀(proce→procedure, view→view, table→table等)识别对象类型\n\n- 遍历目录逐文件调用 `convert_sqlserver_to_dm()`\n\n- 默认参数: src_dir=HRBI_Stage_split, schema_prefix=HRBI_Stage\n\n\n\n### UTF-16 编码文件处理\n\n```bash\n\n# SQL Server 导出的 .sql 文件常为 UTF-16 编码, 需先转 UTF-8:\n\npython3 -c \"\n\nwith open('input.sql','r',encoding='utf-16') as f: content=f.read()\n\nwith open('input_utf8.sql','w',encoding='utf-8') as f: f.write(content)\n\nprint(f'Converted: {len(content.splitlines())} lines')\n\n\"\n\n# 然后用 input_utf8.sql 做拆分\n\n```\n\n\n\n### 批量拆分（目录）\n\n```bash\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --batch <目录路径> [输出目录]\n\n```\n\n\n\n### 批量拆分（多个文件）\n\n```bash\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --batch \"file1.sql,file2.sql,file3.sql\" [输出目录]\n\n```\n\n\n\n### 指定方言\n\n```bash\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --dialect oracle input.sql\n\n```\n\n\n\n支持的方言：`mysql`, `postgresql`, `oracle`, `sqlserver`, `dm`, `generic`\n\n\n\n### 不生成合并脚本\n\n```bash\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --no-merge input.sql\n\n```\n\n\n\n### 预览结果\n\n```bash\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --preview input.sql output_dir\n\n```\n\n\n\n### 检查点管理\n\n```bash\n\n# 列出所有检查点\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --checkpoint --list\n\n\n\n# 查看恢复进度\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --checkpoint --resume input.sql\n\n\n\n# 清理旧检查点\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --checkpoint --clear --days 7\n\n\n\n# 删除检查点\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --checkpoint --delete input.sql\n\n```\n\n\n\n### 配置管理\n\n```bash\n\n# 列出所有配置\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --config --list\n\n\n\n# 保存配置\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --config --save --name oracle --dialect oracle\n\n\n\n# 加载配置\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --config --load --name oracle\n\n\n\n# 导出配置\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --config --export --name oracle --export-path oracle_config.json\n\n\n\n# 导入配置\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --config --import --import-path oracle_config.json --name oracle\n\n```\n\n\n\n### 参数说明\n\n\n\n| 参数 | 说明 |\n\n|------|------|\n\n| `input.sql` | 要拆分的 SQL 文件路径（单文件模式必需） |\n\n| `--batch` | 批量模式标志 |\n\n| `--dialect` | 指定 SQL 方言 |\n\n| `--no-merge` | 不生成依赖排序的合并脚本 |\n\n| `-q`, `--quiet` | 静默模式 |\n\n| `output_dir` | 输出目录（可选，默认：原文件名_split） |\n\n\n\n### 运行测试\n\n\n\n```bash\n\ncd ~/.openclaw/skills/sql-splitter/scripts\n\npython3 -m pytest test_dm_converter.py -v\n\n```\n\n\n\n### 端到端质量验证（大文件转换后）\n\n\n\n转换完成后，建议跑10项质量检查确认残留SQL Server语法：\n\n\n\n```bash\n\nDM_DIR=\"输出目录_dm\"\n\necho \"1. 残留方括号:        $(grep -rl '\\[.*\\]' $DM_DIR --include='*.sql' 2>/dev/null | wc -l)\"\n\necho \"2. 残留dbo.:          $(grep -rl '\\bdbo\\.' $DM_DIR --include='*.sql' 2>/dev/null | wc -l)\"\n\necho \"3. 残留@@变量:        $(grep -rl '@@[A-Z]' $DM_DIR --include='*.sql' 2>/dev/null | wc -l)\"\n\necho \"4. 残留SET NOCOUNT ON:$(grep -rl 'SET NOCOUNT ON' $DM_DIR --include='*.sql' 2>/dev/null | wc -l)\"\n\necho \"5. 残留GO终止符:      $(grep -rwl '^GO$' $DM_DIR --include='*.sql' 2>/dev/null | wc -l)\"\n\necho \"6. 残留GETDATE():     $(grep -rl 'GETDATE()' $DM_DIR --include='*.sql' 2>/dev/null | wc -l)\"\n\necho \"7. 残留ISNULL:        $(grep -rl '\\bISNULL(' $DM_DIR --include='*.sql' 2>/dev/null | wc -l)\"\n\necho \"8. 双重映射(INTEGERINT等): $(grep -rl 'INTEGERINT\\|VARCHARVARCHAR' $DM_DIR --include='*.sql' 2>/dev/null | wc -l)\"\n\necho \"9. 双点号残留:        $(grep -rl '\\.\\.' $DM_DIR --include='*.sql' 2>/dev/null | wc -l)\"\n\necho \"10.CREATE OR REPLACE数:$(grep -rl 'CREATE OR REPLACE' $DM_DIR --include='*.sql' 2>/dev/null | wc -l)\"\n\n```\n\n\n\n所有计数应为0（除了第10项和第4项可能有少量边界case残留需手动处理）。\n\n\n\n## 输出示例\n\n\n\n假设输入文件 `myapp.sql` 包含：\n\n- 表 `users`\n\n- 视图 `v_users`（依赖 users）\n\n- 存储过程 `sp_update`（依赖 users）\n\n\n\n输出：\n\n```\n\nmyapp_split/\n\n├── table_users.sql\n\n├── view_v_users.sql\n\n├── proc_sp_update.sql\n\n└── merge_all.sql          ← 按依赖排序的合并脚本\n\n```\n\n\n\n`merge_all.sql` 内容（以 Oracle 为例）：\n\n```sql\n\n-- [1/3] table: users\n\n@@table_users.sql\n\n\n\n-- [2/3] view: v_users  -- depends on: users\n\n@@view_v_users.sql\n\n\n\n-- [3/3] procedure: sp_update  -- depends on: users\n\n@@proc_sp_update.sql\n\n```\n\n\n\n## 文件结构\n\n\n\n```\n\nsql-splitter/\n\n├── SKILL.md ← 本文档\n├── SKILL.md ← 本文档\n├── references/\n\n│   ├── dm-converter-design.md ← 达梦转换器设计要点\n\n│   ├── dm-converter-v243-fixes.md ← v2.4.3 修复记录\n\n│   ├── dm-converter-v246-fixes.md ← v2.4.6 修复记录（捕获组偏移+suffix贪婪+procedure方括号）\n\n│   ├── dm-converter-v30-fixes.md ← v3.0 修复记录（含HRBI_Stage真实项目验证）\n\n│   ├── dm-converter-v322-fixes.md ← v3.2.2 修复记录（PROC VARCHAR CHAR + CAST nvarchar映射）\n│   ├── dm-converter-v323-fixes.md ← v3.2.3 修复记录（PROCEDURE用AS而非IS）\n│   ├── dm-converter-v330-fixes.md ← v3.3.0 修复记录（双重引号+IDENTITY位置+临时表正则+token碰撞+方括号处理）│   ├── dm-converter-v330-fixes.md ← v3.3.0 修复记录（双重引号+IDENTITY位置+临时表正则+token碰撞+方括号处理）\n\n│   ├── dm-converter-v345-fixes.md ← v3.4.5 修复记录（dbo两段式+DATETIME2排序+方括号架构+schema_prefix传参）\n\n│   ├── dm-converter-v353-fixes.md ← v3.5.3 修复记录（方括号替换规则变更：非类型名加双引号）\n\n│   ├── dm-converter-v340-fixes.md ← v3.4.0 修复记录\n\n│   ├── dm-converter-v245-bracket-dbo-split.md ← v2.4.5 方括号+dbo设计记录\n\n│   ├── split-convert-workflow-20260627.md ← 拆分+转换完整工作流 + 用户反馈理解教训\n│   ├── document-maintenance.md ← 文档维护规范（删除功能时的清理清单+重复条目预防）\n\n└── scripts/\n\n    ├── common.py ← 共享模块（枚举、常量、工具函数）\n\n    ├── split_sql.py ← v2.0 主拆分脚本\n\n    ├── split_sql_v21.py ← v2.1 主拆分脚本（带错误处理+转换集成）\n\n    ├── split_sql_v22.py ← v2.2 主拆分脚本（集成所有新功能+功能守卫）\n\n    ├── dm_converter.py ← 达梦数据库转换器 v3.4.5\n\n    ├── report_generator.py ← 转换质量报告生成器（兼容性评分+风险+HTML/MD/JSON）\n\n    ├── dependency_analyzer.py ← 依赖分析器\n\n    ├── error_handler.py ← 错误处理模块\n\n    ├── gui.py ← GUI 界面（tkinter）\n\n    ├── checkpoint.py ← 断点续传模块\n\n    ├── batch_processor.py ← 批量并行处理模块\n\n    ├── result_previewer.py ← 结果预览和对比模块\n\n    ├── batch_convert.py ← 批量达梦转换脚本(拆分后调用)\n\n    ├── config_manager.py ← 配置文件管理模块\n\n    ├── test_sql_splitter.py ← 拆分单元测试（37个）\n\n    ├── test_v21_features.py ← v2.1 功能测试\n\n    ├── test_dm_converter.py ← 达梦转换单元测试（53个）\n\n    └── test_v22_features.py ← v2.2 功能测试\n\n```\n\n\n\n## 达梦转换器已知问题（v2.4.3）\n\n\n\n> v2.4.3 修复了 9 个核心 BUG（DATEADD参数重排、SELECT INTO、IF/WHILE控制流、PRINT等），40个测试全部通过。详见 [v2.4.3修复记录](references/dm-converter-v243-fixes.md)\n\n\n\n**仍需手动调整的项目：**\n\n- STRING_AGG→LISTAGG 缺少 WITHIN GROUP 子句\n\n- STUFF→OVERLAY、REPLICATE→RPAD 语义不完全对等\n\n- 临时表 #temp → GTT/普通表\n\n- EXEC/EXECUTE 动态SQL → EXECUTE IMMEDIATE\n\n- RAISERROR → RAISE_APPLICATION_ERROR\n\n- TOP n → ROWNUM/FETCH FIRST\n\n- MERGE/游标/WITH(NOLOCK)/IF EXISTS 等差异\n\n\n\n**v2.4.4 已修复的映射：**\n\n- VARCHAR(n) → VARCHAR(n CHAR)：达梦VARCHAR默认BYTE语义，必须加CHAR才等效SQL Server的字符语义\n\n- UNIQUEIDENTIFIER → CHAR(36)：达梦用CHAR(36)而非VARCHAR(36)，UUID是定长\n\n\n\n## v2.4.1 新功能 — 拆分自动加 OR REPLACE\n\n\n\n- **视图和存储过程自动添加 OR REPLACE** — 拆分时对 procedure/function/view/trigger 四类对象，自动将 `CREATE` 转为 `CREATE OR REPLACE`\n\n - 达梦和 Oracle 环境下对象已存在时需要 `OR REPLACE`，否则会报错\n\n - 已有 `OR REPLACE` 的语句不会重复添加\n\n - 所有方言均生效（不仅限于 DM/Oracle）\n\n - 实现在 split_sql_v21.py 的 `obj_content` 提取后、写入文件前\n\n\n\n## 注意事项\n\n\n\n- 使用正则+深度匹配识别 SQL 对象边界，对极复杂嵌套语法可能有局限\n\n- 默认 UTF-8 编码，遇到编码问题自动 replace\n\n- 建议先备份原文件\n\n- 批量模式会自动创建以原文件名命名的子目录\n\n- 自动检测 SQL 方言，也可手动指定\n\n- 同名文件自动追加序号（如 `proc_sp_init_2.sql`）\n\n\n\n## 常见问题\n\n\n\n### 拆分结果不正确（多个对象混在一个文件中）\n\n\n\n**症状**：拆分后生成的文件包含多个 SQL 对象，而不是每个对象一个文件。\n\n\n\n**原因**：原始 SQL 文件中的对象缺少分号结束符。sql-splitter 依赖分号来确定对象的结束位置。\n\n\n\n**解决方案**：为每个 SQL 语句添加分号。例如：\n\n\n\n```sql\n\n-- 错误：缺少分号\n\nCreate table a(\n\n  Id int,\n\n  Name varchar(10)\n\n)\n\n\n\nCreate table b(\n\n  Id int,\n\n  Name varchar(10)\n\n)\n\n\n\n-- 正确：添加分号\n\nCreate table a(\n\n  Id int,\n\n  Name varchar(10)\n\n);\n\n\n\nCreate table b(\n\n  Id int,\n\n  Name varchar(10)\n\n);\n\n```\n\n\n\n**快速修复方法**：\n\n```bash\n\n# 使用 sed 为每个 CREATE 语句后的空行添加分号\n\nsed -i '' '/^Create /,/^)/s/)$/);/' input.sql\n\n```\n\n\n\n### 视图未被识别\n\n\n\n**症状**：拆分后没有生成视图文件，或视图被识别为其他对象类型。\n\n\n\n**原因**：视图语法不规范，缺少 `AS` 关键字。\n\n\n\n**解决方案**：修正视图语法，添加 `AS` 关键字。例如：\n\n\n\n```sql\n\n-- 错误：缺少 AS\n\ncreate view v_a\n\n(\n\nselect * from dual\n\n);\n\n\n\n-- 正确：添加 AS\n\nCREATE VIEW v_a AS\n\nSELECT * FROM dual;\n\n```\n\n\n\n### 存储过程/函数未被正确拆分\n\n\n\n**症状**：多个存储过程混在一个文件中，或产生重复文件。\n\n\n\n**原因**：存储过程语法不规范，缺少 `AS`/`BEGIN` 关键字或分隔符。\n\n\n\n**解决方案**：根据数据库类型修正语法：\n\n\n\n**SQL Server**：\n\n```sql\n\n-- 错误：缺少 AS 和 GO\n\ncreate proc p_a\n\n(\n\nselect * from dual\n\n);\n\ncreate proc p_b\n\n(\n\nselect * from dual\n\n);\n\n\n\n-- 正确：添加 AS 和 GO\n\nCREATE PROCEDURE p_a\n\nAS\n\nBEGIN\n\n    SELECT * FROM dual;\n\nEND\n\nGO\n\n\n\nCREATE PROCEDURE p_b\n\nAS\n\nBEGIN\n\n    SELECT * FROM dual;\n\nEND\n\nGO\n\n```\n\n\n\n**Oracle/达梦**：\n\n```sql\n\n-- 错误：缺少 IS/AS 和 /\n\nCREATE PROCEDURE p_a\n\nBEGIN\n\n    SELECT * FROM dual;\n\nEND\n\n\n\n-- 正确：添加 IS/AS 和 /\n\nCREATE OR REPLACE PROCEDURE p_a AS\n\nBEGIN\n\n    SELECT * FROM dual;\n\nEND;\n\n/\n\n```\n\n\n\n**MySQL**：\n\n```sql\n\n-- 错误：缺少 DELIMITER\n\nCREATE PROCEDURE p_a()\n\nBEGIN\n\n    SELECT * FROM dual;\n\nEND\n\n\n\n-- 正确：使用 DELIMITER\n\nDELIMITER //\n\nCREATE PROCEDURE p_a()\n\nBEGIN\n\n    SELECT * FROM dual;\n\nEND //\n\nDELIMITER ;\n\n```\n\n\n\n### 产生重复文件\n\n\n\n**症状**：拆分后生成多个内容相同或相似的文件（如 `proc_p_a.sql` 和 `proc_p_a_2.sql`）。\n\n\n\n**原因**：对象边界检测失败，通常由以下原因导致：\n\n- 对象之间缺少分隔符（分号、GO、/ 等）\n\n- 对象语法不规范（缺少 AS、BEGIN 等）\n\n- 嵌套对象语法错误\n\n\n\n**解决方案**：\n\n1. 检查并修正原始 SQL 文件的语法\n\n2. 确保每个对象之间有正确的分隔符\n\n3. 使用 `--dialect` 参数明确指定数据库类型\n\n4. 对于复杂情况，考虑手动拆分或使用数据库工具导出\n\n\n\n### 预检查清单\n\n\n\n在运行 sql-splitter 之前，建议检查以下内容：\n\n\n\n- [ ] 每个 SQL 语句都有分号结束符\n\n- [ ] 视图包含 `AS` 关键字\n\n- [ ] 存储过程/函数包含 `AS`/`BEGIN` 关键字\n\n- [ ] SQL Server 对象之间有 `GO` 分隔符\n\n- [ ] Oracle/达梦 对象末尾有 `/` 终止符\n\n- [ ] MySQL 存储过程使用 `DELIMITER`\n\n- [ ] 对象名称没有特殊字符或保留字冲突\n\n- [ ] 文件编码为 UTF-8\n\n\n\n## 文档维护规范\n\n\n\n- 功能描述按版本倒序排列：最新版本(v3.6.2)在最前，旧版本(v2.2.1等)在后\n\n- **更新日志必须严格按版本号降序** — 如v3.6.2→v3.6.0→v3.5.6→v3.5.0→v3.4.5→...→v1.0.0。**每次新增版本后，用`grep '^### v' SKILL.md`检查排序是否正确**\n\n- 避免重复章节：同一功能（如达梦转换）只在一个版本章节下详细描述，其他地方引用即可\n\n- 标题中的版本号必须与 clawhub 发布版本一致\n\n- 更新日志保留完整历史，但主体部分只展开最新版和次新版\n\n- **clawhub版本号冲突时**：发布后如果又改了内容，必须升版本号（如3.2.2→3.2.3）重新发布，clawhub不允许覆盖已发布版本\n\n\n\n## 更新日志\n\n\n\n### v3.6.0 (2026-07-04)\n- **移除 License/产品化相关代码** — 删除 keygen.py、license_verifier.py、private_key.pem、public_key.pem、issued_licenses.json，License 管理功能暂不实施\n- **移除产品化规划文档** — 删除 references/ 下的 sql-splitter-productization.md、sql-splitter-v350-productization.md、sql-splitter-commercialization.md\n- **清理 SKILL.md 重复条目** — 删除\"商业化产品化\"章节、\"License管理\"章节、\"发布前检查清单\"、重复的 dbo 规则/用户规则验证条目\n- **纯开源免费工具** — sql-splitter 不再有功能限制，所有用户可使用全部功能\n\n### v3.5.6 (2026-07-04)\n- **补丁: SKILL.md 修复** — 修复 clawhub 发布后 SKILL.md 被 patch 工具引入的反斜杠转义问题，清理重复条目\n### v3.5.0 (2026-06-21)\n\n- **report_generator.py** — 转换质量报告生成器（兼容性评分0-100 + 风险分级 + HTML/MD/JSON输出 + 7个测试）\n\n- **gui.py重写** — 完整tkinter GUI（文件选择→拆分→转换→质量报告）\n\n- **pip打包** — pyproject.toml + src/sql_splitter/ + `sql-splitter` CLI入口\n\n\n\n### v3.4.5 (2026-06-26)\n\n- **dbo两段式无prefix时去掉dbo** — `dbo.xxx`无schema_prefix时直接去掉dbo变成`xxx`，不再保留\n\n- **DATETIME2→TIMESTAMP排序BUG** — 正则alternation按长度降序排列，避免DATETIME抢先匹配DATETIME2\n\n- **方括号替换移到Step4** — 从Step 6.7(token还原后)移到Step 4(token化后)，注释/字符串已被保护不会误改\n\n- **schema_prefix参数贯穿调用链** — split_sql_file→_convert_split_output→convert全链传参\n\n- **新增CLI --schema-prefix和--dm参数**\n\n- **312个存储过程转换验证通过**(HRBI_Stage=54, HRBI_DW=157, HRBI_DM=101)\n\n- 详见 [v3.4.5修复记录](references/dm-converter-v345-fixes.md)（含dbo演进史）\n\n\n\n### v3.4.0 (2026-06-20)\n\n- **TRUNCATE TABLE → DELETE FROM** — 达梦不支持TRUNCATE在存储过程内，自动将`TRUNCATE TABLE xxx`转换为`DELETE FROM xxx`\n\n- **TABLE/VIEW结尾加分号** — TABLE/VIEW/INDEX/CONSTRAINT/SEQUENCE转换后结尾自动加`;`\n\n- **所有方言拆分后均加分号** — 之前Oracle/DM不加分号，现在统一加\n\n- **53个单元测试全部通过**（新增9个：4个TRUNCATE转换+5个结尾分号）\n\n\n\n### v3.3.0 (2026-06-19)\n\n- **PROCEDURE双重引号bug修复** — 三个顺序正则导致已替换结果被再次匹配，引号叠加成`\"\"sp_test\"\"`。合并为单一正则+分支回调\n\n- **IDENTITY位置修正** — `IDENTITY(\"id\",1,1)` 从`)`前移到`)`后，符合达梦语法。同时兼容单行和多行表定义\n\n- **#临时表正则修复** — 列定义中的`)`截断匹配，改用贪婪匹配。`##`全局临时表不再被替换为`#tmp_`\n\n- **token_map碰撞修复** — Step3重新tokenize占位符key覆盖原key，字符串`'N/A'`变成标识符`\"v_users\"`。新增`start_counter`参数避免碰撞\n\n\n\n### v3.2.4 (2026-06-14)\n\n- **更新日志排序修正** — 所有版本严格按版本号降序排列\n\n- **旧版日志精简** — 去掉重复子项展开，保持简洁\n\n\n\n### v3.2.3 (2026-06-14)\n\n- **存储过程PROCEDURE用AS而非IS** — 达梦存储过程声明用`AS`，函数用`IS`，之前PROCEDURE也用了`IS`是错误\n\n\n\n### v3.2.2 (2026-06-14)\n\n- **存储过程VARCHAR(n)加CHAR语义** — DECLARE变量和参数中的`VARCHAR(n)` → `VARCHAR(n CHAR)`，与TABLE转换一致\n\n- **CAST中nvarchar→VARCHAR(n CHAR)** — `cast(x as nvarchar(50))` → `CAST(x AS VARCHAR(50 CHAR))`，之前nvarchar未映射\n\n- **_post_convert_generic_types增强** — 新增裸类型名映射(via `_bare_type_pattern`)，之前只映射方括号包裹的类型\n\n- **44个单元测试全部通过**(含4个新增PROCEDURE类型映射测试)\n\n- **462个真实SQL对象端到端测试通过**(HRBI_Stage.sql, 7万行)\n\n\n\n### v3.2.1 (2026-06-14)\n\n- **SET NOCOUNT ON/OFF直接删除** — 之前注释保留，用户要求直接去掉(达梦不需要)\n\n- **SET NOCOUNT ON;带分号不匹配** — 正则加`\\s*;?`兼容行末分号，之前只匹配无分号的`SET NOCOUNT ON`\n\n- **批量转换脚本** — 新增`scripts/batch_convert.py`，写脚本文件而非`python3 -c`内联(安全扫描会拦截后者)\n\n\n\n### v3.1.0 (2026-06-13)\n\n- **PROCEDURE参数加括号** - `CREATE PROC name @p1 INT AS` → `CREATE OR REPLACE PROCEDURE name (p1 INT) AS`\n\n- **OR REPLACE兼容** - 正则匹配 `CREATE OR REPLACE PROC`（拆分阶段已加 OR REPLACE 的情况）\n\n- **AS保留** - 存储过程的`AS`关键字保留为`AS`（达梦PROCEDURE用AS，函数用IS）\n\n- **GO;兼容** - `GO;` 也被替换为 `/`（之前只匹配纯 `GO` 行）\n\n- **DATE不再双重映射** - `_post_convert_generic_types` 中对 DATE 类型直接返回 DATE\n\n- **VARCHAR(max)/NVARCHAR(max) → VARCHAR(4096 CHAR)** - 之前映射为TEXT，改为VARCHAR(4096 CHAR)\n\n- **VARCHAR2类型映射** - `varchar2 → VARCHAR2`，`VARCHAR2(max) → VARCHAR2(4096 CHAR)`\n\n\n\n### v3.0.0 (2026-06-13)\n\n- **修复3个dm_converter核心BUG**:\n\n  - 捕获组偏移: `_convert_data_types`正则TYPE_PATTERN用了捕获组，导致group偏移，类型双重映射(`INT`→`INTEGERINT`)。改非捕获组`(?:...)`\n\n  - suffix贪婪匹配: `[^]]*`应为`[^)]*`，导致VARCHAR(100)后所有列定义被吞进suffix，后续类型映射失效\n\n  - procedure方括号不替换: 新增`_post_convert_generic_types`方法，所有对象类型都做方括号→双引号+类型映射+dbo替换\n\n- **方括号替换对所有对象类型生效** - PROCEDURE/FUNCTION/TRIGGER 也做 `[xxx]` → `\"xxx\"` + dbo替换\n\n- **双点号`..`替换** - SQL Server的`database..object`（省略dbo schema）→达梦`database.object`\n\n- **UTF-16编码支持** - SSMS导出脚本转UTF-8后拆分\n\n- **40个单元测试全部通过**\n\n- **462个真实SQL对象端到端测试通过**(HRBI_Stage.sql, 7万行)\n\n- 详见 [v3.0修复记录](references/dm-converter-v30-fixes.md) | [v2.5.1修复记录](references/dm-converter-v251-fixes.md)\n\n\n\n### v2.5.0 (2026-05-31)\n\n- **变量命名规范** - DECLARE局部变量自动加v_前缀, 参数保持原名, 符合达梦开发规范\n\n- **多变量DECLARE** - `DECLARE @v1 INT, @v2 VARCHAR(100)` 正确拆分为多行独立声明\n\n- **类型映射修正** - bit->BOOLEAN, tinyint->SMALLINT (达梦无TINYINT)\n\n- **dbo前缀替换扩展** - 存储过程/函数中的dbo.也被替换为schema前缀\n\n- **存储过程参数格式化** - 参数换行缩进, 加括号, DECIMAL(18,2)等括号内逗号不被误拆\n\n- **类型映射修复** - [datetime] DEFAULT等DEFAULT后缀场景也能正确映射\n\n- **SELECT INTO变量名** - 与DECLARE声明保持一致, 自动加v_前缀\n\n\n\n### v2.4.5 (2026-06-08)\n\n- **方括号→双引号** - `[schema].[table]` → `\"schema\".\"table\"`，类型名`[nvarchar]` → `nvarchar`（去掉方括号并做类型映射）\n\n- **dbo前缀智能处理** - 三段式`[HRBI].[dbo].[Users]` → `\"HRBI\".\"Users\"`(删dbo)；两段式`[dbo].[Users]` → `hrbi_stage.\"Users\"`(用文件名替换)\n\n- **精确拆分增强** - 无`;`/`GO`终止符时，用下一个`CREATE`关键字作为对象边界兜底\n\n- **VARCHAR CHAR语义后处理** - 修复detokenize类型映射绕过CHAR语义的问题\n\n- **schema_prefix自动传递** - 从源文件名自动提取前缀传给dm_converter\n\n- **40个测试全部通过**\n\n\n\n### v2.4.4 (2026-06-07)\n\n- **数据类型映射调整** - 按达梦最佳实践修正\n\n  - VARCHAR(n) → VARCHAR(n CHAR)：达梦VARCHAR默认BYTE语义，必须加CHAR才等效SQL Server的字符语义\n\n  - UNIQUEIDENTIFIER → CHAR(36)：达梦用CHAR(36)而非VARCHAR(36)，UUID是定长\n\n- **40个测试全部通过**\n\n\n\n### v2.4.3 (2026-06-06)\n\n- **达梦转换器BUG修复** - 9个失败测试全部修复，40/40通过\n\n  - BIT→BOOLEAN, TINYINT→SMALLINT 类型映射修正\n\n  - NVARCHAR(n) → VARCHAR(n CHAR) 达梦字符语义转换\n\n  - SET NOCOUNT ON注释格式修正\n\n  - DATEADD专用转换方法（参数重排：DATEADD(day,n,date) → date + INTERVAL 'n' DAY）\n\n  - SELECT赋值区分有无FROM（有FROM→SELECT INTO，无FROM→:=）\n\n  - IF...BEGIN...END → IF...THEN...END IF 控制流转换\n\n  - WHILE...BEGIN...END → WHILE...LOOP...END LOOP 控制流转换\n\n  - PRINT → DBMS_OUTPUT.PUT_LINE 转换（+号连接改||）\n\n- 详见 [v2.4.3修复记录](references/dm-converter-v243-fixes.md) | [缩进调试技巧](references/python-indentation-debugging.md) | [7万行实战](references/hrbi-stage-real-world-test.md)\n\n\n\n### v2.4.1 (2026-05-30)\n\n- **拆分自动加 OR REPLACE** - 对 procedure/function/view/trigger 四类对象，自动将 CREATE 转为 CREATE OR REPLACE\n\n  - 已有 OR REPLACE 的语句不重复添加\n\n  - 所有方言均生效\n\n\n\n### v2.4.0 (2026-05-23)\n\n- **重写达梦数据库转换器** - 完全重写 dm_converter.py\n\n  - token化保护: 字符串/注释替换为占位符后再做正则替换\n\n  - 按对象类型独立转换: procedure/function/view/trigger/table/index/constraint\n\n  - 40+种数据类型映射, 30+种函数映射\n\n  - 变量语法转换: @var -> var, DECLARE @var -> var, SET @var= -> var:=\n\n  - TRY-CATCH -> EXCEPTION WHEN OTHERS THEN\n\n  - 全局变量转换: @@ROWCOUNT -> SQL%ROWCOUNT\n\n  - 触发器伪表: inserted/deleted -> NEW/OLD\n\n  - 转换结果输出到子目录: output_split_dm/\n\n- **拆分后转换集成** - split_sql_v21.py 新增 convert_to 参数\n\n  - 拆分完成后自动调用转换器，按对象类型独立转换\n\n  - 生成达梦版合并脚本 merge_all.sql\n\n- **29个转换单元测试** - test_dm_converter.py 全部通过\n\n- 详见 [v2.4.0修复记录](references/dm-converter-v240-fixes.md)\n\n\n\n### v2.2.1 (2026-05-01)\n\n- **安全修复** - 修复 pickle 反序列化漏洞，替换为 JSON + 数据验证\n\n- **新增安全文档** - 添加 SECURITY.md\n\n- **新增依赖管理** - 添加 requirements.txt\n\n\n\n### v2.2.0 (2026-04-27)\n\n- **新增 GUI 界面** - 提供图形化界面进行 SQL 文件拆分操作\n\n- **新增断点续传功能** - 支持记录处理进度，中断后可以继续处理\n\n- **新增批量并行处理** - 支持同时处理多个 SQL 文件，提升处理速度\n\n- **新增结果预览和对比** - 可视化查看拆分结果，支持与原始文件对比\n\n- **新增配置文件管理** - 保存和加载常用配置，支持导入导出\n\n\n\n### v1.1.0 (2026-04-13)\n\n- 新增索引支持：CREATE INDEX, CREATE UNIQUE INDEX\n\n- 新增约束支持：ALTER TABLE ADD CONSTRAINT\n\n- 所有 6 种方言均支持索引/约束识别\n\n\n\n### v1.0.0\n\n- 初始版本\n\nFile v3.6.3:_meta.json\n\n{\n  \"ownerId\": \"kn76tyr0ztr0j5fyej0qbchjx584wwjp\",\n  \"slug\": \"sql-splitter\",\n  \"version\": \"3.6.3\",\n  \"publishedAt\": 1783130283027\n}\n\nFile v3.6.3:references/dm-converter-design.md\n\n# SQL Server -> 达梦数据库转换器设计要点\n\n## 架构\n\n```\nconvert() 流程:\n Step1: _tokenize() - 字符串/注释 -> 占位符\n Step2: 按对象类型转换 (procedure/function/view/trigger/table/index/constraint)\n Step3: _tokenize() 重新tokenize + 合并token_map\n Step4: 通用转换 (data_types/functions/global_vars/statements/try_catch/transaction)\n Step5: _detokenize() 用合并后的map还原所有占位符\n Step6: _tokenize_strings_only() + _convert_variable_syntax() + detokenize\n Step7: _convert_goto_label()\n Step8: _add_terminator() (仅procedure/function/trigger)\n```\n\n## 已修复的坑（v2.4.0及之前）\n\n### 1. token_map 合并\nStep2的_convert_procedure等返回token化文本(含__TOKEN_0__)，Step3重新_tokenize产生新map。\n如果不合并，Step4 _detokenize用新map找不到__TOKEN_0__的映射。\n\n修复:\n```python\noriginal_token_map = dict(token_map) # Step2前保存\ntokens, token_map = self._tokenize(result) # Step3\nmerged_map = dict(original_token_map)\nmerged_map.update(token_map) # 合并\n# Step4用merged_map\nresult = self._detokenize(result, merged_map)\n```\n\n### 2. content = new_content 不可省略\n_convert_variable_syntax中全局@替换后忘记`content = new_content`，\n导致return content返回的是替换前的文本。\n\n### 3. 嵌套括号匹配\n`VARCHAR(100)`中的`)`会截断`[^)]*`，导致参数列表匹配失败。\n\n错误: `r'\\(([^)]*)\\)'` -> 在`VARCHAR(100)`的`)`处截断\n正确: `r'(\\([^)]*(?:\\([^)]*\\)[^)]*)*\\))'` -> 匹配嵌套括号\n\n### 4. DECLARE上下文的数据类型\n数据类型转换正则的前缀只匹配`,\\(\\s*|\\n\\s*`时，\n`DECLARE @v_date DATETIME`中的DATETIME不会被匹配。\n\n修复: 前缀加上`DECLARE\\s+`\n\n### 5. INSERT INTO误匹配\n`\\n INSERT INT` 被匹配为: 前缀`\\n` + 列名`INSERT` + 类型`INT`\n导致INSERT变成INTEGERO\n\n修复方向: 数据类型替换前检查列名是否为SQL关键字(SELECT/INSERT/UPDATE/DELETE等)\n\n### 6. 变量@转换时机\n在token保护下做@替换时，__TOKEN_0__等占位符名不含@，安全。\n但更安全的做法: 先还原所有token，再用_tokenize_strings_only只保护字符串，\n然后做@替换。\n\n### 7. 终止符 /\nGO在Step2已被转成/，_add_terminator需要先去掉已有的/再添加新的。\n```python\nif content.endswith('/'):\n    content = content[:-1].rstrip()\n```\n\n## 代码审查发现的未修BUG（2026-05-27审查）\n\n用户反馈\"达梦转换不好用\"，代码审查发现以下严重问题：\n\n### BUG-1: 函数映射转换结果语法错误（严重）\n\n**CONVERT函数** (line 118):\n```python\n(r'\\bCONVERT\\s*\\(\\s*(\\w+)\\s*,', r'CAST(', True)\n```\n- 只替换了`CONVERT(type,`为`CAST(`，丢掉了type参数\n- SQL Server: `CONVERT(VARCHAR(50), @val)` → 达梦: `CAST(@val AS VARCHAR(50))`\n- 参数顺序和格式都不对，需要回调函数重排参数\n\n**DATEADD函数** (line 127-128):\n```python\n(r'\\bDATEADD\\s*\\(\\s*day\\s*,', 'CURRENT_TIMESTAMP + ', True)\n(r'\\bDATEADD\\s*\\(\\s*month\\s*,', 'ADD_MONTHS(CURRENT_TIMESTAMP,', True)\n```\n- 丢了date参数，直接硬编码CURRENT_TIMESTAMP\n- SQL Server: `DATEADD(day, 7, @order_date)` → 达梦: `@order_date + 7`\n- SQL Server: `DATEADD(month, 3, @start_date)` → 达梦: `ADD_MONTHS(@start_date, 3)`\n- 需要回调函数提取3个参数并重排\n\n**DATEDIFF函数** (line 129):\n```python\n(r'\\bDATEDIFF\\s*\\(\\s*day\\s*,', '(', True)\n```\n- 直接替换成`(`，语法彻底崩了\n- SQL Server: `DATEDIFF(day, @start, @end)` → 达梦: `@end - @start`（日期差）\n- 需要回调函数提取参数\n\n**STRING_AGG函数** (line 115):\n```python\n(r'\\bSTRING_AGG\\s*\\(', 'LISTAGG(', True)\n```\n- 达梦LISTAGG需要`WITHIN GROUP (ORDER BY ...)`语法，光改名不够\n- SQL Server: `STRING_AGG(name, ',')` → 达梦: `LISTAGG(name, ',') WITHIN GROUP (ORDER BY name)`\n\n**STUFF函数** (line 112):\n```python\n(r'\\bSTUFF\\s*\\(', 'OVERLAY(', True)\n```\n- 达梦不支持OVERLAY函数，参数语义也不同\n- STUFF(str, start, len, replace) vs OVERLAY不同签名\n\n**REPLICATE函数** (line 113):\n```python\n(r'\\bREPLICATE\\s*\\(', 'RPAD(', True)\n```\n- RPAD是右侧填充，REPLICATE是重复字符串，语义不同\n- `REPLICATE('0', 5)` = '00000' vs `RPAD('0', 5)` = '0    '\n\n### BUG-2: 变量@转换过于粗暴（严重）\n\nline 671:\n```python\nnew_content = re.sub(r'@([\\w]+)', r'\\1', content)\n```\n- 全局替换所有`@word`为`word`，会误改email地址（如`user@example.com`→`userexample.com`）\n- 需要增加上下文判断：只替换SQL变量上下文中的@，不替换字符串内的@\n\n**多变量SELECT赋值** (line 659-663):\n```python\nnew_content = re.sub(r'\\bSELECT\\s+@([\\w]+)\\s*=', r'\\1 :=', content, flags=re.IGNORECASE)\n```\n- `SELECT @a=1, @b=2` 只处理了@a，@b被遗留\n- 需要处理逗号分隔的多变量赋值\n\n**DECLARE变量类型残留** (line 637-642):\n```python\nnew_content = re.sub(r'\\bDECLARE\\s+@([\\w]+)', r'\\1', content, flags=re.IGNORECASE)\n```\n- 只删了DECLARE和@前缀，但达梦过程体内变量声明格式完全不同\n- SQL Server: `DECLARE @v_count INT` → 达梦: `v_count INTEGER;`（不需要DECLARE，但需要保留类型+分号）\n- 当前输出: `v_count INT`，缺少分号且类型未映射\n\n### BUG-3: TRY-CATCH正则匹配不可靠（中等）\n\nline 590-597:\n```python\npattern = (\n    r'BEGIN\\s+TRY\\s*(.*?)\\s*END\\s+TRY\\s*'\n    r'BEGIN\\s+CATCH\\s*(.*?)\\s*END\\s+CATCH'\n)\n```\n- `.*?`非贪婪匹配在过程体内有多个BEGIN...END时会匹配到错误位置\n- 如果TRY块内有`BEGIN...END`嵌套，`END TRY`可能匹配到错误的END\n- 需要用括号深度匹配替代纯正则\n\n### BUG-4: GOTO/LABEL误判时间格式（中等）\n\nline 682-687:\n```python\nnew_content = re.sub(r'^(\\s*)([\\w]+)\\s*:\\s*$', r'\\1<<\\2>>', content, flags=re.MULTILINE)\n```\n- `12:30:00`中的`30:`或`00:`在独立行上会被误判为标签\n- 需要排除数字开头的\"标签\"和已知非标签模式（时间、CASE WHEN等）\n\n### BUG-5: 缺失关键转换（严重）\n\n以下SQL Server常用语法完全没有转换支持：\n\n| SQL Server语法 | 达梦语法 | 当前处理 |\n|---------------|---------|---------|\n| `#temp_table` / `##global_temp` | 全局临时表GTT或普通表 | 无 |\n| `EXEC sp_executesql @sql` | `EXECUTE IMMEDIATE sql_str` | 无 |\n| `EXEC(@sql)` | `EXECUTE IMMEDIATE sql_str` | 无 |\n| `PRINT 'msg'` | `DBMS_OUTPUT.PUT_LINE('msg')` | 无 |\n| `RAISERROR('msg', 16, 1)` | `RAISE_APPLICATION_ERROR(-20001, 'msg')` | 无 |\n| `TOP n` | `ROWNUM <= n` 或 `FETCH FIRST n ROWS ONLY` | 无 |\n| `[column_name]` | `\"column_name\"` | 仅token化保护，未转换引号 |\n| `MERGE INTO ...` | 语法差异大 | 无 |\n| `CURSOR` 游标语法 | 参数/打开/关闭语法差异 | 无 |\n| `WITH (NOLOCK)` | 去掉或改写 | 无 |\n| `ROW_NUMBER() OVER(...)` | 达梦支持，但分区函数有差异 | 部分支持 |\n| `OBJECT_ID('name')` | 达梦用USER_OBJECTS视图 | 无 |\n| `IF EXISTS(SELECT ...)` | `SELECT COUNT(*) INTO v_cnt ... IF v_cnt > 0` | 无 |\n| `WHILE @i <= 10` | `WHILE i <= 10 LOOP ... END LOOP;` | 无 |\n\n## 改进路线图\n\n### 紧急（语法错误导致转换结果不可用）\n1. CONVERT/DATEADD/DATEDIFF — 用回调函数做上下文感知替换，正确重排参数\n2. 变量@转换 — 增加上下文判断，避免误改非变量@\n3. 补全EXEC动态SQL、临时表、PRINT、RAISERROR、TOP转换\n\n### 重要（转换结果可用但需手动调整）\n4. TRY-CATCH — 改用括号深度匹配\n5. GOTO/LABEL — 增加排除规则\n6. 多变量SELECT赋值\n7. DECLARE变量声明格式适配达梦\n\n### 增强（减少手动调整量）\n8. STRING_AGG → LISTAGG + WITHIN GROUP\n9. STUFF/REPLICATE → 达梦等效写法\n10. MERGE语句转换\n11. 游标语法转换\n12. WITH(NOLOCK)处理\n\n## 映射表\n\n### 数据类型 (40+种)\n| SQL Server | 达梦 |\n|-----------|------|\n| INT | INTEGER |\n| BIT | TINYINT |\n| DATETIME/DATETIME2 | TIMESTAMP |\n| MONEY | DECIMAL(19,4) |\n| NVARCHAR(n) | VARCHAR(n) |\n| NTEXT | TEXT |\n| IMAGE | BLOB |\n| UNIQUEIDENTIFIER | VARCHAR(36) |\n| XML | TEXT |\n\n### 函数 (30+种)\n| SQL Server | 达梦 | 当前状态 |\n|-----------|------|---------|\n| GETDATE() | CURRENT_TIMESTAMP | OK |\n| ISNULL(a,b) | NVL(a,b) | OK |\n| LEN(s) | LENGTH(s) | OK |\n| SUBSTRING(s,n,l) | SUBSTR(s,n,l) | OK |\n| CHARINDEX(s,t) | INSTR(s,t) | OK |\n| CONVERT(type,val) | CAST(val AS type) | BUG:参数未重排 |\n| DATEADD(day,n,date) | date + n | BUG:丢参数 |\n| DATEDIFF(day,s,e) | e - s | BUG:替换成( |\n| YEAR(d) | EXTRACT(YEAR FROM d) | OK |\n| NEWID() | SYS_GUID() | OK |\n| STRING_AGG(col,sep) | LISTAGG(col,sep) WITHIN GROUP(...) | 缺WITHIN GROUP |\n| STUFF(str,s,l,r) | 需自定义函数 | BUG:OVERLAY不对 |\n| REPLICATE(s,n) | 需自定义或LPAD | BUG:RPAD语义不同 |\n\n### 语句级\n| SQL Server | 达梦 | 当前状态 |\n|-----------|------|---------|\n| SET NOCOUNT ON | -- 注释掉 | OK |\n| COMMIT TRANSACTION | COMMIT | OK |\n| ROLLBACK TRANSACTION | ROLLBACK | OK |\n| BEGIN TRY...CATCH | EXCEPTION WHEN OTHERS | 有嵌套匹配风险 |\n| EXEC @sql | EXECUTE IMMEDIATE | 缺失 |\n| PRINT 'msg' | DBMS_OUTPUT.PUT_LINE | 缺失 |\n| RAISERROR(...) | RAISE_APPLICATION_ERROR(...) | 缺失 |\n| TOP n | ROWNUM/FETCH FIRST | 缺失 |\n\nFile v3.6.3:references/dm-converter-v243-fixes.md\n\n# dm_converter v2.4.3 修复记录\n\n## 修复的9个失败测试\n\n从9个失败 → 40/40全部通过。\n\n### 修复1: BIT→BOOLEAN, TINYINT→SMALLINT\n- **问题**: type_mappings中 `'bit': 'TINYINT'`, `'tinyint': 'TINYINT'`，但达梦没有TINYINT\n- **修复**: `'bit': 'BOOLEAN'`, `'tinyint': 'SMALLINT'`\n- **测试**: test_data_types\n\n### 修复2: NVARCHAR(n) → VARCHAR(n CHAR)\n- **问题**: NVARCHAR(100)只转成VARCHAR(100)，缺少达梦字符语义CHAR标记\n- **根因**: NVARCHAR处理的代码被patch工具放到了`else:`分支内部（即只在suffix为空时执行），导致有suffix时永远无法命中\n- **修复**: 将NVARCHAR检查代码从`else:`块内移到`if suffix:` / `else:` 块**之后**（与if/else同级）\n- **关键坑**: patch工具插入代码时，缩进层级容易搞错。**必须用`python3 -c`检查实际缩进**：\n  ```python\n  with open('dm_converter.py') as f:\n      for i, line in enumerate(f, 1):\n          if i >= 511 and i <= 525:\n              spaces = len(line) - len(line.lstrip())\n              print(f\"{i}: indent={spaces} |{line.rstrip()[:70]}|\")\n  ```\n- **测试**: test_data_types\n\n### 修复3: SET NOCOUNT ON注释格式\n- **问题**: 注释`'-- SET NOCOUNT ON (达梦不需要)'`中包含原文`SET NOCOUNT ON`，测试用assertNotIn检测到\n- **修复**: 改为`'-- NOCOUNT (达梦不需要)'`，去掉注释中的原文关键字\n- **测试**: test_complex_procedure, test_set_nocount_on\n\n### 修复4: DATEADD专用转换\n- **问题**: function_mappings中旧的DATEADD条目只做简单前缀替换（如`DATEADD(day,` → `CURRENT_TIMESTAMP +`），不能正确处理3个参数的重排\n- **修复**: \n  - 从function_mappings删除旧的DATEADD条目\n  - 添加`_parse_function_args()`方法解析嵌套括号的参数列表\n  - 添加`_convert_dateadd()`方法，按unit类型重排参数：\n    - `DATEADD(day, n, date)` → `date + INTERVAL 'n' DAY`\n    - `DATEADD(month, n, date)` → `ADD_MONTHS(date, n)`\n    - 其他单位类似\n  - 在`_convert_functions()`末尾调用\n- **测试**: test_dateadd_conversion\n\n### 修复5: SELECT赋值区分有无FROM\n- **问题**: 所有`SELECT @var = expr`都转成`var := expr`，但有FROM子句时达梦需要`SELECT expr INTO var FROM table`\n- **修复**: \n  - 添加`_convert_select_assign()`方法\n  - 解析赋值列表和FROM子句位置\n  - 有FROM: `SELECT @var=expr FROM t` → `SELECT expr INTO var FROM t`\n  - 无FROM: `SELECT @var=expr` → `var := expr`\n  - 支持多变量赋值: `SELECT @a=col1, @b=col2 FROM t` → `SELECT col1, col2 INTO a, b FROM t`\n- **测试**: test_select_into_with_from, test_select_multi_assign, test_select_assign_no_from\n\n### 修复6: IF→THEN/END IF 控制流\n- **问题**: `IF @x > 0 BEGIN ... END`没有转换为达梦语法\n- **修复**: 添加`_convert_if_else()`方法\n  - `IF condition` → `IF condition THEN`\n  - BEGIN/END配对：IF后的BEGIN跳过，对应的END改为`END IF;`\n  - ELSE保留\n- **测试**: test_if_else, test_if_with_try_catch, test_nested_if\n\n### 修复7: WHILE→LOOP/END LOOP\n- **问题**: `WHILE @x > 0 BEGIN ... END`没有转换\n- **修复**: 添加`_convert_while_loop()`方法\n  - `WHILE condition` → `WHILE condition LOOP`\n  - WHILE后的BEGIN跳过，对应的END改为`END LOOP;`\n- **测试**: test_while_loop\n\n### 修复8: PRINT→DBMS_OUTPUT.PUT_LINE\n- **问题**: `PRINT 'text'`没有转换为达梦语法\n- **修复**: 添加`_convert_print()`和`_print_replacer()`方法\n  - `PRINT expr` → `DBMS_OUTPUT.PUT_LINE(expr);`\n  - SQL Server的`+`字符串连接改为达梦的`||`\n  - `CAST(...AS VARCHAR)` → `CAST(...AS VARCHAR(4000))`（达梦VARCHAR需要长度）\n- **测试**: test_complex_procedure\n\n### 修复9: 流程步骤位置\n- 在convert()方法中，Step 5.5后添加控制流和PRINT调用：\n  ```python\n  # Step 5.5: 控制流转换 (IF/WHILE)\n  result = self._convert_if_else(result)\n  result = self._convert_while_loop(result)\n  \n  # Step 5.6: PRINT → DBMS_OUTPUT.PUT_LINE\n  result = self._convert_print(result)\n  ```\n  位于Step 5（变量语法转换）之后、Step 6（GOTO/LABEL）之前\n\n## 开发过程中的关键教训\n\n### ⚠️ write_file工具会添加行号前缀\n**问题**: 使用Hermes的`write_file`工具写入Python文件时，内容会被加上` NNN|`格式的行号前缀，导致Python文件损坏（语法错误）。\n**修复方法**: 用`terminal`的python脚本或`patch`工具来修改Python文件，**不要用write_file写入代码文件**。如果已经损坏，用以下脚本修复：\n```python\nimport re\nwith open('file.py', 'r') as f:\n    raw = f.read()\nlines = raw.split('\\n')\ncleaned = []\nfor line in lines:\n    m = re.match(r'^\\s*\\d+\\|(.*)$', line)\n    if m:\n        cleaned.append(m.group(1))\n    else:\n        cleaned.append(line)\nwith open('file.py', 'w') as f:\n    f.write('\\n'.join(cleaned))\n```\n\n### ⚠️ patch工具缩进陷阱\npatch工具的缩进处理不可靠，尤其是：\n1. 多行docstring后的代码体缩进容易错位\n2. `else:`块内的代码容易与块外代码混淆\n3. **每次patch后必须验证缩进**：用lint检查或`python3 -c \"import module\"`测试导入\n4. 如果lint报`IndentationError`，用`python3 -c`检查精确缩进层级\n\n### ⚠️ git恢复策略\n当write_file损坏文件时：\n1. `git diff HEAD -- file.py | wc -l` 查看差异量\n2. `wc -l file.py && git show HEAD:file.py | wc -l` 对比行数\n3. 如果差异巨大且行数骤降，说明文件被截断：`git checkout HEAD -- file.py` 恢复\n4. 恢复后重新逐步应用修改\n\nFile v3.6.3:references/dm-converter-v245-bracket-dbo-split.md\n\n# dm_converter v2.4.5 设计记录 — 方括号→双引号 + dbo前缀 + 精确拆分\n\n## 规则1: 方括号[] → 双引号\"\"\n\n### 设计决策\n\n`_detokenize` 方法中，方括号占位符还原时做转换：\n\n- 普通标识符 `[Users]` → `\"Users\"`\n- SQL类型名 `[nvarchar]` → `nvarchar`（去掉方括号 + 自动做类型映射）\n- SQL类型名不能转双引号，否则 `\"nvarchar\"(100)` 变成标识符而非类型，后续类型映射不生效\n\n### 关键坑：类型名在detokenize时必须同时做映射\n\n**问题链**：\n1. `_tokenize` 把 `[nvarchar]` 替换为 `__TOKEN_N__`\n2. Step4 `_convert_data_types` 匹配的是裸名 `NVARCHAR`，但此时是占位符，匹配不上\n3. `_detokenize` 还原时如果只去掉方括号变成 `nvarchar(100)`，已经过了Step4，不会再被类型映射\n\n**解决**：在 `_detokenize` 中，对类型名直接做映射：\n```python\nif inner.upper() in _TYPE_NAMES:\n    mapped = self.type_mappings.get(inner.lower(), inner)\n    replacement = mapped\n```\n\n### 关键坑：VARCHAR CHAR语义后处理\n\ndetokenize中类型名映射绕过了Step4中 `VARCHAR(n) → VARCHAR(n CHAR)` 的逻辑（因为Step4在token保护下，`[nvarchar]`是占位符匹配不上）。\n\n**解决**：在Step5.4加后处理：\n```python\nresult = re.sub(r'\\bVARCHAR\\((\\d+)\\)(?!\\s+CHAR\\b)', r'VARCHAR(\\1 CHAR)', result, flags=re.IGNORECASE)\n```\n\n放在Step5 detokenize之后、Step5.5控制流转换之前。\n\n## 规则2: dbo前缀智能处理\n\n### 三段式 vs 两段式判断\n\n| 模式 | 判断依据 | 处理方式 | 示例 |\n|------|---------|---------|------|\n| `[HRBI].[dbo].[Users]` | dbo前有schema前缀 | 删除dbo. | → `\"HRBI\".\"Users\"` |\n| `[dbo].[Users]` | dbo前无schema前缀 | 用schema_prefix替换dbo | → `hrbi_stage.\"Users\"` |\n\n### 实现位置\n\n`_convert_dbo_prefix` 方法在 **Step4.5** 执行（detokenize之后），因为：\n- detokenize之前：方括号是占位符，正则无法匹配 `__TOKEN_0__.__TOKEN_1__.__TOKEN_2__` 的模式\n- detokenize之后：方括号已转为双引号 `\"HRBI\".\"dbo\".\"Users\"`，正则可以匹配\n\n### 正则设计\n\n**三段式**（必须先处理！）：\n```python\nr'((?:\"[^\"]*\"|\\w+)\\.)(?:\"dbo\"|dbo)\\.((?:\"[^\"]*\"|\\w+))'\n# 匹配: \"HRBI\".\"dbo\".\"Users\" 或 HRBI.dbo.Users 或混合\n```\n\n**两段式**：\n```python\nr'\"dbo\"\\.|\\bdbo\\.'\n# 匹配: \"dbo\".\"Users\" 或 dbo.Users（两种格式都支持）\n```\n\n**重要**：三段式正则中 `\"dbo\"` 必须同时支持双引号包裹和裸名，因为detokenize后所有方括号标识符都变成了双引号格式。\n\n### schema_prefix来源\n\n从源文件名自动提取：\n```python\nbasename = os.path.splitext(os.path.basename(input_file))[0]\nschema_prefix = basename  # 如 hrbi_stage.sql → hrbi_stage\n```\n\n在 `_convert_split_output` 中传递，通过 `input_file` 参数。\n\n## 规则3: 精确拆分增强\n\n### `_find_next_create` 兜底函数\n\n当找不到 `;` 或 `GO` 终止符时，用下一个 `CREATE` 关键字作为对象边界上界：\n\n```python\ndef _find_next_create(sql: str, start: int) -> int:\n    # 跳过字符串、注释内的CREATE\n    # 匹配独立的CREATE关键字（前后非字母数字下划线）\n    # 返回位置或-1\n```\n\n### 在 `find_object_end` 中的调用\n\n所有对象类型的终止符查找都有兜底：\n```python\nend = find_semicolon_end(...)  # 或 find_paren_end / find_block_end\nif end >= n:  # 没找到终止符，到达文件末尾\n    next_create = _find_next_create(sql, start + 10)\n    if next_create > start:\n        boundary = next_create\n        while boundary > start and sql[boundary - 1] in ' \\t\\r\\n':\n            boundary -= 1\n        return boundary\n```\n\n## 开发过程关键教训\n\n### patch工具Python缩进问题 — 终极解决方案\n\n**问题**：patch工具修改Python文件时，方法体内的缩进经常被破坏（8空格变4空格、1空格等）。\n\n**终极方案：用Python脚本替换整个方法**，而非用patch做局部替换：\n\n```python\n#!/usr/bin/env python3\nwith open('dm_converter.py') as f:\n    content = f.read()\n\nold_start = '    def _detokenize(self, content: str, token_map: Dict[str, str]) -> str:'\nnew_method = '''    def _detokenize(self, content: str, token_map: Dict[str, str]) -> str:\n        \"\"\"docstring\"\"\"\n        # 方法体（8空格缩进）\n        ...\n        return content'''\n\nstart = content.find(old_start)\nend = content.find('        return content', start)\nend += len('        return content')\ncontent = content[:start] + new_method + '\\n' + content[end:]\n\nwith open('dm_converter.py', 'w') as f:\n    f.write(content)\n```\n\n**原则**：\n- 涉及Python方法体修改时，**不要用patch**，用Python脚本替换整个方法\n- 每次修改后 `python3 -m py_compile file.py` 验证\n- 用 `sed -n 'Np,Mp' file | xxd` 确认精确缩进（xxd显示每个字符的十六进制）\n\nFile v3.6.3:references/dm-converter-v246-fixes.md\n\n# dm_converter v2.4.6 修复记录\n\n## Bug 1: 存储过程方括号和dbo未替换\n\n**症状**: 转换后54个procedure文件残留`[dbo]`和`[xxx]`方括号标识符\n\n**根因**: `convert()`方法Step 6.7只有TABLE/VIEW走`_post_convert_table_types`做方括号→双引号+dbo替换，非表对象只做`_replace_dbo_prefix`（但此时方括号还在，dbo替换正则匹配不到`[dbo]`格式）\n\n**修复**: 新增`_post_convert_generic_types()`方法，对所有非TABLE/VIEW对象类型做:\n1. 方括号类型名映射 `[int]` → `INTEGER`\n2. 方括号→双引号 `[xxx]` → `\"xxx\"`\n3. dbo前缀替换（含`..`双点号）\n4. GO → /\n\n**位置**: dm_converter.py Step 6.7 分支逻辑\n\n## Bug 2: 数据类型双重映射 (INTEGERINT, VARCHARVARCHAR)\n\n**症状**: `@p1 INT` → `p1 INTEGERINT`, `@p2 VARCHAR(100)` → `p2 VARCHARVARCHAR`\n\n**根因**: `_convert_data_types`的正则中类型名用了捕获组:\n```python\n# 旧: (\\\\[?(INT|VARCHAR|...)\\\\]?)  — 3个捕获组: prefix, col, type(含内部子组), suffix\nr'([,\\\\(]\\\\s*|DECLARE\\\\s+|\\\\n\\\\s*)([\\\\w@]+)\\\\s+(\\\\[?(' + _FULL_TYPE_NAMES_PATTERN + r')\\\\]?)(\\\\(...\\\\))?'\n```\n`_FULL_TYPE_NAMES_PATTERN`拼接后变成`(INT|VARCHAR|...)`,是捕获组group(4)。\n代码里`suffix = m.group(4) or ''`实际拿到的是类型名内部组`INT`而非长度后缀`(100)`。\n替换后`f\"{prefix}{col_name} {new_type}{suffix}\"`变成`p1 INTEGER` + `INT` = `p1 INTEGERINT`\n\n**修复**: 类型名内部组改为非捕获组`(?:...)`:\n```python\n# 新: (\\\\[?(?:INT|VARCHAR|...)\\\\]?)  — 4个捕获组: prefix, col, type, suffix(正确)\nr'([,\\\\(]\\\\s*|DECLARE\\\\s+|\\\\n\\\\s*)([\\\\w@]+)\\\\s+(\\\\[?(?:' + _FULL_TYPE_NAMES_PATTERN + r')\\\\]?)(\\\\(...\\\\))?'\n```\n现在group(4)才是suffix，group(3)是完整类型名（含可选方括号）\n\n**教訓**: Python正则中拼接变量构建捕获组时，务必确认组号偏移。用`(?:...)`非捕获组避免意外偏移\n\n## Bug 3: 类型长度后缀贪婪匹配\n\n**症状**: `name VARCHAR(100), flag BIT` 中`VARCHAR`的suffix吃掉了后面所有内容\n\n**根因**: suffix正则`\\\\([^]]*(?:\\\\([^]]*\\\\)[^]]*)*\\\\)`中`[^]]*`匹配\"非]的字符\"而非\"非)的字符\"。`[^]]`是字符类`[^]`加上字面量`]`，实际等于匹配几乎任何字符（因为`]`很少出现）。应为`[^)]*`匹配\"非)的字符\"。\n\n**修复**: \n```python\n# 旧: \\\\([^]]*(?:\\\\([^]]*\\\\)[^]]*)*\\\\)\n# 新: \\\\([^)]*(?:\\\\([^)]*\\\\)[^)]*)*\\\\)\n```\n\n## Bug 4: SQL Server双点号`..`语法\n\n**症状**: 转换后存储过程中残留`hrbi_stage..Stage_xxx`，达梦不支持\n\n**根因**: SQL Server的`database..object`语法省略了dbo schema，相当于`database.dbo.object`。`_replace_dbo_prefix`中三段式正则`xxx.dbo.yyy`匹配不到两段式`xxx..yyy`\n\n**修复**: 在`_replace_dbo_prefix`开头优先处理双点号:\n```python\n# \"xxx\"..\"yyy\" → \"xxx\".\"yyy\"\ncontent = re.sub(r'\"([^\"]+)\"\\.\\.', r'\"\\1\".', content)\n# xxx..yyy → xxx.yyy\ncontent = re.sub(r'\\b(\\w+)\\.\\.', r'\\1.', content)\n```\n\n## UTF-16编码处理\n\nSQL Server Management Studio导出的.sql文件常用UTF-16编码（带BOM），需先转UTF-8:\n```python\nwith open('input.sql', 'r', encoding='utf-16') as f:\n    content = f.read()\nwith open('input_utf8.sql', 'w', encoding='utf-8') as f:\n    f.write(content)\n```\n\n## 测试验证\n\n```bash\ncd scripts && python3 -m pytest test_dm_converter.py -v\n# 40/40 passed\n```\n\nFile v3.6.3:references/dm-converter-v251-fixes.md\n\n# dm_converter v2.5.1 修复记录\n\n## 修复的问题（2026-06-13，基于 HRBI_Stage.sql 7万行实战测试）\n\n### 1. 存储过程参数不加括号 + AS不换IS\n\n**症状**: 拆分后的文件已有 `CREATE OR REPLACE PROCEDURE`，但转换后参数仍是 `TX_DATE DATE`（无括号），AS也没变成IS。\n\n**根因**: `_convert_procedure` 正则只匹配 `CREATE\\s+PROC`，不匹配 `CREATE OR REPLACE PROC`（拆分阶段 v2.4.1 已自动加 OR REPLACE）。\n\n**修复**: 所有3个procedure正则加 `(?:OR\\s+REPLACE\\s+)?`；name捕获组从 `[\\w.\\[\\\"]]+` 改为 `.+?`（因为方括号在字符类内转义损坏）。\n\n**教训**: 上下游模块的输出可能改变输入格式，下游正则必须兼容。\n\n### 2. 方括号标识符未替换（procedure/function）\n\n**症状**: 54个procedure文件里 `[dbo]`、`[xxx]` 原样保留。\n\n**根因**: `_post_convert_table_types` 只对 TABLE/VIEW 生效（代码 `if ctype in (TABLE, VIEW)`），非表类型只做 `_replace_dbo_prefix`，不做方括号替换和类型映射。\n\n**修复**: 新增 `_post_convert_generic_types` 方法，所有非表/视图类型也做: 方括号类型映射 → `[xxx]→\"xxx\"` → dbo替换。\n\n### 3. `_convert_data_types` 捕获组偏移 → 类型双重映射\n\n**症状**: `p1 INT` → `p1 INTEGERINT`，`p2 VARCHAR(100)` → `p2 VARCHARVARCHAR`。\n\n**根因**: 正则 `(\\[?(INT|VARCHAR|...)\\]?)` 中类型名用了捕获组 `(...)`，导致 group(4) 是类型名而非长度后缀。`_replace_type` 用 `m.group(4)` 取 suffix，实际拿到了类型名。\n\n**修复**: 类型名改非捕获组 `(?:INT|VARCHAR|...)`，保证 group(4) 始终是长度后缀。\n\n**教训**: 正则中每个 `(...)` 都会偏移后续组号，非提取用途的组必须用 `(?:...)`。\n\n### 4. 长度后缀贪婪匹配\n\n**症状**: `VARCHAR(100), flag BIT` 整段被吃进 suffix。\n\n**根因**: suffix 正则 `[^]]*` 匹配\"非`]`的字符\"，文件中无 `]` 所以匹配到文件末尾。应为 `[^)]*`（匹配\"非`)`的字符\"）。\n\n**修复**: `[^]]` → `[^)]`。注意 `[^]]` 在正则引擎中的语义：`[^]` 是字符类匹配\"非 `]`\"，后面 `]` 关闭字符类——不是\"非右方括号\"的意图写法。\n\n### 5. 双点号 `..` 未替换\n\n**症状**: `hrbi_stage..Stage_xxx` 保留原样（SQL Server省略dbo的写法）。\n\n**修复**: `_replace_dbo_prefix` 新增双点号处理，必须在三段式之前做：\n- `\"xxx\"..\"yyy\"` → `\"xxx\".\"yyy\"`\n- `xxx..yyy` → `xxx.yyy`\n\n### 6. GO; 未清理\n\n**症状**: 文件末尾 `GO;` 和 `/` 共存。\n\n**根因**: 正则 `^\\s*GO\\s*$` 不匹配 `GO;`。\n\n**修复**: 改为 `^\\s*GO\\s*;?\\s*$`。\n\n### 7. VARCHAR(max)/VARCHAR2(max) 映射\n\n**需求**: `VARCHAR(max)` → `VARCHAR(4096 CHAR)`，`VARCHAR2(max)` → `VARCHAR2(4096 CHAR)`。\n\n**修复**:\n- TYPE_MAPPINGS 新增 `varchar2 → VARCHAR2`\n- 两处 `(max)` 判断加 `'varchar2'`\n- `VARCHAR2(max)` 保持 `VARCHAR2` 前缀不降级为 `VARCHAR`\n\n## 调试经验\n\n1. **正则捕获组验证**: 改正则后，用 `re.finditer` 打印每个 match 的 `m.groups()` 确认组号对应，比看代码猜更可靠。\n2. **大文件测试**: 单元测试用简单输入无法发现 suffix 贪婪和组偏移问题，需要真实大文件（7万行）才能暴露。\n3. **上下游格式变化**: 拆分阶段加了 `OR REPLACE`，转换器正则必须兼容，否则整个方法体跳过。\n4. **字符类内方括号**: `[\\w.\\[\\\"]]` 中的 `\\[` 和 `\\\"]` 和 `]` 交互导致字符类提前关闭，改用 `.+?` 更安全。\n5. **Python缓存**: 修改 .py 后必须清理 `__pycache__/*.pyc`，否则 pytest 运行旧代码。\n\nFile v3.6.3:references/dm-converter-v30-fixes.md\n\n# dm_converter v3.0 修复记录 (2026-06-13)\n\nv3.0 合并了 v2.4.6 的修复并新增了真实项目验证。\n\n## 修复内容\n\n详见 [v2.4.6修复记录](dm-converter-v246-fixes.md)，包含：\n1. 存储过程/函数方括号未替换\n2. 捕获组偏移导致双重映射\n3. suffix贪婪匹配\n4. 双点号`..`语法\n\n## 真实项目验证\n\n### 测试文件: HRBI_Stage.sql\n- 源文件: 70,396行, UTF-16编码, 229万字符\n- 拆分结果: 462个对象 (373 table + 35 view + 54 procedure)\n- 达梦转换: 462/462全部成功, 0失败\n- 单元测试: 40/40通过\n\n### 验证要点\n\n1. **方括号替换**: 转换后0个文件残留`[dbo]`（修复前54个procedure全部残留）\n2. **双点号替换**: `hrbi_stage..Stage_xxx` → `hrbi_stage.Stage_xxx`，0个文件残留`..`\n3. **类型映射**: `[nvarchar](20)` → `VARCHAR(20 CHAR)`, `[bit]` → `BOOLEAN`, `[datetime]` → `TIMESTAMP`\n4. **dbo前缀**: `[dbo].[xxx]` → `\"HRBI_Stage\".\"xxx\"`\n\n### UTF-16编码处理\n\nSSMS导出的.sql文件常用UTF-16编码，拆分前须转UTF-8:\n```python\nwith open('HRBI_Stage.sql', 'r', encoding='utf-16') as f:\n    content = f.read()\nwith open('HRBI_Stage_utf8.sql', 'w', encoding='utf-8') as f:\n    f.write(content)\n```\n\n### v22 CLI ImportError 临时方案\n\n`split_sql_v22.py` 导入 `SQLSplitterGUI`（依赖 tkinter），无GUI环境会报错。\n临时方案：用 `split_sql_v21.py` 代替（核心拆分逻辑完全相同）:\n```bash\npython3 split_sql_v21.py input.sql output_dir --dialect sqlserver\n```\n\nFile v3.6.3:references/dm-converter-v322-fixes.md\n\n# dm_converter v3.2.2 修复记录\n\n## 日期: 2026-06-14\n\n## 修复内容\n\n### 1. 存储过程VARCHAR(n)加CHAR语义\n\n**问题**: `_post_convert_generic_types` 注释写\"不做 VARCHAR(n) -> VARCHAR(n CHAR) (过程体内变量声明不需要)\"，导致存储过程中所有VARCHAR(n)缺少CHAR语义。\n\n**用户反馈**: \"存储过程中也需要按照之前的类型映射进行修改\"\n\n**修复前**:\n```sql\n--DECLARE变量\nv_Sql VARCHAR(4000);\n--参数\nCREATE OR REPLACE PROCEDURE sp_test (p1 INTEGER, p2 VARCHAR(100)) IS\n--CAST\nCAST(TX_DATE AS nvarchar(50))  -- nvarchar也未映射\n```\n\n**修复后**:\n```sql\nv_Sql VARCHAR(4000 CHAR);\nCREATE OR REPLACE PROCEDURE sp_test (p1 INTEGER, p2 VARCHAR(100 CHAR)) IS\nCAST(TX_DATE AS VARCHAR(50 CHAR))\n```\n\n### 2. CAST中nvarchar未映射\n\n**问题**: `_post_convert_generic_types` 只有 `_bracket_type_pattern` 匹配方括号包裹的类型名（如`[nvarchar]`），但SQL Server过程体中 `cast(x as nvarchar(50))` 的 `nvarchar` 是裸名无方括号，不匹配。\n\n**修复**: 新增 `_bare_type_pattern`，用 `(?<=\\s)` lookbehind 匹配前有空白字符的裸类型名。\n\n```python\n_bare_type_pattern = re.compile(\n    r'(?<=\\s)(' + '|'.join(re.escape(t) for t in _type_map.keys()) + r')(?=\\s*\\(|\\s+NULL|\\s+NOT|\\s+IDENTITY|\\s+DEFAULT|\\s+,|\\s*\\)|\\s*$)',\n    re.IGNORECASE\n)\n```\n\n**设计注意**: `(?<=\\s)` 确保只匹配前面有空白字符的类型名（如 `AS nvarchar(50)` 或 `as VARCHAR(4000)`），避免错误匹配列名（列名通常在逗号或括号后，前面不是空格）。\n\n### 3. _post_convert_generic_types 新增 VARCHAR(n) → VARCHAR(n CHAR)\n\n```python\ncontent = re.sub(\n    r'\\bVARCHAR\\s*\\(\\s*(\\d+)\\s*\\)',\n    r'VARCHAR(\\1 CHAR)',\n    content,\n    flags=re.IGNORECASE\n)\n```\n\n与 `_post_convert_table_types` 使用相同的正则模式。\n\n## 验证结果\n\n- 44个单元测试全部通过（含4个新增 TestProcedureTypeMapping）\n- 462个真实SQL对象端到端测试全部通过（HRBI_Stage.sql, 7万行）\n- 存储过程中nvarchar残留数: 0（全部映射为VARCHAR(n CHAR)）\n- DECLARE变量: `v_Sql VARCHAR(4000 CHAR)` ✓\n- CAST: `CAST(TX_DATE AS VARCHAR(50 CHAR))` ✓\n\n## 测试用例\n\n新增 `TestProcedureTypeMapping` 类，含4个测试:\n- `test_procedure_varchar_char_semantic`: DECLARE变量VARCHAR加CHAR\n- `test_procedure_cast_nvarchar`: CAST中nvarchar→VARCHAR(n CHAR)\n- `test_procedure_parameter_varchar_char`: 参数VARCHAR加CHAR\n- `test_function_returns_varchar_char`: RETURNS VARCHAR加CHAR\n\n## 已有测试断言更新\n\n- `test_procedure_with_params`: `p2 VARCHAR(100)` → `p2 VARCHAR(100 CHAR)`\n- `test_scalar_function`: `RETURN VARCHAR` → `RETURN VARCHAR(50 CHAR)`\n\nFile v3.6.3:references/dm-converter-v323-fixes.md\n\n# dm_converter v3.2.3 修复记录 — PROCEDURE用AS而非IS\n\n## 问题描述\n\n用户指出：**达梦存储过程应该用AS而非IS**。\n\n`_convert_procedure`方法错误地将所有PROCEDURE声明生成`IS`结尾：\n```\nCREATE OR REPLACE PROCEDURE \"HRBI_Stage\".\"PROC_xxx\" (TX_DATE DATE) IS   ← 错误\n```\n\n正确应该是：\n```\nCREATE OR REPLACE PROCEDURE \"HRBI_Stage\".\"PROC_xxx\" (TX_DATE DATE) AS   ← 正确\n```\n\n## 根因\n\n达梦的PROCEDURE声明语法要求`AS`，FUNCTION才用`IS`。这是Oracle和达梦的语法差异：\n- **Oracle**: PROCEDURE 可以用 IS 或 AS\n- **达梦**: PROCEDURE 必须用 AS，FUNCTION 用 IS\n\n代码中`_convert_procedure`硬编码了`IS`（套用了Oracle惯例），没有区分PROCEDURE和FUNCTION。\n\n## 修复\n\n在`_convert_procedure`方法中，将4处`IS`改为`AS`：\n\n1. **有括号参数列表**（第398行）: `... {params} IS` → `... {params} AS`\n2. **无括号参数(单参数)**（第417行）: `... ({params_clean.strip()}) IS` → `... AS`\n3. **无括号参数(多参数)**（第419行）: `... {formatted} IS` → `... {formatted} AS`\n4. **无参数存储过程**（第431行）: `'CREATE OR REPLACE PROCEDURE \\\\1 IS'` → `... \\\\1 AS`\n\n**未修改**: `_convert_function`方法保持`IS`不变。\n\n## 测试\n\n- 旧断言 `assertIn('IS', result.converted)` → `assertIn('AS', result.converted)`\n- 旧断言 `assertIn('sp_simple IS', ...)` → `assertIn('sp_simple AS', ...)`\n- 44个单元测试全部通过\n- 462个真实SQL对象端到端验证：47个proc文件全部从IS改为AS，0个残留\n\n## 关键区分备忘\n\n| 对象类型 | 达梦声明结尾 |\n|---------|------------|\n| PROCEDURE | **AS** |\n| FUNCTION | **IS** |\n\nFile v3.6.3:references/dm-converter-v330-fixes.md\n\n# dm_converter v3.3.0 修复记录\n\n## 修复的问题\n\n### 1. PROCEDURE双重引号bug\n- **症状**: `_convert_procedure`输出 `CREATE OR REPLACE PROCEDURE \"\"sp_test\" (p1 INT)\" AS`\n- **根因**: 三个`re.sub`顺序执行 — 第一个`_format_bracket_params`替换后输出`\"sp_test\"`，第三个`_fmt_no_param_proc`的正则`PROC\\s+(.+?)\\s+AS`又匹配到，把`\"sp_test\" (...)`当name再包引号\n- **修复**: 合并为单一正则`_PROC_PATTERN` + 分支回调`_format_proc`，按group(2)/group(3)区分括号参数/无括号参数/无参数\n- **调试技巧**: 写独立.py文件跑debug（heredoc+python3 -c容易超时），用`re.findall`+`repr()`逐步追踪每个正则的匹配结果\n- **通用教训**: 多个正则顺序替换同类语法时，后面的正则会匹配前面替换的结果\n\n### 2. IDENTITY位置修正\n- **症状**: `IDENTITY(\"id\", 1, 1)`插在了`)`前面（表定义内部），不是`)`后面\n- **根因**: 正则`\\)(\\s*$|\\s*;|...)`中`)\\s*$`匹配到了`VARCHAR(100 CHAR)`行末的`)`（行尾），而非表定义结束的独占一行的`)`\n- **修复**: 先删`ON [PRIMARY]`，再优先匹配独占一行的`^(\\s*)\\)(\\s*$)`，fallback到`\\)(\\s*(?:;|\\n|$))`\n- **通用教训**: 表定义中列类型的括号`)`和表结束的`)`在正则中难以区分——要求结束括号独占一行\n\n### 3. #临时表正则截断\n- **症状**: `CREATE TABLE #TempUsers`的列定义body截断到`NVARCHAR(100`，丢失`)\\n)`\n- **根因**: `[^)]*`遇`NVARCHAR(100)`的`)`当成表结束括号\n- **修复**: 改用`(.+)`贪婪+re.DOTALL匹配到最后一个`)`\n- **附加**: `##`→`#tmp_`bug，先替换`##`→`gtmp_`再替换`#`→`tmp_`\n\n### 4. token_map碰撞\n- **症状**: 视图转换后字符串`'N/A'`变成了标识符`\"v_users\"`\n- **根因**: Step1 tokenize `'N/A'`→`__TOKEN_0__`，Step3重新tokenize`\"v_users\"`也分配`__TOKEN_0__`覆盖原值\n- **修复**: `_tokenize`新增`start_counter`参数，Step3从`max(original_keys)+1`开始编号\n\n### 5. _quote_name方括号处理\n- **症状**: `[dbo].[PROC_xxx]`转换后变成`\"dbo]\".\\\"[PROC_xxx\"` — 方括号残留在引号内\n- **根因**: `_quote_name`先检查整体以`[`开头`]`结尾就剥首尾括号，`[dbo].[PROC_xxx]`整体被剥成`dbo].[PROC_xxx`\n- **修复**: 先按`.`拆分，再逐段去方括号/引号\n\n## 质量验证\n- 44个单元测试全部通过\n- 462个真实SQL对象(HRBI_Stage.sql, 7万行)端到端测试: 0失败, 2秒\n- 10项残留检查: 方括号/dbo/@@变量/SET NOCOUNT/GO/GETDATE/ISNULL/双重映射/双点号 均为0\n\nFile v3.6.3:references/dm-converter-v340-fixes.md\n\n# dm-converter v3.4.0 修改记录\n\n## 日期: 2026-06-20\n\n## 变更1: TRUNCATE TABLE → DELETE FROM\n\n### 背景\n达梦数据库不支持在存储过程内使用 TRUNCATE TABLE 语句，需要转换为 DELETE FROM。\n\n### 实现\n- 新增 `_convert_truncate(self, tokens: str) -> str` 方法\n- 在 `convert()` 的 Step4 中，`_convert_statements` 之后调用\n- 正则: `r'\\bTRUNCATE\\s+TABLE\\s+'` → `'DELETE FROM '` (大小写不敏感)\n\n### 文件变更\n- `dm_converter.py`: 新增方法 + convert()中调用\n- `test_dm_converter.py`: 新增 TestTruncateConversion (4个测试)\n\n## 变更2: TABLE/VIEW结尾加分号\n\n### 背景\n拆分后的表、视图等DDL对象结尾缺少分号，达梦执行时会报错。\n\n### 实现\n- 新增 `_add_ending_semicolon(self, content: str) -> str` 方法\n- 逻辑: 去掉末尾GO/多余分号 → 统一加一个`;`+换行\n- 在 `convert()` 的 Step9 中，对 TABLE/VIEW/INDEX/CONSTRAINT/SEQUENCE 调用\n- PROCEDURE/FUNCTION/TRIGGER 仍用 `_add_terminator`（加`/`终止符）\n\n### split_sql_v21.py 变更\n- 去掉了 Oracle/DM 方言不加分号的特殊逻辑\n- 现在所有方言拆分后均加分号\n\n### 文件变更\n- `dm_converter.py`: 新增方法 + convert()中调用\n- `split_sql_v21.py`: 去掉 `if dialect not in (SQLDialect.ORACLE, SQLDialect.DM)` 判断\n- `test_dm_converter.py`: 新增 TestEndingSemicolon (5个测试)\n\n## 测试结果\n- 全部53个测试通过 (原44个 + 新增9个)\n- 0个失败\n\nArchive v3.6.2: 38 files, 145478 bytes\n\nFiles: references/dm_conversion.py (225b), references/dm-converter-design.md (9174b), references/dm-converter-v243-fixes.md (5550b), references/dm-converter-v245-bracket-dbo-split.md (4853b), references/dm-converter-v246-fixes.md (3357b), references/dm-converter-v251-fixes.md (3634b), references/dm-converter-v30-fixes.md (1512b), references/dm-converter-v322-fixes.md (2691b), references/dm-converter-v323-fixes.md (1693b), references/dm-converter-v330-fixes.md (2547b), references/dm-converter-v340-fixes.md (1459b), references/dm-converter-v345-fixes.md (6488b), references/dm-converter-v353-fixes.md (1889b), references/document-maintenance.md (1660b), references/hrbi-stage-real-world-test.md (2277b), references/python-indentation-debugging.md (2451b), references/split-convert-workflow-20260627.md (2682b), scripts/batch_convert.py (2149b), scripts/batch_processor.py (9333b), scripts/checkpoint.py (9666b), scripts/common.py (8993b), scripts/config_manager.py (9779b), scripts/dependency_analyzer.py (10518b), scripts/dm_converter.py (117494b), scripts/error_handler.py (6806b), scripts/gui.py (1007b), scripts/result_previewer.py (8423b), scripts/split_sql_v21.py (43126b), scripts/split_sql_v22.py (11134b), scripts/split_sql.py (29311b), scripts/test_dm_converter.py (25861b), scripts/test_sql_splitter.py (14305b), scripts/test_v21_features.py (6344b), scripts/test_v22_features.py (9177b), scripts/unlimited_split.py (3755b), skill-card.md (2994b), SKILL.md (68132b), _meta.json (131b)\n\nFile v3.6.2:SKILL.md\n\n---\n\nname: sql-splitter\n\ndescription: 拆分 SQL 文件为独立文件（存储过程、函数、视图、触发器、表结构、索引、约束），自动分析依赖并生成合并脚本\n\n---\n\n\n\n# SQL 文件拆分工具 v3.6.1\n\n\n\n将包含多个 SQL 对象的单一文件或目录拆分为独立的 .sql 文件，\n\n并自动分析对象间依赖关系，生成按依赖排序的合并脚本。\n\n\n\n## v3.4.5 修复 — dbo两段式替换 + DATETIME2排序 + 方括号替换架构优化 + SELECT INTO转换 + DDL分号\n\n\n\n- **v3.5.4 新增: 过程体内所有语句(DML+变量赋值)补分号** — 新增`_ensure_statement_semicolons()`方法(Step 8.6)，处理DML和变量赋值的分号补全，同时正确处理跨行INSERT INTO\n\n- **v3.5.3 行为变更: 非类型名方括号→加双引号** — `[aa]`→`\"aa\"`(v3.5.3起加双引号; v3.5.2及以前去[]不加双引号→`aa`)，类型名`[int]`→`int`仍只去[]不加双引号。修改位置：`_convert_bracket_identifiers()`新增`_replace_bracket`回调函数，用TYPE_MAPPINGS键集合区分类型名和普通标识符\n\n\n\n- **dbo替换规则(v3.5.6最终版)** — 三段式和两段式区别处理：\n\n  - **三段式**（已有其他schema名，如`HRBI_Stage.[dbo].[xxx]`）: dbo是SQL Server默认schema，直接删除\n\n  - **两段式**（只有dbo，如`[dbo].[xxx]`或`dbo.xxx`）: dbo替换为schema_prefix\n\n  - 关键区分：输入中若有其他schema名存在，dbo是冗余层→删除；若只有dbo，dbo是唯一schema标识→替换为prefix\n\n  - **正则替换顺序（不可颠倒）**: 1)双点号`(\\\\w+)\\\\.\\\\.(\\\\w+)`→`\\\\1.\\\\2` 2)三段式引号`\"\\\\w+\"\\\\.dbo\\\\.\\\"(\\\\w+)\\\"`→`\"$1\"` 3)三段式裸名`(\\\\w+)\\\\.dbo\\\\.(\\\\w+)`→`\"$1\\\".\\\"$2\\\"` 4)三段式混合`\"\\\\w+\\\"\\\\.\\\\w+\\\\.(\\\\w+)`→保留 5)两段式引号`\"dbo\\\"\\\\.\\\"(\\\\w+)\\\"`→`prefix.\\\"$1\\\"` 6)两段式裸名`dbo\\\\.(\\\\w+)`→`prefix.$1`\n\n  - 实现位置：`_replace_dbo_prefix()`\n\n  - ⚠️ 改完dbo规则后必须跑全量312过程验证\n\n  - 详见 [dbo演进史](references/dm-converter-v345-fixes.md)\n\n- **DATETIME2→TIMESTAMP2而非TIMESTAMP(BUG)** — TYPE_MAPPINGS的key构建正则alternation时无排序，`DATETIME`排在`DATETIME2`前面抢先匹配，`DATETIME2`被拆成`DATETIME`+`2`变成`TIMESTAMP2`。修复：`sorted(keys, key=len, reverse=True)`按长度降序排列，`DATETIME2`(9字符)排在`DATETIME`(8字符)前面。影响范围：全局`_TYPE_NAMES_PATTERN`、`_FULL_TYPE_NAMES_PATTERN`、以及`_post_convert_table_types`和`_post_convert_generic_types`中的两个`_bare_type_pattern`\n\n- **方括号替换在token还原后执行导致注释内容被误改(架构BUG)** — `_post_convert_table_types`/`_post_convert_generic_types`中的`\\\\[([^\\\\]]+)\\\\]`在Step 6.7(token还原后)执行，注释已还原为原始文本，注释中含方括号(如`---一次性解决率 是[处理人响应次数`)会被误匹配截断。修复：把方括号替换从Step 6.7移到Step 4`_convert_bracket_identifiers()`中执行——此时注释和字符串已被tokenize保护，方括号只出现在真实SQL代码中，不会被注释内容干扰\n\n- **SELECT INTO #临时表→CTAS创建GTT(新规则)** — 达梦不支持`SELECT ... INTO #新表 FROM ...`建表语法。转换规则：临时表(`INTO #xxx`/`INTO tmp_xxx`)→`CREATE GLOBAL TEMPORARY TABLE \"tmp_xxx\" AS SELECT ... FROM ...`(CTAS方式)；普通表暂不自动转换(与达梦变量赋值语法`SELECT expr INTO var FROM`形式相同，无法区分)。修改位置：`_convert_temp_tables()`新增SELECT INTO处理逻辑\n\n- **过程体内DDL语句结尾加分号(新规则)** — 达梦存储过程体内每条DDL(CREATE/ALTER/DROP TABLE/INDEX等)必须以`;`结尾。新增`_ensure_ddl_semicolons()`方法(Step 8.5)，用状态机扫描过程体：单行DDL如`DROP TABLE xxx`直接补`;`，跨行DDL如`CREATE GLOBAL TEMPORARY TABLE \"tmp_xxx\"\\n(id INT)\\n`在`)`行补`;`，CTAS跨行子查询在遇到下一个语句开头时给上一行补`;`，同时处理`ON PRIMARY`文件组语法去掉。312个存储过程中DDL块缺分号数从447降为0\n\n- **过程体内所有语句(DML+变量赋值)结尾加分号(v3.5.4起)** — 新增`_ensure_statement_semicolons()`方法(Step 8.6)。达梦存储过程体内DML(INSERT/DELETE/UPDATE/SELECT)和变量赋值(`:=`)也必须以分号结尾。关键：跨行INSERT INTO(table_name后无括号，列定义在下一行)不加分号；单行DELETE FROM...WHERE...补分号；v_xxx := expr补分号；跨行赋值((/+,/结尾)不补分号。312个存储过程全部验证通过。\n\n- **过程体内DML语句和变量赋值结尾加分号(v3.5.6新增)** — 达梦存储过程体内每条DML(INSERT/DELETE/UPDATE/SELECT)和变量赋值(`v_xxx := expr`)必须以`;`结尾。新增`_ensure_statement_semicolons()`方法(Step 8.6)，逐行扫描过程体：单行DML如`delete FROM xxx WHERE y=1`直接补`;`，跨行INSERT INTO(table_name\\n(columns)\\nSELECT ...)不加分号，列定义结束后自动补`;`；变量赋值`v_xxx := expr`直接补`;`，跨行赋值(`v_xxx := ('`或`v_xxx := expr +`)不加分号。312个存储过程中DML/变量赋值缺分号数从数百降为0\n\n- **schema_prefix传参链路断裂** — `split_sql_v21.py`的`_convert_split_output()`函数签名有schema_prefix参数，但调用处(第886行)没传，convert调用(第977行)也没传，导致dbo替换永远不生效。修复：调用链全程传递schema_prefix\n\n- **新增CLI参数`--schema-prefix`和`--dm`** — `--schema-prefix`手动指定dbo替换前缀；`--dm`是`--convert-to dm`快捷方式。默认从输入文件名自动提取schema_prefix\n\n- **6条用户规则验证通过**：1) []替换规则：`[aa]`→`\"aa\"`(去[]加双引号)、`[hrbi].[xxx]`→`\"hrbi\".\"xxx\"`(schema/表名加双引号)、`[int]`→`int`/`[nvarchar]`→`nvarchar`(类型名去[]不加双引号，后续做类型映射) 2) dbo替换规则：三段式`[HRBI].[dbo].[xxx]`→`\"HRBI\".\"xxx\"`(删dbo)/两段式`[dbo].[xxx]`→`\"hrbi_stage\".\"xxx\"`(替换为prefix)/裸名`dbo.xxx`→`hrbi_stage.xxx`(替换为prefix) 3) VARCHAR(n)加CHAR定义 4) DATETIME/DATETIME2→TIMESTAMP 5) NCHAR→CHAR 6) SELECT INTO #临时表→CTAS创建GTT\n\n- **7条用户规则验证通过**：1) []替换规则：`[aa]`→`\"aa\"`(去[]加双引号)、`[hrbi].[xxx]`→`\"hrbi\".\"xxx\"`(schema/表名加双引号)、`[int]`→`int`/`[nvarchar]`→`nvarchar`(类型名去[]不加双引号，后续做类型映射) 2) dbo替换规则：三段式`[HRBI].[dbo].[xxx]`→`\"HRBI\".\"xxx\"`(删dbo)/两段式`[dbo].[xxx]`→`\"hrbi_stage\".\"xxx\"`(替换为prefix)/裸名`dbo.xxx`→`hrbi_stage.xxx`(替换为prefix) 3) VARCHAR(n)加CHAR定义 4) DATETIME/DATETIME2→TIMESTAMP 5) NCHAR→CHAR 6) SELECT INTO #临时表→CTAS创建GTT 7) 过程体内DDL语句结尾加分号\n\n- **312个存储过程转换全部成功**\n\n\n\n当多个模式有公共前缀时（如`DATETIME`和`DATETIME2`），正则alternation`|`从左到右匹配，公共前缀排在前面会抢先匹配，留下后缀字符。**必须按长度降序排列alternation中的模式**。同理适用于`SMALLDATETIME`(13字符)>`DATETIMEOFFSET`(15字符)>`DATETIME2`(9字符)>`DATETIME`(8字符)等所有有前缀关系的类型名。这不仅是dm_converter的问题——任何用`|`连接多个可变长度keyword的正则都需注意此陷阱。\n\n\n\n### ⚠️ 方括号替换必须在token保护下执行\n\n\n\n`_convert_bracket_identifiers()`（Step 4，token化后/还原前）是方括号替换的唯一正确位置。此时注释和字符串已被替换为`__TOKEN_N__`占位符，方括号只出现在真实SQL代码中。如果在token还原后（Step 6.7）做方括号替换，注释中的`[`和`]`也会被误处理——即使`[^\\]\\n]+`限制不跨行，注释内不含换行时仍会跨标识符贪婪匹配。\n\n\n\n### ⚠️ schema_prefix必须贯穿调用链\n\n\n\n`split_sql_file()` → `_convert_split_output()` → `convert_sqlserver_to_dm_with_result()` → `DMConverter.convert()` → `_replace_dbo_prefix()`。任何一环漏传schema_prefix，dbo替换就不生效。新增`schema_prefix`参数到`split_sql_file()`签名，CLI默认从输入文件名提取。\n\n\n\n## v3.4.0 新功能 — TRUNCATE→DELETE FROM + TABLE/VIEW结尾加分号 + 所有方言拆分加分号\n\n\n\n- **TRUNCATE TABLE → DELETE FROM** — 达梦不支持TRUNCATE在存储过程内，自动将 `TRUNCATE TABLE xxx` 转换为 `DELETE FROM xxx`\n\n- **TABLE/VIEW/INDEX/CONSTRAINT/SEQUENCE结尾加分号** — 拆分后的表、视图等DDL对象结尾自动加 `;`，确保达梦可直接执行（新增 `_add_ending_semicolon` 方法）\n\n- **所有方言拆分后均加分号** — 之前Oracle/DM方言拆分后不加分号（用`/`代替），现在统一加分号（split_sql_v21.py已去掉Oracle/DM特殊逻辑）\n\n- **53个单元测试全部通过**（新增9个：4个TRUNCATE转换+5个结尾分号）\n\n- **⚠️ TRUNCATE→DELETE语义差异**：TRUNCATE是DDL（不可回滚、重置自增），DELETE是DML（可回滚、不重置自增）。自动转换后行为不完全等价，但这是达梦过程体内的唯一可行方案。如果需要重置自增列，需在DELETE后手动调用序列重置。\n\n\n\n## v3.3.0 新功能 — PROCEDURE双重引号修复 + IDENTITY位置修正 + 临时表正则修复 + token碰撞修复\n\n\n\n- **存储过程PROCEDURE双重引号bug修复** — 之前三个正则顺序执行，第一个替换`PROCEDURE sp_test(...)` 后输出 `\"sp_test\"`，第三个又匹配到已替换的结果再包引号变成 `\"\"sp_test\"\"` 。修复：合并为一个正则+分支回调，确保每个存储过程声明只被匹配和替换一次\n\n- **IDENTITY自增列位置修正** — 之前 `IDENTITY(\"id\",1,1)` 被插在表定义的 `)` 前面，达梦语法要求紧跟 `)` 后面。修复：优先匹配独占一行的 `)` ，也兼容单行写法 `(id INT IDENTITY(1,1) NOT NULL)`\n\n- **#临时表正则修复** — 之前 `[^]]*` 匹配到 `NVARCHAR(100)` 的 `)` 就截断，导致临时表列定义不完整。修复：改用贪婪 `(.+)` + `re.DOTALL` 匹配到最后一个 `)` 。同时修复 `##` 全局临时表先被替换为 `#tmp_` 的bug\n\n- **token_map碰撞修复** — Step3重新tokenize时counter从0开始，`\"v_users\"` 新占位符覆盖了 `'N/A'` 原占位符，导致字符串被替换为标识符。修复：新增 `start_counter` 参数，Step3从原最大key+1开始编号\n\n- **44个单元测试全部通过**\n\n\n\n## v3.2.x 功能 — PROCEDURE用AS + VARCHAR(n CHAR)对PROC生效 + CAST中nvarchar映射\n\n\n\n- **存储过程PROCEDURE用AS而非IS(v3.2.3)** — 达梦存储过程声明用`AS`，函数用`IS`，之前PROCEDURE错误用了Oracle风格的`IS`。**关键区分：PROCEDURE→AS，FUNCTION→IS**\n\n- **存储过程VARCHAR(n)加CHAR语义(v3.2.2)** — DECLARE变量和参数中的`VARCHAR(n)` → `VARCHAR(n CHAR)`，与TABLE转换一致\n\n- **CAST中nvarchar→VARCHAR(n CHAR)(v3.2.2)** — `cast(x as nvarchar(50))` → `CAST(x AS VARCHAR(50 CHAR))`，之前nvarchar在CAST中未被映射\n\n- **_post_convert_generic_types增强(v3.2.2)** — 新增`_bare_type_pattern`映射裸类型名（如CAST中nvarchar），之前只映射方括号包裹的类型\n\n- **SET NOCOUNT ON/OFF直接删除(v3.2.1)** — 达梦不需要，之前注释保留，现在直接整行删除\n\n- **462个真实SQL对象端到端测试通过**(HRBI_Stage.sql, 7万行)\n\n\n\n### 旧版功能(v3.0/v2.4.x)\n\n\n\n- **存储过程/函数方括号替换(v3.0)** — `_post_convert_generic_types`方法，所有对象类型统一做方括号→双引号+dbo替换\n\n- **双点号`..`替换(v3.0)** — SQL Server的`database..object`（省略dbo）→达梦`database.object`\n\n- **数据类型正则bug修复(v3.0)** — 捕获组改为非捕获组`(?:...)`修复双重映射；suffix `[^]]`→`[^)]`修复贪婪匹配\n\n- **UTF-16自动转换** — SQL Server导出文件常为UTF-16编码，需先用Python转UTF-8再拆分\n\n\n\n## v2.4.5 功能 — 方括号转双引号 + dbo前缀智能处理 + 精确拆分\n\n\n\n- **方括号→双引号(v3.5.3起)** - `[schema].[table]` → `\"schema\".\"table\"`\n\n - 普通标识符/列名 `[aa]` → `\"aa\"` (v3.5.3起加双引号; v3.5.2及以前去[]不加双引号→`aa`)\n\n - SQL类型名 `[nvarchar]` → `nvarchar`（去掉方括号+类型映射，不加双引号）\n\n - 支持30+种SQL Server类型名识别\n\n- **dbo替换规则(v3.5.6最终版)** — 三段式和两段式区别处理:\n\n  - **三段式**（已有schema）: dbo直接删除。`[HRBI_Stage].[dbo].[Users]` → `\"HRBI_Stage\".\"Users\"` / `HRBI_Stage.[dbo].[Users]` → `HRBI_Stage.\"Users\"`\n\n  - **两段式**（无schema）: dbo替换为schema_prefix。`[dbo].[Users]` → `\"hrbi_stage\".\"Users\"` / `dbo.Users` → `hrbi_stage.Users`\n\n  - **双点号** `xxx..yyy` → `xxx.yyy`（SQL Server省略dbo的写法）\n\n  - **正则顺序（不可颠倒）**: 1)双点号→2)三段式引号→3)三段式裸名→4)三段式混合→5)两段式引号→6)两段式裸名\n\n  - ⚠️ 改完dbo规则后必须跑全量312过程验证\n\n - 支持双引号包裹格式：`\"dbo\".\"Users\"` 和裸名格式：`dbo.Users` 均可正确匹配\n\n- **精确拆分增强** - 无明确终止符时的兜底逻辑\n\n - 新增`_find_next_create`函数：当找不到`;`或`GO`终止符时，用下一个`CREATE`关键字作为对象边界上界\n\n - 跳过字符串和注释内的`CREATE`，只匹配真正的CREATE语句开头\n\n - 所有对象类型（table/view/procedure/function/trigger/index/constraint）均有兜底\n\n- **VARCHAR CHAR语义后处理** - `VARCHAR(n)` → `VARCHAR(n CHAR)`\n\n - 修复detokenize中类型名映射绕过CHAR语义的问题\n\n\n\n- **重写达梦数据库转换器** - 完全重写 dm_converter.py\n\n  - token化保护: 字符串/注释替换为占位符后再做正则替换，避免误改字符串内容\n\n  - 按对象类型独立转换: procedure/function/view/trigger/table/index/constraint\n\n  - 40+种数据类型映射, 30+种函数映射\n\n  - 变量语法转换: @var -> var, DECLARE @var -> var, SET @var= -> var:=\n\n  - TRY-CATCH -> EXCEPTION WHEN OTHERS THEN\n\n  - 全局变量转换: @@ROWCOUNT -> SQL%ROWCOUNT\n\n  - 触发器伪表: inserted/deleted -> NEW/OLD\n\n  - 转换结果输出到子目录: output_split_dm/\n\n- **拆分后转换集成** - split_sql_v21.py 新增 convert_to 参数\n\n  - 拆分完成后自动调用转换器，按对象类型独立转换\n\n  - 生成达梦版合并脚本 merge_all.sql\n\n- **CLI参数** - split_sql_v22.py 新增 --convert-to dm\n\n- **29个转换单元测试** - test_dm_converter.py 全部通过\n\n- **修复已知bug**:\n\n  - INSERT INTO 不再被误替换为 INTEGERO\n\n  - token_map 合并避免占位符还原丢失\n\n  - content = new_content 遗漏导致变量@替换无效\n\n  - 嵌套括号 VARCHAR(100) 导致参数列表正则截断\n\n  - 终止符 / 不再重复添加\n\n\n\n### 过滤特定对象类型（只提取存储过程等）\n\n\n\n拆分默认输出所有对象类型。如果只需要某类对象（如只要存储过程），有两种方式：\n\n\n\n**方式1：拆分后拷贝（推荐，最简单）**\n\n```bash\n\n# 先完整拆分\n\npython3 ~/.hermes/skills/sql-splitter/scripts/split_sql_v21.py input.sql output_dir --dialect sqlserver\n\n\n\n# 再只拷贝目标类型到独立目录\n\ncp output_dir/proc_*.sql /path/to/output_proc/\n\n```\n\n\n\n**方式2：拆分后删除不需要的文件**\n\n```bash\n\n# 保留存储过程，删除其他\n\ncd output_dir && ls -1 | grep -v '^proc_' | xargs rm\n\n```\n\n\n\n- **⚠️ 拆分→转换→过滤的完整工作流(v3.5.4)** — 正确顺序：(1)拆分 `split_sql_v21.py input.sql output_split` (2)转换 `batch_convert.py output_split output_split_dm schema_prefix` (3)过滤 `cp output_split_dm/proc_*.sql output_final/`。**关键坑**：如果先过滤再转换，`batch_convert.py` 只能转换过滤后的文件，schema_prefix可能不对（文件名变了）；如果转换后新旧文件混在同一目录，`cp` 可能把旧文件也拷进去。**必须先转换再过滤，过滤目录必须清空**。转换后验证：`grep -rl '\\bdbo\\.' output_split_dm/` 应为0\n\n\n\n### 达梦转换使用方法\n\n\n\n```bash\n\n# 拆分SQL Server文件并转换为达梦数据库语法\n\npython3 ~/.hermes/skills/sql-splitter/scripts/split_sql_v22.py input.sql output_dir --dialect sqlserver --convert-to dm\n\n\n\n# 仅转换(不拆分)\n\npython3 -c \"from dm_converter import convert_sqlserver_to_dm; print(convert_sqlserver_to_dm('SELECT GETDATE()', 'generic'))\"\n\n```\n\n\n\n### 质量报告使用方法\n\n\n\n```python\n\n# 单对象报告\n\nfrom dm_converter import DMConverter\n\nfrom report_generator import ConversionReportGenerator\n\n\n\nconverter = DMConverter()\n\nresult = converter.convert(sql_content, 'procedure', schema_prefix='hrbi')\n\nreport = ConversionReportGenerator.generate_single(result, 'sp_test', 'procedure')\n\nprint(report.to_markdown())\n\nprint(f'兼容性评分: {report.score}/100')\n\n\n\n# 批量报告\n\nbatch = ConversionReportGenerator.generate_batch(results, schema_prefix='hrbi')\n\nbatch.save_html('report.html')    # 暗色主题HTML\n\nbatch.save_json('report.json')    # 结构化JSON\n\nbatch.save_markdown('report.md')  # Markdown\n\n\n\n# 快速评分(不生成报告)\n\nscore = ConversionReportGenerator.quick_score(result)\n\n```\n\n\n\n### 批量转换(推荐，用脚本文件)\n\n\n\nv21不支持`--convert-to`参数，需分两步：**拆分→转换→过滤**。详见 [拆分+转换完整工作流](references/split-convert-workflow-20260627.md)。\n\n\n\n⚠️ **拆分≠转换**: 拆分产出的是原始SQL Server语法文件，必须经过`batch_convert.py`转换才能得到达梦语法。只拆分不转换是常见错误。\n\n\n\n```bash\n\n# 1) 拆分\n\npython3 ~/.hermes/skills/sql-splitter/scripts/split_sql_v21.py input.sql output_dir --dialect sqlserver\n\n\n\n# 2) 批量转换(写脚本文件方式)\n\ncat > /tmp/batch_convert.py << 'PYEOF'\n\n#!/usr/bin/env python3\n\nimport os, sys\n\nsys.path.insert(0, '/Users/a1234/.hermes/skills/sql-splitter/scripts')\n\nfrom dm_converter import convert_sqlserver_to_dm\n\n\n\nsrc_dir = sys.argv[1] if len(sys.argv) > 1 else 'output_dir'\n\ndm_dir = sys.argv[2] if len(sys.argv) > 2 else src_dir + '_dm'\n\nschema_prefix = sys.argv[3] if len(sys.argv) > 3 else os.path.basename(src_dir).replace('_split','')\n\nos.makedirs(dm_dir, exist_ok=True)\n\n\n\nok = err = 0\n\nerr_list = []\n\nfor f in sorted(os.listdir(src_dir)):\n\n    if not f.endswith('.sql') or f == 'merge_all.sql': continue\n\n    obj_type = f.split('_')[0]\n\n    type_map = {'proc':'procedure','func':'function','trig':'trigger',\n\n                'view':'view','table':'table','idx':'index','uidx':'index',\n\n                'con':'constraint','seq':'sequence'}\n\n    mapped_type = type_map.get(obj_type, 'generic')\n\n    with open(os.path.join(src_dir, f)) as fh: c = fh.read()\n\n    try:\n\n        converted = convert_sqlserver_to_dm(c, mapped_type, schema_prefix=schema_prefix)\n\n        with open(os.path.join(dm_dir, f), 'w') as fh: fh.write(converted)\n\n        ok += 1\n\n    except Exception as e:\n\n        err += 1; err_list.append(f'{f}: {str(e)[:120]}')\n\n\n\nprint(f'转换完成: {ok} 成功, {err} 失败')\n\nif err_list:\n\n    for e in err_list[:15]: print(f'  - {e}')\n\nPYEOF\n\n\n\npython3 /tmp/batch_convert.py /path/to/output_dir /path/to/output_dir_dm schema_prefix\n\n```\n\n\n\n### 转换规则\n\n\n\n| 类别 | SQL Server | 达梦 |\n\n|------|-----------|------|\n\n| 标识符 | `[aa]`(列名) → `\"aa\"`(去[]加双引号)/`[hrbi].[xxx]`(schema/表名) → `\"hrbi\".\"xxx\"`(去[]加双引号)/`[nvarchar]`(类型名) → `nvarchar`(去[]不加双引号，后续做类型映射) |\n\n| 声明 | CREATE PROCEDURE ... AS | CREATE OR REPLACE PROCEDURE ...(p1 INT) **AS** |\n\n| 数据类型 | INT/BIT/DATETIME/MONEY/NVARCHAR/VARCHAR/UNIQUEIDENTIFIER | INTEGER/BOOLEAN/TIMESTAMP/DECIMAL(19,4)/VARCHAR(n CHAR)/CHAR(36) |\n\n| 函数 | GETDATE()/ISNULL()/LEN()/CONVERT() | CURRENT_TIMESTAMP/NVL()/LENGTH()/CAST() |\n\n| 变量 | @var / DECLARE @var / SET @var= | var / var / var:= |\n\n| 异常 | BEGIN TRY...END TRY BEGIN CATCH...END CATCH | BEGIN...EXCEPTION WHEN OTHERS THEN...END; |\n\n| 事务 | COMMIT TRANSACTION / ROLLBACK TRANSACTION | COMMIT / ROLLBACK |\n\n| 全局变量 | @@ROWCOUNT / @@ERROR | SQL%ROWCOUNT / SQL%ERROR_CODE |\n\n| 触发器 | inserted/deleted | NEW/OLD |\n\n| 终止符 | GO | / |\n\n| 清表 | TRUNCATE TABLE xxx | DELETE FROM xxx |\n\n| 建临时表 | SELECT * INTO #tmp FROM src | CREATE GLOBAL TEMPORARY TABLE \"tmp_xxx\" AS SELECT * FROM src |\n\n| DDL结尾 | 过程体DDL无分号 | 自动补分号(单行/跨行/CTAS/ON PRIMARY均处理, 仅PROC/FUNC/TRIG) |\n\n\n\n### 输出目录结构\n\n\n\n```\n\ninput_split/ ← 原始拆分结果\n\n├── proc_sp_test.sql\n\n├── table_users.sql\n\n├── view_v_users.sql\n\n└── merge_all.sql\n\n\n\ninput_split_dm/ ← 达梦转换版本\n\n├── proc_sp_test.sql\n\n├── table_users.sql\n\n├── view_v_users.sql\n\n└── merge_all.sql\n\n```\n\n\n\n### 支持的对象类型转换\n\n\n\n| 对象类型 | 转换策略 |\n\n|---------|---------|\n\n| 存储过程 | CREATE OR REPLACE + **AS** + 参数@去除 + 参数加括号 + 终止符/ |\n\n| 函数 | CREATE OR REPLACE + RETURN + IS + 终止符/ |\n\n| 视图 | CREATE OR REPLACE + SCHEMABINDING去除 |\n\n| 触发器 | CREATE OR REPLACE + inserted/deleted->NEW/OLD + 终止符/ |\n\n| 表 | IDENTITY保留 + 表选项去除 + 类型映射 |\n\n| 索引 | CLUSTERED/NONCLUSTERED去除 + INCLUDE去除 |\n\n| 约束 | WITH NOCHECK去除 |\n\n\n\n### 转换器核心设计要点（开发调试血泪史）\n\n- **token化保护**: 字符串/注释替换为占位符后再做正则替换，避免误改字符串内容\n\n- **token_map合并**: Step2对象类型转换后重新tokenize时，必须合并旧token_map，否则`__TOKEN_0__`等占位符还原丢失\n\n- **变量@前缀**: 在token还原后再做，且用`_tokenize_strings_only`只保护字符串(不保护注释，注释里@变量也要转)\n\n- **`content = new_content` 不可省略**: re.sub后必须更新content变量，否则后续替换基于旧文本\n\n- **嵌套括号**: `VARCHAR(100)`中的`)`会截断`[^)]*`，参数列表匹配需用`(\\([^)]*(?:\\([^)]*\\)[^)]*)*\\))`匹配嵌套\n\n- **数据类型上下文**: 前缀需包含`DECLARE\\s+`，否则`DECLARE @v DATETIME`中的DATETIME不会被转换\n\n- **INSERT INTO误匹配**: `INSERT INT`被匹配为前缀`\\n`+列名`INSERT`+类型`INT`，需在数据类型替换中排除SQL关键字作为列名\n\n- **⚠️ 多正则顺序执行重复匹配陷阱(v3.3.0修复)**: `_convert_procedure`中三个re.sub顺序处理同类模式(有括号参数/无括号参数/无参数存储过程)，第一个替换后的结果被后续正则再次匹配，导致引号叠加`\"\"sp_test\"\"`。**绝不可用多个re.sub顺序处理同一token的不同形式**——必须用单一正则+分支回调，确保每个模式只被匹配一次。详见 [v3.3.0修复记录](references/dm-converter-v330-fixes.md)\n\n- **⚠️ 添加新转换规则的流程**: 在dm_converter中添加新规则（如TRUNCATE→DELETE）的标准流程：(1)在`_convert_statements`或新增专用方法中实现逻辑 (2)在convert() Step4方法链中调用 (3)在test_dm_converter.py添加测试(至少3个:基本转换+大小写+上下文) (4)跑全部测试确认无回归 (5)更新SKILL.md转换规则表和更新日志 (6)更新wiki页面\n\n- **⚠️ 不用delegate_task做代码审查**: dm_converter.py有2265行，delegate_task子任务逐行审查会超时(600s)。正确做法：主agent直接用search_files+read_file定位关键函数，逐条验证规则覆盖，手动补缺失。delegate_task适合独立可并行的任务，不适合需要大量file I/O的逐行审查\n\n- **⚠️ IDENTITY插入位置陷阱(v3.3.0修复)**: SQL中`)`出现在很多上下文(列类型`VARCHAR(100)`、函数调用、表定义结束)。匹配表结束的`)`必须用上下文锚定(独占一行`^(\\s*)\\)(\\s*$)`或紧跟`;`/换行)，不能简单匹配第一个`)后行尾`——会匹配到列定义中的嵌套`)`。详见 [v3.3.0修复记录](references/dm-converter-v330-fixes.md)\n\n- **⚠️ token_map占位符key碰撞(v3.3.0修复)**: Step3重新tokenize时counter从0开始，新占位符`__TOKEN_0__`覆盖了Step1中同key的原始内容，导致字符串`'N/A'`被还原为标识符`\"v_users\"`。修复：`_tokenize`新增`start_counter`参数，Step3传入`max(已存在key)+1`。**任何生成占位符的系统重新运行时，必须从已存在key的最大值+1开始**。详见 [v3.3.0修复记录](references/dm-converter-v330-fixes.md)\n\n- **⚠️ 方括号替换规则变更(v3.5.3)**: `_convert_bracket_identifiers()`从v3.5.3起区分类型名和非类型名：非类型名`[aa]`→`\"aa\"`(加双引号)，类型名`[int]`→`int`(不加双引号，后续做类型映射)。判断逻辑：取方括号内首段token，与TYPE_MAPPINGS键集合(小写)匹配。**注意**：`[int identity]`这种含后缀的方括号内容，只有首段`int`参与类型名判断，整个内容会被保留为裸名(`int identity`不加双引号)——这是正确行为，因为这种写法只出现在列定义中，后续类型映射会处理。v3.5.2及以前所有方括号内容都不加双引号(→裸名)，需要额外`_post_convert_generic_types`步骤加双引号\n\n- **⚠️ _quote_name先split再去方括号(v3.3.0修复)**: `_quote_name`先检查整体`[...body...]`格式，但`[dbo].[PROC_xxx]`以`[`开头`]`结尾被误当成单个方括号标识符，去首尾后变成`dbo].[PROC_xxx`。**当输入可能是schema.name格式时，必须先split('.')再逐段去方括号/引号**，绝不能先对整体做去除外层处理。详见 [v3.3.0修复记录](references/dm-converter-v330-fixes.md)\n\n- **⚠️ `##`全局临时表替换顺序(v3.3.0修复)**: `re.sub(r'#(\\w+)',...)`对`##GlobalTemp`只替换第二个`#`变成`#tmp_`。修复：先替换`##`→`gtmp_`再替换`#`→`tmp_`。详见 [v3.3.0修复记录](references/dm-converter-v330-fixes.md)\n\n- **⚠️ TRUNCATE TABLE在达梦存储过程内不支持(v3.4.0)**: 达梦不支持在存储过程内使用TRUNCATE TABLE，`_convert_truncate`自动将`TRUNCATE TABLE xxx` → `DELETE FROM xxx`。注意：DELETE FROM没有TRUNCATE的重置IDENTITY/不写日志等语义差异，但达梦存储过程内只能用DELETE\n\n- **⚠️ _ensure_ddl_semicolons状态机实现要点(v3.4.5)**: 达梦存储过程体内DDL必须以`;`结尾。用状态机扫描过程体：(1)单行DDL如`DROP TABLE xxx`直接补`;` (2)跨行建表用括号深度跟踪，在`)`行补`;` (3)CTAS跨行子查询遇到下一个语句开头时回补`;` (4)ON PRIMARY文件组语法去掉后补`;`。**关键：此步骤仅对PROCEDURE/FUNCTION/TRIGGER执行**，TABLE/VIEW等独立对象由Step9处理。312个存储过程DDL缺分号从447降为0\n\n- **⚠️ SELECT INTO #临时表达梦不支持(v3.4.5)**: 达梦不支持`SELECT ... INTO #新表 FROM ...`建表语法。`_convert_temp_tables`自动将临时表的SELECT INTO转为CTAS：`CREATE GLOBAL TEMPORARY TABLE \"tmp_xxx\" AS SELECT ... FROM ...`。**非临时表的SELECT INTO暂不自动转换**——因为与达梦变量赋值语法`SELECT expr INTO var FROM ...`形式相同，无法程序化区分。用户需手动处理非临时表的SELECT INTO（改为先CREATE TABLE再INSERT INTO ... SELECT）\n\n- **⚠️ delegate_task不适合逐行代码审查(v3.4.5)**: 2265行dm_converter.py用delegate_task子任务逐行审查600s超时。正确做法：主agent直接用search_files+read_file定位关键函数，逐条验证规则覆盖，手动补缺失。delegate_task适合独立可并行的任务，不适合大量file I/O的审查\n\n- **⚠️ TABLE/VIEW/INDEX/CONSTRAINT/SEQUENCE结尾必须加分号(v3.4.0)**: 之前只有PROCEDURE/FUNCTION/TRIGGER有`_add_terminator`加`/`终止符，TABLE/VIEW等DDL对象结尾没有统一加分号。新增`_add_ending_semicolon`确保这些对象以`;`结尾。拆分阶段(split_sql_v21.py)也去掉了Oracle/DM不加分号的特殊逻辑，所有方言统一加分号\n\n- **⚠️ 函数参数链路断裂是隐蔽BUG(v3.4.5)**: `_convert_split_output()`签名有schema_prefix参数但调用处没传，`convert_sqlserver_to_dm_with_result()`也没传——函数链路每环漏传，整个功能静默失效。**教训：新增参数到函数签名时，必须grep所有调用点确保传参**，否则功能\"存在但不生效\"的BUG极难发现\n\n- **⚠️ \"完全拆分出X\"≠\"拆分所有\"**：当用户说\"完全拆分出存储过程\"时，意思是**只提取存储过程**，不是拆分全部对象类型。应使用过滤方式（见上方\"过滤特定对象类型\"章节），先拆分再按前缀筛选，而非默认全量输出后让用户自己找。\n\n\n\n- **⚠️ patch工具缩进陷阱（严重，已反复触发）**: patch工具修改Python缩进时极易出错：(1) else块内代码被放到块外 (2) if子块和if本身同缩进 (3) 修复脚本的缩进也可能不对（17空格vs16空格的1位偏差导致整个if块变成else子块）。**终极方案：涉及Python方法体修改时，不要用patch，用Python脚本替换整个方法（find方法定义起始→find return content结束→拼接新方法体）。每次修改后必须用`python3 -m py_compile file.py`验证。仅靠lint不够——py_compile才能发现缩进导致的SyntaxError/IndentationError**。详见 [v2.4.5设计记录](references/dm-converter-v245-bracket-dbo-split.md)\n\n- **⚠️ Python缓存陷阱**: 修改.py后pytest可能运行旧的`__pycache__/*.pyc`。修改后必须`find . -name '*.pyc' -delete`或`PYTHONDONTWRITEBYTECODE=1 python3 -m pytest ...`。否则改了代码但测试结果不变，误导调试方向\n\n- **⚠️ write_file不能写代码文件**: Hermes的write_file工具会给内容添加`NNN|`行号前缀，导致Python文件损坏。**代码文件只能用patch工具或terminal的python脚本修改**。详见 [v2.4.3修复记录](references/dm-converter-v243-fixes.md)\n\n- **⚠️ Python脚本嵌套字符串修改dm_converter**: execute_code中用字符串拼接修改dm_converter.py会因缩进/引号嵌套报IndentationError。**正确做法**：写独立.py脚本文件到/tmp/再用terminal执行。步骤：(1)write_file写patch脚本到/tmp (2)terminal运行`python3 /tmp/patch_xxx.py` (3)py_compile验证 (4)跑测试。这是patch工具和terminal python脚本的补充方案——当patch工具做复杂多位置修改时，脚本文件更可控\n\n- **⚠️ detokenize类型名映射陷阱**: 方括号包裹的类型名`[nvarchar]`在token保护下不会被Step4类型映射匹配，必须在detokenize还原时同时做映射+去掉方括号，否则变成`nvarchar(100)`但已过Step4不再映射。详见 [v2.4.5设计记录](references/dm-converter-v245-bracket-dbo-split.md)\n\n- **⚠️ _convert_data_types捕获组偏移陷阱(v3.0修复)**: 正则`(\\[?(TYPE_PATTERN)\\]?)`中TYPE_PATTERN本身是`(INT|VARCHAR|...)`捕获组，导致type_name是group(3)而内部type是group(4)，suffix本应在group(4)却变成了group(5)。症状：`INT`映射成`INTEGERINT`、`VARCHAR`映射成`VARCHARVARCHAR`。修复：改`(TYPE_PATTERN)`为`(?:TYPE_PATTERN)`非捕获组\n\n- **⚠️ _convert_data_types suffix贪婪匹配陷阱(v3.0修复)**: suffix正则`(\\([^]]*...) used `[^]]*` (match non-`]`) instead of `[^)]*` (match non-`)`)。`[^]]*` matches everything up to a `]` which rarely appears in SQL, so `(100)` 后的所有内容全被吞进suffix。症状：第一个类型后面所有列定义都被当作suffix，后续列的类型映射全部失效。修复：`[^]]` → `[^)]`\n\n- **⚠️ procedure/function方括号不替换(v3.0修复)**: `_post_convert_table_types`只对TABLE/VIEW做方括号→双引号+类型映射+dbo替换，PROCEDURE/FUNCTION走的是`_replace_dbo_prefix`只处理双引号格式的dbo。但procedure原始SQL是`[dbo].[xxx]`方括号格式，dbo替换匹配不到。修复：新增`_post_convert_generic_types`对所有非TABLE/VIEW类型做方括号→双引号+类型映射+dbo替换\n\n- **⚠️ 存储过程VARCHAR(n)缺少CHAR语义(v3.2.2修复)**: 之前`_post_convert_generic_types`注释写\"不做 VARCHAR(n) -> VARCHAR(n CHAR) (过程体内变量声明不需要)\"，但用户要求存储过程中的VARCHAR也必须加CHAR语义，与TABLE一致。修复：(1)在`_post_convert_generic_types`中新增`VARCHAR(n) → VARCHAR(n CHAR)`替换正则 (2)DECLARE变量/参数也会被加CHAR语义\n\n- **⚠️ CAST中nvarchar未映射(v3.2.2修复)**: `_post_convert_generic_types`原来只有`_bracket_type_pattern`(匹配方括号包裹的类型如`[nvarchar]`)，但SQL Server过程体中`cast(x as nvarchar(50))`的`nvarchar`是裸名无方括号，不匹配。修复：新增`_bare_type_pattern`用`(?<=\\s)`前缀匹配裸类型名。注意：`_bare_type_pattern`必须用lookbehind `(?<=\\s)`避免匹配列名(列名在逗号/括号后不会有空格前缀)\n\n- **⚠️ PROCEDURE三正则顺序执行导致双重引号(v3.3.0修复)**: `_convert_procedure`中三个`re.sub`顺序执行，第一个`_format_bracket_params`替换后输出`PROCEDURE \"sp_test\" (...)`，第三个`_fmt_no_param_proc`的正则`PROC\\s+(.+?)\\s+AS`又匹配到了这个结果，把`\"sp_test\" (...)`当成name再包引号变成`\"\"sp_test\"\"`。修复：合并为单一正则+分支回调`_format_proc`，确保每个存储过程声明只被匹配和替换一次。**教训：多个正则顺序替换同一类语法时，后面的正则会匹配前面替换的结果——必须用单一正则或标记已替换区域**\n\n- **⚠️ SET NOCOUNT ON/OFF带分号不匹配(v3.2.1修复)**: `_convert_statements`正则`^\\s*SET NOCOUNT ON\\s*$`不匹配`SET NOCOUNT ON;`（行末带分号），导致过程体内部的SET NOCOUNT ON未被转换。修复：正则加`\\s*;?`兼容分号。同时用户要求**直接删除**而非注释保留，所以`SET NOCOUNT ON`/`SET NOCOUNT OFF`映射为空字符串，正则加`\\n?`吃掉换行不留空行\n\n- **⚠️ PROCEDURE三正则顺序执行导致双重引号(v3.3.0修复)**: `_convert_procedure`中三个`re.sub`顺序执行，第一个替换后输出`PROCEDURE \"sp_test\" (...)`，第三个正则又匹配到把`\"sp_test\"(...)`当name再包引号变成`\"\"sp_test\"\"`。修复：合并为单一正则+分支回调。**教训：多个正则顺序替换同一类语法时，后面的会匹配前面替换的结果——必须用单一正则或标记已替换区域**\n\n- **⚠️ IDENTITY子句插入位置(v3.3.0修复)**: 达梦语法`CREATE TABLE \"name\" (...) IDENTITY(\"col\", 1, 1)`。之前正则匹配到`VARCHAR(100 CHAR)`行末的`)`把IDENTITY插在了`)`前面。修复：优先匹配独占一行的`^(\\s*)\\)(\\s*$)`，fallback到行尾`)`。**教训：表定义中列类型括号里的`)`和表结束`)`在正则中难以区分——要求结束括号独占一行或用锚点精确匹配**\n\n- **⚠️ 临时表正则嵌套括号截断(v3.3.0修复)**: `[^)]*`遇`NVARCHAR(100)`的`)`就截断。修复：改用贪婪`(.+)`+`re.DOTALL`匹配到最后一个`)`。**教训：匹配\"最后一个右括号\"时，排除式模式不可靠，改用贪婪+DOTALL**\n\n- **⚠️ `##`全局临时表替换顺序(v3.3.0修复)**: `re.sub(r'#(\\w+)',...)`对`##GlobalTemp`只替换第二个`#`变成`#tmp_`。修复：先替换`##`→`gtmp_`再替换`#`→`tmp_`。**教训：替换含`##`的标识符时必须先处理双#再处理单#**\n\n- **⚠️ token_map碰撞(v3.3.0修复)**: Step1 tokenize`'N/A'`→`__TOKEN_0__`，Step3重新tokenize`\"v_users\"`又分配`__TOKEN_0__`覆盖原值。修复：`_tokenize`新增`start_counter`参数，Step3从原最大key+1开始。**教训：pipeline中多次tokenize必须保证key不碰撞——传起始偏移量**\n\n- **⚠️ _quote_name处理[dbo].[xxx](v3.3.0修复)**: `[dbo].[PROC_xxx]`整体被误当单个方括号标识符，剥首尾括号变成`dbo].[PROC_xxx`。修复：先按`.`拆分再逐段去方括号/引号。**教训：处理含`.`的标识符时，必须先拆分再清理每段括号**\n\n- **⚠️ SET NOCOUNT ON 在过程体内部不转换**: 转换器只处理紧跟 `AS` 后的 `SET NOCOUNT ON`（转为注释）。如果 `SET NOCOUNT ON` 出现在过程体中间（如第7行），不会被转换，残留到输出中。达梦不支持该语句，需手动注释或删除。实测462对象中2个存此问题（0.43%），属已知边界case\n\n- **⚠️ git push分支对齐**: 本地git可能在`master`分支提交，但GitHub仓库HEAD分支可能是`main`。push到`master`不更新GitHub默认展示的`main`分支，导致网页看不到最新代码。修正：`git remote show origin`确认HEAD分支 → `git checkout main && git merge master && git push origin main`\n\n- **⚠️ GitHub API上传大文件超时**: dm_converter.py(88KB+)通过GitHub Contents API上传时，base64后请求体巨大，curl经常超时(300s+)。**推荐方式**：直接`git add && git commit && git push`，比API逐文件PUT快得多且更可靠。之前memory记录\"api.github.com可达但github.com被墙\"已过时——2026-06-14实测git push可正常工作。仅在git push完全不通时才fallback到API上传\n\n- **⚠️ UTF-16编码SQL文件**: SSMS导出的SQL脚本常为UTF-16编码(带BOM)，拆分前必须先转UTF-8，否则内容被当成二进制乱码。转换命令: `python3 -c \"open('out.sql','w',encoding='utf-8').write(open('in.sql',encoding='utf-16').read())\"` 详见 [v3.0修复与UTF-16转换记录](references/dm-converter-v30-fixes.md)\n\n- **⚠️ DATE类型映射重复陷阱**: 当存储过程参数类型为`DATE`时（无方括号包裹），detokenize的类型映射可能在Step4已经替换过一次`DATE→DATE`（因为DATE在达梦也是合法类型名），但如果正则边界不够精确，会把`DATE`后面的换行/空白也吃进去，导致相邻关键字拼接，如`DATE\\nAS`变成`DATEDATE\\nAS`或`DATEDATEAS`。根因：类型映射正则的后缀锚点需用`\\b`或`(?=\\s|,|\\)|$)`精确截断，不能贪婪吃进换行符。**每次修改类型映射正则后，必须跑`test_dm_converter.py`验证**\n\n- **⚠️ dbo前缀正则陷阱**: detokenize后方括号变成双引号，正则必须同时匹配`\"dbo\".`和`dbo.`两种格式。三段式必须在两段式之前处理，否则`schema.dbo.object`中的`dbo.object`先被两段式误匹配。详见 [v2.4.5设计记录](references/dm-converter-v245-bracket-dbo-split.md)\n\n\n\n### 运行转换测试\n\n\n\n```bash\n\ncd ~/.openclaw/skills/sql-splitter/scripts\n\npython3 -m pytest test_dm_converter.py -v\n\n```\n\n\n\n### 发布到 clawhub.ai\n\n\n\n```bash\n\n# ⚠️ 必须用绝对路径，不能用相对路径`.`\n\nclawhub publish /Users/a1234/.hermes/skills/sql-splitter --slug sql-splitter --version X.Y.Z\n\n# 错误: clawhub publish .  → \"Error: SKILL.md required\" (即使SKILL.md明明存在)\n\n# 正确: clawhub publish /absolute/path/to/skill-dir\n\n# ⚠️ 版本号冲突：如果同名版本已发布，clawhub会报\"Version X.Y.Z already exists\"，必须升版本号(如3.2.2→3.2.3)重新发布，不能覆盖\n\n```\n\n\n\n**注意：clawhub上可能存在同slug不同owner的技能**（如`@fish1981bimmer/sql-splitter`和`@kingaiwork/sql-splitter`），`clawhub inspect`可能报`AMBIGUOUS_SKILL_SLUG`错误。此时用`clawhub install @fish1981bimmer/sql-splitter`指定owner。\n\n\n\n### 发布到 GitHub\n\n\n\n```bash\n\ncd /Users/a1234/.hermes/skills/sql-splitter\n\ngit add -A && git commit -m \"vX.Y.Z: 变更说明\"\n\n# ⚠️ 确认远程主分支名！git remote show origin 查看HEAD branch\n\n# 如果远程HEAD是main但本地在master上提交，push到master不会更新GitHub默认展示的main\n\n# 修正: git checkout main && git merge master && git push origin main\n\ngit push origin main\n\n```\n\n\n\n## 支持的 SQL 方言\n\n\n\n- MySQL\n\n- PostgreSQL\n\n- Oracle\n\n- SQL Server\n\n- 达梦 (DM)\n\n- 通用 (Generic)\n\n\n\n## v2.2.1 功能\n\n\n\n- **GUI 界面** - 提供图形化界面进行 SQL 文件拆分操作\n\n- **断点续传** - 支持记录处理进度，中断后可以继续处理\n\n- **批量并行处理** - 支持同时处理多个 SQL 文件，提升处理速度\n\n- **结果预览和对比** - 可视化查看拆分结果，支持与原始文件对比\n\n- **配置文件管理** - 保存和加载常用配置，支持导入导出\n\n- **详细错误处理** - 结构化错误信息，包含错误类型、上下文和修复建议\n\n- **Dry-run 预览模式** - 预览拆分结果而不实际创建文件\n\n- **安全修复** - pickle反序列化漏洞修复，检查点改用JSON序列化\n\n\n\n## 支持的 SQL 对象类型\n\n\n\n| 类型 | 前缀 | 说明 |\n\n|------|------|------|\n\n| 存储过程 | `proc_` | CREATE PROCEDURE |\n\n| 函数 | `func_` | CREATE FUNCTION |\n\n| 视图 | `view_` | CREATE VIEW |\n\n| 触发器 | `trig_` | CREATE TRIGGER |\n\n| 表结构 | `table_` | CREATE TABLE |\n\n| 包 | `pkg_` | CREATE PACKAGE |\n\n| 索引 | `idx_` | CREATE INDEX |\n\n| 唯一索引 | `uidx_` | CREATE UNIQUE INDEX |\n\n| 约束 | `con_` | ALTER TABLE ADD CONSTRAINT |\n\n| 序列 | `seq_` | CREATE SEQUENCE |\n\n| 同义词 | `syn_` | CREATE SYNONYM (Oracle) |\n\n| 事件 | `evt_` | CREATE EVENT (MySQL) |\n\n| 物化视图 | `mv_` | CREATE MATERIALIZED VIEW (PostgreSQL) |\n\n| 类型 | `type_` | CREATE TYPE |\n\n\n\n## v2.0 核心改进\n\n\n\n### 边界检测重写\n\n- 使用 **BEGIN...END 深度匹配**确定存储过程/函数/触发器边界\n\n- 支持 IF...THEN...END IF、CASE...END CASE、LOOP...END LOOP 嵌套\n\n- 不再依赖\"下一个 CREATE 位置\"做上界，**正确处理过程体内的嵌套 CREATE 语句**\n\n- Oracle/DM: 通过 `/` 终止符定位；SQL Server: 通过 `GO` 定位\n\n- PostgreSQL: 支持 `$$...$$` 包裹语法\n\n- 字符串和注释内的分号/关键字不会干扰边界检测\n\n\n\n### 依赖分析改进\n\n- 函数调用检测改为**限定上下文模式**（:= 赋值、WHERE/HAVING 子句等），大幅减少误报\n\n- SQL 关键字过滤表扩展到 150+ 个，涵盖内置函数、控制流、聚合等\n\n- 自引用自动排除\n\n- 循环依赖不再报错，按类型优先级追加\n\n\n\n### 合并脚本方言适配\n\n- Oracle/DM: `@@filename` + `SET DEFINE OFF`\n\n- SQL Server: `:r filename` + `GO`\n\n- PostgreSQL: `\\i filename` + `ON_ERROR_STOP`\n\n- MySQL: `source filename`\n\n- 通用: 注释方式\n\n\n\n### 架构优化\n\n- 提取 `common.py` 共享模块：SQLDialect 枚举、对象前缀、类型优先级、关键字表\n\n- `dependency_analyzer.py` 不再重复定义枚举，直接引用 common\n\n- 拆分后自动调用依赖分析，生成 `merge_all.sql`\n\n- 新增 37 个单元测试\n\n\n\n## 使用方法\n\n\n\n### GUI 模式（推荐）\n\n```bash\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/gui.py\n\n```\n\n\n\n### 单文件拆分\n\n```bash\n\n# 推荐: 用 v21 (CLI稳定, 支持所有拆分功能)\n\npython3 ~/.hermes/skills/sql-splitter/scripts/split_sql_v21.py <input.sql> [output_dir] --dialect sqlserver\n\n\n\n# v22 目前在无GUI环境会 ImportError (SQLSplitterGUI 依赖 tkinter)\n\n# 如需使用, 确保系统有 tkinter: apt install python3-tk / brew install python-tk\n\npython3 ~/.hermes/skills/sql-splitter/scripts/split_sql_v22.py <input.sql> [output_dir] 2>/dev/null || \\\n\n  python3 ~/.hermes/skills/sql-splitter/scripts/split_sql_v21.py <input.sql> [output_dir]\n\n```\n\n### 拆分后转达梦（两步法）\n\n\n\n```bash\n\n# v21 不支持 --convert-to 参数, 需分两步:\n\n# 1) 拆分\n\npython3 ~/.hermes/skills/sql-splitter/scripts/split_sql_v21.py input.sql output_dir --dialect sqlserver\n\n# 2) 批量转换（用 dm_converter 直接调用）\n\n# ⚠️ 注意: 不要用 python3 -c \"复杂多行脚本\"，安全扫描会拦截\n\n# 推荐写临时脚本文件再运行:\n\npython3 /tmp/batch_convert.py  # 脚本内容见 scripts/batch_convert.py\n\n```\n\n\n\n**批量转换脚本**：`scripts/batch_convert.py` — 用法: `python3 scripts/batch_convert.py [src_dir] [dm_dir] [schema_prefix]`\n\n- 自动按文件名前缀(proce→procedure, view→view, table→table等)识别对象类型\n\n- 遍历目录逐文件调用 `convert_sqlserver_to_dm()`\n\n- 默认参数: src_dir=HRBI_Stage_split, schema_prefix=HRBI_Stage\n\n\n\n### UTF-16 编码文件处理\n\n```bash\n\n# SQL Server 导出的 .sql 文件常为 UTF-16 编码, 需先转 UTF-8:\n\npython3 -c \"\n\nwith open('input.sql','r',encoding='utf-16') as f: content=f.read()\n\nwith open('input_utf8.sql','w',encoding='utf-8') as f: f.write(content)\n\nprint(f'Converted: {len(content.splitlines())} lines')\n\n\"\n\n# 然后用 input_utf8.sql 做拆分\n\n```\n\n\n\n### 批量拆分（目录）\n\n```bash\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --batch <目录路径> [输出目录]\n\n```\n\n\n\n### 批量拆分（多个文件）\n\n```bash\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --batch \"file1.sql,file2.sql,file3.sql\" [输出目录]\n\n```\n\n\n\n### 指定方言\n\n```bash\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --dialect oracle input.sql\n\n```\n\n\n\n支持的方言：`mysql`, `postgresql`, `oracle`, `sqlserver`, `dm`, `generic`\n\n\n\n### 不生成合并脚本\n\n```bash\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --no-merge input.sql\n\n```\n\n\n\n### 预览结果\n\n```bash\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --preview input.sql output_dir\n\n```\n\n\n\n### 检查点管理\n\n```bash\n\n# 列出所有检查点\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --checkpoint --list\n\n\n\n# 查看恢复进度\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --checkpoint --resume input.sql\n\n\n\n# 清理旧检查点\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --checkpoint --clear --days 7\n\n\n\n# 删除检查点\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --checkpoint --delete input.sql\n\n```\n\n\n\n### 配置管理\n\n```bash\n\n# 列出所有配置\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --config --list\n\n\n\n# 保存配置\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --config --save --name oracle --dialect oracle\n\n\n\n# 加载配置\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --config --load --name oracle\n\n\n\n# 导出配置\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --config --export --name oracle --export-path oracle_config.json\n\n\n\n# 导入配置\n\npython3 ~/.openclaw/skills/sql-splitter/scripts/split_sql_v22.py --config --import --import-path oracle_config.json --name oracle\n\n```\n\n\n\n### 参数说明\n\n\n\n| 参数 | 说明 |\n\n|------|------|\n\n| `input.sql` | 要拆分的 SQL 文件路径（单文件模式必需） |\n\n| `--batch` | 批量模式标志 |\n\n| `--dialect` | 指定 SQL 方言 |\n\n| `--no-merge` | 不生成依赖排序的合并脚本 |\n\n| `-q`, `--quiet` | 静默模式 |\n\n| `output_dir` | 输出目录（可选，默认：原文件名_split） |\n\n\n\n### 运行测试\n\n\n\n```bash\n\ncd ~/.openclaw/skills/sql-splitter/scripts\n\npython3 -m pytest test_dm_converter.py -v\n\n```\n\n\n\n### 端到端质量验证（大文件转换后）\n\n\n\n转换完成后，建议跑10项质量检查确认残留SQL Server语法：\n\n\n\n```bash\n\nDM_DIR=\"输出目录_dm\"\n\necho \"1. 残留方括号:        $(grep -rl '\\[.*\\]' $DM_DIR --include='*.sql' 2>/dev/null | wc -l)\"\n\necho \"2. 残留dbo.:          $(grep -rl '\\bdbo\\.' $DM_DIR --include='*.sql' 2>/dev/null | wc -l)\"\n\necho \"3. 残留@@变量:        $(grep -rl '@@[A-Z]' $DM_DIR --include='*.sql' 2>/dev/null | wc -l)\"\n\necho \"4. 残留SET NOCOUNT ON:$(grep -rl 'SET NOCOUNT ON' $DM_DIR --include='*.sql' 2>/dev/null | wc -l)\"\n\necho \"5. 残留GO终止符:      $(grep -rwl '^GO$' $DM_DIR --include='*.sql' 2>/dev/null | wc -l)\"\n\necho \"6. 残留GETDATE():     $(grep -rl 'GETDATE()' $DM_DIR --include='*.sql' 2>/dev/null | wc -l)\"\n\necho \"7. 残留ISNULL:        $(grep -rl '\\bISNULL(' $DM_DIR --include='*.sql' 2>/dev/null | wc -l)\"\n\necho \"8. 双重映射(INTEGERINT等): $(grep -rl 'INTEGERINT\\|VARCHARVARCHAR' $DM_DIR --include='*.sql' 2>/dev/null | wc -l)\"\n\necho \"9. 双点号残留:        $(grep -rl '\\.\\.' $DM_DIR --include='*.sql' 2>/dev/null | wc -l)\"\n\necho \"10.CREATE OR REPLACE数:$(grep -rl 'CREATE OR REPLACE' $DM_DIR --include='*.sql' 2>/dev/null | wc -l)\"\n\n```\n\n\n\n所有计数应为0（除了第10项和第4项可能有少量边界case残留需手动处理）。\n\n\n\n## 输出示例\n\n\n\n假设输入文件 `myapp.sql` 包含：\n\n- 表 `users`\n\n- 视图 `v_users`（依赖 users）\n\n- 存储过程 `sp_update`（依赖 users）\n\n\n\n输出：\n\n```\n\nmyapp_split/\n\n├── table_users.sql\n\n├── view_v_users.sql\n\n├── proc_sp_update.sql\n\n└── merge_all.sql          ← 按依赖排序的合并脚本\n\n```\n\n\n\n`merge_all.sql` 内容（以 Oracle 为例）：\n\n```sql\n\n-- [1/3] table: users\n\n@@table_users.sql\n\n\n\n-- [2/3] view: v_users  -- depends on: users\n\n@@view_v_users.sql\n\n\n\n-- [3/3] procedure: sp_update  -- depends on: users\n\n@@proc_sp_update.sql\n\n```\n\n\n\n## 文件结构\n\n\n\n```\n\nsql-splitter/\n\n├── SKILL.md ← 本文档\n\n├── V21_USAGE_GUIDE.md ← v2.1 使用指南\n\n├── SECURITY.md ← 安全文档\n\n├── requirements.txt ← 依赖\n\n├── references/\n\n│   ├── dm-converter-design.md ← 达梦转换器设计要点\n\n│   ├── dm-converter-v243-fixes.md ← v2.4.3 修复记录\n\n│   ├── dm-converter-v246-fixes.md ← v2.4.6 修复记录（捕获组偏移+suffix贪婪+procedure方括号）\n\n│   ├── dm-converter-v30-fixes.md ← v3.0 修复记录（含HRBI_Stage真实项目验证）\n\n│   ├── dm-converter-v322-fixes.md ← v3.2.2 修复记录（PROC VARCHAR CHAR + CAST nvarchar映射）\n\n│   ├── dm-converter-v323-fixes.md ← v3.2.3 修复记录（PROCEDURE用AS而非IS）\n\n│   ├── dm-converter-v323-fixes.md ← v3.2.3 修复记录（PROCEDURE用AS而非IS）\n\n│   ├── dm-converter-v330-fixes.md ← v3.3.0 修复记录（双重引号+IDENTITY位置+临时表正则+token碰撞+方括号处理）\n\n│   ├── dm-converter-v345-fixes.md ← v3.4.5 修复记录（dbo两段式+DATETIME2排序+方括号架构+schema_prefix传参）\n\n│   ├── dm-converter-v353-fixes.md ← v3.5.3 修复记录（方括号替换规则变更：非类型名加双引号）\n\n│   ├── dm-converter-v340-fixes.md ← v3.4.0 修复记录\n\n│   ├── dm-converter-v245-bracket-dbo-split.md ← v2.4.5 方括号+dbo设计记录\n\n│   ├── split-convert-workflow-20260627.md ← 拆分+转换完整工作流 + 用户反馈理解教训\n\n└── scripts/\n\n    ├── common.py ← 共享模块（枚举、常量、工具函数）\n\n    ├── split_sql.py ← v2.0 主拆分脚本\n\n    ├── split_sql_v21.py ← v2.1 主拆分脚本（带错误处理+转换集成+License限制检查）\n\n    ├── split_sql_v22.py ← v2.2 主拆分脚本（集成所有新功能+功能守卫）\n\n    ├── dm_converter.py ← 达梦数据库转换器 v3.4.5\n\n    ├── report_generator.py ← 转换质量报告生成器（兼容性评分+风险+HTML/MD/JSON）\n\n    ├── dependency_analyzer.py ← 依赖分析器\n\n    ├── error_handler.py ← 错误处理模块\n\n    ├── gui.py ← GUI 界面（tkinter，已完整实现）\n\n    ├── checkpoint.py ← 断点续传模块\n\n    ├── batch_processor.py ← 批量并行处理模块\n\n    ├── result_previewer.py ← 结果预览和对比模块\n\n    ├── batch_convert.py ← 批量达梦转换脚本(拆分后调用)\n\n    ├── config_manager.py ← 配置文件管理模块\n\n    ├── test_sql_splitter.py ← 拆分单元测试（37个）\n\n    ├── test_v21_features.py ← v2.1 功能测试\n\n    ├── test_dm_converter.py ← 达梦转换单元测试（53个）\n\n    ├── test_report_generator.py ← 报告生成器单元测试（7个）\n\n    └── test_v22_features.py ← v2.2 功能测试\n\n```\n\n\n\n## 达梦转换器已知问题（v2.4.3）\n\n\n\n> v2.4.3 修复了 9 个核心 BUG（DATEADD参数重排、SELECT INTO、IF/WHILE控制流、PRINT等），40个测试全部通过。详见 [v2.4.3修复记录](references/dm-converter-v243-fixes.md)\n\n\n\n**仍需手动调整的项目：**\n\n- STRING_AGG→LISTAGG 缺少 WITHIN GROUP 子句\n\n- STUFF→OVERLAY、REPLICATE→RPAD 语义不完全对等\n\n- 临时表 #temp → GTT/普通表\n\n- EXEC/EXECUTE 动态SQL → EXECUTE IMMEDIATE\n\n- RAISERROR → RAISE_APPLICATION_ERROR\n\n- TOP n → ROWNUM/FETCH FIRST\n\n- MERGE/游标/WITH(NOLOCK)/IF EXISTS 等差异\n\n\n\n**v2.4.4 已修复的映射：**\n\n- VARCHAR(n) → VARCHAR(n CHAR)：达梦VARCHAR默认BYTE语义，必须加CHAR才等效SQL Server的字符语义\n\n- UNIQUEIDENTIFIER → CHAR(36)：达梦用CHAR(36)而非VARCHAR(36)，UUID是定长\n\n\n\n## v2.4.1 新功能 — 拆分自动加 OR REPLACE\n\n\n\n- **视图和存储过程自动添加 OR REPLACE** — 拆分时对 procedure/function/view/trigger 四类对象，自动将 `CREATE` 转为 `CREATE OR REPLACE`\n\n - 达梦和 Oracle 环境下对象已存在时需要 `OR REPLACE`，否则会报错\n\n - 已有 `OR REPLACE` 的语句不会重复添加\n\n - 所有方言均生效（不仅限于 DM/Oracle）\n\n - 实现在 split_sql_v21.py 的 `obj_content` 提取后、写入文件前\n\n\n\n## 注意事项\n\n\n\n- 使用正则+深度匹配识别 SQL 对象边界，对极复杂嵌套语法可能有局限\n\n- 默认 UTF-8 编码，遇到编码问题自动 replace\n\n- 建议先备份原文件\n\n- 批量模式会自动创建以原文件名命名的子目录\n\n- 自动检测 SQL 方言，也可手动指定\n\n- 同名文件自动追加序号（如 `proc_sp_init_2.sql`）\n\n\n\n## 常见问题\n\n\n\n### 拆分结果不正确（多个对象混在一个文件中）\n\n\n\n**症状**：拆分后生成的文件包含多个 SQL 对象，而不是每个对象一个文件。\n\n\n\n**原因**：原始 SQL 文件中的对象缺少分号结束符。sql-splitter 依赖分号来确定对象的结束位置。\n\n\n\n**解决方案**：为每个 SQL 语句添加分号。例如：\n\n\n\n```sql\n\n-- 错误：缺少分号\n\nCreate table a(\n\n  Id int,\n\n  Name varchar(10)\n\n)\n\n\n\nCreate table b(\n\n  Id int,\n\n  Name varchar(10)\n\n)\n\n\n\n-- 正确：添加分号\n\nCreate table a(\n\n  Id int,\n\n  Name varchar(10)\n\n);\n\n\n\nCreate table b(\n\n  Id int,\n\n  Name varchar(10)\n\n);\n\n```\n\n\n\n**快速修复方法**：\n\n```bash\n\n# 使用 sed 为每个 CREATE 语句后的空行添加分号\n\nsed -i '' '/^Create /,/^)/s/)$/);/' input.sql\n\n```\n\n\n\n### 视图未被识别\n\n\n\n**症状**：拆分后没有生成视图文件，或视图被识别为其他对象类型。\n\n\n\n**原因**：视图语法不规范，缺少 `AS` 关键字。\n\n\n\n**解决方案**：修正视图语法，添加 `AS` 关键字。例如：\n\n\n\n```sql\n\n-- 错误：缺少 AS\n\ncreate view v_a\n\n(\n\nselect * from dual\n\n);\n\n\n\n-- 正确：添加 AS\n\nCREATE VIEW v_a AS\n\nSELECT * FROM dual;\n\n```\n\n\n\n### 存储过程/函数未被正确拆分\n\n\n\n**症状**：多个存储过程混在一个文件中，或产生重复文件。\n\n\n\n**原因**：存储过程语法不规范，缺少 `AS`/`BEGIN` 关键字或分隔符。\n\n\n\n**解决方案**：根据数据库类型修正语法：\n\n\n\n**SQL Server**：\n\n```sql\n\n-- 错误：缺少 AS 和 GO\n\ncreate proc p_a\n\n(\n\nselect * from dual\n\n);\n\ncreate proc p_b\n\n(\n\nselect * from dual\n\n);\n\n\n\n-- 正确：添加 AS 和 GO\n\nCREATE PROCEDURE p_a\n\nAS\n\nBEGIN\n\n    SELECT * FROM dual;\n\nEND\n\nGO\n\n\n\nCREATE PROCEDURE p_b\n\nAS\n\nBEGIN\n\n    SELECT * FROM dual;\n\nEND\n\nGO\n\n```\n\n\n\n**Oracle/达梦**：\n\n```sql\n\n-- 错误：缺少 IS/AS 和 /\n\nCREATE PROCEDURE p_a\n\nBEGIN\n\n    SELECT * FROM dual;\n\nEND\n\n\n\n-- 正确：添加 IS/AS 和 /\n\nCREATE OR REPLACE PROCEDURE p_a AS\n\nBEGIN\n\n    SELECT * FROM dual;\n\nEND;\n\n/\n\n```\n\n\n\n**MySQL**：\n\n```sql\n\n-- 错误：缺少 DELIMITER\n\nCREATE PROCEDURE p_a()\n\nBEGIN\n\n    SELECT * FROM dual;\n\nEND\n\n\n\n-- 正确：使用 DELIMITER\n\nDELIMITER //\n\nCREATE PROCEDURE p_a()\n\nBEGIN\n\n    SELECT * FROM dual;\n\nEND //\n\nDELIMITER ;\n\n```\n\n\n\n### 产生重复文件\n\n\n\n**症状**：拆分后生成多个内容相同或相似的文件（如 `proc_p_a.sql` 和 `proc_p_a_2.sql`）。\n\n\n\n**原因**：对象边界检测失败，通常由以下原因导致：\n\n- 对象之间缺少分隔符（分号、GO、/ 等）\n\n- 对象语法不规范（缺少 AS、BEGIN 等）\n\n- 嵌套对象语法错误\n\n\n\n**解决方案**：\n\n1. 检查并修正原始 SQL 文件的语法\n\n2. 确保每个对象之间有正确的分隔符\n\n3. 使用 `--dialect` 参数明确指定数据库类型\n\n4. 对于复杂情况，考虑手动拆分或使用数据库工具导出\n\n\n\n### 预检查清单\n\n\n\n在运行 sql-splitter 之前，建议检查以下内容：\n\n\n\n- [ ] 每个 SQL 语句都有分号结束符\n\n- [ ] 视图包含 `AS` 关键字\n\n- [ ] 存储过程/函数包含 `AS`/`BEGIN` 关键字\n\n- [ ] SQL Server 对象之间有 `GO` 分隔符\n\n- [ ] Oracle/达梦 对象末尾有 `/` 终止符\n\n- [ ] MySQL 存储过程使用 `DELIMITER`\n\n- [ ] 对象名称没有特殊字符或保留字冲突\n\n- [ ] 文件编码为 UTF-8\n\n\n\n## 文档维护规范\n\n\n\n- 功能描述按版本倒序排列：最新版本(v3.2.4)在最前，旧版本(v2.2.1等)在后\n\n- **更新日志必须严格按版本号降序** — 如v3.2.3→v3.2.2→v3.1.0→v3.0.0→v2.5.0→v2.4.5→...→v1.0.0。之前出现过v2.5.0排在v2.4.3后面、v2.4.5排在v3.0.0后面的混乱，clawhub.ai展示后用户看到旧版在前。**每次新增版本后，用`grep '^### v' SKILL.md`检查排序是否正确**\n\n- 避免重复章节：同一功能（如达梦转换）只在一个版本章节下详细描述，其他地方引用即可\n\n- 标题中的版本号必须与 clawhub 发布版本一致\n\n- 更新日志保留完整历史，但主体部分只展开最新版和次新版\n\n- **clawhub版本号冲突时**：发布后如果又改了内容，必须升版本号（如3.2.2→3.2.3）重新发布，clawhub不允许覆盖已发布版本\n\n\n\n## 更新日志\n\n\n\n### v3.6.0 (2026-07-04)\n- **移除 License/产品化相关代码** — 删除 keygen.py、license_verifier.py、private_key.pem、public_key.pem、issued_licenses.json，License 管理功能暂不实施\n- **移除产品化规划文档** — 删除 references/ 下的 sql-splitter-productization.md、sql-splitter-v350-productization.md、sql-splitter-commercialization.md\n- **清理 SKILL.md 重复条目** — 删除\"商业化产品化\"章节、\"License管理\"章节、\"发布前检查清单\"、重复的 dbo 规则/用户规则验证条目\n- **修复 clawhub 版本号冲突** — 发布后务必用 `clawhub inspect <slug>` 确认最新版本号的实际行为，发现问题升版本号重新发布\n### v3.5.0 (2026-06-21)\n\n- **report_generator.py** — 转换质量报告生成器（兼容性评分0-100 + 风险分级 + HTML/MD/JSON输出 + 7个测试）\n\n- **gui.py重写** — 完整tkinter GUI（文件选择→拆分→转换→质量报告）\n\n- **pip打包** — pyproject.toml + src/sql_splitter/ + `sql-splitter` CLI入口\n\n\n\n### v3.4.5 (2026-06-26)\n\n- **dbo两段式无prefix时去掉dbo** — `dbo.xxx`无schema_prefix时直接去掉dbo变成`xxx`，不再保留\n\n- **DATETIME2→TIMESTAMP排序BUG** — 正则alternation按长度降序排列，避免DATETIME抢先匹配DATETIME2\n\n- **方括号替换移到Step4** — 从Step 6.7(token还原后)移到Step 4(token化后)，注释/字符串已被保护不会误改\n\n- **schema_prefix参数贯穿调用链** — split_sql_file→_convert_split_output→convert全链传参\n\n- **新增CLI --schema-prefix和--dm参数**\n\n- **312个存储过程转换验证通过**(HRBI_Stage=54, HRBI_DW=157, HRBI_DM=101)\n\n- 详见 [v3.4.5修复记录](references/dm-converter-v345-fixes.md)（含dbo演进史）\n\n\n\n### v3.4.0 (2026-06-20)\n\n- **TRUNCATE TABLE → DELETE FROM** — 达梦不支持TRUNCATE在存储过程内，自动将`TRUNCATE TABLE xxx`转换为`DELETE FROM xxx`\n\n- **TABLE/VIEW结尾加分号** — TABLE/VIEW/INDEX/CONSTRAINT/SEQUENCE转换后结尾自动加`;`\n\n- **所有方言拆分后均加分号** — 之前Oracle/DM不加分号，现在统一加\n\n- **53个单元测试全部通过**（新增9个：4个TRUNCATE转换+5个结尾分号）\n\n\n\n### v3.3.0 (2026-06-19)\n\n- **PROCEDURE双重引号bug修复** — 三个顺序正则导致已替换结果被再次匹配，引号叠加成`\"\"sp_test\"\"`。合并为单一正则+分支回调\n\n- **IDENTITY位置修正** — `IDENTITY(\"id\",1,1)` 从`)`前移到`)`后，符合达梦语法。同时兼容单行和多行表定义\n\n- **#临时表正则修复** — 列定义中的`)`截断匹配，改用贪婪匹配。`##`全局临时表不再被替换为`#tmp_`\n\n- **token_map碰撞修复** — Step3重新tokenize占位符key覆盖原key，字符串`'N/A'`变成标识符`\"v_users\"`。新增`start_counter`参数避免碰撞\n\n\n\n### v3.2.4 (2026-06-14)\n\n- **更新日志排序修正** — 所有版本严格按版本号降序排列\n\n- **旧版日志精简** — 去掉重复子项展开，保持简洁\n\n\n\n### v3.2.3 (2026-06-14)\n\n- **存储过程PROCEDURE用AS而非IS** — 达梦存储过程声明用`AS`，函数用`IS`，之前PROCEDURE也用了`IS`是错误\n\n\n\n### v3.2.2 (2026-06-14)\n\n- **存储过程VARCHAR(n)加CHAR语义** — DECLARE变量和参数中的`VARCHAR(n)` → `VARCHAR(n CHAR)`，与TABLE转换一致\n\n- **CAST中nvarchar→VARCHAR(n CHAR)** — `cast(x as nvarchar(50))` → `CAST(x AS VARCHAR(50 CHAR))`，之前nvarchar未映射\n\n- **_post_convert_generic_types增强** — 新增裸类型名映射(via `_bare_type_pattern`)，之前只映射方括号包裹的类型\n\n- **44个单元测试全部通过**(含4个新增PROCEDURE类型映射测试)\n\n- **462个真实SQL对象端到端测试通过**(HRBI_Stage.sql, 7万行)\n\n\n\n### v3.2.1 (2026-06-14)\n\n- **SET NOCOUNT ON/OFF直接删除** — 之前注释保留，用户要求直接去掉(达梦不需要)\n\n- **SET NOCOUNT ON;带分号不匹配** — 正则加`\\s*;?`兼容行末分号，之前只匹配无分号的`SET NOCOUNT ON`\n\n- **批量转换脚本** — 新增`scripts/batch_convert.py`，写脚本文件而非`python3 -c`内联(安全扫描会拦截后者)\n\n\n\n### v3.1.0 (2026-06-13)\n\n- **PROCEDURE参数加括号** - `CREATE PROC name @p1 INT AS` → `CREATE OR REPLACE PROCEDURE name (p1 INT) AS`\n\n- **OR REPLACE兼容** - 正则匹配 `CREATE OR REPLACE PROC`（拆分阶段已加 OR REPLACE 的情况）\n\n- **AS保留** - 存储过程的`AS`关键字保留为`AS`（达梦PROCEDURE用AS，函数用IS）\n\n- **GO;兼容** - `GO;` 也被替换为 `/`（之前只匹配纯 `GO` 行）\n\n- **DATE不再双重映射** - `_post_convert_generic_types` 中对 DATE 类型直接返回 DATE\n\n- **VARCHAR(max)/NVARCHAR(max) → VARCHAR(4096 CHAR)** - 之前映射为TEXT，改为VARCHAR(4096 CHAR)\n\n- **VARCHAR2类型映射** - `varchar2 → VARCHAR2`，`VARCHAR2(max) → VARCHAR2(4096 CHAR)`\n\n\n\n### v3.0.0 (2026-06-13)\n\n- **修复3个dm_converter核心BUG**:\n\n  - 捕获组偏移: `_convert_data_types`正则TYPE_PATTERN用了捕获组，导致group偏移，类型双重映射(`INT`→`INTEGERINT`)。改非捕获组`(?:...)`\n\n  - suffix贪婪匹配: `[^]]*`应为`[^)]*`，导致VARCHAR(100)后所有列定义被吞进suffix，后续类型映射失效\n\n  - procedure方括号不替换: 新增`_post_convert_generic_types`方法，所有对象类型都做方括号→双引号+类型映射+dbo替换\n\n- **方括号替换对所有对象类型生效** - PROCEDURE/FUNCTION/TRIGGER 也做 `[xxx]` → `\"xxx\"` + dbo替换\n\n- **双点号`..`替换** - SQL Server的`database..object`（省略dbo schema）→达梦`database.object`\n\n- **UTF-16编码支持** - SSMS导出脚本转UTF-8后拆分\n\n- **40个单元测试全部通过**\n\n- **462个真实SQL对象端到端测试通过**(HRBI_Stage.sql, 7万行)\n\n- 详见 [v3.0修复记录](references/dm-converter-v30-fixes.md) | [v2.5.1修复记录](references/dm-converter-v251-fixes.md)\n\n\n\n### v2.5.0 (2026-05-31)\n\n- **变量命名规范** - DECLARE局部变量自动加v_前缀, 参数保持原名, 符合达梦开发规范\n\n- **多变量DECLARE** - `DECLARE @v1 INT, @v2 VARCHAR(100)` 正确拆分为多行独立声明\n\n- **类型映射修正** - bit->BOOLEAN, tinyint->SMALLINT (达梦无TINYINT)\n\n- **dbo前缀替换扩展** - 存储过程/函数中的dbo.也被替换为schema前缀\n\n- **存储过程参数格式化** - 参数换行缩进, 加括号, DECIMAL(18,2)等括号内逗号不被误拆\n\n- **类型映射修复** - [datetime] DEFAULT等DEFAULT后缀场景也能正确映射\n\n- **SELECT INTO变量名** - 与DECLARE声明保持一致, 自动加v_前缀\n\n\n\n### v2.4.5 (2026-06-08)\n\n- **方括号→双引号** - `[schema].[table]` → `\"schema\".\"table\"`，类型名`[nvarchar]` → `nvarchar`（去掉方括号并做类型映射）\n\n- **dbo前缀智能处理** - 三段式`[HRBI].[dbo].[Users]` → `\"HRBI\".\"Users\"`(删dbo)；两段式`[dbo].[Users]` → `hrbi_stage.\"Users\"`(用文件名替换)\n\n- **精确拆分增强** - 无`;`/`GO`终止符时，用下一个`CREATE`关键字作为对象边界兜底\n\n- **VARCHAR CHAR语义后处理** - 修复detokenize类型映射绕过CHAR语义的问题\n\n- **schema_prefix自动传递** - 从源文件名自动提取前缀传给dm_converter\n\n- **40个测试全部通过**\n\n\n\n### v2.4.4 (2026-06-07)\n\n- **数据类型映射调整** - 按达梦最佳实践修正\n\n  - VARCHAR(n) → VARCHAR(n CHAR)：达梦VARCHAR默认BYTE语义，必须加CHAR才等效SQL Server的字符语义\n\n  - UNIQUEIDENTIFIER → CHAR(36)：达梦用CHAR(36)而非VARCHAR(36)，UUID是定长\n\n- **40个测试全部通过**\n\n\n\n### v2.4.3 (2026-06-06)\n\n- **达梦转换器BUG修复** - 9个失败测试全部修复，40/40通过\n\n  - BIT→BOOLEAN, TINYINT→SMALLINT 类型映射修正\n\n  - NVARCHAR(n) → VARCHAR(n CHAR) 达梦字符语义转换\n\n  - SET NOCOUNT ON注释格式修正\n\n  - DATEADD专用转换方法（参数重排：DATEADD(day,n,date) → date + INTERVAL 'n' DAY）\n\n  - SELECT赋值区分有无FROM（有FROM→SELECT INTO，无FROM→:=）\n\n  - IF...BEGIN...END → IF...THEN...END IF 控制流转换\n\n  - WHILE...BEGIN...END → WHILE...LOOP...END LOOP 控制流转换\n\n  - PRINT → DBMS_OUTPUT.PUT_LINE 转换（+号连接改||）\n\n- 详见 [v2.4.3修复记录](references/dm-converter-v243-fixes.md) | [缩进调试技巧](references/python-indentation-debugging.md) | [7万行实战](references/hrbi-stage-real-world-test.md)\n\n\n\n### v2.4.1 (2026-05-30)\n\n- **拆分自动加 OR REPLACE** - 对 procedure/function/view/trigger 四类对象，自动将 CREATE 转为 CREATE OR REPLACE\n\n  - 已有 OR REPLACE 的语句不重复添加\n\n  - 所有方言均生效\n\n\n\n### v2.4.0 (2026-05-23)\n\n- **重写达梦数据库转换器** - 完全重写 dm_converter.py\n\n  - token化保护: 字符串/注释替换为占位符后再做正则替换\n\n  - 按对象类型独立转换: procedure/function/view/trigger/table/index/constraint\n\n  - 40+种数据类型映射, 30+种函数映射\n\n  - 变量语法转换: @var -> var, DECLARE @var -> var, SET @var= -> var:=\n\n  - TRY-CATCH -> EXCEPTION WHEN OTHERS THEN\n\n  - 全局变量转换: @@ROWCOUNT -> SQL%ROWCOUNT\n\n  - 触发器伪表: inserted/deleted -> NEW/OLD\n\n  - 转换结果输出到子目录: output_split_dm/\n\n- **拆分后转换集成** - split_sql_v21.py 新增 convert_to 参数\n\n  - 拆分完成后自动调用转换器，按对象类型独立转换\n\n  - 生成达梦版合并脚本 merge_all.sql\n\n- **29个转换单元测试** - test_dm_converter.py 全部通过\n\n- 详见 [v2.4.0修复记录](references/dm-converter-v240-fixes.md)\n\n\n\n### v2.2.1 (2026-05-01)\n\n- **安全修复** - 修复 pickle 反序列化漏洞，替换为 JSON + 数据验证\n\n- **新增安全文档** - 添加 SECURITY.md\n\n- **新增依赖管理** - 添加 requirements.txt\n\n\n\n### v2.2.0 (2026-04-27)\n\n- **新增 GUI 界面** - 提供图形化界面进行 SQL 文件拆分操作\n\n- **新增断点续传功能** - 支持记录处理进度，中断后可以继续处理\n\n- **新增批量并行处理** - 支持同时处理多个 SQL 文件，提升处理速度\n\n- **新增结果预览和对比** - 可视化查看拆分结果，支持与原始文件对比\n\n- **新增配置文件管理** - 保存和加载常用配置，支持导入导出\n\n\n\n### v1.1.0 (2026-04-13)\n\n- 新增索引支持：CREATE INDEX, CREATE UNIQUE INDEX\n\n- 新增约束支持：ALTER TABLE ADD CONSTRAINT\n\n- 所有 6 种方言均支持索引/约束识别\n\n\n\n### v1.0.0\n\n- 初始版本\n\nFile v3.6.2:_meta.json\n\n{\n  \"ownerId\": \"kn76tyr0ztr0j5fyej0qbchjx584wwjp\",\n  \"slug\": \"sql-splitter\",\n  \"version\": \"3.6.2\",\n  \"publishedAt\": 1783129030408\n}\n\nFile v3.6.2:references/dm-converter-design.md\n\n# SQL Server -> 达梦数据库转换器设计要点\n\n## 架构\n\n```\nconvert() 流程:\n Step1: _tokenize() - 字符串/注释 -> 占位符\n Step2: 按对象类型转换 (procedure/function/view/trigger/table/index/constraint)\n Step3: _tokenize() 重新tokenize + 合并token_map\n Step4: 通用转换 (data_types/functions/global_vars/statements/try_catch/transaction)\n Step5: _detokenize() 用合并后的map还原所有占位符\n Step6: _tokenize_strings_only() + _convert_variable_syntax() + detokenize\n Step7: _convert_goto_label()\n Step8: _add_terminator() (仅procedure/function/trigger)\n```\n\n## 已修复的坑（v2.4.0及之前）\n\n### 1. token_map 合并\nStep2的_convert_procedure等返回token化文本(含__TOKEN_0__)，Step3重新_tokenize产生新map。\n如果不合并，Step4 _detokenize用新map找不到__TOKEN_0__的映射。\n\n修复:\n```python\noriginal_token_map = dict(token_map) # Step2前保存\ntokens, token_map = self._tokenize(result) # Step3\nmerged_map = dict(original_token_map)\nmerged_map.update(token_map) # 合并\n# Step4用merged_map\nresult = self._detokenize(result, merged_map)\n```\n\n### 2. content = new_content 不可省略\n_convert_variable_syntax中全局@替换后忘记`content = new_content`，\n导致return content返回的是替换前的文本。\n\n### 3. 嵌套括号匹配\n`VARCHAR(100)`中的`)`会截断`[^)]*`，导致参数列表匹配失败。\n\n错误: `r'\\(([^)]*)\\)'` -> 在`VARCHAR(100)`的`)`处截断\n正确: `r'(\\([^)]*(?:\\([^)]*\\)[^)]*)*\\))'` -> 匹配嵌套括号\n\n### 4. DECLARE上下文的数据类型\n数据类型转换正则的前缀只匹配`,\\(\\s*|\\n\\s*`时，\n`DECLARE @v_date DATETIME`中的DATETIME不会被匹配。\n\n修复: 前缀加上`DECLARE\\s+`\n\n### 5. INSERT INTO误匹配\n`\\n INSERT INT` 被匹配为: 前缀`\\n` + 列名`INSERT` + 类型`INT`\n导致INSERT变成INTEGERO\n\n修复方向: 数据类型替换前检查列名是否为SQL关键字(SELECT/INSERT/UPDATE/DELETE等)\n\n### 6. 变量@转换时机\n在token保护下做@替换时，__TOKEN_0__等占位符名不含@，安全。\n但更安全的做法: 先还原所有token，再用_tokenize_strings_only只保护字符串，\n然后做@替换。\n\n### 7. 终止符 /\nGO在Step2已被转成/，_add_terminator需要先去掉已有的/再添加新的。\n```python\nif content.endswith('/'):\n    content = content[:-1].rstrip()\n```\n\n## 代码审查发现的未修BUG（2026-05-27审查）\n\n用户反馈\"达梦转换不好用\"，代码审查发现以下严重问题：\n\n### BUG-1: 函数映射转换结果语法错误（严重）\n\n**CONVERT函数** (line 118):\n```python\n(r'\\bCONVERT\\s*\\(\\s*(\\w+)\\s*,', r'CAST(', True)\n```\n- 只替换了`CONVERT(type,`为`CAST(`，丢掉了type参数\n- SQL Server: `CONVERT(VARCHAR(50), @val)` → 达梦: `CAST(@val AS VARCHAR(50))`\n- 参数顺序和格式都不对，需要回调函数重排参数\n\n**DATEADD函数** (line 127-128):\n```python\n(r'\\bDATEADD\\s*\\(\\s*day\\s*,', 'CURRENT_TIMESTAMP + ', True)\n(r'\\bDATEADD\\s*\\(\\s*month\\s*,', 'ADD_MONTHS(CURRENT_TIMESTAMP,', True)\n```\n- 丢了date参数，直接硬编码CURRENT_TIMESTAMP\n- SQL Server: `DATEADD(day, 7, @order_date)` → 达梦: `@order_date + 7`\n- SQL Server: `DATEADD(month, 3, @start_date)` → 达梦: `ADD_MONTHS(@start_date, 3)`\n- 需要回调函数提取3个参数并重排\n\n**DATEDIFF函数** (line 129):\n```python\n(r'\\bDATEDIFF\\s*\\(\\s*day\\s*,', '(', True)\n```\n- 直接替换成`(`，语法彻底崩了\n- SQL Server: `DATEDIFF(day, @start, @end)` → 达梦: `@end - @start`（日期差）\n- 需要回调函数提取参数\n\n**STRING_AGG函数** (line 115):\n```python\n(r'\\bSTRING_AGG\\s*\\(', 'LISTAGG(', True)\n```\n- 达梦LISTAGG需要`WITHIN GROUP (ORDER BY ...)`语法，光改名不够\n- SQL Server: `STRING_AGG(name, ',')` → 达梦: `LISTAGG(name, ',') WITHIN GROUP (ORDER BY name)`\n\n**STUFF函数** (line 112):\n```python\n(r'\\bSTUFF\\s*\\(', 'OVERLAY(', True)\n```\n- 达梦不支持OVERLAY函数，参数语义也不同\n- STUFF(str, start, len, replace) vs OVERLAY不同签名\n\n**REPLICATE函数** (line 113):\n```python\n(r'\\bREPLICATE\\s*\\(', 'RPAD(', True)\n```\n- RPAD是右侧填充，REPLICATE是重复字符串，语义不同\n- `REPLICATE('0', 5)` = '00000' vs `RPAD('0', 5)` = '0    '\n\n### BUG-2: 变量@转换过于粗暴（严重）\n\nline 671:\n```python\nnew_content = re.sub(r'@([\\w]+)', r'\\1', content)\n```\n- 全局替换所有`@word`为`word`，会误改email地址（如`user@example.com`→`userexample.com`）\n- 需要增加上下文判断：只替换SQL变量上下文中的@，不替换字符串内的@\n\n**多变量SELECT赋值** (line 659-663):\n```python\nnew_content = re.sub(r'\\bSELECT\\s+@([\\w]+)\\s*=', r'\\1 :=', content, flags=re.IGNORECASE)\n```\n- `SELECT @a=1, @b=2` 只处理了@a，@b被遗留\n- 需要处理逗号分隔的多变量赋值\n\n**DECLARE变量类型残留** (line 637-642):\n```python\nnew_content = re.sub(r'\\bDECLARE\\s+@([\\w]+)', r'\\1', content, flags=re.IGNORECASE)\n```\n- 只删了DECLARE和@前缀，但达梦过程体内变量声明格式完全不同\n- SQL Server: `DECLARE @v_count INT` → 达梦: `v_count INTEGER;`（不需要DECLARE，但需要保留类型+分号）\n- 当前输出: `v_count INT`，缺少分号且类型未映射\n\n### BUG-3: TRY-CATCH正则匹配不可靠（中等）\n\nline 590-597:\n```python\npattern = (\n    r'BEGIN\\s+TRY\\s*(.*?)\\s*END\\s+TRY\\s*'\n    r'BEGIN\\s+CATCH\\s*(.*?)\\s*END\\s+CATCH'\n)\n```\n- `.*?`非贪婪匹配在过程体内有多个BEGIN...END时会匹配到错误位置\n- 如果TRY块内有`BEGIN...END`嵌套，`END TRY`可能匹配到错误的END\n- 需要用括号深度匹配替代纯正则\n\n### BUG-4: GOTO/LABEL误判时间格式（中等）\n\nline 682-687:\n```python\nnew_content = re.sub(r'^(\\s*)([\\w]+)\\s*:\\s*$', r'\\1<<\\2>>', content, flags=re.MULTILINE)\n```\n- `12:30:00`中的`30:`或`00:`在独立行上会被误判为标签\n- 需要排除数字开头的\"标签\"和已知非标签模式（时间、CASE WHEN等）\n\n### BUG-5: 缺失关键转换（严重）\n\n以下SQL Server常用语法完全没有转换支持：\n\n| SQL Server语法 | 达梦语法 | 当前处理 |\n|---------------|---------|---------|\n| `#temp_table` / `##global_temp` | 全局临时表GTT或普通表 | 无 |\n| `EXEC sp_executesql @sql` | `EXECUTE IMMEDIATE sql_str` | 无 |\n| `EXEC(@sql)` | `EXECUTE IMMEDIATE sql_str` | 无 |\n| `PRINT 'msg'` | `DBMS_OUTPUT.PUT_LINE('msg')` | 无 |\n| `RAISERROR('msg', 16, 1)` | `RAISE_APPLICATION_ERROR(-20001, 'msg')` | 无 |\n| `TOP n` | `ROWNUM <= n` 或 `FETCH FIRST n ROWS ONLY` | 无 |\n| `[column_name]` | `\"column_name\"` | 仅token化保护，未转换引号 |\n| `MERGE INTO ...` | 语法差异大 | 无 |\n| `CURSOR` 游标语法 | 参数/打开/关闭语法差异 | 无 |\n| `WITH (NOLOCK)` | 去掉或改写 | 无 |\n| `ROW_NUMBER() OVER(...)` | 达梦支持，但分区函数有差异 | 部分支持 |\n| `OBJECT_ID('name')` | 达梦用USER_OBJECTS视图 | 无 |\n| `IF EXISTS(SELECT ...)` | `SELECT COUNT(*) INTO v_cnt ... IF v_cnt > 0` | 无 |\n| `WHILE @i <= 10` | `WHILE i <= 10 LOOP ... END LOOP;` | 无 |\n\n## 改进路线图\n\n### 紧急（语法错误导致转换结果不可用）\n1. CONVERT/DATEADD/DATEDIFF — 用回调函数做上下文感知替换，正确重排参数\n2. 变量@转换 — 增加上下文判断，避免误改非变量@\n3. 补全EXEC动态SQL、临时表、PRINT、RAISERROR、TOP转换\n\n### 重要（转换结果可用但需手动调整）\n4. TRY-CATCH — 改用括号深度匹配\n5. GOTO/LABEL — 增加排除规则\n6. 多变量SELECT赋值\n7. DECLARE变量声明格式适配达梦\n\n### 增强（减少手动调整量）\n8. STRING_AGG → LISTAGG + WITHIN GROUP\n9. STUFF/REPLICATE → 达梦等效写法\n10. MERGE语句转换\n11. 游标语法转换\n12. WITH(NOLOCK)处理\n\n## 映射表\n\n### 数据类型 (40+种)\n| SQL Server | 达梦 |\n|-----------|------|\n| INT | INTEGER |\n| BIT | TINYINT |\n| DATETIME/DATETIME2 | TIMESTAMP |\n| MONEY | DECIMAL(19,4) |\n| NVARCHAR(n) | VARCHAR(n) |\n| NTEXT | TEXT |\n| IMAGE | BLOB |\n| UNIQUEIDENTIFIER | VARCHAR(36) |\n| XML | TEXT |\n\n### 函数 (30+种)\n| SQL Server | 达梦 | 当前状态 |\n|-----------|------|---------|\n| GETDATE() | CURRENT_TIMESTAMP | OK |\n| ISNULL(a,b) | NVL(a,b) | OK |\n| LEN(s) | LENGTH(s) | OK |\n| SUBSTRING(s,n,l) | SUBSTR(s,n,l) | OK |\n| CHARINDEX(s,t) | INSTR(s,t) | OK |\n| CONVERT(type,val) | CAST(val AS type) | BUG:参数未重排 |\n| DATEADD(day,n,date) | date + n | BUG:丢参数 |\n| DATEDIFF(day,s,e) | e - s | BUG:替换成( |\n| YEAR(d) | EXTRACT(YEAR FROM d) | OK |\n| NEWID() | SYS_GUID() | OK |\n| STRING_AGG(col,sep) | LISTAGG(col,sep) WITHIN GROUP(...) | 缺WITHIN GROUP |\n| STUFF(str,s,l,r) | 需自定义函数 | BUG:OVERLAY不对 |\n| REPLICATE(s,n) | 需自定义或LPAD | BUG:RPAD语义不同 |\n\n### 语句级\n| SQL Server | 达梦 | 当前状态 |\n|-----------|------|---------|\n| SET NOCOUNT ON | -- 注释掉 | OK |\n| COMMIT TRANSACTION | COMMIT | OK |\n| ROLLBACK TRANSACTION | ROLLBACK | OK |\n| BEGIN TRY...CATCH | EXCEPTION WHEN OTHERS | 有嵌套匹配风险 |\n| EXEC @sql | EXECUTE IMMEDIATE | 缺失 |\n| PRINT 'msg' | DBMS_OUTPUT.PUT_LINE | 缺失 |\n| RAISERROR(...) | RAISE_APPLICATION_ERROR(...) | 缺失 |\n| TOP n | ROWNUM/FETCH FIRST | 缺失 |\n\nFile v3.6.2:references/dm-converter-v243-fixes.md\n\n# dm_converter v2.4.3 修复记录\n\n## 修复的9个失败测试\n\n从9个失败 → 40/40全部通过。\n\n### 修复1: BIT→BOOLEAN, TINYINT→SMALLINT\n- **问题**: type_mappings中 `'bit': 'TINYINT'`, `'tinyint': 'TINYINT'`，但达梦没有TINYINT\n- **修复**: `'bit': 'BOOLEAN'`, `'tinyint': 'SMALLINT'`\n- **测试**: test_data_types\n\n### 修复2: NVARCHAR(n) → VARCHAR(n CHAR)\n- **问题**: NVARCHAR(100)只转成VARCHAR(100)，缺少达梦字符语义CHAR标记\n- **根因**: NVARCHAR处理的代码被patch工具放到了`else:`分支内部（即只在suffix为空时执行），导致有suffix时永远无法命中\n- **修复**: 将NVARCHAR检查代码从`else:`块内移到`if suffix:` / `else:` 块**之后**（与if/else同级）\n- **关键坑**: patch工具插入代码时，缩进层级容易搞错。**必须用`python3 -c`检查实际缩进**：\n  ```python\n  with open('dm_converter.py') as f:\n      for i, line in enumerate(f, 1):\n          if i >= 511 and i <= 525:\n              spaces = len(line) - len(line.lstrip())\n              print(f\"{i}: indent={spaces} |{line.rstrip()[:70]}|\")\n  ```\n- **测试**: test_data_types\n\n### 修复3: SET NOCOUNT ON注释格式\n- **问题**: 注释`'-- SET NOCOUNT ON (达梦不需要)'`中包含原文`SET NOCOUNT ON`，测试用assertNotIn检测到\n- **修复**: 改为`'-- NOCOUNT (达梦不需要)'`，去掉注释中的原文关键字\n- **测试**: test_complex_procedure, test_set_nocount_on\n\n### 修复4: DATEADD专用转换\n- **问题**: function_mappings中旧的DATEADD条目只做简单前缀替换（如`DATEADD(day,` → `CURRENT_TIMESTAMP +`），不能正确处理3个参数的重排\n- **修复**: \n  - 从function_mappings删除旧的DATEADD条目\n  - 添加`_parse_function_args()`方法解析嵌套括号的参数列表\n  - 添加`_convert_dateadd()`方法，按unit类型重排参数：\n    - `DATEADD(day, n, date)` → `date + INTERVAL 'n' DAY`\n    - `DATEADD(month, n, date)` → `ADD_MONTHS(date, n)`\n    - 其他单位类似\n  - 在`_convert_functions()`末尾调用\n- **测试**: test_dateadd_conversion\n\n### 修复5: SELECT赋值区分有无FROM\n- **问题**: 所有`SELECT @var = expr`都转成`var := expr`，但有FROM子句时达梦需要`SELECT expr INTO var FROM table`\n- **修复**: \n  - 添加`_convert_select_assign()`方法\n  - 解析赋值列表和FROM子句位置\n  - 有FROM: `SELECT @var=expr FROM t` → `SELECT expr INTO var FROM t`\n  - 无FROM: `SELECT @var=expr` → `var := expr`\n  - 支持多变量赋值: `SELECT @a=col1, @b=col2 FROM t` → `SELECT col1, col2 INTO a, b FROM t`\n- **测试**: test_select_into_with_from, test_select_multi_assign, test_select_assign_no_from\n\n### 修复6: IF→THEN/END IF 控制流\n- **问题**: `IF @x > 0 BEGIN ... END`没有转换为达梦语法\n- **修复**: 添加`_convert_if_else()`方法\n  - `IF condition` → `IF condition THEN`\n  - BEGIN/END配对：IF后的BEGIN跳过，对应的END改为`END IF;`\n  - ELSE保留\n- **测试**: test_if_else, test_if_with_try_catch, test_nested_if\n\n### 修复7: WHILE→LOOP/END LOOP\n- **问题**: `WHILE @x > 0 BEGIN ... END`没有转换\n- **修复**: 添加`_convert_while_loop()`方法\n  - `WHILE condition` → `WHILE condition LOOP`\n  - WHILE后的BEGIN跳过，对应的END改为`END LOOP;`\n- **测试**: test_while_loop\n\n### 修复8: PRINT→DBMS_OUTPUT.PUT_LINE\n- **问题**: `PRINT 'text'`没有转换为达梦语法\n- **修复**: 添加`_convert_print()`和`_print_replacer()`方法\n  - `PRINT expr` → `DBMS_OUTPUT.PUT_LINE(expr);`\n  - SQL Server的`+`字符串连接改为达梦的`||`\n  - `CAST(...AS VARCHAR)` → `CAST(...AS VARCHAR(4000))`（达梦VARCHAR需要长度）\n- **测试**: test_complex_procedure\n\n### 修复9: 流程步骤位置\n- 在convert()方法中，Step 5.5后添加控制流和PRINT调用：\n  ```python\n  # Step 5.5: 控制流转换 (IF/WHILE)\n  result = self._convert_if_else(result)\n  result = self._convert_while_loop(result)\n  \n  # Step 5.6: PRINT → DBMS_OUTPUT.PUT_LINE\n  result = self._convert_print(result)\n  ```\n  位于Step 5（变量语法转换）之后、Step 6（GOTO/LABEL）之前\n\n## 开发过程中的关键教训\n\n### ⚠️ write_file工具会添加行号前缀\n**问题**: 使用Hermes的`write_file`工具写入Python文件时，内容会被加上` NNN|`格式的行号前缀，导致Python文件损坏（语法错误）。\n**修复方法**: 用`terminal`的python脚本或`patch`工具来修改Python文件，**不要用write_file写入代码文件**。如果已经损坏，用以下脚本修复：\n```python\nimport re\nwith open('file.py', 'r') as f:\n    raw = f.read()\nlines = raw.split('\\n')\ncleaned = []\nfor line in lines:\n    m = re.match(r'^\\s*\\d+\\|(.*)$', line)\n    if m:\n        cleaned.append(m.group(1))\n    else:\n        cleaned.append(line)\nwith open('file.py', 'w') as f:\n    f.write('\\n'.join(cleaned))\n```\n\n### ⚠️ patch工具缩进陷阱\npatch工具的缩进处理不可靠，尤其是：\n1. 多行docstring后的代码体缩进容易错位\n2. `else:`块内的代码容易与块外代码混淆\n3. **每次patch后必须验证缩进**：用lint检查或`python3 -c \"import module\"`测试导入\n4. 如果lint报`IndentationError`，用`python3 -c`检查精确缩进层级\n\n### ⚠️ git恢复策略\n当write_file损坏文件时：\n1. `git diff HEAD -- file.py | wc -l` 查看差异量\n2. `wc -l file.py && git show HEAD:file.py | wc -l` 对比行数\n3. 如果差异巨大且行数骤降，说明文件被截断：`git checkout HEAD -- file.py` 恢复\n4. 恢复后重新逐步应用修改\n\nFile v3.6.2:references/dm-converter-v245-bracket-dbo-split.md\n\n# dm_converter v2.4.5 设计记录 — 方括号→双引号 + dbo前缀 + 精确拆分\n\n## 规则1: 方括号[] → 双引号\"\"\n\n### 设计决策\n\n`_detokenize` 方法中，方括号占位符还原时做转换：\n\n- 普通标识符 `[Users]` → `\"Users\"`\n- SQL类型名 `[nvarchar]` → `nvarchar`（去掉方括号 + 自动做类型映射）\n- SQL类型名不能转双引号，否则 `\"nvarchar\"(100)` 变成标识符而非类型，后续类型映射不生效\n\n### 关键坑：类型名在detokenize时必须同时做映射\n\n**问题链**：\n1. `_tokenize` 把 `[nvarchar]` 替换为 `__TOKEN_N__`\n2. Step4 `_convert_data_types` 匹配的是裸名 `NVARCHAR`，但此时是占位符，匹配不上\n3. `_detokenize` 还原时如果只去掉方括号变成 `nvarchar(100)`，已经过了Step4，不会再被类型映射\n\n**解决**：在 `_detokenize` 中，对类型名直接做映射：\n```python\nif inner.upper() in _TYPE_NAMES:\n    mapped = self.type_mappings.get(inner.lower(), inner)\n    replacement = mapped\n```\n\n### 关键坑：VARCHAR CHAR语义后处理\n\ndetokenize中类型名映射绕过了Step4中 `VARCHAR(n) → VARCHAR(n CHAR)` 的逻辑（因为Step4在token保护下，`[nvarchar]`是占位符匹配不上）。\n\n**解决**：在Step5.4加后处理：\n```python\nresult = re.sub(r'\\bVARCHAR\\((\\d+)\\)(?!\\s+CHAR\\b)', r'VARCHAR(\\1 CHAR)', result, flags=re.IGNORECASE)\n```\n\n放在Step5 detokenize之后、Step5.5控制流转换之前。\n\n## 规则2: dbo前缀智能处理\n\n### 三段式 vs 两段式判断\n\n| 模式 | 判断依据 | 处理方式 | 示例 |\n|------|---------|---------|------|\n| `[HRBI].[dbo].[Users]` | dbo前有schema前缀 | 删除dbo. | → `\"HRBI\".\"Users\"` |\n| `[dbo].[Users]` | dbo前无schema前缀 | 用schema_prefix替换dbo | → `hrbi_stage.\"Users\"` |\n\n### 实现位置\n\n`_convert_dbo_prefix` 方法在 **Step4.5** 执行（detokenize之后），因为：\n- detokenize之前：方括号是占位符，正则无法匹配 `__TOKEN_0__.__TOKEN_1__.__TOKEN_2__` 的模式\n- detokenize之后：方括号已转为双引号 `\"HRBI\".\"dbo\".\"Users\"`，正则可以匹配\n\n### 正则设计\n\n**三段式**（必须先处理！）：\n```python\nr'((?:\"[^\"]*\"|\\w+)\\.)(?:\"dbo\"|dbo)\\.((?:\"[^\"]*\"|\\w+))'\n# 匹配: \"HRBI\".\"dbo\".\"Users\" 或 HRBI.dbo.Users 或混合\n```\n\n**两段式**：\n```python\nr'\"dbo\"\\.|\\bdbo\\.'\n# 匹配: \"dbo\".\"Users\" 或 dbo.Users（两种格式都支持）\n```\n\n**重要**：三段式正则中 `\"dbo\"` 必须同时支持双引号包裹和裸名，因为detokenize后所有方括号标识符都变成了双引号格式。\n\n### schema_prefix来源\n\n从源文件名自动提取：\n```python\nbasename = os.path.splitext(os.path.basename(input_file))[0]\nschema_prefix = basename  # 如 hrbi_stage.sql → hrbi_stage\n```\n\n在 `_convert_split_output` 中传递，通过 `input_file` 参数。\n\n## 规则3: 精确拆分增强\n\n### `_find_next_create` 兜底函数\n\n当找不到 `;` 或 `GO` 终止符时，用下一个 `CREATE` 关键字作为对象边界上界：\n\n```python\ndef _find_next_create(sql: str, start: int) -> int:\n    # 跳过字符串、注释内的CREATE\n    # 匹配独立的CREATE关键字（前后非字母数字下划线）\n    # 返回位置或-1\n```\n\n### 在 `find_object_end` 中的调用\n\n所有对象类型的终止符查找都有兜底：\n```python\nend = find_semicolon_end(...)  # 或 find_paren_end / find_block_end\nif end >= n:  # 没找到终止符，到达文件末尾\n    next_create = _find_next_create(sql, start + 10)\n    if next_create > start:\n        boundary = next_create\n        while boundary > start and sql[boundary - 1] in ' \\t\\r\\n':\n            boundary -= 1\n        return boundary\n```\n\n## 开发过程关键教训\n\n### patch工具Python缩进问题 — 终极解决方案\n\n**问题**：patch工具修改Python文件时，方法体内的缩进经常被破坏（8空格变4空格、1空格等）。\n\n**终极方案：用Python脚本替换整个方法**，而非用patch做局部替换：\n\n```python\n#!/usr/bin/env python3\nwith open('dm_converter.py') as f:\n    content = f.read()\n\nold_start = '    def _detokenize(self, content: str, token_map: Dict[str, str]) -> str:'\nnew_method = '''    def _detokenize(self, content: str, token_map: Dict[str, str]) -> str:\n        \"\"\"docstring\"\"\"\n        # 方法体（8空格缩进）\n        ...\n        return content'''\n\nstart = content.find(old_start)\nend = content.find('        return content', start)\nend += len('        return content')\ncontent = content[:start] + new_method + '\\n' + content[end:]\n\nwith open('dm_converter.py', 'w') as f:\n    f.write(content)\n```\n\n**原则**：\n- 涉及Python方法体修改时，**不要用patch**，用Python脚本替换整个方法\n- 每次修改后 `python3 -m py_compile file.py` 验证\n- 用 `sed -n 'Np,Mp' file | xxd` 确认精确缩进（xxd显示每个字符的十六进制）\n\nFile v3.6.2:references/dm-converter-v246-fixes.md\n\n# dm_converter v2.4.6 修复记录\n\n## Bug 1: 存储过程方括号和dbo未替换\n\n**症状**: 转换后54个procedure文件残留`[dbo]`和`[xxx]`方括号标识符\n\n**根因**: `convert()`方法Step 6.7只有TABLE/VIEW走`_post_convert_table_types`做方括号→双引号+dbo替换，非表对象只做`_replace_dbo_prefix`（但此时方括号还在，dbo替换正则匹配不到`[dbo]`格式）\n\n**修复**: 新增`_post_convert_generic_types()`方法，对所有非TABLE/VIEW对象类型做:\n1. 方括号类型名映射 `[int]` → `INTEGER`\n2. 方括号→双引号 `[xxx]` → `\"xxx\"`\n3. dbo前缀替换（含`..`双点号）\n4. GO → /\n\n**位置**: dm_converter.py Step 6.7 分支逻辑\n\n## Bug 2: 数据类型双重映射 (INTEGERINT, VARCHARVARCHAR)\n\n**症状**: `@p1 INT` → `p1 INTEGERINT`, `@p2 VARCHAR(100)` → `p2 VARCHARVARCHAR`\n\n**根因**: `_convert_data_types`的正则中类型名用了捕获组:\n```python\n# 旧: (\\\\[?(INT|VARCHAR|...)\\\\]?)  — 3个捕获组: prefix, col, type(含内部子组), suffix\nr'([,\\\\(]\\\\s*|DECLARE\\\\s+|\\\\n\\\\s*)([\\\\w@]+)\\\\s+(\\\\[?(' + _FULL_TYPE_NAMES_PATTERN + r')\\\\]?)(\\\\(...\\\\))?'\n```\n`_FULL_TYPE_NAMES_PATTERN`拼接后变成`(INT|VARCHAR|...)`,是捕获组group(4)。\n代码里`suffix = m.group(4) or ''`实际拿到的是类型名内部组`INT`而非长度后缀`(100)`。\n替换后`f\"{prefix}{col_name} {new_type}{suffix}\"`变成`p1 INTEGER` + `INT` = `p1 INTEGERINT`\n\n**修复**: 类型名内部组改为非捕获组`(?:...)`:\n```python\n# 新: (\\\\[?(?:INT|VARCHAR|...)\\\\]?)  — 4个捕获组: prefix, col, type, suffix(正确)\nr'([,\\\\(]\\\\s*|DECLARE\\\\s+|\\\\n\\\\s*)([\\\\w@]+)\\\\s+(\\\\[?(?:' + _FULL_TYPE_NAMES_PATTERN + r')\\\\]?)(\\\\(...\\\\))?'\n```\n现在group(4)才是suffix，group(3)是完整类型名（含可选方括号）\n\n**教訓**: Python正则中拼接变量构建捕获组时，务必确认组号偏移。用`(?:...)`非捕获组避免意外偏移\n\n## Bug 3: 类型长度后缀贪婪匹配\n\n**症状**: `name VARCHAR(100), flag BIT` 中`VARCHAR`的suffix吃掉了后面所有内容\n\n**根因**: suffix正则`\\\\([^]]*(?:\\\\([^]]*\\\\)[^]]*)*\\\\)`中`[^]]*`匹配\"非]的字符\"而非\"非)的字符\"。`[^]]`是字符类`[^]`加上字面量`]`，实际等于匹配几乎任何字符（因为`]`很少出现）。应为`[^)]*`匹配\"非)的字符\"。\n\n**修复**: \n```python\n# 旧: \\\\([^]]*(?:\\\\([^]]*\\\\)[^]]*)*\\\\)\n# 新: \\\\([^)]*(?:\\\\([^)]*\\\\)[^)]*)*\\\\)\n```\n\n## Bug 4: SQL Server双点号`..`语法\n\n**症状**: 转换后存储过程中残留`hrbi_stage..Stage_xxx`，达梦不支持\n\n**根因**: SQL Server的`database..object`语法省略了dbo schema，相当于`database.dbo.object`。`_replace_dbo_prefix`中三段式正则`xxx.dbo.yyy`匹配不到两段式`xxx..yyy`\n\n**修复**: 在`_replace_dbo_prefix`开头优先处理双点号:\n```python\n# \"xxx\"..\"yyy\" → \"xxx\".\"yyy\"\ncontent = re.sub(r'\"([^\"]+)\"\\.\\.', r'\"\\1\".', content)\n# xxx..yyy → xxx.yyy\ncontent = re.sub(r'\\b(\\w+)\\.\\.', r'\\1.', content)\n```\n\n## UTF-16编码处理\n\nSQL Server Management Studio导出的.sql文件常用UTF-16编码（带BOM），需先转UTF-8:\n```python\nwith open('input.sql', 'r', encoding='utf-16') as f:\n    content = f.read()\nwith open('input_utf8.sql', 'w', encoding='utf-8') as f:\n    f.write(content)\n```\n\n## 测试验证\n\n```bash\ncd scripts && python3 -m pytest test_dm_converter.py -v\n# 40/40 passed\n```\n\nFile v3.6.2:references/dm-converter-v251-fixes.md\n\n# dm_converter v2.5.1 修复记录\n\n## 修复的问题（2026-06-13，基于 HRBI_Stage.sql 7万行实战测试）\n\n### 1. 存储过程参数不加括号 + AS不换IS\n\n**症状**: 拆分后的文件已有 `CREATE OR REPLACE PROCEDURE`，但转换后参数仍是 `TX_DATE DATE`（无括号），AS也没变成IS。\n\n**根因**: `_convert_procedure` 正则只匹配 `CREATE\\s+PROC`，不匹配 `CREATE OR REPLACE PROC`（拆分阶段 v2.4.1 已自动加 OR REPLACE）。\n\n**修复**: 所有3个procedure正则加 `(?:OR\\s+REPLACE\\s+)?`；name捕获组从 `[\\w.\\[\\\"]]+` 改为 `.+?`（因为方括号在字符类内转义损坏）。\n\n**教训**: 上下游模块的输出可能改变输入格式，下游正则必须兼容。\n\n### 2. 方括号标识符未替换（procedure/function）\n\n**症状**: 54个procedure文件里 `[dbo]`、`[xxx]` 原样保留。\n\n**根因**: `_post_convert_table_types` 只对 TABLE/VIEW 生效（代码 `if ctype in (TABLE, VIEW)`），非表类型只做 `_replace_dbo_prefix`，不做方括号替换和类型映射。\n\n**修复**: 新增 `_post_convert_generic_types` 方法，所有非表/视图类型也做: 方括号类型映射 → `[xxx]→\"xxx\"` → dbo替换。\n\n### 3. `_convert_data_types` 捕获组偏移 → 类型双重映射\n\n**症状**: `p1 INT` → `p1 INTEGERINT`，`p2 VARCHAR(100)` → `p2 VARCHARVARCHAR`。\n\n**根因**: 正则 `(\\[?(INT|VARCHAR|...)\\]?)` 中类型名用了捕获组 `(...)`，导致 group(4) 是类型名而非长度后缀。`_replace_type` 用 `m.group(4)` 取 suffix，实际拿到了类型名。\n\n**修复**: 类型名改非捕获组 `(?:INT|VARCHAR|...)`，保证 group(4) 始终是长度后缀。\n\n**教训**: 正则中每个 `(...)` 都会偏移后续组号，非提取用途的组必须用 `(?:...)`。\n\n### 4. 长度后缀贪婪匹配\n\n**症状**: `VARCHAR(100), flag BIT` 整段被吃进 suffix。\n\n**根因**: suffix 正则 `[^]]*` 匹配\"非`]`的字符\"，文件中无 `]` 所以匹配到文件末尾。应为 `[^)]*`（匹配\"非`)`的字符\"）。\n\n**修复**: `[^]]` → `[^)]`。注意 `[^]]` 在正则引擎中的语义：`[^]` 是字符类匹配\"非 `]`\"，后面 `]` 关闭字符类——不是\"非右方括号\"的意图写法。\n\n### 5. 双点号 `..` 未替换\n\n**症状**: `hrbi_stage..Stage_xxx` 保留原样（SQL Server省略dbo的写法）。\n\n**修复**: `_replace_dbo_prefix` 新增双点号处理，必须在三段式之前做：\n- `\"xxx\"..\"yyy\"` → `\"xxx\".\"yyy\"`\n- `xxx..yyy` → `xxx.yyy`\n\n### 6. GO; 未清理\n\n**症状**: 文件末尾 `GO;` 和 `/` 共存。\n\n**根因**: 正则 `^\\s*GO\\s*$` 不匹配 `GO;`。\n\n**修复**: 改为 `^\\s*GO\\s*;?\\s*$`。\n\n### 7. VARCHAR(max)/VARCHAR2(max) 映射\n\n**需求**: `VARCHAR(max)` → `VARCHAR(4096 CHAR)`，`VARCHAR2(max)` → `VARCHAR2(4096 CHAR)`。\n\n**修复**:\n- TYPE_MAPPINGS 新增 `varchar2 → VARCHAR2`\n- 两处 `(max)` 判断加 `'varchar2'`\n- `VARCHAR2(max)` 保持 `VARCHAR2` 前缀不降级为 `VARCHAR`\n\n## 调试经验\n\n1. **正则捕获组验证**: 改正则后，用 `re.finditer` 打印每个 match 的 `m.groups()` 确认组号对应，比看代码猜更可靠。\n2. **大文件测试**: 单元测试用简单输入无法发现 suffix 贪婪和组偏移问题，需要真实大文件（7万行）才能暴露。\n3. **上下游格式变化**: 拆分阶段加了 `OR REPLACE`，转换器正则必须兼容，否则整个方法体跳过。\n4. **字符类内方括号**: `[\\w.\\[\\\"]]` 中的 `\\[` 和 `\\\"]` 和 `]` 交互导致字符类提前关闭，改用 `.+?` 更安全。\n5. **Python缓存**: 修改 .py 后必须清理 `__pycache__/*.pyc`，否则 pytest 运行旧代码。\n\nFile v3.6.2:references/dm-converter-v30-fixes.md\n\n# dm_converter v3.0 修复记录 (2026-06-13)\n\nv3.0 合并了 v2.4.6 的修复并新增了真实项目验证。\n\n## 修复内容\n\n详见 [v2.4.6修复记录](dm-converter-v246-fixes.md)，包含：\n1. 存储过程/函数方括号未替换\n2. 捕获组偏移导致双重映射\n3. suffix贪婪匹配\n4. 双点号`..`语法\n\n## 真实项目验证\n\n### 测试文件: HRBI_Stage.sql\n- 源文件: 70,396行, UTF-16编码, 229万字符\n- 拆分结果: 462个对象 (373 table + 35 view + 54 procedure)\n- 达梦转换: 462/462全部成功, 0失败\n- 单元测试: 40/40通过\n\n### 验证要点\n\n1. **方括号替换**: 转换后0个文件残留`[dbo]`（修复前54个procedure全部残留）\n2. **双点号替换**: `hrbi_stage..Stage_xxx` → `hrbi_stage.Stage_xxx`，0个文件残留`..`\n3. **类型映射**: `[nvarchar](20)` → `VARCHAR(20 CHAR)`, `[bit]` → `BOOLEAN`, `[datetime]` → `TIMESTAMP`\n4. **dbo前缀**: `[dbo].[xxx]` → `\"HRBI_Stage\".\"xxx\"`\n\n### UTF-16编码处理\n\nSSMS导出的.sql文件常用UTF-16编码，拆分前须转UTF-8:\n```python\nwith open('HRBI_Stage.sql', 'r', encoding='utf-16') as f:\n    content = f.read()\nwith open('HRBI_Stage_utf8.sql', 'w', encoding='utf-8') as f:\n    f.write(content)\n```\n\n### v22 CLI ImportError 临时方案\n\n`split_sql_v22.py` 导入 `SQLSplitterGUI`（依赖 tkinter），无GUI环境会报错。\n临时方案：用 `split_sql_v21.py` 代替（核心拆分逻辑完全相同）:\n```bash\npython3 split_sql_v21.py input.sql output_dir --dialect sqlserver\n```\n\nFile v3.6.2:references/dm-converter-v322-fixes.md\n\n# dm_converter v3.2.2 修复记录\n\n## 日期: 2026-06-14\n\n## 修复内容\n\n### 1. 存储过程VARCHAR(n)加CHAR语义\n\n**问题**: `_post_convert_generic_types` 注释写\"不做 VARCHAR(n) -> VARCHAR(n CHAR) (过程体内变量声明不需要)\"，导致存储过程中所有VARCHAR(n)缺少CHAR语义。\n\n**用户反馈**: \"存储过程中也需要按照之前的类型映射进行修改\"\n\n**修复前**:\n```sql\n--DECLARE变量\nv_Sql VARCHAR(4000);\n--参数\nCREATE OR REPLACE PROCEDURE sp_test (p1 INTEGER, p2 VARCHAR(100)) IS\n--CAST\nCAST(TX_DATE AS nvarchar(50))  -- nvarchar也未映射\n```\n\n**修复后**:\n```sql\nv_Sql VARCHAR(4000 CHAR);\nCREATE OR REPLACE PROCEDURE sp_test (p1 INTEGER, p2 VARCHAR(100 CHAR)) IS\nCAST(TX_DATE AS VARCHAR(50 CHAR))\n```\n\n### 2. CAST中nvarchar未映射\n\n**问题**: `_post_convert_generic_types` 只有 `_bracket_type_pattern` 匹配方括号包裹的类型名（如`[nvarchar]`），但SQL Server过程体中 `cast(x as nvarchar(50))` 的 `nvarchar` 是裸名无方括号，不匹配。\n\n**修复**: 新增 `_bare_type_pattern`，用 `(?<=\\s)` lookbehind 匹配前有空白字符的裸类型名。\n\n```python\n_bare_type_pattern = re.compile(\n    r'(?<=\\s)(' + '|'.join(re.escape(t) for t in _type_map.keys()) + r')(?=\\s*\\(|\\s+NULL|\\s+NOT|\\s+IDENTITY|\\s+DEFAULT|\\s+,|\\s*\\)|\\s*$)',\n    re.IGNORECASE\n)\n```\n\n**设计注意**: `(?<=\\s)` 确保只匹配前面有空白字符的类型名（如 `AS nvarchar(50)` 或 `as VARCHAR(4000)`），避免错误匹配列名（列名通常在逗号或括号后，前面不是空格）。\n\n### 3. _post_convert_generic_types 新增 VARCHAR(n) → VARCHAR(n CHAR)\n\n```python\ncontent = re.sub(\n    r'\\bVARCHAR\\s*\\(\\s*(\\d+)\\s*\\)',\n    r'VARCHAR(\\1 CHAR)',\n    content,\n    flags=re.IGNORECASE\n)\n```\n\n与 `_post_convert_table_types` 使用相同的正则模式。\n\n## 验证结果\n\n- 44个单元测试全部通过（含4个新增 TestProcedureTypeMapping）\n- 462个真实SQL对象端到端测试全部通过（HRBI_Stage.sql, 7万行）\n- 存储过程中nvarchar残留数: 0（全部映射为VARCHAR(n CHAR)）\n- DECLARE变量: `v_Sql VARCHAR(4000 CHAR)` ✓\n- CAST: `CAST(TX_DATE AS VARCHAR(50 CHAR))` ✓\n\n## 测试用例\n\n新增 `TestProcedureTypeMapping` 类，含4个测试:\n- `test_procedure_varchar_char_semantic`: DECLARE变量VARCHAR加CHAR\n- `test_procedure_cast_nvarchar`: CAST中nvarchar→VARCHAR(n CHAR)\n- `test_procedure_parameter_varchar_char`: 参数VARCHAR加CHAR\n- `test_function_returns_varchar_char`: RETURNS VARCHAR加CHAR\n\n## 已有测试断言更新\n\n- `test_procedure_with_params`: `p2 VARCHAR(100)` → `p2 VARCHAR(100 CHAR)`\n- `test_scalar_function`: `RETURN VARCHAR` → `RETURN VARCHAR(50 CHAR)`\n\nFile v3.6.2:references/dm-converter-v323-fixes.md\n\n# dm_converter v3.2.3 修复记录 — PROCEDURE用AS而非IS\n\n## 问题描述\n\n用户指出：**达梦存储过程应该用AS而非IS**。\n\n`_convert_procedure`方法错误地将所有PROCEDURE声明生成`IS`结尾：\n```\nCREATE OR REPLACE PROCEDURE \"HRBI_Stage\".\"PROC_xxx\" (TX_DATE DATE) IS   ← 错误\n```\n\n正确应该是：\n```\nCREATE OR REPLACE PROCEDURE \"HRBI_Stage\".\"PROC_xxx\" (TX_DATE DATE) AS   ← 正确\n```\n\n## 根因\n\n达梦的PROCEDURE声明语法要求`AS`，FUNCTION才用`IS`。这是Oracle和达梦的语法差异：\n- **Oracle**: PROCEDURE 可以用 IS 或 AS\n- **达梦**: PROCEDURE 必须用 AS，FUNCTION 用 IS\n\n代码中`_convert_procedure`硬编码了`IS`（套用了Oracle惯例），没有区分PROCEDURE和FUNCTION。\n\n## 修复\n\n在`_convert_procedure`方法中，将4处`IS`改为`AS`：\n\n1. **有括号参数列表**（第398行）: `... {params} IS` → `... {params} AS`\n2. **无括号参数(单参数)**（第417行）: `... ({params_clean.strip()}) IS` → `... AS`\n3. **无括号参数(多参数)**（第419行）: `... {formatted} IS` → `... {formatted} AS`\n4. **无参数存储过程**（第431行）: `'CREATE OR REPLACE PROCEDURE \\\\1 IS'` → `... \\\\1 AS`\n\n**未修改**: `_convert_function`方法保持`IS`不变。\n\n## 测试\n\n- 旧断言 `assertIn('IS', result.converted)` → `assertIn('AS', result.converted)`\n- 旧断言 `assertIn('sp_simple IS', ...)` → `assertIn('sp_simple AS', ...)`\n- 44个单元测试全部通过\n- 462个真实SQL对象端到端验证：47个proc文件全部从IS改为AS，0个残留\n\n## 关键区分备忘\n\n| 对象类型 | 达梦声明结尾 |\n|---------|------------|\n| PROCEDURE | **AS** |\n| FUNCTION | **IS** |\n\nFile v3.6.2:references/dm-converter-v330-fixes.md\n\n# dm_converter v3.3.0 修复记录\n\n## 修复的问题\n\n### 1. PROCEDURE双重引号bug\n- **症状**: `_convert_procedure`输出 `CREATE OR REPLACE PROCEDURE \"\"sp_test\" (p1 INT)\" AS`\n- **根因**: 三个`re.sub`顺序执行 — 第一个`_format_bracket_params`替换后输出`\"sp_test\"`，第三个`_fmt_no_param_proc`的正则`PROC\\s+(.+?)\\s+\n\nArchive v3.6.1: 38 files, 145488 bytes\n\nFiles: references/dm_conversion.py (225b), references/dm-converter-design.md (9174b), references/dm-converter-v243-fixes.md (5550b), references/dm-converter-v245-bracket-dbo-split.md (4853b), references/dm-converter-v246-fixes.md (3357b), references/dm-converter-v251-fixes.md (3634b), references/dm-converter-v30-fixes.md (1512b), references/dm-converter-v322-fixes.md (2691b), references/dm-converter-v323-fixes.md (1693b), references/dm-converter-v330-fixes.md (2547b), references/dm-converter-v340-fixes.md (1459b), references/dm-converter-v345-fixes.md (6488b), references/dm-converter-v353-fixes.md (1889b), references/document-maintenance.md (1660b), references/hrbi-stage-real-world-test.md (2277b), references/python-indentation-debugging.md (2451b), references/split-convert-workflow-20260627.md (2682b), scripts/batch_convert.py (2149b), scripts/batch_processor.py (9333b), scripts/checkpoint.py (9666b), scripts/common.py (8993b), scripts/config_manager.py (9779b), scripts/dependency_analyzer.py (10518b), scripts/dm_converter.py (117494b), scripts/error_handler.py (6806b), scripts/gui.py (1007b), scripts/result_previewer.py (8423b), scripts/split_sql_v21.py (43126b), scripts/split_sql_v22.py (11134b), scripts/split_sql.py (29311b), scripts/test_dm_converter.py (25861b), scripts/test_sql_splitter.py (14305b), scripts/test_v21_features.py (6344b), scripts/test_v22_features.py (9177b), scripts/unlimited_split.py (3755b), skill-card.md (2661b), SKILL.md (68132b), _meta.json (131b)\n\nArchive v3.6.0: 37 files, 143907 bytes\n\nFiles: references/dm_conversion.py (225b), references/dm-converter-design.md (9174b), references/dm-converter-v243-fixes.md (5550b), references/dm-converter-v245-bracket-dbo-split.md (4853b), references/dm-converter-v246-fixes.md (3357b), references/dm-converter-v251-fixes.md (3634b), references/dm-converter-v30-fixes.md (1512b), references/dm-converter-v322-fixes.md (2691b), references/dm-converter-v323-fixes.md (1693b), references/dm-converter-v330-fixes.md (2547b), references/dm-converter-v340-fixes.md (1459b), references/dm-converter-v345-fixes.md (6488b), references/dm-converter-v353-fixes.md (1889b), references/hrbi-stage-real-world-test.md (2277b), references/python-indentation-debugging.md (2451b), references/split-convert-workflow-20260627.md (2682b), scripts/batch_convert.py (2149b), scripts/batch_processor.py (9333b), scripts/checkpoint.py (9666b), scripts/common.py (8993b), scripts/config_manager.py (9779b), scripts/dependency_analyzer.py (10518b), scripts/dm_converter.py (117494b), scripts/error_handler.py (6806b), scripts/gui.py (1007b), scripts/result_previewer.py (8423b), scripts/split_sql_v21.py (43126b), scripts/split_sql_v22.py (11134b), scripts/split_sql.py (29311b), scripts/test_dm_converter.py (25861b), scripts/test_sql_splitter.py (14305b), scripts/test_v21_features.py (6344b), scripts/test_v22_features.py (9177b), scripts/unlimited_split.py (3755b), skill-card.md (2915b), SKILL.md (67429b), _meta.json (131b)\n\nArchive v3.5.7: 40 files, 151310 bytes\n\nFiles: references/dm_conversion.py (225b), references/dm-converter-design.md (9174b), references/dm-converter-v243-fixes.md (5550b), references/dm-converter-v245-bracket-dbo-split.md (4853b), references/dm-converter-v246-fixes.md (3357b), references/dm-converter-v251-fixes.md (3634b), references/dm-converter-v30-fixes.md (1512b), references/dm-converter-v322-fixes.md (2691b), references/dm-converter-v323-fixes.md (1693b), references/dm-converter-v330-fixes.md (2547b), references/dm-converter-v340-fixes.md (1459b), references/dm-converter-v345-fixes.md (6488b), references/dm-converter-v353-fixes.md (1889b), references/hrbi-stage-real-world-test.md (2277b), references/python-indentation-debugging.md (2451b), references/split-convert-workflow-20260627.md (2682b), references/sql-splitter-commercialization.md (3262b), references/sql-splitter-productization.md (4265b), references/sql-splitter-v350-productization.md (4002b), scripts/batch_convert.py (2149b), scripts/batch_processor.py (9333b), scripts/checkpoint.py (9666b), scripts/common.py (8993b), scripts/config_manager.py (9779b), scripts/dependency_analyzer.py (10518b), scripts/dm_converter.py (117494b), scripts/error_handler.py (6806b), scripts/gui.py (1007b), scripts/result_previewer.py (8423b), scripts/split_sql_v21.py (43126b), scripts/split_sql_v22.py (11134b), scripts/split_sql.py (29311b), scripts/test_dm_converter.py (25861b), scripts/test_sql_splitter.py (14305b), scripts/test_v21_features.py (6344b), scripts/test_v22_features.py (9177b), scripts/unlimited_split.py (3755b), skill-card.md (2421b), SKILL.md (68161b), _meta.json (131b)\n\nArchive v3.5.6: 39 files, 146759 bytes\n\nFiles: references/dm_conversion.py (225b), references/dm-converter-design.md (9174b), references/dm-converter-v243-fixes.md (5550b), references/dm-converter-v245-bracket-dbo-split.md (4853b), references/dm-converter-v246-fixes.md (3357b), references/dm-converter-v251-fixes.md (3634b), references/dm-converter-v30-fixes.md (1512b), references/dm-converter-v322-fixes.md (2691b), references/dm-converter-v323-fixes.md (1693b), references/dm-converter-v330-fixes.md (2547b), references/dm-converter-v340-fixes.md (1459b...","readmeExcerpt":"Skill: Sql Splitter Owner: fish1981bimmer Summary: 拆分 SQL 文件为独立文件（存储过程、函数、视图、触发器、表结构、索引、约束），自动分析依赖并生成合并脚本 Tags: database:1.1.0, latest:3.6.3, sql:1.1.0, utility:1.1.0 Version history: v3.6.3 | 2026-07-04T01:58:03.027Z | auto sql-splitter v3.6.3 - Updated SKILL.md documentation, including detailed usage guides, transformation rules, and version history up to v3.6.2. - No code-level changes listed; this release focuses","codeSnippets":[],"executableExamples":[{"language":"bash","snippet":"# 先完整拆分\n\npython3 ~/.hermes/skills/sql-splitter/scripts/split_sql_v21.py input.sql output_dir --dialect sqlserver\n\n\n\n# 再只拷贝目标类型到独立目录\n\ncp output_dir/proc_*.sql /path/to/output_proc/"},{"language":"bash","snippet":"# 保留存储过程，删除其他\n\ncd output_dir && ls -1 | grep -v '^proc_' | xargs rm"},{"language":"bash","snippet":"# 拆分SQL Server文件并转换为达梦数据库语法\n\npython3 ~/.hermes/skills/sql-splitter/scripts/split_sql_v22.py input.sql output_dir --dialect sqlserver --convert-to dm\n\n\n\n# 仅转换(不拆分)\n\npython3 -c \"from dm_converter import convert_sqlserver_to_dm; print(convert_sqlserver_to_dm('SELECT GETDATE()', 'generic'))\""},{"language":"python","snippet":"# 单对象报告\n\nfrom dm_converter import DMConverter\n\nfrom report_generator import ConversionReportGenerator\n\n\n\nconverter = DMConverter()\n\nresult = converter.convert(sql_content, 'procedure', schema_prefix='hrbi')\n\nreport = ConversionReportGenerator.generate_single(result, 'sp_test', 'procedure')\n\nprint(report.to_markdown())\n\nprint(f'兼容性评分: {report.score}/100')\n\n\n\n# 批量报告\n\nbatch = ConversionReportGenerator.generate_batch(results, schema_prefix='hrbi')\n\nbatch.save_html('report.html')    # 暗色主题HTML\n\nbatch.save_json('report.json')    # 结构化JSON\n\nbatch.save_markdown('report.md')  # Markdown\n\n\n\n# 快速评分(不生成报告)\n\nscore = ConversionReportGenerator.quick_score(result)"},{"language":"bash","snippet":"# 1) 拆分\n\npython3 ~/.hermes/skills/sql-splitter/scripts/split_sql_v21.py input.sql output_dir --dialect sqlserver\n\n\n\n# 2) 批量转换(写脚本文件方式)\n\ncat > /tmp/batch_convert.py << 'PYEOF'\n\n#!/usr/bin/env python3\n\nimport os, sys\n\nsys.path.insert(0, '/Users/a1234/.hermes/skills/sql-splitter/scripts')\n\nfrom dm_converter import convert_sqlserver_to_dm\n\n\n\nsrc_dir = sys.argv[1] if len(sys.argv) > 1 else 'output_dir'\n\ndm_dir = sys.argv[2] if len(sys.argv) > 2 else src_dir + '_dm'\n\nschema_prefix = sys.argv[3] if len(sys.argv) > 3 else os.path.basename(src_dir).replace('_split','')\n\nos.makedirs(dm_dir, exist_ok=True)\n\n\n\nok = err = 0\n\nerr_list = []\n\nfor f in sorted(os.listdir(src_dir)):\n\n    if not f.endswith('.sql') or f == 'merge_all.sql': continue\n\n    obj_type = f.split('_')[0]\n\n    type_map = {'proc':'procedure','func':'function','trig':'trigger',\n\n                'view':'view','table':'table','idx':'index','uidx':'index',\n\n                'con':'constraint','seq':'sequence'}\n\n    mapped_type = type_map.get(obj_type, 'generic')\n\n    with open(os.path.join(src_dir, f)) as fh: c = fh.read()\n\n    try:\n\n        converted = convert_sqlserver_to_dm(c, mapped_type, schema_prefix=schema_prefix)\n\n        with open(os.path.join(dm_dir, f), 'w') as fh: fh.write(converted)\n\n        ok += 1\n\n    except Exception as e:\n\n        err += 1; err_list.append(f'{f}: {str(e)[:120]}')\n\n\n\nprint(f'转换完成: {ok} 成功, {err} 失败')\n\nif err_list:\n\n    for e in err_list[:15]: print(f'  - {e}')\n\nPYEOF\n\n\n\npython3 /tmp/batch_convert.py /path/to/output_dir /path/to/output_dir_dm schema_prefix"},{"language":"text","snippet":"input_split/ ← 原始拆分结果\n\n├── proc_sp_test.sql\n\n├── table_users.sql\n\n├── view_v_users.sql\n\n└── merge_all.sql\n\n\n\ninput_split_dm/ ← 达梦转换版本\n\n├── proc_sp_test.sql\n\n├── table_users.sql\n\n├── view_v_users.sql\n\n└── merge_all.sql"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\n\nname: sql-splitter\n\ndescription: 拆分 SQL 文件为独立文件（存储过程、函数、视图、触发器、表结构、索引、约束），自动分析依赖并生成合并脚本\n\n---\n\n\n\n# SQL 文件拆分工具 v3.6.2\n\n\n\n将包含多个 SQL 对象的单一文件或目录拆分为独立的 .sql 文件，\n\n并自动分析对象间依赖关系，生成按依赖排序的合并脚本。\n\n\n\n## v3.4.5 修复 — dbo两段式替换 + DATETIME2排序 + 方括号替换架构优化 + SELECT INTO转换 + DDL分号\n\n\n\n- **v3.5.4 新增: 过程体内所有语句(DML+变量赋值)补分号** — 新增`_ensure_statement_semicolons()`方法(Step 8.6)，处理DML和变量赋值的分号补全，同时正确处理跨行INSERT INTO\n\n- **v3.5.3 行为变更: 非类型名方括号→加双引号** — `[aa]`→`\"aa\"`(v3.5.3起加双引号; v3.5.2及以前去[]不加双引号→`aa`)，类型名`[int]`→`int`仍只去[]不加双引号。修改位置：`_convert_bracket_identifiers()`新增`_replace_bracket`回调函数，用TYPE_MAPPINGS键集合区分类型名和普通标识符\n\n\n\n- **dbo替换规则(v3.5.6最终版)** — 三段式和两段式区别处理：\n\n  - **三段式**（已有其他schema名，如`HRBI_Stage.[dbo].[xxx]`）: dbo是SQL Server默认schema，直接删除\n\n  - **两段式**（只有dbo，如`[dbo].[xxx]`或`dbo.xxx`）: dbo替换为schema_prefix\n\n  - 关键区分：输入中若有其他schema名存在，dbo是冗余层→删除；若只有dbo，dbo是唯一schema标识→替换为prefix\n\n  - **正则替换顺序（不可颠倒）**: 1)双点号`(\\\\w+)\\\\.\\\\.(\\\\w+)`→`\\\\1.\\\\2` 2)三段式引号`\"\\\\w+\"\\\\.dbo\\\\.\\\"(\\\\w+)\\\"`→`\"$1\"` 3)三段式裸名`(\\\\w+)\\\\.dbo\\\\.(\\\\w+)`→`\"$1\\\".\\\"$2\\\"` 4)三段式混合`\"\\\\w+\\\"\\\\.\\\\w+\\\\.(\\\\w+)`→保留 5)两段式引号`\"dbo\\\"\\\\.\\\"(\\\\w+)\\\"`→`prefix.\\\"$1\\\"` 6)两段式裸名`dbo\\\\.(\\\\w+)`→`prefix.$1`\n\n  - 实现位置：`_replace_dbo_prefix()`\n\n  - ⚠️ 改完dbo规则后必须跑全量312过程验证\n\n  - 详见 [dbo演进史](references/dm-converter-v345-fixes.md)\n\n- **DATETIME2→TIMESTAMP2而非TIMESTAMP(BUG)** — TYPE_MAPPINGS的key构建正则alternation时无排序，`DATETIME`排在`DATETIME2`前面抢先匹配，`DATETIME2`被拆成`DATETIME`+`2`变成`TIMESTAMP2`。修复：`sorted(keys, key=len, reverse=True)`按长度降序排列，`DATETIME2`(9字符)排在`DATETIME`(8字符)前面。影响范围：全局`_TYPE_NAMES_PATTERN`、`_FULL_TYPE_NAMES_PATTERN`、以及`_post_convert_table_types`和`_post_convert_generic_types`中的两个`_bare_type_pattern`\n\n- **方括号替换在token还原后执行导致注释内容被误改(架构BUG)** — `_post_convert_table_types`/`_post_convert_generic_types`中的`\\\\[([^\\\\]]+)\\\\]`在Step 6.7(token还原后)执行，注释已还原为原始文本，注释中含方括号(如`---一次性解决率 是[处理人响应次数`)会被误匹配截断。修复：把方括号替换从Step 6.7移到Step 4`_convert_bracket_identifiers()`中执行——此时注释和字符串已被tokenize保护，方括号只出现在真实SQL代码中，不会被注释内容干扰\n\n- **SELECT INTO #临时表→CTAS创建GTT(新规则)** — 达梦不支持`SELECT ... INTO #新表 FROM ...`建表语法。转换规则：临时表(`INTO #xxx`/`INTO tmp_xxx`)→`CREATE GLOBAL TEMPORARY TABLE \"tmp_xxx\" AS SELECT ... FROM ...`(CTAS方式)；普通表暂不自动转换(与达梦变量赋值语法`SELECT expr INTO var FROM`形式相同，无法区分)。修改位置：`_convert_temp_tables()`新增SELECT INTO处理逻辑\n\n- **过程体内DDL语句结尾加分号(新规则)** — 达梦存储过程体内每条DDL(CREATE/ALTER/DROP TABLE/INDEX等)必须以`;`结尾。新增`_ensure_ddl_semicolons()`方法(Step 8.5)，用状态机扫描过程体：单行DDL如`DROP TABLE xxx`直接补`;`，跨行DDL如`CREATE GLOBAL TEMPORARY TABLE \"tmp_xxx\"\\n(id INT)\\n`在`)`行补`;`，CTAS跨行子查询在遇到下一个语句开头时给上一行补`;`，同时处理`ON PRIMARY`文件组语法去掉。312个存储过程中DDL块缺分号数从447降为0\n\n- **过程体内所有语句(DML+变量赋值)结尾加分号(v3.5.4起)** — 新增`_ensure_statement_semicolons()`方法(Step 8.6)。达梦存储过程体内DML(INSERT/DELETE/UPDATE/SELECT)和变量赋值(`:=`)也必须以分号结尾。关键：跨行INSERT INTO(table_name后无括号，列定义在下一行)不加分号；单行DELETE FROM...WHERE...补分号；v_xxx := expr补分号；跨行赋值((/+,/结尾)不补分号。312个存储过程全部验证通过。\n\n- **过程体内DML语句和变量赋值结尾加分号(v3.5.6新增)** — 达梦存储过程体内每条DML(INSERT/DELETE/UPDATE/SELECT)和变量赋值(`v_xxx := expr`)必须以`;`结尾。新增`_ensure_statement_semicolons()`方法(Step 8.6)，逐行扫描过程体：单行DML如`delete FROM xxx WHERE y=1`直接补`;"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn76tyr0ztr0j5fyej0qbchjx584wwjp\",\n  \"slug\": \"sql-splitter\",\n  \"version\": \"3.6.3\",\n  \"publishedAt\": 1783130283027\n}"},{"path":"references/dm-converter-design.md","content":"# SQL Server -> 达梦数据库转换器设计要点\n\n## 架构\n\n```\nconvert() 流程:\n Step1: _tokenize() - 字符串/注释 -> 占位符\n Step2: 按对象类型转换 (procedure/function/view/trigger/table/index/constraint)\n Step3: _tokenize() 重新tokenize + 合并token_map\n Step4: 通用转换 (data_types/functions/global_vars/statements/try_catch/transaction)\n Step5: _detokenize() 用合并后的map还原所有占位符\n Step6: _tokenize_strings_only() + _convert_variable_syntax() + detokenize\n Step7: _convert_goto_label()\n Step8: _add_terminator() (仅procedure/function/trigger)\n```\n\n## 已修复的坑（v2.4.0及之前）\n\n### 1. token_map 合并\nStep2的_convert_procedure等返回token化文本(含__TOKEN_0__)，Step3重新_tokenize产生新map。\n如果不合并，Step4 _detokenize用新map找不到__TOKEN_0__的映射。\n\n修复:\n```python\noriginal_token_map = dict(token_map) # Step2前保存\ntokens, token_map = self._tokenize(result) # Step3\nmerged_map = dict(original_token_map)\nmerged_map.update(token_map) # 合并\n# Step4用merged_map\nresult = self._detokenize(result, merged_map)\n```\n\n### 2. content = new_content 不可省略\n_convert_variable_syntax中全局@替换后忘记`content = new_content`，\n导致return content返回的是替换前的文本。\n\n### 3. 嵌套括号匹配\n`VARCHAR(100)`中的`)`会截断`[^)]*`，导致参数列表匹配失败。\n\n错误: `r'\\(([^)]*)\\)'` -> 在`VARCHAR(100)`的`)`处截断\n正确: `r'(\\([^)]*(?:\\([^)]*\\)[^)]*)*\\))'` -> 匹配嵌套括号\n\n### 4. DECLARE上下文的数据类型\n数据类型转换正则的前缀只匹配`,\\(\\s*|\\n\\s*`时，\n`DECLARE @v_date DATETIME`中的DATETIME不会被匹配。\n\n修复: 前缀加上`DECLARE\\s+`\n\n### 5. INSERT INTO误匹配\n`\\n INSERT INT` 被匹配为: 前缀`\\n` + 列名`INSERT` + 类型`INT`\n导致INSERT变成INTEGERO\n\n修复方向: 数据类型替换前检查列名是否为SQL关键字(SELECT/INSERT/UPDATE/DELETE等)\n\n### 6. 变量@转换时机\n在token保护下做@替换时，__TOKEN_0__等占位符名不含@，安全。\n但更安全的做法: 先还原所有token，再用_tokenize_strings_only只保护字符串，\n然后做@替换。\n\n### 7. 终止符 /\nGO在Step2已被转成/，_add_terminator需要先去掉已有的/再添加新的。\n```python\nif content.endswith('/'):\n    content = content[:-1].rstrip()\n```\n\n## 代码审查发现的未修BUG（2026-05-27审查）\n\n用户反馈\"达梦转换不好用\"，代码审查发现以下严重问题：\n\n### BUG-1: 函数映射转换结果语法错误（严重）\n\n**CONVERT函数** (line 118):\n```python\n(r'\\bCONVERT\\s*\\(\\s*(\\w+)\\s*,', r'CAST(', True)\n```\n- 只替换了`CONVERT(type,`为`CAST(`，丢掉了type参数\n- SQL Server: `CONVERT(VARCHAR(50), @val)` → 达梦: `CAST(@val AS VARCHAR(50))`\n- 参数顺序和格式都不对，需要回调函数重排参数\n\n**DATEADD函数** (line 127-128):\n```python\n(r'\\bDATEADD\\s*\\(\\s*day\\s*,', 'CURRENT_TIMESTAMP + ', True)\n(r'\\bDATEADD\\s*\\(\\s*month\\s*,', 'ADD_MONTHS(CURRENT_TIMESTAMP,', True)\n```\n- 丢了date参数，直接硬编码CURRENT_TIMESTAMP\n- SQL Server: `DATEADD(day, 7, @order_date)` → 达梦: `@order_date + 7`\n- SQL Server: `DATEADD(month, 3, @start_date)` → 达梦: `ADD_MONTHS(@start_date, 3)`\n- 需要回调函数提取3个参数并重排\n\n**DATEDIFF函数** (line 129):\n```python\n(r'\\bDATEDIFF\\s*\\(\\s*day\\s*,', '(', True)\n```\n- 直接替换成`(`，语法彻底崩了\n- SQL Server: `DATEDIFF(day, @start, @end)` → 达梦: `@end - @start`（日期差）\n- 需要回调函数提取参数\n\n**STRING_AGG函数** (line 115):\n```python\n(r'\\bSTRING_AGG\\s*\\(', 'LISTAGG(', True)\n```\n- 达梦LISTAGG需要`WITHIN GROUP (ORDER BY ...)`语法，光改名不够\n- SQL Server: `STRING_AGG(name, ',')` → 达梦: `LISTAGG(name, ',') WITHIN GROUP (ORDER BY name)`\n\n**STUFF函数** (line 112):\n```python\n(r'\\bSTUFF\\s*\\(', 'OVERLAY(', True)\n```\n- 达梦不支持OVERLAY函数，参数语义也不同\n- STUFF(str, start, len, replace) vs OVERLAY不同签名\n\n**REPLICATE函数** (line 113):"},{"path":"references/dm-converter-v243-fixes.md","content":"# dm_converter v2.4.3 修复记录\n\n## 修复的9个失败测试\n\n从9个失败 → 40/40全部通过。\n\n### 修复1: BIT→BOOLEAN, TINYINT→SMALLINT\n- **问题**: type_mappings中 `'bit': 'TINYINT'`, `'tinyint': 'TINYINT'`，但达梦没有TINYINT\n- **修复**: `'bit': 'BOOLEAN'`, `'tinyint': 'SMALLINT'`\n- **测试**: test_data_types\n\n### 修复2: NVARCHAR(n) → VARCHAR(n CHAR)\n- **问题**: NVARCHAR(100)只转成VARCHAR(100)，缺少达梦字符语义CHAR标记\n- **根因**: NVARCHAR处理的代码被patch工具放到了`else:`分支内部（即只在suffix为空时执行），导致有suffix时永远无法命中\n- **修复**: 将NVARCHAR检查代码从`else:`块内移到`if suffix:` / `else:` 块**之后**（与if/else同级）\n- **关键坑**: patch工具插入代码时，缩进层级容易搞错。**必须用`python3 -c`检查实际缩进**：\n  ```python\n  with open('dm_converter.py') as f:\n      for i, line in enumerate(f, 1):\n          if i >= 511 and i <= 525:\n              spaces = len(line) - len(line.lstrip())\n              print(f\"{i}: indent={spaces} |{line.rstrip()[:70]}|\")\n  ```\n- **测试**: test_data_types\n\n### 修复3: SET NOCOUNT ON注释格式\n- **问题**: 注释`'-- SET NOCOUNT ON (达梦不需要)'`中包含原文`SET NOCOUNT ON`，测试用assertNotIn检测到\n- **修复**: 改为`'-- NOCOUNT (达梦不需要)'`，去掉注释中的原文关键字\n- **测试**: test_complex_procedure, test_set_nocount_on\n\n### 修复4: DATEADD专用转换\n- **问题**: function_mappings中旧的DATEADD条目只做简单前缀替换（如`DATEADD(day,` → `CURRENT_TIMESTAMP +`），不能正确处理3个参数的重排\n- **修复**: \n  - 从function_mappings删除旧的DATEADD条目\n  - 添加`_parse_function_args()`方法解析嵌套括号的参数列表\n  - 添加`_convert_dateadd()`方法，按unit类型重排参数：\n    - `DATEADD(day, n, date)` → `date + INTERVAL 'n' DAY`\n    - `DATEADD(month, n, date)` → `ADD_MONTHS(date, n)`\n    - 其他单位类似\n  - 在`_convert_functions()`末尾调用\n- **测试**: test_dateadd_conversion\n\n### 修复5: SELECT赋值区分有无FROM\n- **问题**: 所有`SELECT @var = expr`都转成`var := expr`，但有FROM子句时达梦需要`SELECT expr INTO var FROM table`\n- **修复**: \n  - 添加`_convert_select_assign()`方法\n  - 解析赋值列表和FROM子句位置\n  - 有FROM: `SELECT @var=expr FROM t` → `SELECT expr INTO var FROM t`\n  - 无FROM: `SELECT @var=expr` → `var := expr`\n  - 支持多变量赋值: `SELECT @a=col1, @b=col2 FROM t` → `SELECT col1, col2 INTO a, b FROM t`\n- **测试**: test_select_into_with_from, test_select_multi_assign, test_select_assign_no_from\n\n### 修复6: IF→THEN/END IF 控制流\n- **问题**: `IF @x > 0 BEGIN ... END`没有转换为达梦语法\n- **修复**: 添加`_convert_if_else()`方法\n  - `IF condition` → `IF condition THEN`\n  - BEGIN/END配对：IF后的BEGIN跳过，对应的END改为`END IF;`\n  - ELSE保留\n- **测试**: test_if_else, test_if_with_try_catch, test_nested_if\n\n### 修复7: WHILE→LOOP/END LOOP\n- **问题**: `WHILE @x > 0 BEGIN ... END`没有转换\n- **修复**: 添加`_convert_while_loop()`方法\n  - `WHILE condition` → `WHILE condition LOOP`\n  - WHILE后的BEGIN跳过，对应的END改为`END LOOP;`\n- **测试**: test_while_loop\n\n### 修复8: PRINT→DBMS_OUTPUT.PUT_LINE\n- **问题**: `PRINT 'text'`没有转换为达梦语法\n- **修复**: 添加`_convert_print()`和`_print_replacer()`方法\n  - `PRINT expr` → `DBMS_OUTPUT.PUT_LINE(expr);`\n  - SQL Server的`+`字符串连接改为达梦的`||`\n  - `CAST(...AS VARCHAR)` → `CAST(...AS VARCHAR(4000))`（达梦VARCHAR需要长度）\n- **测试**: test_complex_procedure\n\n### 修复9: 流程步骤位置\n- 在convert()方法中，Step 5.5后添加控制流和PRINT调用：\n  ```python\n  # Step 5.5: 控制流转换 (IF/WHILE)\n  result = self._convert_if_else(result)\n  result = self._convert_while_loop(result)\n  \n  # Step 5.6: PRI"},{"path":"references/dm-converter-v245-bracket-dbo-split.md","content":"# dm_converter v2.4.5 设计记录 — 方括号→双引号 + dbo前缀 + 精确拆分\n\n## 规则1: 方括号[] → 双引号\"\"\n\n### 设计决策\n\n`_detokenize` 方法中，方括号占位符还原时做转换：\n\n- 普通标识符 `[Users]` → `\"Users\"`\n- SQL类型名 `[nvarchar]` → `nvarchar`（去掉方括号 + 自动做类型映射）\n- SQL类型名不能转双引号，否则 `\"nvarchar\"(100)` 变成标识符而非类型，后续类型映射不生效\n\n### 关键坑：类型名在detokenize时必须同时做映射\n\n**问题链**：\n1. `_tokenize` 把 `[nvarchar]` 替换为 `__TOKEN_N__`\n2. Step4 `_convert_data_types` 匹配的是裸名 `NVARCHAR`，但此时是占位符，匹配不上\n3. `_detokenize` 还原时如果只去掉方括号变成 `nvarchar(100)`，已经过了Step4，不会再被类型映射\n\n**解决**：在 `_detokenize` 中，对类型名直接做映射：\n```python\nif inner.upper() in _TYPE_NAMES:\n    mapped = self.type_mappings.get(inner.lower(), inner)\n    replacement = mapped\n```\n\n### 关键坑：VARCHAR CHAR语义后处理\n\ndetokenize中类型名映射绕过了Step4中 `VARCHAR(n) → VARCHAR(n CHAR)` 的逻辑（因为Step4在token保护下，`[nvarchar]`是占位符匹配不上）。\n\n**解决**：在Step5.4加后处理：\n```python\nresult = re.sub(r'\\bVARCHAR\\((\\d+)\\)(?!\\s+CHAR\\b)', r'VARCHAR(\\1 CHAR)', result, flags=re.IGNORECASE)\n```\n\n放在Step5 detokenize之后、Step5.5控制流转换之前。\n\n## 规则2: dbo前缀智能处理\n\n### 三段式 vs 两段式判断\n\n| 模式 | 判断依据 | 处理方式 | 示例 |\n|------|---------|---------|------|\n| `[HRBI].[dbo].[Users]` | dbo前有schema前缀 | 删除dbo. | → `\"HRBI\".\"Users\"` |\n| `[dbo].[Users]` | dbo前无schema前缀 | 用schema_prefix替换dbo | → `hrbi_stage.\"Users\"` |\n\n### 实现位置\n\n`_convert_dbo_prefix` 方法在 **Step4.5** 执行（detokenize之后），因为：\n- detokenize之前：方括号是占位符，正则无法匹配 `__TOKEN_0__.__TOKEN_1__.__TOKEN_2__` 的模式\n- detokenize之后：方括号已转为双引号 `\"HRBI\".\"dbo\".\"Users\"`，正则可以匹配\n\n### 正则设计\n\n**三段式**（必须先处理！）：\n```python\nr'((?:\"[^\"]*\"|\\w+)\\.)(?:\"dbo\"|dbo)\\.((?:\"[^\"]*\"|\\w+))'\n# 匹配: \"HRBI\".\"dbo\".\"Users\" 或 HRBI.dbo.Users 或混合\n```\n\n**两段式**：\n```python\nr'\"dbo\"\\.|\\bdbo\\.'\n# 匹配: \"dbo\".\"Users\" 或 dbo.Users（两种格式都支持）\n```\n\n**重要**：三段式正则中 `\"dbo\"` 必须同时支持双引号包裹和裸名，因为detokenize后所有方括号标识符都变成了双引号格式。\n\n### schema_prefix来源\n\n从源文件名自动提取：\n```python\nbasename = os.path.splitext(os.path.basename(input_file))[0]\nschema_prefix = basename  # 如 hrbi_stage.sql → hrbi_stage\n```\n\n在 `_convert_split_output` 中传递，通过 `input_file` 参数。\n\n## 规则3: 精确拆分增强\n\n### `_find_next_create` 兜底函数\n\n当找不到 `;` 或 `GO` 终止符时，用下一个 `CREATE` 关键字作为对象边界上界：\n\n```python\ndef _find_next_create(sql: str, start: int) -> int:\n    # 跳过字符串、注释内的CREATE\n    # 匹配独立的CREATE关键字（前后非字母数字下划线）\n    # 返回位置或-1\n```\n\n### 在 `find_object_end` 中的调用\n\n所有对象类型的终止符查找都有兜底：\n```python\nend = find_semicolon_end(...)  # 或 find_paren_end / find_block_end\nif end >= n:  # 没找到终止符，到达文件末尾\n    next_create = _find_next_create(sql, start + 10)\n    if next_create > start:\n        boundary = next_create\n        while boundary > start and sql[boundary - 1] in ' \\t\\r\\n':\n            boundary -= 1\n        return boundary\n```\n\n## 开发过程关键教训\n\n### patch工具Python缩进问题 — 终极解决方案\n\n**问题**：patch工具修改Python文件时，方法体内的缩进经常被破坏（8空格变4空格、1空格等）。\n\n**终极方案：用Python脚本替换整个方法**，而非用patch做局部替换：\n\n```python\n#!/usr/bin/env python3\nwith open('dm_converter.py') as f:\n    content = f.read()\n\nold_start = '    def _detokenize(self, content: str, token_map: Dict[str, str]) -> str:'\nnew_method = '''    def _detokenize(self, content: str, token_map: Dict[str, str]) -> str:\n        \"\"\"docstring\"\"\"\n        # 方法体（8空格缩进）\n    "}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":null,"editorialQuality":{"score":100,"threshold":65,"status":"thin","wordCount":1614,"uniquenessScore":41,"reasons":["uniqueness-below-45"]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-09T15:58:56.983Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-09T15:58:56.983Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-10T04:37:58.986Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}