Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## [Unreleased]

### 🚀 新增功能
- **可驗證邏輯搬移的第一階段**:SQL Server 專用相容工具加入受限雙向跨版本;Avalonia 新增 SQLite STRICT ↔ PostgreSQL 的整數/二進位搬移,預覽來源/目標版本、schema/data 轉換與阻擋原因,確認後才寫入另建空目標。單一交易、分批、內容校驗與不確定狀態禁止重試,不將一般資料傳輸或原生備份宣稱為無限制跨版本/跨引擎還原。

- **SQL Server 降版相容搬移(Windows,有限範圍預覽版)**:選取實際目標版本,預覽結構與列數並輸入目標名稱確認後,只寫入另建的空資料庫。僅 dbo 一般 heap 與白名單型別;identity、constraints、indexes、triggers、procs、schema 相依與進階功能明確阻擋,定序必須相同。分批寫入單一交易,內容與結構驗證通過才提交,不自動重試;2016–2025 尚僅離線/mock 驗證,不宣稱真引擎矩陣通過。

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ Windows 完整版介面支援繁體中文與英文;資料庫密碼、SSH 密

目前發版版本:`v1.0.0.22`,最新版請看 [GitHub Releases](https://github.com/shadowjohn/mySQLPunk/releases)。

未發版的可驗證邏輯搬移:Windows SQL Server 專用工具支援 2016–2025 受限雙向跨版本;Avalonia「邏輯搬移」提供 SQLite STRICT ↔ PostgreSQL 的 INTEGER/bigint、BLOB/bytea 子集。只接受另建空目標、明確確認、單一交易與內容校驗;PK/FK、索引、default、identity、字串定序、時間與其他未忠實轉換的功能在預檢阻擋,不是任意引擎/版本的原生 restore。支援方向與實機/mock 證據區分見 [搬移矩陣](docs/LOGICAL_MIGRATION_MATRIX.md)。

目前的 `v1.0.0.22` GitHub Release 同時提供 Windows x64 完整版 setup、self-contained Linux x64/ARM64 安裝壓縮檔,以及 macOS Intel/Apple Silicon `.app.zip`。Linux 壓縮檔內附使用者層級的 `install.sh`/`uninstall.sh`;macOS 預覽目前採 ad-hoc 簽署、尚未 Apple notarize。每個跨平台資產都有獨立 `.sha256` 可驗證完整性。完整變更請見 `CHANGELOG.md`。

## 開發環境
Expand Down
3 changes: 3 additions & 0 deletions docs/FEATURE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -624,3 +624,6 @@

桌面 gate 等完整 task 停止才釋放,取消結果不寫回 grid。隔離 smoke 實際執行 50,000,000 次 recursive 計算,驗證快速返回、三次取消與後續查詢、一般 SQL 錯誤及已提交 DML 不回滾。Xvfb harness 使用正式 SQL/Cancel handlers 驗證 UI tick、重複點擊、busy edit/delete/connect、取消後查詢。這不是資料同步或所有 metadata 路徑的背景化保證;鎖等待仍受 provider timeout 限制。

# 可驗證雙向與跨引擎邏輯搬移

Windows SQL Server 專用工具加入受限升版/降版/同版本方向;Avalonia 工作台新增 SQLite STRICT 與 PostgreSQL 的 INTEGER/bigint、BLOB/bytea 搬移。兩種工具都只寫入明確確認的新空目標,不把未支援型別、鍵、索引、預設值、trigger/procedure 等靜默丟掉。方向、實際版本、交易/取消/不確定重試及測試矩陣見 [LOGICAL_MIGRATION_MATRIX.md](LOGICAL_MIGRATION_MATRIX.md)。一般資料傳輸與 SQL 文字轉換不等於此處的可驗證邏輯還原。
43 changes: 43 additions & 0 deletions docs/LOGICAL_MIGRATION_MATRIX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# 可驗證邏輯搬移:分階段矩陣

邏輯搬移讀取 schema/資料、轉換到目標 DDL、參數化寫入並驗證內容,不是原生 restore。既有一般 DataTransferService 的型別對映及列數 checkpoint、SchemaSyncScriptService 的手動修正 SQL,不具備這個工具的完整預檢與內容保證,沒有直接拿來當跨引擎還原器。

## 已落地方向

| 來源 → 目標 | 程式允許的版本 | 可執行範圍 | 驗證區分 |
| --- | --- | --- | --- |
| SQL Server → SQL Server | 2016/2017/2019/2022/2025 任意雙向及同版本,共 25 組 | Windows 既有專用工具;dbo heap、既有型別白名單、同定序、無 advanced features | 25 組預檢及交易 mock;不是 25 組真引擎驗證 |
| SQLite → SQLite | 3.37–3.53 | STRICT、INTEGER/BLOB、NULL/NOT NULL | 本機實際 SQLite 3.53.3;歷史 minor 版本未逐一跑 |
| SQLite → PostgreSQL | SQLite 3.37–3.53 → PostgreSQL 14–17 | INTEGER → bigint、BLOB → bytea | 既有隔離 CI PostgreSQL 16 fixture 有雙向內容 round-trip 測試;以該 SHA 的 CI 結果為準 |
| PostgreSQL → SQLite | PostgreSQL 14–17 → SQLite 3.37–3.53 | bigint → INTEGER、bytea → BLOB | 同上;14/15/17 未實機跑 |
| PostgreSQL → PostgreSQL | 14–17 雙向及同版本 | 同一 bigint/bytea 子集與全部預檢 | 邏輯共用路徑;沒有完整跨版本真機矩陣 |

SQLite/PostgreSQL 工具入口在 Avalonia 工作台「邏輯搬移」。來源使用目前連線的記憶體設定;目標選已儲存的連線及另建空資料庫,明確選版本,必要密碼只由使用者輸入,不額外讀秘密庫。獨立 provider owner 保有 SSH tunnel,所有開啟/失敗/結束都有 scope 釋放;不從 redacted ConnectionString 重建。

## 寫入前安全檢查

- SQLite 檔案必須已存在;來源唯讀開啟,目標 ReadWrite(不自動建立),使用 private cache、停用 pooling。缺檔、非 UTF-8、非 STRICT、virtual table、生成欄位、default、check、PK/FK、index、trigger、view、內部 sequence 等一律拒絕。
- SQLite CREATE TABLE 只接受可證明完整的 INTEGER/BLOB STRICT grammar;不是從欄位猜 DDL 後丟掉未辨識 clause。PostgreSQL 查正式 catalog:額外 schema、extension、view、sequence、constraint、index、default、identity、trigger(含 database event trigger)、function/procedure、rule、policy、user type、partition、inheritance、RLS、unlogged、large object、publication/subscription、自訂定序/轉換/運算子/文字搜尋物件等都阻擋。查詢失敗也不放行;不自動搬移帳號、權限或安全設定。
- 名稱限制 ASCII identifier 63 字元;轉到 SQLite 另阻擋只差大小寫的表/欄位。文字 encoding/collation、時間與浮點/decimal precision、identity/sequence 的忠實轉換尚未實作,因此不是靜默轉成 TEXT 或捨棄鍵/預設值。
- 預覽顯示實際版本、方向、目標、表、列數、轉換 SQL 與阻擋原因,不顯示資料值。來源內容也納入確認 fingerprint;輸入完整目標名稱才可執行,確認後來源內容(包括相同列數下的變更)/結構/實際引擎識別/目標變動會拒絕。
- SQLite/PostgreSQL 全部 DDL+資料在單一目標交易。SQLite 用 immediate 交易;PostgreSQL 持有工具專用 advisory lock,來源全部表 SHARE lock 後讀取並在提交前再檢查。這不是在線無鎖搬移;請用隔離副本/維護時段,外部工具不會自動遵守 advisory lock,也不能宣稱能禁止其他管理者在最後檢查之後建立額外物件。
- 每批至多 100 列/200 參數,最多 100,000 列;每欄至多 1 MiB,每輪快照資料至多 64 MiB。驗證 schema、列數、Int64/binary/NULL 的 SHA-256 多重集合(保留重複列)。不保證隱含 rowid、實體排序或儲存配置跨引擎相同。
- 取消等實際工作停止,SQLite 用原生 progress handler,PostgreSQL 使用 provider cancellation;工作台 gate 持續到對話框關閉,不能同時 SQL/edit/delete/connect。變更目標/版本/認證即失效原預覽。

## 重試與恢復

沒有逐表 checkpoint:全部驗證後才提交。失敗且確認回滾的結果是 `RolledBack`,須重新預檢;已驗證提交是 `Verified`,不再寫入。commit 開始後失敗、回覆遺失或回滾失敗是 `Uncertain`,UI 停止再次預檢/寫入,禁止盲目重試,必須由 DBA 核對;不宣稱放棄 Task 就已停止或回滾。已有內容的目標即使是前次結果也不能自動 resume。

## 尚未實作、執行前拒絕

MySQL/MariaDB、SQL Server 與 SQLite/PostgreSQL 之間的通用 verified conversion 尚未接入;MySQL 目標的 implicit-commit DDL 不能沿用此單一交易保證,需另一套可驗證的 staging/恢復設計。Windows Oracle/Snowflake 也未加入此 verified 路徑。MongoDB、Redis/Garnet 等非關聯式 model 沒有通用無損表格映射,不能當同義還原目標。選不支援的 provider,服務在開連線前明確拒絕。

有 PK/FK、索引、字串定序、時間、decimal、identity、procedure/trigger 的一般資料庫不能在此階段搬移;替代路徑是引擎官方 schema/data migration 工具與人工轉換/驗證,不是宣稱 bacpac 可無限制降版。新版 SQL Server .bak 仍不能直接還原到舊引擎,compatibility level 不改備份格式;既有原生 restore 預檢繼續保留。

## 回歸證據

實際隔離 SQLite:1,005 列、多批、Int64 極值、空/非空 bytes、NULL、重複列、反向內容一致、錯誤確認、缺目標不自動建立、來源同列數內容改變、unsupported features、部分寫入失敗、內容/結構驗證失敗、途中取消,以及真正 commit 後注入 ack lost(保留資料且禁止重試)。不是單靠手動設 mock Blockers。

Avalonia/Xvfb:正式入口持有主視窗 gate、預覽、目標變更失效、錯誤確認不寫入、實際確認寫入且禁止重複 resume。既有跨平台 CI fixture 另測 SQLite ↔ PostgreSQL 16,包括正式 catalog 逐項加 feature/移除 feature 後恢復可預檢。

真 SQL Server 跨版本、SQL auth+SSH 真機、PostgreSQL 14/15/17、SQLite 歷史版本及非關聯式轉換皆沒有完整矩陣證據;不得把 mock/同版本一般 provider round-trip 當全部資料庫版本已通過。
11 changes: 4 additions & 7 deletions docs/MSSQL_COMPATIBILITY_MIGRATION.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
# SQL Server 降版相容搬移(有限範圍預覽版)
# SQL Server 雙向跨版本相容搬移(有限範圍預覽版)

這是 Windows 資料庫右鍵的獨立邏輯搬移工具,不是完整資料庫降版器。一般資料傳輸仍只驗證列數,不能當成降版驗證。

## 引擎矩陣與驗證狀態

| 來源引擎 | 可選舊版目標 | 狀態 |
| 來源引擎 | 可選目標 | 狀態 |
| --- | --- | --- |
| 2025 (17) | 2022 (16)、2019 (15)、2017 (14)、2016 (13) | 預檢與交易流程只有 mock 驗證 |
| 2022 (16) | 2019 (15)、2017 (14)、2016 (13) | 同一受限實作;沒有真引擎矩陣證據 |
| 2019 (15) | 2017 (14)、2016 (13) | 同上 |
| 2017 (14) | 2016 (13) | 同上 |
| 2016 (13) 或更早/未知 | 無 | 拒絕;不猜測最低版本 |
| 2016–2025 (13–17) | 2016–2025 (13–17),含升版、降版、同版本 | 25 組預檢與交易流程 mock;沒有完整真引擎矩陣證據 |
| 更早/未知 | 無 | 拒絕;不猜測最低版本 |

UI 不預選引擎,必須明確選取目標,並與 SERVERPROPERTY 實際版本一致。這張表代表程式允許預檢的範圍,不代表各版本已通過真 SQL Server 驗證,也不宣稱生產支援。Linux/macOS 目前只有共用核心 mock 測試,尚無此搬移 UI。

Expand Down
9 changes: 9 additions & 0 deletions docs/RELEASE_CANDIDATE_1.0.0.23.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# 下一批發布準備(尚未核准發布)

建議候選版號 `1.0.0.23`,保持目前四段版本規則;這不是已發布版本,也不在本輪改 AssemblyVersion/README 發版版本、打 tag、加 Release-Now 或 dispatch force。

候選重點:已合併的連線 ownership、操作 gate、checkpoint、DDL trigger 補正,SQLite SQL 背景執行與真正取消,以及受限雙向/跨引擎邏輯搬移。Release 說明必須明列 schema/data 子集與真機矩陣限制,不能寫成「所有資料庫都可無損還原」。

發布前還需要本輪獨立驗收,以及待發布精確 SHA 的 Windows/四架構 CI 成功。SQLite 修復 head `187b5c3ba55deef07c9d82bead45fa03df56984d` 的 PR #44 CI 已通過:Windows `38507339195`、跨平台 `38507339160`;後續功能 head 的證據另存交付記錄,不以初輪 CI 替代。

收到同一工作階段的最終發布指示後,沿用 `Auto Release Gate` 的 `force` 原子版更完整 Unreleased 批次;流程會同步 CHANGELOG、AssemblyInfo、README、tag 及既有 Release workflow。繼續要求 Windows setup、四種 self-contained 壓縮檔、四個 SHA-256 共九資產一次齊全,沿用既有簽章/macOS ad-hoc 狀態,不換發布目的地、不繞過建置或簽章檢查。
17 changes: 17 additions & 0 deletions mySQLPunk.Core/Models/LogicalMigration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace MySqlPunk.Core.Models;

public sealed record LogicalMigrationColumn(string Name, string Kind, bool Nullable);
public sealed record LogicalMigrationTable(string Name, IReadOnlyList<LogicalMigrationColumn> Columns, long Rows, string ContentHash);
public sealed record LogicalMigrationPreview(
string SourceVersion, string TargetVersion, string TargetDatabase,
IReadOnlyList<LogicalMigrationTable> Tables, IReadOnlyList<string> Statements,
IReadOnlyList<string> Blockers, string Fingerprint)
{
public bool CanStart => Blockers.Count == 0 && Tables.Count > 0;
}

public enum LogicalMigrationOutcome { Verified, RolledBack, Uncertain, Refused }
public sealed record LogicalMigrationResult(LogicalMigrationOutcome Outcome, long Rows, string Report)
{
public bool RetryBlocked => Outcome is LogicalMigrationOutcome.Uncertain or LogicalMigrationOutcome.Verified;
}
25 changes: 25 additions & 0 deletions mySQLPunk.Core/Providers/AdoDatabaseSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,31 @@ protected AdoDatabaseSession(ConnectionProfile profile)

protected abstract DbConnection CreateConnection(string? database);

internal async Task<DbConnection> OpenMigrationConnectionAsync(string database, CancellationToken cancellationToken, bool readOnly = false)
{
var connection = await CreateConnectionAsync(database, cancellationToken).ConfigureAwait(false);
try
{
if (connection is Microsoft.Data.Sqlite.SqliteConnection sqlite)
{
var builder = new Microsoft.Data.Sqlite.SqliteConnectionStringBuilder(sqlite.ConnectionString)
{
Mode = readOnly ? Microsoft.Data.Sqlite.SqliteOpenMode.ReadOnly : Microsoft.Data.Sqlite.SqliteOpenMode.ReadWrite,
Cache = Microsoft.Data.Sqlite.SqliteCacheMode.Private,
Pooling = false
};
sqlite.ConnectionString = builder.ConnectionString;
}
await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
return connection;
}
catch
{
await connection.DisposeAsync().ConfigureAwait(false);
throw;
}
}

/// <summary>
/// Creates a closed connection after making sure the SSH tunnel (if the profile enables one) is up. All
/// database work goes through here so a tunnelled profile can never fall back to dialling the host directly.
Expand Down
Loading
Loading