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
10 changes: 7 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- `cd frontend && npm run check` (Biome 格式化与静态检查)
- `cd backend && go test -v ./...` (后端集成测试,需 Docker)
- `cd frontend && npm run docker:up` (启动测试所需的 PG/MySQL 容器)
- `cd backend && ./scripts/gen_proto.sh` (生成 gRPC 代码并同步协议文件到前端)

## Project overview

Expand All @@ -19,6 +20,7 @@ vstable 是一款专为开发者设计的现代数据库管理工具,支持可
- **Frontend**: React 19 (TypeScript), TailwindCSS 4.0, Monaco Editor
- **Desktop Runtime**: Electron 40, electron-vite
- **Backend Engine**: Go 1.24 (vstable-engine)
- **Communication**: gRPC, Protocol Buffers (Strict types)
- **Database Drivers**: `pgx/v5` (PostgreSQL), `go-sql-driver/mysql` (MySQL)
- **Testing**: Playwright (E2E), Vitest (Unit), Go test (Integration)
- **Infrastructure**: Docker Compose (Database testing environments)
Expand All @@ -28,17 +30,19 @@ vstable 是一款专为开发者设计的现代数据库管理工具,支持可
该项目采用解耦的三层架构:
1. **Frontend (React)**: 基于 React 19、TailwindCSS 4.0 和 Monaco Editor 构建的现代用户界面。负责处理用户交互、SQL 编辑以及可视化的表结构设计。
2. **Desktop Runtime (Electron)**: 作为操作系统与 Web 内容之间的桥梁。主进程(Main process)负责管理 Go 引擎的生命周期、持久化存储、IPC 路由以及原生窗口管理。
3. **Backend Engine (Go)**: 一个使用 Go 1.24 编写的高性能守护进程。它在本地运行并向 Electron 主进程暴露 REST API(例如 `/api/connect`, `/api/query`)。它承担繁重的计算任务,包括数据库连接管理、AST 解析,以及基于状态对齐进行 Schema Diff 并生成精确的 DDL 语句。
3. **Backend Engine (Go)**: 一个使用 Go 1.24 编写的高性能守护进程。它在本地运行并向 Electron 主进程暴露 gRPC API(定义于 `vstable.proto`)。它承担繁重的计算任务,包括数据库连接管理、AST 解析,以及基于状态对齐进行 Schema Diff 并生成精确的 DDL 语句。

## Major modules and interfaces

- **Go Engine (`backend/`)**:
- `internal/ast`: 核心 Schema Diff 引擎。提供 AST 类型定义以及特定数据库方言(PostgreSQL/MySQL)的编译器,用于基于状态对齐生成精确的 DDL Diff。
- `internal/db`: 数据库连接管理器和驱动程序抽象。
- `main.go`: 处理来自 Electron 主进程 HTTP 请求的路由服务。
- `main.go`: 启动 gRPC Server,处理来自 Electron 主进程的远程过程调用。
- `scripts/gen_proto.sh`: 构建脚本,用于从 `.proto` 文件生成 Go 代码,并同步协议定义到前端资源目录供运行时加载。
- **Electron Main (`frontend/src/main/`)**:
- `daemon.ts`: 管理 Go 后端引擎进程的生命周期(启动、日志记录和停止)。
- `index.ts`: 处理 IPC 路由(如 `db:connect`, `db:query`,并通过 HTTP 代理到 Go 引擎)以及窗口管理。
- `grpcClient.ts`: 封装 gRPC 客户端,通过严格类型的 Protobuf 协议与后端引擎通信。
- `index.ts`: 处理 IPC 路由(如 `db:connect`, `db:query`,并通过 `grpcClient` 调用 Go 引擎)以及窗口管理。
- `store.ts`: 处理应用程序配置、加密凭据以及工作区状态(标签页、会话)的持久化。
- **React Renderer (`frontend/src/renderer/`)**:
- `features/`: 包含核心功能模块:
Expand Down
163 changes: 163 additions & 0 deletions backend/api/vstable.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
syntax = "proto3";

package vstable;
option go_package = "vstable-engine/internal/pb";

import "google/protobuf/struct.proto";
import "google/protobuf/wrappers.proto";

service EngineService {
rpc Ping(PingRequest) returns (PingResponse);
rpc Connect(ConnectRequest) returns (ConnectResponse);
rpc Disconnect(DisconnectRequest) returns (DisconnectResponse);
rpc Query(QueryRequest) returns (QueryResponse);
rpc GenerateAlterTable(DiffRequest) returns (GenerateSQLResponse);
rpc GenerateCreateTable(DiffRequest) returns (GenerateSQLResponse);
}

message PingRequest {}

message PingResponse {
string status = 1;
}

message ConnectRequest {
string id = 1;
string dialect = 2;
string dsn = 3;
}

message ConnectResponse {
bool success = 1;
}

message DisconnectRequest {
string id = 1;
}

message DisconnectResponse {
bool success = 1;
}

message QueryRequest {
string id = 1;
string sql = 2;
google.protobuf.ListValue params = 3;
}

message FieldInfo {
string name = 1;
string type = 2;
}

message QueryResponse {
bool success = 1;
repeated google.protobuf.Struct rows = 2;
repeated FieldInfo fields = 3;
}

message ColumnDefinition {
string id = 1;
string name = 2;
string type = 3;
repeated string enum_values = 4;
google.protobuf.Value length = 5;
google.protobuf.Value precision = 6;
google.protobuf.Value scale = 7;
bool nullable = 8;
google.protobuf.StringValue default_value = 9;
bool is_default_expression = 10;
bool is_primary_key = 11;
bool is_auto_increment = 12;
bool is_identity = 13;
string comment = 14;
string pk_constraint_name = 15;
int32 original_index = 16;
ColumnDefinition original = 17;
}

message IndexDefinition {
string id = 1;
string name = 2;
repeated string columns = 3;
bool is_unique = 4;
IndexDefinition original = 5;
}

message ForeignKeyDefinition {
string id = 1;
string name = 2;
repeated string columns = 3;
string referenced_table = 4;
repeated string referenced_columns = 5;
string on_delete = 6;
string on_update = 7;
ForeignKeyDefinition original = 8;
}

message CheckConstraintDefinition {
string id = 1;
string name = 2;
string expression = 3;
CheckConstraintDefinition original = 4;
}

message ViewDefinition {
string id = 1;
string name = 2;
string definition = 3;
ViewDefinition original = 4;
}

message TriggerDefinition {
string id = 1;
string name = 2;
string timing = 3;
string event = 4;
string table_name = 5;
string definition = 6;
bool enabled = 7;
TriggerDefinition original = 8;
}

message RoutineDefinition {
string id = 1;
string name = 2;
string type = 3;
string definition = 4;
RoutineDefinition original = 5;
}

message DatabaseConfig {
string charset = 1;
string collation = 2;
string engine = 3;
int32 auto_increment_offset = 4;
}

message DiffRequest {
string dialect = 1;
string schema = 2;
string table_name = 3;
string old_table_name = 4;
repeated ColumnDefinition columns = 5;
repeated IndexDefinition indexes = 6;
repeated ColumnDefinition deleted_columns = 7;
repeated IndexDefinition deleted_indexes = 8;
repeated ForeignKeyDefinition foreign_keys = 9;
repeated ForeignKeyDefinition deleted_foreign_keys = 10;
repeated CheckConstraintDefinition check_constraints = 11;
repeated CheckConstraintDefinition deleted_checks = 12;
repeated ViewDefinition views = 13;
repeated ViewDefinition deleted_views = 14;
repeated TriggerDefinition triggers = 15;
repeated TriggerDefinition deleted_triggers = 16;
repeated RoutineDefinition routines = 17;
repeated RoutineDefinition deleted_routines = 18;
DatabaseConfig config = 19;
}

message GenerateSQLResponse {
bool success = 1;
repeated string sqls = 2;
}
9 changes: 7 additions & 2 deletions backend/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ require (
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/grpc v1.79.2 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)
14 changes: 14 additions & 0 deletions backend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,24 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
Loading
Loading