alterdb
以 SQLite 為底層存儲格式的自定義數據庫引擎,附帶開箱即用的 Web 管理介面。
狀態:早期開發中 — axum HTTP API 骨架、Askama 渲染的首頁與 SQLite 存儲層(rusqlite + 連接池)已就緒,數據模型 Account(帳戶)與 Database(外部數據庫連接,mysql / postgres)的 CRUD 與 REST API 已可用;Web 介面的登入 / 會話(Cookie + 進程內存會話存儲)、變更密碼與數據庫連接管理頁已完成,連接詳情頁可實際連線目標庫(測試連接狀態、瀏覽數據表),數據表管理頁可檢視欄位結構並新增 / 刪除欄位,數據管理頁可分頁瀏覽並編輯(以主鍵定位修改資料列)表內資料,管理介面其餘部分與查詢引擎仍在開發中。
專案簡介
alterdb 是一個使用 Rust 編寫的自定義數據庫引擎。它不是又一個 SQLite 管理工具,而是:
- 自定義查詢引擎:對外提供自己的查詢介面與數據模型,查詢的解析、規劃與執行均由 alterdb 自行實作;
- SQLite 作為底層存儲:數據最終落在單一 SQLite 檔案中,直接享受其成熟可靠的持久化與事務能力,而無需從零重造存儲層;
- Web 管理介面:以本地伺服器方式運行,打開瀏覽器即可瀏覽數據、執行查詢、管理表結構。
核心特性(規劃中)
- 自定義查詢引擎:查詢的解析與執行由本項目實作,不直接透傳 SQL
- SQLite 存儲後端:單檔案持久化,支援事務
- Web 管理介面:
- 數據庫 / 表 / 結構瀏覽
- 線上執行查詢,結果以表格呈現
- 數據新增、編輯、刪除
- 單一可執行檔部署:編譯為單一 binary,內嵌前端資源,無外部運行時依賴
架構規劃
┌─────────────┐ HTTP ┌───────────────────┐
│ 瀏覽器 │ ◄────────────► │ HTTP API 伺服器 │
│ (Web UI) │ │ (Rust) │
└─────────────┘ └─────────┬─────────┘
│
┌─────────▼─────────┐
│ 查詢引擎 │
│ 解析 / 規劃 / 執行 │
└─────────┬─────────┘
│
┌─────────▼─────────┐
│ 存儲層 │
│ (SQLite 檔案) │
└───────────────────┘
技術選型:
| 項目 | 選擇 | 備註 |
|---|---|---|
| 語言 | Rust(edition 2024) | 需 Rust 1.85+ |
| Web 框架 | axum 0.8 + tokio | REST API;中間件透過 tower-http |
| 模板引擎 | Askama 0.16 | 編譯期模板,HTML 內嵌於執行檔 |
| 存儲 | SQLite(rusqlite bundled) | deadpool-sqlite 連接池;WAL 模式 |
| 外部庫連線 | mysql_async / tokio-postgres | 連接測試與數據表瀏覽;暫未啟用 TLS |
| 密碼雜湊 | argon2 0.5 | 帳戶密碼以 Argon2id + 隨機鹽雜湊存儲,永不落明文 |
快速開始
環境需求:Rust 1.85 或以上版本(本項目使用 edition 2024)。
# 編譯並運行
cargo run
# 伺服器啟動後:
# - 管理介面首頁:http://127.0.0.1:8080/(未登入會被導向 /login)
# - 數據庫連接管理頁:http://127.0.0.1:8080/databases(需登入)
# - 變更密碼頁:http://127.0.0.1:8080/password(需登入)
# - 登入頁:http://127.0.0.1:8080/login(默認帳號 admin / admin)
# - 健康檢查 API:http://127.0.0.1:8080/api/health
# - 帳戶 API:http://127.0.0.1:8080/api/accounts
# - 數據庫連接 API:http://127.0.0.1:8080/api/databases
資料庫檔案默認為工作目錄下的 alterdb.db,可透過環境變數 ALTERDB_DB_PATH 指定其他路徑。
SQLite 數據檔(*.db 等)已被 .gitignore 排除,不會被誤提交到版本庫。
登入與會話
Web 介面頁面(目前為首頁 /)需登入後才能訪問,未登入的請求會被重定向到 /login:
| 方法 | 路徑 | 說明 |
|---|---|---|
| GET | /login |
登入頁(已登入者直接回到首頁);帶 ?changed=1 時顯示密碼變更成功提示 |
| POST | /login |
提交用戶名 / 密碼;成功建立會話並下發 Cookie |
| POST | /logout |
銷毀會話並清除 Cookie |
| GET | /password |
變更密碼頁(需登入) |
| POST | /password |
校驗原密碼後更新密碼;成功即銷毀該帳戶全部會話,導向 /login?changed=1 |
實作要點:
- 密碼以 Argon2id 校驗;帳號不存在時仍執行一次雜湊運算,避免以回應時間差列舉用戶名;已停用(
is_active = false)的帳戶無法登入 - 會話保存在進程內存,以 256 位隨機 token 為鑰匙,透過
HttpOnly+SameSite=LaxCookie 傳遞,有效期 24 小時;伺服器重啟後會話失效(重新登入即可) - 變更密碼需驗證原密碼;新密碼長度 8–128 字符、兩次輸入需一致且不得與原密碼相同;成功後以新 Argon2 雜湊覆寫,該帳戶全部會話(含當前)即刻失效,需以新密碼重新登入
/api/*端點暫不要求會話,後續將統一接入認證
Web 管理介面
登入後的頁面共用側邊導航欄(_shell.html 應用外殼):左側為品牌、主導航(首頁 / 數據庫,當前頁以 aria-current="page" 高亮)與底部的登入者資訊、變更密碼入口及登出;窄屏(≤ 48rem)自動折疊為頂欄。登入頁與 404 頁保持無側欄的獨立版式。頁面外殼由 Askama 渲染,數據操作由內嵌 vanilla JS 調用 /api/* 完成(無任何外部前端依賴):
全站支援深淺色主題:預設跟隨系統偏好(color-scheme: light dark),點擊側邊欄底部(獨立版式頁為頁首右側)的「深色 / 淺色模式」按鈕可手動固定,選擇保存在 localStorage(鍵 theme),並由 <head> 內的同步內聯腳本在首幀繪製前重新套用,避免刷新閃爍。配色完全由 color-scheme 驅動(currentColor 混色與 light-dark()),無任何主題相關的服務端狀態。
| 路徑 | 說明 |
|---|---|
/ |
首頁:服務資訊與各管理入口 |
/databases |
數據庫連接列表(名稱連到詳情頁;含刪除) |
/databases/new |
新增連接(獨立表單頁) |
/databases/{id} |
連接詳情頁:服務端渲染連接資訊,頁面載入即自動測試連接(狀態徽章 + 伺服器版本 + 延遲)並列出目標庫當前 schema 的數據表(名稱連到表管理頁);可「重新測試」 |
/databases/{id}/edit |
編輯連接(獨立表單頁,服務端預填現值;連接不存在返回 404) |
/databases/{id}/tables/{table} |
數據表管理頁:欄位結構(名稱 / 類型 / 可空 / 默認值,支援新增與刪除欄位),由內嵌 JS 即時連線目標庫取得;頁面不請求表內資料 |
/databases/{id}/tables/{table}/data |
數據管理頁:表內資料分頁瀏覽(每頁 25–200 列、顯示總列數)與編輯資料列(以主鍵定位,逐欄輸入、可空欄位可設 NULL),由內嵌 JS 即時連線目標庫取得 |
/password |
變更密碼(表單:原密碼 / 新密碼 / 確認新密碼;成功後全部會話失效並導向登入頁) |
/databases 相關頁面要點:
- 埠留空按類型自動補默認(MySQL 3306 / PostgreSQL 5432)
- 編輯頁由服務端預填現值,但密碼永不回填(API 與頁面皆不輸出密碼);編輯時密碼欄留空表示保留原密碼(對應 PUT 語義:
password鍵缺省=保留、空字串=清空、有值=覆寫) - 表單提交由 JS 調用
/api/databases,成功後跳回列表頁;操作錯誤(驗證失敗、名稱衝突等)以頁面錯誤條展示 API 返回的錯誤訊息 - 詳情頁(
/databases/{id})載入時自動並行發起連接測試與數據表查詢,狀態以徽章呈現(已連接 / 無法連接 / 測試中),數據表類型映射為「表 / 視圖」 - 表管理頁(
/databases/{id}/tables/{table})載入時僅載入欄位結構,不請求表內資料;工具列「數據管理」連到對應的數據管理頁 - 數據管理頁(
/databases/{id}/tables/{table}/data)載入時載入第一頁資料;支援換頁、調整每頁列數與手動刷新,NULL 儲存格以斜體NULL呈現,過長內容截斷並以懸浮提示顯示全文;頁面對視圖(VIEW)同樣適用 - 數據管理頁可編輯資料列:有主鍵的表每列附「編輯」按鈕,對話框逐欄輸入(可空欄位附 NULL 勾選),只送出有變更的欄位,以編輯前的主鍵原值定位資料列;沒有主鍵的表(含視圖)僅能瀏覽並顯示提示;目標列已被刪除或主鍵已變更時返回衝突錯誤並保留表單內容
- 表管理頁的欄位結構區可新增欄位(內嵌表單:名稱 / 類型(含常用建議)/ 可空(默認允許)/ 默認值)與刪除欄位(紅色按鈕,彈出確認對話框後才執行);結構變更成功後自動刷新欄位結構
- 所有動態內容經
textContent寫入,不以字串拼接 HTML
帳戶 API
管理介面用戶帳戶的 CRUD 端點(後續將由自定義查詢引擎統一驅動):
| 方法 | 路徑 | 說明 |
|---|---|---|
| GET | /api/accounts |
列出所有帳戶 |
| POST | /api/accounts |
建立帳戶;username、email 必填且全表唯一 |
| GET | /api/accounts/{id} |
取得單一帳戶,不存在時返回 404 |
| PUT | /api/accounts/{id} |
覆寫帳戶可編輯欄位(updated_at 自動刷新) |
| DELETE | /api/accounts/{id} |
刪除帳戶 |
輸入會先被正規化(去除首尾空白、display_name 缺省時同 username、is_active 缺省為啟用);驗證失敗返回 400,唯一欄位衝突返回 409。
默認管理員帳號
資料庫初始化時會自動建立默認管理員帳號(僅在不存在時建立,重啟不會覆蓋日後的變更):
- 用戶名:
admin - 密碼:
admin
密碼以 Argon2 雜湊存儲,password_hash 欄位永不透過 API 序列化輸出;既有舊版資料庫啟動時會自動遷移(補上 password_hash 欄位)。初始密碼即帳號名,建議儘早變更。
# 建立帳戶
curl -X POST http://127.0.0.1:8080/api/accounts \
-H 'Content-Type: application/json' \
-d '{"username": "alice", "email": "alice@example.com", "display_name": "Alice"}'
# 列出所有帳戶
curl http://127.0.0.1:8080/api/accounts
數據庫連接 API
管理外部數據庫連接(MySQL / PostgreSQL)的 CRUD 端點,以及對目標庫的實際連線操作(測試連接、列出數據表、檢視欄位結構、分頁瀏覽與修改資料,由 mysql_async / tokio-postgres 驅動):
| 方法 | 路徑 | 說明 |
|---|---|---|
| GET | /api/databases |
列出所有連接 |
| POST | /api/databases |
建立連接;name 必填且全表唯一 |
| GET | /api/databases/{id} |
取得單一連接,不存在時返回 404 |
| PUT | /api/databases/{id} |
覆寫連接可編輯欄位(updated_at 自動刷新) |
| DELETE | /api/databases/{id} |
刪除連接 |
| POST | /api/databases/{id}/test |
實際連線目標庫並查詢伺服器版本;成功與失敗都是 200(結果在 ok 欄位,附 server_version / error 與 elapsed_ms),連接記錄不存在返回 404 |
| GET | /api/databases/{id}/tables |
列出目標庫當前 schema 的數據表(name + kind,按名稱排序);連不上目標時返回 502 並附錯誤訊息 |
| GET | /api/databases/{id}/tables/{table}/columns |
列出資料表欄位結構(name / type / nullable / default / primary_key,按表定義順序);資料表不存在返回 404 |
| POST | /api/databases/{id}/tables/{table}/columns |
新增欄位(ALTER TABLE … ADD COLUMN):name(≤64 字符)、type(原生 SQL 片段,如 varchar(255))、nullable(缺省允許)、default(可選,原生 SQL 片段);欄位名衝突返回 409,成功返回 204 |
| DELETE | /api/databases/{id}/tables/{table}/columns/{column} |
刪除欄位(ALTER TABLE … DROP COLUMN,資料一併丟失且無法復原);欄位不存在返回 404,成功返回 204 |
| GET | /api/databases/{id}/tables/{table}/rows |
分頁瀏覽資料:?limit=&offset=(limit 1–200、缺省 50;offset ≥ 0),回應含 columns、字串化的 rows(NULL 為 null)、total(COUNT(*))與生效的分頁參數 |
| PATCH | /api/databases/{id}/tables/{table}/rows |
修改資料列:body 為 {"where": {主鍵欄位: 原值}, "set": {欄位: 新值}}(值為字串或 null=設為 NULL,按欄位型別隱式轉換);where 須恰好覆蓋主鍵欄位,set 欄位須存在且值 ≤65536 字符;目標列不存在(已被刪除或主鍵已變更)返回 409,成功返回 204 |
連線操作要點:
- 每次操作建立全新連線、用完即斷(不做連接複用);整體逾時 5 秒,目標不可達時不會長時間佔住請求
- 錯誤訊息展平驅動庫的錯誤鏈,可直接區分連線被拒、認證失敗等原因(如
db error: FATAL: password authentication failed for user "app") - 表名 / 欄位名拼入 SQL 前先經 information_schema 白名單核對(不存在的表或欄位直接 404,不進入 SQL),再以驅動各自的引號規則轉義(MySQL 反引號、PostgreSQL 雙引號),杜絕經標識符注入
- 新增欄位的
type/default是原生 SQL 片段(管理工具的固有語義):驗證攔截分號、註解起始符(--、/*)與控制字元,配合兩驅動皆走的預備語句協議(不啟用多語句),即使片段被惡意構造也無法執行第二條語句;目標端語義錯誤(如對有資料的表加 NOT NULL 無默認值欄位)由驅動錯誤原樣回報(502) - 刪除欄位屬不可復原的破壞性操作;
/api/*端點暫不要求會話(接入認證前,對外暴露埠時須自行做好網路隔離) - 瀏覽資料時儲存格一律轉為字串表示(PostgreSQL 逐欄
::text轉換),NULL 保留為null由前端呈現 - 修改資料列以主鍵定位(欄位是否為主鍵經
TABLE_CONSTRAINTS連接KEY_COLUMN_USAGE判定):MySQL 的值以預備語句參數綁定(並啟用CLIENT_FOUND_ROWS,使 affected rows 為匹配列數);PostgreSQL 的值以引號加倍轉義的字面值拼入(標準遵循字串下反斜線無特殊含義),未知型別字面值由伺服器隱式轉換為欄位型別;型別無法轉換、違反約束等錯誤由目標庫原樣回報(502) - 暫未啟用 TLS:對目標庫的連線為明文,僅適合配合受信任網路使用(MySQL 的
caching_sha2_password認證在無 TLS 下經伺服器 RSA 金鑰完成)
輸入欄位:
| 欄位 | 必填 | 說明 |
|---|---|---|
name |
是 | 連接顯示名稱,全表唯一,≤ 128 字符 |
type |
是 | 數據庫類型,目前僅支援 "mysql" / "postgres" |
host |
是 | 主機名或 IP,≤ 255 字符 |
port |
否 | 埠,1–65535;缺省按類型補 3306(mysql)/ 5432(postgres) |
username |
是 | 連接用戶名,≤ 64 字符 |
password |
否 | 連接密碼,默認空字串;永不透過 API 序列化輸出(連線需明文可用,故不做單向雜湊) |
database_name |
是 | 目標資料庫名,≤ 64 字符 |
輸入會先被正規化(去除首尾空白,密碼除外;port 缺省補默認);驗證失敗返回 400,name 衝突返回 409,type 為不支援的類型時由反序列化直接拒絕。
# 建立連接(port 自動補 3306,回應不含 password)
curl -X POST http://127.0.0.1:8080/api/databases \
-H 'Content-Type: application/json' \
-d '{"name": "shop-prod", "type": "mysql", "host": "10.0.0.5",
"username": "app", "password": "secret", "database_name": "shop"}'
# 列出所有連接
curl http://127.0.0.1:8080/api/databases
開發路線圖
- 階段一:存儲層 MVP(進行中) — 連接池(deadpool-sqlite)與 WAL 初始化已完成;Account 模型的 CRUD 與 REST API(
/api/accounts)已就緒,通用表管理開發中 - 階段二:自定義查詢引擎 — 查詢語法定義、解析與執行
- 階段三:HTTP API(進行中) — axum 伺服器骨架、健康檢查與帳戶 CRUD 端點已完成,登入 / 登出 / 會話已就緒(頁面已受保護,API 端點接入認證開發中),其餘 API 端點開發中
- 階段四:Web 管理介面(進行中) — 登入頁、會話、變更密碼頁、數據庫連接管理頁(
/databases)、連接詳情頁(實際連線測試 + 數據表瀏覽)、數據表管理頁(欄位結構 + 新增 / 刪除欄位)與數據管理頁(表內資料分頁瀏覽 + 以主鍵定位編輯資料列)已完成;瀏覽器端 UI:查詢執行、新增 / 刪除資料列開發中 - 進階功能 — 匯入 / 匯出、備份、多用戶與權限等
專案現狀與結構
目前已完成 HTTP API 骨架、存儲層接線、首頁渲染與登入 / 會話(axum + rusqlite + askama),並以 Account(帳戶)打通「模型 → 存儲 → REST API」全鏈路;Database(外部數據庫連接,mysql / postgres)為第二個模型,同構接入同一鏈路,且已具備實際連線能力(connect 模組:測試連接、列出數據表、檢視欄位結構、分頁瀏覽資料):
alterdb/
├── Cargo.toml # 項目清單(axum / tokio / rusqlite / askama / mysql_async / tokio-postgres 等)
├── .gitignore
├── README.md
├── templates/ # Askama 模板(編譯期內嵌進執行檔)
│ ├── base.html # 頁面骨架(基礎樣式與布局塊)
│ ├── _shell.html # 應用外殼:側邊導航欄(品牌 / 導航 / 登入者與登出)+ 主內容區
│ ├── index.html # 首頁(登入後可見)
│ ├── databases.html # 數據庫連接列表頁(外殼 + 內嵌 JS 調用 /api/databases)
│ ├── database_detail.html # 連接詳情頁(連接資訊 + 連接狀態 + 數據表,內嵌 JS 即時連線)
│ ├── table_detail.html # 數據表管理頁(欄位結構管理,內嵌 JS 即時連線)
│ ├── table_data.html # 數據管理頁(表內資料分頁瀏覽 + 編輯資料列,內嵌 JS 即時連線)
│ ├── database_form.html # 新增 / 編輯連接共用表單頁(服務端預填 + 提交 JS)
│ ├── password.html # 變更密碼頁(原密碼 + 新密碼兩次確認)
│ ├── login.html # 登入頁(無側欄)
│ └── 404.html # 404 頁(無側欄)
└── src/
├── main.rs # 程序入口:初始化存儲層並啟動 HTTP 伺服器
├── state.rs # 應用共享狀態(資料庫連接池 + 會話存儲)
├── session.rs # 會話管理:進程內存存儲、Cookie 解析與 CurrentUser 提取器
├── connect.rs # 實際連線能力:測試連接(版本 / 延遲)、列出數據表、檢視欄位結構與分頁瀏覽資料(mysql_async / tokio-postgres)
├── models/ # 模型層:數據結構與輸入驗證(不含存儲細節)
│ ├── account.rs # Account 模型(帳戶)與輸入正規化、密碼雜湊
│ └── database.rs # Database 模型(外部數據庫連接)與 DatabaseType 枚舉
├── storage/ # 存儲層:各模型對應表的 SQL 與 CRUD
│ ├── mod.rs # 連接池建立、資料庫初始化與共用錯誤類型
│ ├── account.rs # accounts 表結構與 CRUD
│ └── database.rs # databases 表結構與 CRUD
└── controllers/ # 控制器層(HTTP API + 頁面)
├── mod.rs # 路由匯總
├── accounts.rs # 帳戶 CRUD API(/api/accounts)
├── auth.rs # 登入 / 登出 / 變更密碼(/login、/logout、/password)
├── database.rs # 數據庫連接 CRUD API 與連線操作(/api/databases)
├── health.rs # 健康檢查 API(GET /api/health,含資料庫狀態)
└── pages.rs # 頁面渲染(GET /、/databases 等,需登入)
開發
cargo build # 編譯
cargo test # 執行測試
cargo run # 運行
授權
尚未決定(TBD)。