From 16cb12f850aa86e491faf5c8ff3703e7dc28b307 Mon Sep 17 00:00:00 2001 From: circle33 Date: Mon, 9 Mar 2026 09:48:53 +0800 Subject: [PATCH 1/3] feat: add search functionality to table filter dropdown --- .../table-viewer/components/FilterBar.tsx | 65 +++++++++++++++---- requirement/todo.md | 2 +- 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/frontend/src/renderer/features/table-viewer/components/FilterBar.tsx b/frontend/src/renderer/features/table-viewer/components/FilterBar.tsx index 30f698e..329fb00 100644 --- a/frontend/src/renderer/features/table-viewer/components/FilterBar.tsx +++ b/frontend/src/renderer/features/table-viewer/components/FilterBar.tsx @@ -1,4 +1,4 @@ -import { Check, Plus, Settings, X } from 'lucide-react'; +import { Check, Plus, Search, Settings, X } from 'lucide-react'; import React, { useRef, useState } from 'react'; import type { FilterCondition } from '../../../types/session'; @@ -11,6 +11,7 @@ interface CustomDropdownProps { align?: 'left' | 'center'; testId?: string; disabled?: boolean; + searchable?: boolean; } const CustomDropdown: React.FC = ({ @@ -22,8 +23,14 @@ const CustomDropdown: React.FC = ({ align = 'left', testId, disabled, + searchable, }) => { const [isOpen, setIsOpen] = useState(false); + const [searchQuery, setSearchQuery] = useState(''); + + const filteredOptions = searchable + ? options.filter((opt) => opt.label.toLowerCase().includes(searchQuery.toLowerCase())) + : options; return (
= ({
{isOpen && ( <> -
setIsOpen(false)} /> +
{ + setIsOpen(false); + setSearchQuery(''); + }} + />
+ {searchable && ( +
+
+ + setSearchQuery(e.target.value)} + autoFocus + /> +
+
+ )}
- {options.map((opt) => ( -
{ - onChange(opt.value); - setIsOpen(false); - }} - > - {opt.label} + {filteredOptions.length > 0 ? ( + filteredOptions.map((opt) => ( +
{ + onChange(opt.value); + setIsOpen(false); + setSearchQuery(''); + }} + > + {opt.label} +
+ )) + ) : ( +
+ No matches
- ))} + )}
@@ -139,6 +177,7 @@ export const FilterBar: React.FC = ({ options={structure.map((c) => ({ label: c.column_name, value: c.column_name }))} onChange={(val) => onUpdateFilter(filter.id, 'column', val)} disabled={!filter.enabled} + searchable={true} /> Date: Mon, 9 Mar 2026 11:21:20 +0800 Subject: [PATCH 2/3] feat: migrate frontend-backend communication from REST/HTTP to gRPC --- AGENTS.md | 8 +- backend/api/vstable.proto | 163 ++ backend/go.mod | 9 +- backend/go.sum | 14 + backend/internal/mapper/mapper.go | 351 ++++ backend/internal/pb/vstable.pb.go | 1685 ++++++++++++++++++++ backend/internal/pb/vstable_grpc.pb.go | 311 ++++ backend/main.go | 247 +-- frontend/e2e/advanced.spec.ts | 9 +- frontend/e2e/connection.spec.ts | 9 +- frontend/e2e/data-grid.spec.ts | 9 +- frontend/e2e/schema.spec.ts | 9 +- frontend/e2e/workspace-persistence.spec.ts | 18 +- frontend/package.json | 9 + frontend/resources/api/vstable.proto | 163 ++ frontend/src/main/grpcClient.ts | 143 ++ frontend/src/main/index.ts | 53 +- 17 files changed, 3039 insertions(+), 171 deletions(-) create mode 100644 backend/api/vstable.proto create mode 100644 backend/internal/mapper/mapper.go create mode 100644 backend/internal/pb/vstable.pb.go create mode 100644 backend/internal/pb/vstable_grpc.pb.go create mode 100644 frontend/resources/api/vstable.proto create mode 100644 frontend/src/main/grpcClient.ts diff --git a/AGENTS.md b/AGENTS.md index 76b3280..b58929b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,6 +19,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) @@ -28,17 +29,18 @@ 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 主进程的远程过程调用。 - **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/`: 包含核心功能模块: diff --git a/backend/api/vstable.proto b/backend/api/vstable.proto new file mode 100644 index 0000000..bc68502 --- /dev/null +++ b/backend/api/vstable.proto @@ -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; +} diff --git a/backend/go.mod b/backend/go.mod index 2ff3054..4fd7dda 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -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 ) diff --git a/backend/go.sum b/backend/go.sum index c38ccd9..b076436 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -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= diff --git a/backend/internal/mapper/mapper.go b/backend/internal/mapper/mapper.go new file mode 100644 index 0000000..63f8bb3 --- /dev/null +++ b/backend/internal/mapper/mapper.go @@ -0,0 +1,351 @@ +package mapper + +import ( + "fmt" + "log" + "math" + "math/big" + "time" + + "github.com/jackc/pgx/v5/pgtype" + "google.golang.org/protobuf/types/known/structpb" + + "vstable-engine/internal/ast" + "vstable-engine/internal/pb" +) + +func ToASTDiffRequest(req *pb.DiffRequest) ast.DiffRequest { + if req == nil { + return ast.DiffRequest{} + } + + res := ast.DiffRequest{ + Dialect: req.Dialect, + Schema: req.Schema, + TableName: req.TableName, + OldTableName: req.OldTableName, + Columns: mapColumns(req.Columns), + Indexes: mapIndexes(req.Indexes), + DeletedColumns: mapColumns(req.DeletedColumns), + DeletedIndexes: mapIndexes(req.DeletedIndexes), + + ForeignKeys: mapForeignKeys(req.ForeignKeys), + DeletedForeignKeys: mapForeignKeys(req.DeletedForeignKeys), + CheckConstraints: mapCheckConstraints(req.CheckConstraints), + DeletedChecks: mapCheckConstraints(req.DeletedChecks), + + Views: mapViews(req.Views), + DeletedViews: mapViews(req.DeletedViews), + + Triggers: mapTriggers(req.Triggers), + DeletedTriggers: mapTriggers(req.DeletedTriggers), + + Routines: mapRoutines(req.Routines), + DeletedRoutines: mapRoutines(req.DeletedRoutines), + } + + if req.Config != nil { + res.Config = &ast.DatabaseConfig{ + Charset: req.Config.Charset, + Collation: req.Config.Collation, + Engine: req.Config.Engine, + AutoIncrementOffset: int(req.Config.AutoIncrementOffset), + } + } + + return res +} + +func mapColumns(cols []*pb.ColumnDefinition) []ast.ColumnDefinition { + if cols == nil { + return nil + } + res := make([]ast.ColumnDefinition, len(cols)) + for i, c := range cols { + res[i] = mapColumn(c) + } + return res +} + +func mapColumn(c *pb.ColumnDefinition) ast.ColumnDefinition { + if c == nil { + return ast.ColumnDefinition{} + } + + var defaultValue *string + if c.DefaultValue != nil { + defaultValue = &c.DefaultValue.Value + } + + var original *ast.ColumnDefinition + if c.Original != nil { + orig := mapColumn(c.Original) + original = &orig + } + + return ast.ColumnDefinition{ + ID: c.Id, + Name: c.Name, + Type: c.Type, + EnumValues: c.EnumValues, + Length: fromValue(c.Length), + Precision: fromValue(c.Precision), + Scale: fromValue(c.Scale), + Nullable: c.Nullable, + DefaultValue: defaultValue, + IsDefaultExpression: c.IsDefaultExpression, + IsPrimaryKey: c.IsPrimaryKey, + IsAutoIncrement: c.IsAutoIncrement, + IsIdentity: c.IsIdentity, + Comment: c.Comment, + PkConstraintName: c.PkConstraintName, + OriginalIndex: int(c.OriginalIndex), + Original: original, + } +} + +func fromValue(v *structpb.Value) interface{} { + if v == nil { + return nil + } + return v.AsInterface() +} + +func mapIndexes(idxs []*pb.IndexDefinition) []ast.IndexDefinition { + if idxs == nil { + return nil + } + res := make([]ast.IndexDefinition, len(idxs)) + for i, idx := range idxs { + res[i] = mapIndex(idx) + } + return res +} + +func mapIndex(idx *pb.IndexDefinition) ast.IndexDefinition { + if idx == nil { + return ast.IndexDefinition{} + } + var original *ast.IndexDefinition + if idx.Original != nil { + orig := mapIndex(idx.Original) + original = &orig + } + return ast.IndexDefinition{ + ID: idx.Id, + Name: idx.Name, + Columns: idx.Columns, + IsUnique: idx.IsUnique, + Original: original, + } +} + +func mapForeignKeys(fks []*pb.ForeignKeyDefinition) []ast.ForeignKeyDefinition { + if fks == nil { + return nil + } + res := make([]ast.ForeignKeyDefinition, len(fks)) + for i, fk := range fks { + res[i] = mapForeignKey(fk) + } + return res +} + +func mapForeignKey(fk *pb.ForeignKeyDefinition) ast.ForeignKeyDefinition { + if fk == nil { + return ast.ForeignKeyDefinition{} + } + var original *ast.ForeignKeyDefinition + if fk.Original != nil { + orig := mapForeignKey(fk.Original) + original = &orig + } + return ast.ForeignKeyDefinition{ + ID: fk.Id, + Name: fk.Name, + Columns: fk.Columns, + ReferencedTable: fk.ReferencedTable, + ReferencedColumns: fk.ReferencedColumns, + OnDelete: fk.OnDelete, + OnUpdate: fk.OnUpdate, + Original: original, + } +} + +func mapCheckConstraints(checks []*pb.CheckConstraintDefinition) []ast.CheckConstraintDefinition { + if checks == nil { + return nil + } + res := make([]ast.CheckConstraintDefinition, len(checks)) + for i, c := range checks { + res[i] = mapCheckConstraint(c) + } + return res +} + +func mapCheckConstraint(c *pb.CheckConstraintDefinition) ast.CheckConstraintDefinition { + if c == nil { + return ast.CheckConstraintDefinition{} + } + var original *ast.CheckConstraintDefinition + if c.Original != nil { + orig := mapCheckConstraint(c.Original) + original = &orig + } + return ast.CheckConstraintDefinition{ + ID: c.Id, + Name: c.Name, + Expression: c.Expression, + Original: original, + } +} + +func mapViews(views []*pb.ViewDefinition) []ast.ViewDefinition { + if views == nil { + return nil + } + res := make([]ast.ViewDefinition, len(views)) + for i, v := range views { + res[i] = mapView(v) + } + return res +} + +func mapView(v *pb.ViewDefinition) ast.ViewDefinition { + if v == nil { + return ast.ViewDefinition{} + } + var original *ast.ViewDefinition + if v.Original != nil { + orig := mapView(v.Original) + original = &orig + } + return ast.ViewDefinition{ + ID: v.Id, + Name: v.Name, + Definition: v.Definition, + Original: original, + } +} + +func mapTriggers(triggers []*pb.TriggerDefinition) []ast.TriggerDefinition { + if triggers == nil { + return nil + } + res := make([]ast.TriggerDefinition, len(triggers)) + for i, t := range triggers { + res[i] = mapTrigger(t) + } + return res +} + +func mapTrigger(t *pb.TriggerDefinition) ast.TriggerDefinition { + if t == nil { + return ast.TriggerDefinition{} + } + var original *ast.TriggerDefinition + if t.Original != nil { + orig := mapTrigger(t.Original) + original = &orig + } + return ast.TriggerDefinition{ + ID: t.Id, + Name: t.Name, + Timing: t.Timing, + Event: t.Event, + TableName: t.TableName, + Definition: t.Definition, + Enabled: t.Enabled, + Original: original, + } +} + +func mapRoutines(routines []*pb.RoutineDefinition) []ast.RoutineDefinition { + if routines == nil { + return nil + } + res := make([]ast.RoutineDefinition, len(routines)) + for i, r := range routines { + res[i] = mapRoutine(r) + } + return res +} + +func mapRoutine(r *pb.RoutineDefinition) ast.RoutineDefinition { + if r == nil { + return ast.RoutineDefinition{} + } + var original *ast.RoutineDefinition + if r.Original != nil { + orig := mapRoutine(r.Original) + original = &orig + } + return ast.RoutineDefinition{ + ID: r.Id, + Name: r.Name, + Type: r.Type, + Definition: r.Definition, + Original: original, + } +} + +func RowsToStructs(rows []map[string]interface{}) []*structpb.Struct { + res := make([]*structpb.Struct, 0, len(rows)) + for _, r := range rows { + safeMap := make(map[string]interface{}) + for k, v := range r { + if v == nil { + safeMap[k] = nil + continue + } + + switch val := v.(type) { + case int: + safeMap[k] = float64(val) + case int32: + safeMap[k] = float64(val) + case int64: + safeMap[k] = float64(val) + case uint32: + safeMap[k] = float64(val) + case uint64: + safeMap[k] = float64(val) + case float32: + safeMap[k] = float64(val) + case float64: + safeMap[k] = val + case *big.Int: + f, _ := new(big.Float).SetInt(val).Float64() + safeMap[k] = f + case pgtype.Numeric: + if val.Valid { + f, _ := new(big.Float).SetInt(val.Int).Float64() + if val.Exp != 0 { + f *= math.Pow10(int(val.Exp)) + } + safeMap[k] = f + } else { + safeMap[k] = nil + } + case time.Time: + safeMap[k] = val.Format(time.RFC3339Nano) + case []byte: + safeMap[k] = string(val) + case bool: + safeMap[k] = val + case string: + safeMap[k] = val + default: + // Fallback for types like pgtype.Numeric or other custom structs + safeMap[k] = fmt.Sprintf("%v", val) + } + } + st, err := structpb.NewStruct(safeMap) + if err == nil { + res = append(res, st) + } else { + log.Printf("Failed to convert row to struct: %v, safeMap: %v", err, safeMap) + } + } + return res +} diff --git a/backend/internal/pb/vstable.pb.go b/backend/internal/pb/vstable.pb.go new file mode 100644 index 0000000..0bcee0f --- /dev/null +++ b/backend/internal/pb/vstable.pb.go @@ -0,0 +1,1685 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v7.34.0 +// source: api/vstable.proto + +package pb + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + structpb "google.golang.org/protobuf/types/known/structpb" + wrapperspb "google.golang.org/protobuf/types/known/wrapperspb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type PingRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PingRequest) Reset() { + *x = PingRequest{} + mi := &file_api_vstable_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PingRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PingRequest) ProtoMessage() {} + +func (x *PingRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_vstable_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PingRequest.ProtoReflect.Descriptor instead. +func (*PingRequest) Descriptor() ([]byte, []int) { + return file_api_vstable_proto_rawDescGZIP(), []int{0} +} + +type PingResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PingResponse) Reset() { + *x = PingResponse{} + mi := &file_api_vstable_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PingResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PingResponse) ProtoMessage() {} + +func (x *PingResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_vstable_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PingResponse.ProtoReflect.Descriptor instead. +func (*PingResponse) Descriptor() ([]byte, []int) { + return file_api_vstable_proto_rawDescGZIP(), []int{1} +} + +func (x *PingResponse) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +type ConnectRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Dialect string `protobuf:"bytes,2,opt,name=dialect,proto3" json:"dialect,omitempty"` + Dsn string `protobuf:"bytes,3,opt,name=dsn,proto3" json:"dsn,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConnectRequest) Reset() { + *x = ConnectRequest{} + mi := &file_api_vstable_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConnectRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectRequest) ProtoMessage() {} + +func (x *ConnectRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_vstable_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectRequest.ProtoReflect.Descriptor instead. +func (*ConnectRequest) Descriptor() ([]byte, []int) { + return file_api_vstable_proto_rawDescGZIP(), []int{2} +} + +func (x *ConnectRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ConnectRequest) GetDialect() string { + if x != nil { + return x.Dialect + } + return "" +} + +func (x *ConnectRequest) GetDsn() string { + if x != nil { + return x.Dsn + } + return "" +} + +type ConnectResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConnectResponse) Reset() { + *x = ConnectResponse{} + mi := &file_api_vstable_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConnectResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectResponse) ProtoMessage() {} + +func (x *ConnectResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_vstable_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectResponse.ProtoReflect.Descriptor instead. +func (*ConnectResponse) Descriptor() ([]byte, []int) { + return file_api_vstable_proto_rawDescGZIP(), []int{3} +} + +func (x *ConnectResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +type DisconnectRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DisconnectRequest) Reset() { + *x = DisconnectRequest{} + mi := &file_api_vstable_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DisconnectRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DisconnectRequest) ProtoMessage() {} + +func (x *DisconnectRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_vstable_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DisconnectRequest.ProtoReflect.Descriptor instead. +func (*DisconnectRequest) Descriptor() ([]byte, []int) { + return file_api_vstable_proto_rawDescGZIP(), []int{4} +} + +func (x *DisconnectRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type DisconnectResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DisconnectResponse) Reset() { + *x = DisconnectResponse{} + mi := &file_api_vstable_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DisconnectResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DisconnectResponse) ProtoMessage() {} + +func (x *DisconnectResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_vstable_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DisconnectResponse.ProtoReflect.Descriptor instead. +func (*DisconnectResponse) Descriptor() ([]byte, []int) { + return file_api_vstable_proto_rawDescGZIP(), []int{5} +} + +func (x *DisconnectResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +type QueryRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Sql string `protobuf:"bytes,2,opt,name=sql,proto3" json:"sql,omitempty"` + Params *structpb.ListValue `protobuf:"bytes,3,opt,name=params,proto3" json:"params,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *QueryRequest) Reset() { + *x = QueryRequest{} + mi := &file_api_vstable_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *QueryRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*QueryRequest) ProtoMessage() {} + +func (x *QueryRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_vstable_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use QueryRequest.ProtoReflect.Descriptor instead. +func (*QueryRequest) Descriptor() ([]byte, []int) { + return file_api_vstable_proto_rawDescGZIP(), []int{6} +} + +func (x *QueryRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *QueryRequest) GetSql() string { + if x != nil { + return x.Sql + } + return "" +} + +func (x *QueryRequest) GetParams() *structpb.ListValue { + if x != nil { + return x.Params + } + return nil +} + +type FieldInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FieldInfo) Reset() { + *x = FieldInfo{} + mi := &file_api_vstable_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FieldInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FieldInfo) ProtoMessage() {} + +func (x *FieldInfo) ProtoReflect() protoreflect.Message { + mi := &file_api_vstable_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FieldInfo.ProtoReflect.Descriptor instead. +func (*FieldInfo) Descriptor() ([]byte, []int) { + return file_api_vstable_proto_rawDescGZIP(), []int{7} +} + +func (x *FieldInfo) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *FieldInfo) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +type QueryResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + Rows []*structpb.Struct `protobuf:"bytes,2,rep,name=rows,proto3" json:"rows,omitempty"` + Fields []*FieldInfo `protobuf:"bytes,3,rep,name=fields,proto3" json:"fields,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *QueryResponse) Reset() { + *x = QueryResponse{} + mi := &file_api_vstable_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *QueryResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*QueryResponse) ProtoMessage() {} + +func (x *QueryResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_vstable_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use QueryResponse.ProtoReflect.Descriptor instead. +func (*QueryResponse) Descriptor() ([]byte, []int) { + return file_api_vstable_proto_rawDescGZIP(), []int{8} +} + +func (x *QueryResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *QueryResponse) GetRows() []*structpb.Struct { + if x != nil { + return x.Rows + } + return nil +} + +func (x *QueryResponse) GetFields() []*FieldInfo { + if x != nil { + return x.Fields + } + return nil +} + +type ColumnDefinition struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"` + EnumValues []string `protobuf:"bytes,4,rep,name=enum_values,json=enumValues,proto3" json:"enum_values,omitempty"` + Length *structpb.Value `protobuf:"bytes,5,opt,name=length,proto3" json:"length,omitempty"` + Precision *structpb.Value `protobuf:"bytes,6,opt,name=precision,proto3" json:"precision,omitempty"` + Scale *structpb.Value `protobuf:"bytes,7,opt,name=scale,proto3" json:"scale,omitempty"` + Nullable bool `protobuf:"varint,8,opt,name=nullable,proto3" json:"nullable,omitempty"` + DefaultValue *wrapperspb.StringValue `protobuf:"bytes,9,opt,name=default_value,json=defaultValue,proto3" json:"default_value,omitempty"` + IsDefaultExpression bool `protobuf:"varint,10,opt,name=is_default_expression,json=isDefaultExpression,proto3" json:"is_default_expression,omitempty"` + IsPrimaryKey bool `protobuf:"varint,11,opt,name=is_primary_key,json=isPrimaryKey,proto3" json:"is_primary_key,omitempty"` + IsAutoIncrement bool `protobuf:"varint,12,opt,name=is_auto_increment,json=isAutoIncrement,proto3" json:"is_auto_increment,omitempty"` + IsIdentity bool `protobuf:"varint,13,opt,name=is_identity,json=isIdentity,proto3" json:"is_identity,omitempty"` + Comment string `protobuf:"bytes,14,opt,name=comment,proto3" json:"comment,omitempty"` + PkConstraintName string `protobuf:"bytes,15,opt,name=pk_constraint_name,json=pkConstraintName,proto3" json:"pk_constraint_name,omitempty"` + OriginalIndex int32 `protobuf:"varint,16,opt,name=original_index,json=originalIndex,proto3" json:"original_index,omitempty"` + Original *ColumnDefinition `protobuf:"bytes,17,opt,name=original,proto3" json:"original,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ColumnDefinition) Reset() { + *x = ColumnDefinition{} + mi := &file_api_vstable_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ColumnDefinition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ColumnDefinition) ProtoMessage() {} + +func (x *ColumnDefinition) ProtoReflect() protoreflect.Message { + mi := &file_api_vstable_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ColumnDefinition.ProtoReflect.Descriptor instead. +func (*ColumnDefinition) Descriptor() ([]byte, []int) { + return file_api_vstable_proto_rawDescGZIP(), []int{9} +} + +func (x *ColumnDefinition) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ColumnDefinition) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ColumnDefinition) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *ColumnDefinition) GetEnumValues() []string { + if x != nil { + return x.EnumValues + } + return nil +} + +func (x *ColumnDefinition) GetLength() *structpb.Value { + if x != nil { + return x.Length + } + return nil +} + +func (x *ColumnDefinition) GetPrecision() *structpb.Value { + if x != nil { + return x.Precision + } + return nil +} + +func (x *ColumnDefinition) GetScale() *structpb.Value { + if x != nil { + return x.Scale + } + return nil +} + +func (x *ColumnDefinition) GetNullable() bool { + if x != nil { + return x.Nullable + } + return false +} + +func (x *ColumnDefinition) GetDefaultValue() *wrapperspb.StringValue { + if x != nil { + return x.DefaultValue + } + return nil +} + +func (x *ColumnDefinition) GetIsDefaultExpression() bool { + if x != nil { + return x.IsDefaultExpression + } + return false +} + +func (x *ColumnDefinition) GetIsPrimaryKey() bool { + if x != nil { + return x.IsPrimaryKey + } + return false +} + +func (x *ColumnDefinition) GetIsAutoIncrement() bool { + if x != nil { + return x.IsAutoIncrement + } + return false +} + +func (x *ColumnDefinition) GetIsIdentity() bool { + if x != nil { + return x.IsIdentity + } + return false +} + +func (x *ColumnDefinition) GetComment() string { + if x != nil { + return x.Comment + } + return "" +} + +func (x *ColumnDefinition) GetPkConstraintName() string { + if x != nil { + return x.PkConstraintName + } + return "" +} + +func (x *ColumnDefinition) GetOriginalIndex() int32 { + if x != nil { + return x.OriginalIndex + } + return 0 +} + +func (x *ColumnDefinition) GetOriginal() *ColumnDefinition { + if x != nil { + return x.Original + } + return nil +} + +type IndexDefinition struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Columns []string `protobuf:"bytes,3,rep,name=columns,proto3" json:"columns,omitempty"` + IsUnique bool `protobuf:"varint,4,opt,name=is_unique,json=isUnique,proto3" json:"is_unique,omitempty"` + Original *IndexDefinition `protobuf:"bytes,5,opt,name=original,proto3" json:"original,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *IndexDefinition) Reset() { + *x = IndexDefinition{} + mi := &file_api_vstable_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *IndexDefinition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IndexDefinition) ProtoMessage() {} + +func (x *IndexDefinition) ProtoReflect() protoreflect.Message { + mi := &file_api_vstable_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IndexDefinition.ProtoReflect.Descriptor instead. +func (*IndexDefinition) Descriptor() ([]byte, []int) { + return file_api_vstable_proto_rawDescGZIP(), []int{10} +} + +func (x *IndexDefinition) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *IndexDefinition) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *IndexDefinition) GetColumns() []string { + if x != nil { + return x.Columns + } + return nil +} + +func (x *IndexDefinition) GetIsUnique() bool { + if x != nil { + return x.IsUnique + } + return false +} + +func (x *IndexDefinition) GetOriginal() *IndexDefinition { + if x != nil { + return x.Original + } + return nil +} + +type ForeignKeyDefinition struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Columns []string `protobuf:"bytes,3,rep,name=columns,proto3" json:"columns,omitempty"` + ReferencedTable string `protobuf:"bytes,4,opt,name=referenced_table,json=referencedTable,proto3" json:"referenced_table,omitempty"` + ReferencedColumns []string `protobuf:"bytes,5,rep,name=referenced_columns,json=referencedColumns,proto3" json:"referenced_columns,omitempty"` + OnDelete string `protobuf:"bytes,6,opt,name=on_delete,json=onDelete,proto3" json:"on_delete,omitempty"` + OnUpdate string `protobuf:"bytes,7,opt,name=on_update,json=onUpdate,proto3" json:"on_update,omitempty"` + Original *ForeignKeyDefinition `protobuf:"bytes,8,opt,name=original,proto3" json:"original,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ForeignKeyDefinition) Reset() { + *x = ForeignKeyDefinition{} + mi := &file_api_vstable_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ForeignKeyDefinition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ForeignKeyDefinition) ProtoMessage() {} + +func (x *ForeignKeyDefinition) ProtoReflect() protoreflect.Message { + mi := &file_api_vstable_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ForeignKeyDefinition.ProtoReflect.Descriptor instead. +func (*ForeignKeyDefinition) Descriptor() ([]byte, []int) { + return file_api_vstable_proto_rawDescGZIP(), []int{11} +} + +func (x *ForeignKeyDefinition) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ForeignKeyDefinition) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ForeignKeyDefinition) GetColumns() []string { + if x != nil { + return x.Columns + } + return nil +} + +func (x *ForeignKeyDefinition) GetReferencedTable() string { + if x != nil { + return x.ReferencedTable + } + return "" +} + +func (x *ForeignKeyDefinition) GetReferencedColumns() []string { + if x != nil { + return x.ReferencedColumns + } + return nil +} + +func (x *ForeignKeyDefinition) GetOnDelete() string { + if x != nil { + return x.OnDelete + } + return "" +} + +func (x *ForeignKeyDefinition) GetOnUpdate() string { + if x != nil { + return x.OnUpdate + } + return "" +} + +func (x *ForeignKeyDefinition) GetOriginal() *ForeignKeyDefinition { + if x != nil { + return x.Original + } + return nil +} + +type CheckConstraintDefinition struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Expression string `protobuf:"bytes,3,opt,name=expression,proto3" json:"expression,omitempty"` + Original *CheckConstraintDefinition `protobuf:"bytes,4,opt,name=original,proto3" json:"original,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CheckConstraintDefinition) Reset() { + *x = CheckConstraintDefinition{} + mi := &file_api_vstable_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CheckConstraintDefinition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CheckConstraintDefinition) ProtoMessage() {} + +func (x *CheckConstraintDefinition) ProtoReflect() protoreflect.Message { + mi := &file_api_vstable_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CheckConstraintDefinition.ProtoReflect.Descriptor instead. +func (*CheckConstraintDefinition) Descriptor() ([]byte, []int) { + return file_api_vstable_proto_rawDescGZIP(), []int{12} +} + +func (x *CheckConstraintDefinition) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *CheckConstraintDefinition) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CheckConstraintDefinition) GetExpression() string { + if x != nil { + return x.Expression + } + return "" +} + +func (x *CheckConstraintDefinition) GetOriginal() *CheckConstraintDefinition { + if x != nil { + return x.Original + } + return nil +} + +type ViewDefinition struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Definition string `protobuf:"bytes,3,opt,name=definition,proto3" json:"definition,omitempty"` + Original *ViewDefinition `protobuf:"bytes,4,opt,name=original,proto3" json:"original,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ViewDefinition) Reset() { + *x = ViewDefinition{} + mi := &file_api_vstable_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ViewDefinition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ViewDefinition) ProtoMessage() {} + +func (x *ViewDefinition) ProtoReflect() protoreflect.Message { + mi := &file_api_vstable_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ViewDefinition.ProtoReflect.Descriptor instead. +func (*ViewDefinition) Descriptor() ([]byte, []int) { + return file_api_vstable_proto_rawDescGZIP(), []int{13} +} + +func (x *ViewDefinition) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ViewDefinition) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ViewDefinition) GetDefinition() string { + if x != nil { + return x.Definition + } + return "" +} + +func (x *ViewDefinition) GetOriginal() *ViewDefinition { + if x != nil { + return x.Original + } + return nil +} + +type TriggerDefinition struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Timing string `protobuf:"bytes,3,opt,name=timing,proto3" json:"timing,omitempty"` + Event string `protobuf:"bytes,4,opt,name=event,proto3" json:"event,omitempty"` + TableName string `protobuf:"bytes,5,opt,name=table_name,json=tableName,proto3" json:"table_name,omitempty"` + Definition string `protobuf:"bytes,6,opt,name=definition,proto3" json:"definition,omitempty"` + Enabled bool `protobuf:"varint,7,opt,name=enabled,proto3" json:"enabled,omitempty"` + Original *TriggerDefinition `protobuf:"bytes,8,opt,name=original,proto3" json:"original,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TriggerDefinition) Reset() { + *x = TriggerDefinition{} + mi := &file_api_vstable_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TriggerDefinition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TriggerDefinition) ProtoMessage() {} + +func (x *TriggerDefinition) ProtoReflect() protoreflect.Message { + mi := &file_api_vstable_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TriggerDefinition.ProtoReflect.Descriptor instead. +func (*TriggerDefinition) Descriptor() ([]byte, []int) { + return file_api_vstable_proto_rawDescGZIP(), []int{14} +} + +func (x *TriggerDefinition) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *TriggerDefinition) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *TriggerDefinition) GetTiming() string { + if x != nil { + return x.Timing + } + return "" +} + +func (x *TriggerDefinition) GetEvent() string { + if x != nil { + return x.Event + } + return "" +} + +func (x *TriggerDefinition) GetTableName() string { + if x != nil { + return x.TableName + } + return "" +} + +func (x *TriggerDefinition) GetDefinition() string { + if x != nil { + return x.Definition + } + return "" +} + +func (x *TriggerDefinition) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +func (x *TriggerDefinition) GetOriginal() *TriggerDefinition { + if x != nil { + return x.Original + } + return nil +} + +type RoutineDefinition struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"` + Definition string `protobuf:"bytes,4,opt,name=definition,proto3" json:"definition,omitempty"` + Original *RoutineDefinition `protobuf:"bytes,5,opt,name=original,proto3" json:"original,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RoutineDefinition) Reset() { + *x = RoutineDefinition{} + mi := &file_api_vstable_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RoutineDefinition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoutineDefinition) ProtoMessage() {} + +func (x *RoutineDefinition) ProtoReflect() protoreflect.Message { + mi := &file_api_vstable_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RoutineDefinition.ProtoReflect.Descriptor instead. +func (*RoutineDefinition) Descriptor() ([]byte, []int) { + return file_api_vstable_proto_rawDescGZIP(), []int{15} +} + +func (x *RoutineDefinition) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *RoutineDefinition) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *RoutineDefinition) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *RoutineDefinition) GetDefinition() string { + if x != nil { + return x.Definition + } + return "" +} + +func (x *RoutineDefinition) GetOriginal() *RoutineDefinition { + if x != nil { + return x.Original + } + return nil +} + +type DatabaseConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Charset string `protobuf:"bytes,1,opt,name=charset,proto3" json:"charset,omitempty"` + Collation string `protobuf:"bytes,2,opt,name=collation,proto3" json:"collation,omitempty"` + Engine string `protobuf:"bytes,3,opt,name=engine,proto3" json:"engine,omitempty"` + AutoIncrementOffset int32 `protobuf:"varint,4,opt,name=auto_increment_offset,json=autoIncrementOffset,proto3" json:"auto_increment_offset,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DatabaseConfig) Reset() { + *x = DatabaseConfig{} + mi := &file_api_vstable_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DatabaseConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DatabaseConfig) ProtoMessage() {} + +func (x *DatabaseConfig) ProtoReflect() protoreflect.Message { + mi := &file_api_vstable_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DatabaseConfig.ProtoReflect.Descriptor instead. +func (*DatabaseConfig) Descriptor() ([]byte, []int) { + return file_api_vstable_proto_rawDescGZIP(), []int{16} +} + +func (x *DatabaseConfig) GetCharset() string { + if x != nil { + return x.Charset + } + return "" +} + +func (x *DatabaseConfig) GetCollation() string { + if x != nil { + return x.Collation + } + return "" +} + +func (x *DatabaseConfig) GetEngine() string { + if x != nil { + return x.Engine + } + return "" +} + +func (x *DatabaseConfig) GetAutoIncrementOffset() int32 { + if x != nil { + return x.AutoIncrementOffset + } + return 0 +} + +type DiffRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Dialect string `protobuf:"bytes,1,opt,name=dialect,proto3" json:"dialect,omitempty"` + Schema string `protobuf:"bytes,2,opt,name=schema,proto3" json:"schema,omitempty"` + TableName string `protobuf:"bytes,3,opt,name=table_name,json=tableName,proto3" json:"table_name,omitempty"` + OldTableName string `protobuf:"bytes,4,opt,name=old_table_name,json=oldTableName,proto3" json:"old_table_name,omitempty"` + Columns []*ColumnDefinition `protobuf:"bytes,5,rep,name=columns,proto3" json:"columns,omitempty"` + Indexes []*IndexDefinition `protobuf:"bytes,6,rep,name=indexes,proto3" json:"indexes,omitempty"` + DeletedColumns []*ColumnDefinition `protobuf:"bytes,7,rep,name=deleted_columns,json=deletedColumns,proto3" json:"deleted_columns,omitempty"` + DeletedIndexes []*IndexDefinition `protobuf:"bytes,8,rep,name=deleted_indexes,json=deletedIndexes,proto3" json:"deleted_indexes,omitempty"` + ForeignKeys []*ForeignKeyDefinition `protobuf:"bytes,9,rep,name=foreign_keys,json=foreignKeys,proto3" json:"foreign_keys,omitempty"` + DeletedForeignKeys []*ForeignKeyDefinition `protobuf:"bytes,10,rep,name=deleted_foreign_keys,json=deletedForeignKeys,proto3" json:"deleted_foreign_keys,omitempty"` + CheckConstraints []*CheckConstraintDefinition `protobuf:"bytes,11,rep,name=check_constraints,json=checkConstraints,proto3" json:"check_constraints,omitempty"` + DeletedChecks []*CheckConstraintDefinition `protobuf:"bytes,12,rep,name=deleted_checks,json=deletedChecks,proto3" json:"deleted_checks,omitempty"` + Views []*ViewDefinition `protobuf:"bytes,13,rep,name=views,proto3" json:"views,omitempty"` + DeletedViews []*ViewDefinition `protobuf:"bytes,14,rep,name=deleted_views,json=deletedViews,proto3" json:"deleted_views,omitempty"` + Triggers []*TriggerDefinition `protobuf:"bytes,15,rep,name=triggers,proto3" json:"triggers,omitempty"` + DeletedTriggers []*TriggerDefinition `protobuf:"bytes,16,rep,name=deleted_triggers,json=deletedTriggers,proto3" json:"deleted_triggers,omitempty"` + Routines []*RoutineDefinition `protobuf:"bytes,17,rep,name=routines,proto3" json:"routines,omitempty"` + DeletedRoutines []*RoutineDefinition `protobuf:"bytes,18,rep,name=deleted_routines,json=deletedRoutines,proto3" json:"deleted_routines,omitempty"` + Config *DatabaseConfig `protobuf:"bytes,19,opt,name=config,proto3" json:"config,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DiffRequest) Reset() { + *x = DiffRequest{} + mi := &file_api_vstable_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DiffRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DiffRequest) ProtoMessage() {} + +func (x *DiffRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_vstable_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DiffRequest.ProtoReflect.Descriptor instead. +func (*DiffRequest) Descriptor() ([]byte, []int) { + return file_api_vstable_proto_rawDescGZIP(), []int{17} +} + +func (x *DiffRequest) GetDialect() string { + if x != nil { + return x.Dialect + } + return "" +} + +func (x *DiffRequest) GetSchema() string { + if x != nil { + return x.Schema + } + return "" +} + +func (x *DiffRequest) GetTableName() string { + if x != nil { + return x.TableName + } + return "" +} + +func (x *DiffRequest) GetOldTableName() string { + if x != nil { + return x.OldTableName + } + return "" +} + +func (x *DiffRequest) GetColumns() []*ColumnDefinition { + if x != nil { + return x.Columns + } + return nil +} + +func (x *DiffRequest) GetIndexes() []*IndexDefinition { + if x != nil { + return x.Indexes + } + return nil +} + +func (x *DiffRequest) GetDeletedColumns() []*ColumnDefinition { + if x != nil { + return x.DeletedColumns + } + return nil +} + +func (x *DiffRequest) GetDeletedIndexes() []*IndexDefinition { + if x != nil { + return x.DeletedIndexes + } + return nil +} + +func (x *DiffRequest) GetForeignKeys() []*ForeignKeyDefinition { + if x != nil { + return x.ForeignKeys + } + return nil +} + +func (x *DiffRequest) GetDeletedForeignKeys() []*ForeignKeyDefinition { + if x != nil { + return x.DeletedForeignKeys + } + return nil +} + +func (x *DiffRequest) GetCheckConstraints() []*CheckConstraintDefinition { + if x != nil { + return x.CheckConstraints + } + return nil +} + +func (x *DiffRequest) GetDeletedChecks() []*CheckConstraintDefinition { + if x != nil { + return x.DeletedChecks + } + return nil +} + +func (x *DiffRequest) GetViews() []*ViewDefinition { + if x != nil { + return x.Views + } + return nil +} + +func (x *DiffRequest) GetDeletedViews() []*ViewDefinition { + if x != nil { + return x.DeletedViews + } + return nil +} + +func (x *DiffRequest) GetTriggers() []*TriggerDefinition { + if x != nil { + return x.Triggers + } + return nil +} + +func (x *DiffRequest) GetDeletedTriggers() []*TriggerDefinition { + if x != nil { + return x.DeletedTriggers + } + return nil +} + +func (x *DiffRequest) GetRoutines() []*RoutineDefinition { + if x != nil { + return x.Routines + } + return nil +} + +func (x *DiffRequest) GetDeletedRoutines() []*RoutineDefinition { + if x != nil { + return x.DeletedRoutines + } + return nil +} + +func (x *DiffRequest) GetConfig() *DatabaseConfig { + if x != nil { + return x.Config + } + return nil +} + +type GenerateSQLResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + Sqls []string `protobuf:"bytes,2,rep,name=sqls,proto3" json:"sqls,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GenerateSQLResponse) Reset() { + *x = GenerateSQLResponse{} + mi := &file_api_vstable_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GenerateSQLResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GenerateSQLResponse) ProtoMessage() {} + +func (x *GenerateSQLResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_vstable_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GenerateSQLResponse.ProtoReflect.Descriptor instead. +func (*GenerateSQLResponse) Descriptor() ([]byte, []int) { + return file_api_vstable_proto_rawDescGZIP(), []int{18} +} + +func (x *GenerateSQLResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *GenerateSQLResponse) GetSqls() []string { + if x != nil { + return x.Sqls + } + return nil +} + +var File_api_vstable_proto protoreflect.FileDescriptor + +const file_api_vstable_proto_rawDesc = "" + + "\n" + + "\x11api/vstable.proto\x12\avstable\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1egoogle/protobuf/wrappers.proto\"\r\n" + + "\vPingRequest\"&\n" + + "\fPingResponse\x12\x16\n" + + "\x06status\x18\x01 \x01(\tR\x06status\"L\n" + + "\x0eConnectRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" + + "\adialect\x18\x02 \x01(\tR\adialect\x12\x10\n" + + "\x03dsn\x18\x03 \x01(\tR\x03dsn\"+\n" + + "\x0fConnectResponse\x12\x18\n" + + "\asuccess\x18\x01 \x01(\bR\asuccess\"#\n" + + "\x11DisconnectRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\".\n" + + "\x12DisconnectResponse\x12\x18\n" + + "\asuccess\x18\x01 \x01(\bR\asuccess\"d\n" + + "\fQueryRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x10\n" + + "\x03sql\x18\x02 \x01(\tR\x03sql\x122\n" + + "\x06params\x18\x03 \x01(\v2\x1a.google.protobuf.ListValueR\x06params\"3\n" + + "\tFieldInfo\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" + + "\x04type\x18\x02 \x01(\tR\x04type\"\x82\x01\n" + + "\rQueryResponse\x12\x18\n" + + "\asuccess\x18\x01 \x01(\bR\asuccess\x12+\n" + + "\x04rows\x18\x02 \x03(\v2\x17.google.protobuf.StructR\x04rows\x12*\n" + + "\x06fields\x18\x03 \x03(\v2\x12.vstable.FieldInfoR\x06fields\"\xab\x05\n" + + "\x10ColumnDefinition\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x12\n" + + "\x04type\x18\x03 \x01(\tR\x04type\x12\x1f\n" + + "\venum_values\x18\x04 \x03(\tR\n" + + "enumValues\x12.\n" + + "\x06length\x18\x05 \x01(\v2\x16.google.protobuf.ValueR\x06length\x124\n" + + "\tprecision\x18\x06 \x01(\v2\x16.google.protobuf.ValueR\tprecision\x12,\n" + + "\x05scale\x18\a \x01(\v2\x16.google.protobuf.ValueR\x05scale\x12\x1a\n" + + "\bnullable\x18\b \x01(\bR\bnullable\x12A\n" + + "\rdefault_value\x18\t \x01(\v2\x1c.google.protobuf.StringValueR\fdefaultValue\x122\n" + + "\x15is_default_expression\x18\n" + + " \x01(\bR\x13isDefaultExpression\x12$\n" + + "\x0eis_primary_key\x18\v \x01(\bR\fisPrimaryKey\x12*\n" + + "\x11is_auto_increment\x18\f \x01(\bR\x0fisAutoIncrement\x12\x1f\n" + + "\vis_identity\x18\r \x01(\bR\n" + + "isIdentity\x12\x18\n" + + "\acomment\x18\x0e \x01(\tR\acomment\x12,\n" + + "\x12pk_constraint_name\x18\x0f \x01(\tR\x10pkConstraintName\x12%\n" + + "\x0eoriginal_index\x18\x10 \x01(\x05R\roriginalIndex\x125\n" + + "\boriginal\x18\x11 \x01(\v2\x19.vstable.ColumnDefinitionR\boriginal\"\xa2\x01\n" + + "\x0fIndexDefinition\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x18\n" + + "\acolumns\x18\x03 \x03(\tR\acolumns\x12\x1b\n" + + "\tis_unique\x18\x04 \x01(\bR\bisUnique\x124\n" + + "\boriginal\x18\x05 \x01(\v2\x18.vstable.IndexDefinitionR\boriginal\"\xa3\x02\n" + + "\x14ForeignKeyDefinition\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x18\n" + + "\acolumns\x18\x03 \x03(\tR\acolumns\x12)\n" + + "\x10referenced_table\x18\x04 \x01(\tR\x0freferencedTable\x12-\n" + + "\x12referenced_columns\x18\x05 \x03(\tR\x11referencedColumns\x12\x1b\n" + + "\ton_delete\x18\x06 \x01(\tR\bonDelete\x12\x1b\n" + + "\ton_update\x18\a \x01(\tR\bonUpdate\x129\n" + + "\boriginal\x18\b \x01(\v2\x1d.vstable.ForeignKeyDefinitionR\boriginal\"\x9f\x01\n" + + "\x19CheckConstraintDefinition\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x1e\n" + + "\n" + + "expression\x18\x03 \x01(\tR\n" + + "expression\x12>\n" + + "\boriginal\x18\x04 \x01(\v2\".vstable.CheckConstraintDefinitionR\boriginal\"\x89\x01\n" + + "\x0eViewDefinition\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x1e\n" + + "\n" + + "definition\x18\x03 \x01(\tR\n" + + "definition\x123\n" + + "\boriginal\x18\x04 \x01(\v2\x17.vstable.ViewDefinitionR\boriginal\"\xf6\x01\n" + + "\x11TriggerDefinition\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x16\n" + + "\x06timing\x18\x03 \x01(\tR\x06timing\x12\x14\n" + + "\x05event\x18\x04 \x01(\tR\x05event\x12\x1d\n" + + "\n" + + "table_name\x18\x05 \x01(\tR\ttableName\x12\x1e\n" + + "\n" + + "definition\x18\x06 \x01(\tR\n" + + "definition\x12\x18\n" + + "\aenabled\x18\a \x01(\bR\aenabled\x126\n" + + "\boriginal\x18\b \x01(\v2\x1a.vstable.TriggerDefinitionR\boriginal\"\xa3\x01\n" + + "\x11RoutineDefinition\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x12\n" + + "\x04type\x18\x03 \x01(\tR\x04type\x12\x1e\n" + + "\n" + + "definition\x18\x04 \x01(\tR\n" + + "definition\x126\n" + + "\boriginal\x18\x05 \x01(\v2\x1a.vstable.RoutineDefinitionR\boriginal\"\x94\x01\n" + + "\x0eDatabaseConfig\x12\x18\n" + + "\acharset\x18\x01 \x01(\tR\acharset\x12\x1c\n" + + "\tcollation\x18\x02 \x01(\tR\tcollation\x12\x16\n" + + "\x06engine\x18\x03 \x01(\tR\x06engine\x122\n" + + "\x15auto_increment_offset\x18\x04 \x01(\x05R\x13autoIncrementOffset\"\xbf\b\n" + + "\vDiffRequest\x12\x18\n" + + "\adialect\x18\x01 \x01(\tR\adialect\x12\x16\n" + + "\x06schema\x18\x02 \x01(\tR\x06schema\x12\x1d\n" + + "\n" + + "table_name\x18\x03 \x01(\tR\ttableName\x12$\n" + + "\x0eold_table_name\x18\x04 \x01(\tR\foldTableName\x123\n" + + "\acolumns\x18\x05 \x03(\v2\x19.vstable.ColumnDefinitionR\acolumns\x122\n" + + "\aindexes\x18\x06 \x03(\v2\x18.vstable.IndexDefinitionR\aindexes\x12B\n" + + "\x0fdeleted_columns\x18\a \x03(\v2\x19.vstable.ColumnDefinitionR\x0edeletedColumns\x12A\n" + + "\x0fdeleted_indexes\x18\b \x03(\v2\x18.vstable.IndexDefinitionR\x0edeletedIndexes\x12@\n" + + "\fforeign_keys\x18\t \x03(\v2\x1d.vstable.ForeignKeyDefinitionR\vforeignKeys\x12O\n" + + "\x14deleted_foreign_keys\x18\n" + + " \x03(\v2\x1d.vstable.ForeignKeyDefinitionR\x12deletedForeignKeys\x12O\n" + + "\x11check_constraints\x18\v \x03(\v2\".vstable.CheckConstraintDefinitionR\x10checkConstraints\x12I\n" + + "\x0edeleted_checks\x18\f \x03(\v2\".vstable.CheckConstraintDefinitionR\rdeletedChecks\x12-\n" + + "\x05views\x18\r \x03(\v2\x17.vstable.ViewDefinitionR\x05views\x12<\n" + + "\rdeleted_views\x18\x0e \x03(\v2\x17.vstable.ViewDefinitionR\fdeletedViews\x126\n" + + "\btriggers\x18\x0f \x03(\v2\x1a.vstable.TriggerDefinitionR\btriggers\x12E\n" + + "\x10deleted_triggers\x18\x10 \x03(\v2\x1a.vstable.TriggerDefinitionR\x0fdeletedTriggers\x126\n" + + "\broutines\x18\x11 \x03(\v2\x1a.vstable.RoutineDefinitionR\broutines\x12E\n" + + "\x10deleted_routines\x18\x12 \x03(\v2\x1a.vstable.RoutineDefinitionR\x0fdeletedRoutines\x12/\n" + + "\x06config\x18\x13 \x01(\v2\x17.vstable.DatabaseConfigR\x06config\"C\n" + + "\x13GenerateSQLResponse\x12\x18\n" + + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x12\n" + + "\x04sqls\x18\x02 \x03(\tR\x04sqls2\x96\x03\n" + + "\rEngineService\x123\n" + + "\x04Ping\x12\x14.vstable.PingRequest\x1a\x15.vstable.PingResponse\x12<\n" + + "\aConnect\x12\x17.vstable.ConnectRequest\x1a\x18.vstable.ConnectResponse\x12E\n" + + "\n" + + "Disconnect\x12\x1a.vstable.DisconnectRequest\x1a\x1b.vstable.DisconnectResponse\x126\n" + + "\x05Query\x12\x15.vstable.QueryRequest\x1a\x16.vstable.QueryResponse\x12H\n" + + "\x12GenerateAlterTable\x12\x14.vstable.DiffRequest\x1a\x1c.vstable.GenerateSQLResponse\x12I\n" + + "\x13GenerateCreateTable\x12\x14.vstable.DiffRequest\x1a\x1c.vstable.GenerateSQLResponseB\x1cZ\x1avstable-engine/internal/pbb\x06proto3" + +var ( + file_api_vstable_proto_rawDescOnce sync.Once + file_api_vstable_proto_rawDescData []byte +) + +func file_api_vstable_proto_rawDescGZIP() []byte { + file_api_vstable_proto_rawDescOnce.Do(func() { + file_api_vstable_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_api_vstable_proto_rawDesc), len(file_api_vstable_proto_rawDesc))) + }) + return file_api_vstable_proto_rawDescData +} + +var file_api_vstable_proto_msgTypes = make([]protoimpl.MessageInfo, 19) +var file_api_vstable_proto_goTypes = []any{ + (*PingRequest)(nil), // 0: vstable.PingRequest + (*PingResponse)(nil), // 1: vstable.PingResponse + (*ConnectRequest)(nil), // 2: vstable.ConnectRequest + (*ConnectResponse)(nil), // 3: vstable.ConnectResponse + (*DisconnectRequest)(nil), // 4: vstable.DisconnectRequest + (*DisconnectResponse)(nil), // 5: vstable.DisconnectResponse + (*QueryRequest)(nil), // 6: vstable.QueryRequest + (*FieldInfo)(nil), // 7: vstable.FieldInfo + (*QueryResponse)(nil), // 8: vstable.QueryResponse + (*ColumnDefinition)(nil), // 9: vstable.ColumnDefinition + (*IndexDefinition)(nil), // 10: vstable.IndexDefinition + (*ForeignKeyDefinition)(nil), // 11: vstable.ForeignKeyDefinition + (*CheckConstraintDefinition)(nil), // 12: vstable.CheckConstraintDefinition + (*ViewDefinition)(nil), // 13: vstable.ViewDefinition + (*TriggerDefinition)(nil), // 14: vstable.TriggerDefinition + (*RoutineDefinition)(nil), // 15: vstable.RoutineDefinition + (*DatabaseConfig)(nil), // 16: vstable.DatabaseConfig + (*DiffRequest)(nil), // 17: vstable.DiffRequest + (*GenerateSQLResponse)(nil), // 18: vstable.GenerateSQLResponse + (*structpb.ListValue)(nil), // 19: google.protobuf.ListValue + (*structpb.Struct)(nil), // 20: google.protobuf.Struct + (*structpb.Value)(nil), // 21: google.protobuf.Value + (*wrapperspb.StringValue)(nil), // 22: google.protobuf.StringValue +} +var file_api_vstable_proto_depIdxs = []int32{ + 19, // 0: vstable.QueryRequest.params:type_name -> google.protobuf.ListValue + 20, // 1: vstable.QueryResponse.rows:type_name -> google.protobuf.Struct + 7, // 2: vstable.QueryResponse.fields:type_name -> vstable.FieldInfo + 21, // 3: vstable.ColumnDefinition.length:type_name -> google.protobuf.Value + 21, // 4: vstable.ColumnDefinition.precision:type_name -> google.protobuf.Value + 21, // 5: vstable.ColumnDefinition.scale:type_name -> google.protobuf.Value + 22, // 6: vstable.ColumnDefinition.default_value:type_name -> google.protobuf.StringValue + 9, // 7: vstable.ColumnDefinition.original:type_name -> vstable.ColumnDefinition + 10, // 8: vstable.IndexDefinition.original:type_name -> vstable.IndexDefinition + 11, // 9: vstable.ForeignKeyDefinition.original:type_name -> vstable.ForeignKeyDefinition + 12, // 10: vstable.CheckConstraintDefinition.original:type_name -> vstable.CheckConstraintDefinition + 13, // 11: vstable.ViewDefinition.original:type_name -> vstable.ViewDefinition + 14, // 12: vstable.TriggerDefinition.original:type_name -> vstable.TriggerDefinition + 15, // 13: vstable.RoutineDefinition.original:type_name -> vstable.RoutineDefinition + 9, // 14: vstable.DiffRequest.columns:type_name -> vstable.ColumnDefinition + 10, // 15: vstable.DiffRequest.indexes:type_name -> vstable.IndexDefinition + 9, // 16: vstable.DiffRequest.deleted_columns:type_name -> vstable.ColumnDefinition + 10, // 17: vstable.DiffRequest.deleted_indexes:type_name -> vstable.IndexDefinition + 11, // 18: vstable.DiffRequest.foreign_keys:type_name -> vstable.ForeignKeyDefinition + 11, // 19: vstable.DiffRequest.deleted_foreign_keys:type_name -> vstable.ForeignKeyDefinition + 12, // 20: vstable.DiffRequest.check_constraints:type_name -> vstable.CheckConstraintDefinition + 12, // 21: vstable.DiffRequest.deleted_checks:type_name -> vstable.CheckConstraintDefinition + 13, // 22: vstable.DiffRequest.views:type_name -> vstable.ViewDefinition + 13, // 23: vstable.DiffRequest.deleted_views:type_name -> vstable.ViewDefinition + 14, // 24: vstable.DiffRequest.triggers:type_name -> vstable.TriggerDefinition + 14, // 25: vstable.DiffRequest.deleted_triggers:type_name -> vstable.TriggerDefinition + 15, // 26: vstable.DiffRequest.routines:type_name -> vstable.RoutineDefinition + 15, // 27: vstable.DiffRequest.deleted_routines:type_name -> vstable.RoutineDefinition + 16, // 28: vstable.DiffRequest.config:type_name -> vstable.DatabaseConfig + 0, // 29: vstable.EngineService.Ping:input_type -> vstable.PingRequest + 2, // 30: vstable.EngineService.Connect:input_type -> vstable.ConnectRequest + 4, // 31: vstable.EngineService.Disconnect:input_type -> vstable.DisconnectRequest + 6, // 32: vstable.EngineService.Query:input_type -> vstable.QueryRequest + 17, // 33: vstable.EngineService.GenerateAlterTable:input_type -> vstable.DiffRequest + 17, // 34: vstable.EngineService.GenerateCreateTable:input_type -> vstable.DiffRequest + 1, // 35: vstable.EngineService.Ping:output_type -> vstable.PingResponse + 3, // 36: vstable.EngineService.Connect:output_type -> vstable.ConnectResponse + 5, // 37: vstable.EngineService.Disconnect:output_type -> vstable.DisconnectResponse + 8, // 38: vstable.EngineService.Query:output_type -> vstable.QueryResponse + 18, // 39: vstable.EngineService.GenerateAlterTable:output_type -> vstable.GenerateSQLResponse + 18, // 40: vstable.EngineService.GenerateCreateTable:output_type -> vstable.GenerateSQLResponse + 35, // [35:41] is the sub-list for method output_type + 29, // [29:35] is the sub-list for method input_type + 29, // [29:29] is the sub-list for extension type_name + 29, // [29:29] is the sub-list for extension extendee + 0, // [0:29] is the sub-list for field type_name +} + +func init() { file_api_vstable_proto_init() } +func file_api_vstable_proto_init() { + if File_api_vstable_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_vstable_proto_rawDesc), len(file_api_vstable_proto_rawDesc)), + NumEnums: 0, + NumMessages: 19, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_api_vstable_proto_goTypes, + DependencyIndexes: file_api_vstable_proto_depIdxs, + MessageInfos: file_api_vstable_proto_msgTypes, + }.Build() + File_api_vstable_proto = out.File + file_api_vstable_proto_goTypes = nil + file_api_vstable_proto_depIdxs = nil +} diff --git a/backend/internal/pb/vstable_grpc.pb.go b/backend/internal/pb/vstable_grpc.pb.go new file mode 100644 index 0000000..150299b --- /dev/null +++ b/backend/internal/pb/vstable_grpc.pb.go @@ -0,0 +1,311 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.1 +// - protoc v7.34.0 +// source: api/vstable.proto + +package pb + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + EngineService_Ping_FullMethodName = "/vstable.EngineService/Ping" + EngineService_Connect_FullMethodName = "/vstable.EngineService/Connect" + EngineService_Disconnect_FullMethodName = "/vstable.EngineService/Disconnect" + EngineService_Query_FullMethodName = "/vstable.EngineService/Query" + EngineService_GenerateAlterTable_FullMethodName = "/vstable.EngineService/GenerateAlterTable" + EngineService_GenerateCreateTable_FullMethodName = "/vstable.EngineService/GenerateCreateTable" +) + +// EngineServiceClient is the client API for EngineService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type EngineServiceClient interface { + Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error) + Connect(ctx context.Context, in *ConnectRequest, opts ...grpc.CallOption) (*ConnectResponse, error) + Disconnect(ctx context.Context, in *DisconnectRequest, opts ...grpc.CallOption) (*DisconnectResponse, error) + Query(ctx context.Context, in *QueryRequest, opts ...grpc.CallOption) (*QueryResponse, error) + GenerateAlterTable(ctx context.Context, in *DiffRequest, opts ...grpc.CallOption) (*GenerateSQLResponse, error) + GenerateCreateTable(ctx context.Context, in *DiffRequest, opts ...grpc.CallOption) (*GenerateSQLResponse, error) +} + +type engineServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewEngineServiceClient(cc grpc.ClientConnInterface) EngineServiceClient { + return &engineServiceClient{cc} +} + +func (c *engineServiceClient) Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PingResponse) + err := c.cc.Invoke(ctx, EngineService_Ping_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *engineServiceClient) Connect(ctx context.Context, in *ConnectRequest, opts ...grpc.CallOption) (*ConnectResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ConnectResponse) + err := c.cc.Invoke(ctx, EngineService_Connect_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *engineServiceClient) Disconnect(ctx context.Context, in *DisconnectRequest, opts ...grpc.CallOption) (*DisconnectResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DisconnectResponse) + err := c.cc.Invoke(ctx, EngineService_Disconnect_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *engineServiceClient) Query(ctx context.Context, in *QueryRequest, opts ...grpc.CallOption) (*QueryResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(QueryResponse) + err := c.cc.Invoke(ctx, EngineService_Query_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *engineServiceClient) GenerateAlterTable(ctx context.Context, in *DiffRequest, opts ...grpc.CallOption) (*GenerateSQLResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GenerateSQLResponse) + err := c.cc.Invoke(ctx, EngineService_GenerateAlterTable_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *engineServiceClient) GenerateCreateTable(ctx context.Context, in *DiffRequest, opts ...grpc.CallOption) (*GenerateSQLResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GenerateSQLResponse) + err := c.cc.Invoke(ctx, EngineService_GenerateCreateTable_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// EngineServiceServer is the server API for EngineService service. +// All implementations must embed UnimplementedEngineServiceServer +// for forward compatibility. +type EngineServiceServer interface { + Ping(context.Context, *PingRequest) (*PingResponse, error) + Connect(context.Context, *ConnectRequest) (*ConnectResponse, error) + Disconnect(context.Context, *DisconnectRequest) (*DisconnectResponse, error) + Query(context.Context, *QueryRequest) (*QueryResponse, error) + GenerateAlterTable(context.Context, *DiffRequest) (*GenerateSQLResponse, error) + GenerateCreateTable(context.Context, *DiffRequest) (*GenerateSQLResponse, error) + mustEmbedUnimplementedEngineServiceServer() +} + +// UnimplementedEngineServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedEngineServiceServer struct{} + +func (UnimplementedEngineServiceServer) Ping(context.Context, *PingRequest) (*PingResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Ping not implemented") +} +func (UnimplementedEngineServiceServer) Connect(context.Context, *ConnectRequest) (*ConnectResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Connect not implemented") +} +func (UnimplementedEngineServiceServer) Disconnect(context.Context, *DisconnectRequest) (*DisconnectResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Disconnect not implemented") +} +func (UnimplementedEngineServiceServer) Query(context.Context, *QueryRequest) (*QueryResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Query not implemented") +} +func (UnimplementedEngineServiceServer) GenerateAlterTable(context.Context, *DiffRequest) (*GenerateSQLResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GenerateAlterTable not implemented") +} +func (UnimplementedEngineServiceServer) GenerateCreateTable(context.Context, *DiffRequest) (*GenerateSQLResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GenerateCreateTable not implemented") +} +func (UnimplementedEngineServiceServer) mustEmbedUnimplementedEngineServiceServer() {} +func (UnimplementedEngineServiceServer) testEmbeddedByValue() {} + +// UnsafeEngineServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to EngineServiceServer will +// result in compilation errors. +type UnsafeEngineServiceServer interface { + mustEmbedUnimplementedEngineServiceServer() +} + +func RegisterEngineServiceServer(s grpc.ServiceRegistrar, srv EngineServiceServer) { + // If the following call panics, it indicates UnimplementedEngineServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&EngineService_ServiceDesc, srv) +} + +func _EngineService_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PingRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EngineServiceServer).Ping(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: EngineService_Ping_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EngineServiceServer).Ping(ctx, req.(*PingRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _EngineService_Connect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ConnectRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EngineServiceServer).Connect(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: EngineService_Connect_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EngineServiceServer).Connect(ctx, req.(*ConnectRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _EngineService_Disconnect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DisconnectRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EngineServiceServer).Disconnect(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: EngineService_Disconnect_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EngineServiceServer).Disconnect(ctx, req.(*DisconnectRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _EngineService_Query_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EngineServiceServer).Query(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: EngineService_Query_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EngineServiceServer).Query(ctx, req.(*QueryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _EngineService_GenerateAlterTable_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DiffRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EngineServiceServer).GenerateAlterTable(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: EngineService_GenerateAlterTable_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EngineServiceServer).GenerateAlterTable(ctx, req.(*DiffRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _EngineService_GenerateCreateTable_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DiffRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EngineServiceServer).GenerateCreateTable(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: EngineService_GenerateCreateTable_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EngineServiceServer).GenerateCreateTable(ctx, req.(*DiffRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// EngineService_ServiceDesc is the grpc.ServiceDesc for EngineService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var EngineService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "vstable.EngineService", + HandlerType: (*EngineServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Ping", + Handler: _EngineService_Ping_Handler, + }, + { + MethodName: "Connect", + Handler: _EngineService_Connect_Handler, + }, + { + MethodName: "Disconnect", + Handler: _EngineService_Disconnect_Handler, + }, + { + MethodName: "Query", + Handler: _EngineService_Query_Handler, + }, + { + MethodName: "GenerateAlterTable", + Handler: _EngineService_GenerateAlterTable_Handler, + }, + { + MethodName: "GenerateCreateTable", + Handler: _EngineService_GenerateCreateTable_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "api/vstable.proto", +} diff --git a/backend/main.go b/backend/main.go index e9f8071..913550e 100644 --- a/backend/main.go +++ b/backend/main.go @@ -2,158 +2,165 @@ package main import ( "context" - "encoding/json" "fmt" "log" - "net/http" + "net" "os" "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "vstable-engine/internal/ast" "vstable-engine/internal/db" + "vstable-engine/internal/mapper" + "vstable-engine/internal/pb" ) -type Response struct { - Success bool `json:"success"` - Data interface{} `json:"data,omitempty"` - Error string `json:"error,omitempty"` +type engineServer struct { + pb.UnimplementedEngineServiceServer + dbManager *db.Manager } -type ConnectRequest struct { - ID string `json:"id"` - Dialect string `json:"dialect"` - DSN string `json:"dsn"` +func (s *engineServer) Ping(ctx context.Context, req *pb.PingRequest) (*pb.PingResponse, error) { + return &pb.PingResponse{Status: "ok"}, nil } -type QueryRequest struct { - ID string `json:"id"` - SQL string `json:"sql"` - Params []interface{} `json:"params"` +func (s *engineServer) Connect(ctx context.Context, req *pb.ConnectRequest) (*pb.ConnectResponse, error) { + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + + if err := s.dbManager.Connect(ctx, req.Id, req.Dialect, req.Dsn); err != nil { + return nil, status.Errorf(codes.Internal, "connect failed: %v", err) + } + return &pb.ConnectResponse{Success: true}, nil } -func main() { - port := os.Getenv("VSTABLE_PORT") - if port == "" { - port = "39082" +func (s *engineServer) Disconnect(ctx context.Context, req *pb.DisconnectRequest) (*pb.DisconnectResponse, error) { + if err := s.dbManager.Disconnect(req.Id); err != nil { + return nil, status.Errorf(codes.Internal, "disconnect failed: %v", err) } + return &pb.DisconnectResponse{Success: true}, nil +} - dbManager := db.NewManager() - mux := http.NewServeMux() - - // 健康检查 - mux.HandleFunc("/api/ping", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]string{"status": "ok"}) - }) - - // 建立连接 - mux.HandleFunc("/api/connect", func(w http.ResponseWriter, r *http.Request) { - var req ConnectRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - sendError(w, http.StatusBadRequest, err) - return - } +func (s *engineServer) Query(ctx context.Context, req *pb.QueryRequest) (*pb.QueryResponse, error) { + driver, err := s.dbManager.Get(req.Id) + if err != nil { + return nil, status.Errorf(codes.NotFound, "session not found: %v", err) + } - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() + ctx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() - if err := dbManager.Connect(ctx, req.ID, req.Dialect, req.DSN); err != nil { - sendError(w, http.StatusInternalServerError, err) - return - } - sendJSON(w, http.StatusOK, Response{Success: true}) - }) - - // 执行查询 - mux.HandleFunc("/api/query", func(w http.ResponseWriter, r *http.Request) { - var req QueryRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - sendError(w, http.StatusBadRequest, err) - return + var params []interface{} + if req.Params != nil { + for _, v := range req.Params.Values { + params = append(params, v.AsInterface()) } + } + + result, err := driver.Query(ctx, req.Sql, params) + if err != nil { + return nil, status.Errorf(codes.Internal, "query error: %v", err) + } - driver, err := dbManager.Get(req.ID) - if err != nil { - sendError(w, http.StatusInternalServerError, err) - return + fields := make([]*pb.FieldInfo, len(result.Fields)) + for i, f := range result.Fields { + fields[i] = &pb.FieldInfo{ + Name: f.Name, + Type: f.Type, } + } - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() + rows := mapper.RowsToStructs(result.Rows) - result, err := driver.Query(ctx, req.SQL, req.Params) - if err != nil { - sendError(w, http.StatusInternalServerError, err) - return - } + return &pb.QueryResponse{ + Success: true, + Rows: rows, + Fields: fields, + }, nil +} - // 适配前端预期的格式 - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(map[string]interface{}{ - "success": true, - "rows": result.Rows, - "fields": result.Fields, - "data": result.Rows, // 兼容性字段 - }) - }) - - // 断开连接 - mux.HandleFunc("/api/disconnect", func(w http.ResponseWriter, r *http.Request) { - id := r.URL.Query().Get("id") - if err := dbManager.Disconnect(id); err != nil { - sendError(w, http.StatusInternalServerError, err) - return - } - sendJSON(w, http.StatusOK, Response{Success: true}) - }) - - // SQL 生成:生成 ALTER TABLE SQL - mux.HandleFunc("/api/diff", func(w http.ResponseWriter, r *http.Request) { - var req ast.DiffRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - sendError(w, http.StatusBadRequest, err) - return - } +func (s *engineServer) GenerateAlterTable(ctx context.Context, req *pb.DiffRequest) (*pb.GenerateSQLResponse, error) { + astReq := mapper.ToASTDiffRequest(req) - compiler, err := ast.GetCompiler(req.Dialect) - if err != nil { - sendError(w, http.StatusBadRequest, err) - return - } + compiler, err := ast.GetCompiler(req.Dialect) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "get compiler failed: %v", err) + } + + sqls := compiler.GenerateAlterTableSql(astReq) + return &pb.GenerateSQLResponse{ + Success: true, + Sqls: sqls, + }, nil +} + +func (s *engineServer) GenerateCreateTable(ctx context.Context, req *pb.DiffRequest) (*pb.GenerateSQLResponse, error) { + astReq := mapper.ToASTDiffRequest(req) - sqls := compiler.GenerateAlterTableSql(req) - sendJSON(w, http.StatusOK, Response{Success: true, Data: sqls}) - }) + compiler, err := ast.GetCompiler(req.Dialect) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "get compiler failed: %v", err) + } - // SQL 生成:生成 CREATE TABLE SQL - mux.HandleFunc("/api/create-table", func(w http.ResponseWriter, r *http.Request) { - var req ast.DiffRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - sendError(w, http.StatusBadRequest, err) - return + sqls := compiler.GenerateCreateTableSql(astReq) + return &pb.GenerateSQLResponse{ + Success: true, + Sqls: sqls, + }, nil +} + +// UnaryInterceptor handles panics and generic errors +func UnaryInterceptor( + ctx context.Context, + req interface{}, + info *grpc.UnaryServerInfo, + handler grpc.UnaryHandler, +) (resp interface{}, err error) { + defer func() { + if r := recover(); r != nil { + err = status.Errorf(codes.Internal, "panic: %v", r) } + }() - compiler, err := ast.GetCompiler(req.Dialect) - if err != nil { - sendError(w, http.StatusBadRequest, err) - return + resp, err = handler(ctx, req) + if err != nil { + // Just ensure it's a grpc status error + if _, ok := status.FromError(err); !ok { + err = status.Errorf(codes.Unknown, "%v", err) } + log.Printf("[gRPC Error] %s: %v", info.FullMethod, err) + } + return resp, err +} - sqls := compiler.GenerateCreateTableSql(req) - sendJSON(w, http.StatusOK, Response{Success: true, Data: sqls}) - }) +func main() { + port := os.Getenv("VSTABLE_PORT") + if port == "" { + port = "39082" + } - addr := ":" + port - fmt.Printf("Engine listening on %s...\n", addr) - log.Fatal(http.ListenAndServe(addr, mux)) -} + // 50MB is generous for large schemas and query results + maxMsgSize := 1024 * 1024 * 50 + + lis, err := net.Listen("tcp", ":"+port) + if err != nil { + log.Fatalf("failed to listen: %v", err) + } -func sendJSON(w http.ResponseWriter, status int, resp interface{}) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(status) - json.NewEncoder(w).Encode(resp) -} + grpcServer := grpc.NewServer( + grpc.UnaryInterceptor(UnaryInterceptor), + grpc.MaxRecvMsgSize(maxMsgSize), + grpc.MaxSendMsgSize(maxMsgSize), + ) -func sendError(w http.ResponseWriter, status int, err error) { - sendJSON(w, status, Response{Success: false, Error: err.Error()}) + dbManager := db.NewManager() + pb.RegisterEngineServiceServer(grpcServer, &engineServer{dbManager: dbManager}) + + fmt.Printf("gRPC Engine listening on :%s...\n", port) + if err := grpcServer.Serve(lis); err != nil { + log.Fatalf("failed to serve: %v", err) + } } diff --git a/frontend/e2e/advanced.spec.ts b/frontend/e2e/advanced.spec.ts index 97cb435..88ad49d 100644 --- a/frontend/e2e/advanced.spec.ts +++ b/frontend/e2e/advanced.spec.ts @@ -24,8 +24,13 @@ test.describe('Advanced Features Tests', () => { // Wait for Go engine await expect(async () => { - const response = await window.request.get('http://127.0.0.1:39082/api/ping'); - expect(response.ok()).toBeTruthy(); + const pingOk = await window.evaluate(() => + (window as any).api + .enginePing() + .then((r: any) => r.status === 'ok') + .catch(() => false) + ); + expect(pingOk).toBeTruthy(); }).toPass({ timeout: 15000 }); // Connect to PostgreSQL diff --git a/frontend/e2e/connection.spec.ts b/frontend/e2e/connection.spec.ts index 283ae98..30411d3 100644 --- a/frontend/e2e/connection.spec.ts +++ b/frontend/e2e/connection.spec.ts @@ -23,8 +23,13 @@ test.describe('Connection Management Tests', () => { await window.waitForLoadState('domcontentloaded'); await expect(async () => { - const response = await window.request.get('http://127.0.0.1:39082/api/ping'); - expect(response.ok()).toBeTruthy(); + const pingOk = await window.evaluate(() => + (window as any).api + .enginePing() + .then((r: any) => r.status === 'ok') + .catch(() => false) + ); + expect(pingOk).toBeTruthy(); }).toPass({ timeout: 15000 }); }); diff --git a/frontend/e2e/data-grid.spec.ts b/frontend/e2e/data-grid.spec.ts index 8c69079..aebdd83 100644 --- a/frontend/e2e/data-grid.spec.ts +++ b/frontend/e2e/data-grid.spec.ts @@ -25,8 +25,13 @@ test.describe('Data Grid Tests', () => { // Wait for Go engine await expect(async () => { - const response = await window.request.get('http://127.0.0.1:39082/api/ping'); - expect(response.ok()).toBeTruthy(); + const pingOk = await window.evaluate(() => + (window as any).api + .enginePing() + .then((r: any) => r.status === 'ok') + .catch(() => false) + ); + expect(pingOk).toBeTruthy(); }).toPass({ timeout: 15000 }); // Connect to PostgreSQL diff --git a/frontend/e2e/schema.spec.ts b/frontend/e2e/schema.spec.ts index 5a6929e..f0b6740 100644 --- a/frontend/e2e/schema.spec.ts +++ b/frontend/e2e/schema.spec.ts @@ -24,8 +24,13 @@ test.describe('Schema Designer Tests', () => { // Wait for Go engine await expect(async () => { - const response = await window.request.get('http://127.0.0.1:39082/api/ping'); - expect(response.ok()).toBeTruthy(); + const pingOk = await window.evaluate(() => + (window as any).api + .enginePing() + .then((r: any) => r.status === 'ok') + .catch(() => false) + ); + expect(pingOk).toBeTruthy(); }).toPass({ timeout: 15000 }); // Connect to PostgreSQL diff --git a/frontend/e2e/workspace-persistence.spec.ts b/frontend/e2e/workspace-persistence.spec.ts index ea612bf..58e04d4 100644 --- a/frontend/e2e/workspace-persistence.spec.ts +++ b/frontend/e2e/workspace-persistence.spec.ts @@ -28,8 +28,13 @@ test.describe('Workspace Persistence Tests', () => { // Wait for engine await expect(async () => { - const response = await window.request.get('http://127.0.0.1:39082/api/ping'); - expect(response.ok()).toBeTruthy(); + const pingOk = await window.evaluate(() => + (window as any).api + .enginePing() + .then((r: any) => r.status === 'ok') + .catch(() => false) + ); + expect(pingOk).toBeTruthy(); }).toPass({ timeout: 15000 }); // Connect to PostgreSQL @@ -100,8 +105,13 @@ test.describe('Workspace Persistence Tests', () => { // Wait for engine await expect(async () => { - const response = await window.request.get('http://127.0.0.1:39082/api/ping'); - expect(response.ok()).toBeTruthy(); + const pingOk = await window.evaluate(() => + (window as any).api + .enginePing() + .then((r: any) => r.status === 'ok') + .catch(() => false) + ); + expect(pingOk).toBeTruthy(); }).toPass({ timeout: 15000 }); // the tab should be restored diff --git a/frontend/package.json b/frontend/package.json index 934c529..73de264 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -38,6 +38,13 @@ "filter": [ "**/*" ] + }, + { + "from": "resources/api", + "to": "api", + "filter": [ + "**/*.proto" + ] } ], "mac": { @@ -83,6 +90,8 @@ "dependencies": { "@electron-toolkit/preload": "^3.0.2", "@electron-toolkit/utils": "^4.0.0", + "@grpc/grpc-js": "^1.14.3", + "@grpc/proto-loader": "^0.8.0", "@monaco-editor/react": "^4.7.0", "@tailwindcss/postcss": "^4.1.18", "lucide-react": "^0.563.0", diff --git a/frontend/resources/api/vstable.proto b/frontend/resources/api/vstable.proto new file mode 100644 index 0000000..bc68502 --- /dev/null +++ b/frontend/resources/api/vstable.proto @@ -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; +} diff --git a/frontend/src/main/grpcClient.ts b/frontend/src/main/grpcClient.ts new file mode 100644 index 0000000..3f420ae --- /dev/null +++ b/frontend/src/main/grpcClient.ts @@ -0,0 +1,143 @@ +import * as grpc from '@grpc/grpc-js'; +import * as protoLoader from '@grpc/proto-loader'; +import { app } from 'electron'; +import { join } from 'path'; + +function wrapValue(val: any): any { + if (val === null || val === undefined) return { nullValue: 0 }; + if (typeof val === 'number') return { numberValue: val }; + if (typeof val === 'string') return { stringValue: val }; + if (typeof val === 'boolean') return { boolValue: val }; + if (Array.isArray(val)) return { listValue: { values: val.map(wrapValue) } }; + if (typeof val === 'object') return { structValue: wrapStruct(val) }; + return { stringValue: String(val) }; +} + +function wrapStruct(obj: any): any { + if (!obj) return { fields: {} }; + const fields: any = {}; + for (const [k, v] of Object.entries(obj)) { + fields[k] = wrapValue(v); + } + return { fields }; +} + +function unwrapValue(val: any): any { + if (val === null || val === undefined) return null; + if (val.nullValue !== undefined) return null; + if (val.numberValue !== undefined) return val.numberValue; + if (val.stringValue !== undefined) return val.stringValue; + if (val.boolValue !== undefined) return val.boolValue; + if (val.structValue !== undefined) return unwrapStruct(val.structValue); + if (val.listValue !== undefined) + return val.listValue.values ? val.listValue.values.map(unwrapValue) : []; + return val; +} + +function unwrapStruct(struct: any): any { + if (!struct || !struct.fields) return {}; + const res: any = {}; + for (const [k, v] of Object.entries(struct.fields)) { + res[k] = unwrapValue(v); + } + return res; +} + +function processDiffRequest(req: any): any { + if (!req) return req; + const processed = JSON.parse(JSON.stringify(req)); + + const processCol = (col: any) => { + if (!col) return; + if ('length' in col) col.length = wrapValue(col.length); + if ('precision' in col) col.precision = wrapValue(col.precision); + if ('scale' in col) col.scale = wrapValue(col.scale); + if (col.defaultValue !== undefined && col.defaultValue !== null) { + col.defaultValue = { value: String(col.defaultValue) }; + } else { + col.defaultValue = null; + } + if (col.original) processCol(col.original); + }; + + const processCols = (cols: any[]) => { + if (!Array.isArray(cols)) return; + cols.forEach(processCol); + }; + + processCols(processed.columns); + processCols(processed.deletedColumns); + return processed; +} + +export class GrpcClient { + private client: any; + + constructor(port: number) { + const isDev = !app.isPackaged; + const protoPath = isDev + ? join(app.getAppPath(), 'resources/api/vstable.proto') + : join(process.resourcesPath, 'api/vstable.proto'); + + const packageDefinition = protoLoader.loadSync(protoPath, { + keepCase: false, // use camelCase for standard gRPC JS behavior + longs: String, + enums: String, + defaults: false, + oneofs: true, + }); + const protoDescriptor = grpc.loadPackageDefinition(packageDefinition); + const vstable = protoDescriptor.vstable as any; + + this.client = new vstable.EngineService(`127.0.0.1:${port}`, grpc.credentials.createInsecure()); + } + + private callRpc(method: string, request: any): Promise { + return new Promise((resolve, reject) => { + this.client[method](request, (err: any, response: any) => { + if (err) { + console.error(`[gRPC] Error calling ${method}:`, err); + resolve({ success: false, error: err.details || err.message || 'Unknown error' }); + } else { + resolve({ ...response, success: true }); + } + }); + }); + } + + async ping() { + const res = await this.callRpc('Ping', {}); + if (!res.success) throw new Error(res.error); + return res; + } + + async connect(req: any) { + return this.callRpc('Connect', req); + } + + async disconnect(id: string) { + return this.callRpc('Disconnect', { id }); + } + + async query(id: string, sql: string, params: any[]) { + const pbParams = { values: params.map(wrapValue) }; + const res = await this.callRpc('Query', { id, sql, params: pbParams }); + if (!res.success) return res; + + // Unwrap the rows from google.protobuf.Struct + const rows = res.rows ? res.rows.map(unwrapStruct) : []; + return { ...res, rows, data: rows }; + } + + async generateAlterTable(req: any) { + const res = await this.callRpc('GenerateAlterTable', processDiffRequest(req)); + if (!res.success) throw new Error(res.error); + return res.sqls || []; + } + + async generateCreateTable(req: any) { + const res = await this.callRpc('GenerateCreateTable', processDiffRequest(req)); + if (!res.success) throw new Error(res.error); + return res.sqls || []; + } +} diff --git a/frontend/src/main/index.ts b/frontend/src/main/index.ts index 2972794..e533481 100644 --- a/frontend/src/main/index.ts +++ b/frontend/src/main/index.ts @@ -1,9 +1,12 @@ import { is, optimizer } from '@electron-toolkit/utils'; import { app, BrowserWindow, ipcMain, shell } from 'electron'; import { join } from 'path'; +import { GrpcClient } from './grpcClient'; import { logger } from './logger'; import * as store from './store'; +let grpcClient: GrpcClient; + function createWindow(): void { const mainWindow = new BrowserWindow({ width: 1000, @@ -51,24 +54,7 @@ function handleIPC(channel: string, listener: (event: any, ...args: any[]) => an }); } -// 辅助函数:向 Go 引擎发送请求 -async function engineFetch(path: string, method: string, body?: any) { - const url = `http://127.0.0.1:${daemonManager.port}${path}`; - try { - const response = await fetch(url, { - method, - headers: { 'Content-Type': 'application/json' }, - body: body ? JSON.stringify(body) : undefined, - }); - const result = await response.json(); - return result; - } catch (error: any) { - console.error(`[Main] engineFetch error for ${path}:`, error.message, error.code); - return { success: false, error: `Engine communication error: ${error.message}` }; - } -} - -// 数据库 IPC 处理 (代理到 Go) +// 数据库 IPC 处理 (代理到 Go gRPC) handleIPC('db:connect', async (_, id, config) => { // 处理保存的加密密码 let password = config.password || ''; @@ -84,7 +70,7 @@ handleIPC('db:connect', async (_, id, config) => { dsn = `${config.user}:${password}@tcp(${config.host}:${config.port})/${config.database}`; } - return await engineFetch('/api/connect', 'POST', { + return await grpcClient.connect({ id, dialect: config.dialect, dsn, @@ -92,36 +78,34 @@ handleIPC('db:connect', async (_, id, config) => { }); handleIPC('db:disconnect', async (_, id) => { - return await engineFetch(`/api/disconnect?id=${id}`, 'GET'); + return await grpcClient.disconnect(id); }); handleIPC('db:query', async (_, id, sql, params) => { - // query 接口前端需要返回的也是完整对象 { success, data, error } - return await engineFetch('/api/query', 'POST', { - id, - sql, - params: params || [], - }); + return await grpcClient.query(id, sql, params || []); }); // SQL 生成代理 handleIPC('sql:generate-alter', async (_, req) => { - const result = await engineFetch('/api/diff', 'POST', req); - if (result.success) return result.data || []; - throw new Error(result.error || 'SQL generation failed'); + try { + return await grpcClient.generateAlterTable(req); + } catch (err: any) { + throw new Error(err.message || 'SQL generation failed'); + } }); handleIPC('sql:generate-create', async (_, req) => { - const result = await engineFetch('/api/create-table', 'POST', req); - if (result.success) return result.data || []; - throw new Error(result.error || 'SQL generation failed'); + try { + return await grpcClient.generateCreateTable(req); + } catch (err: any) { + throw new Error(err.message || 'SQL generation failed'); + } }); // Go Engine 通信测试代理 handleIPC('engine:ping', async () => { try { - const response = await fetch(`http://127.0.0.1:${daemonManager.port}/api/ping`); - return await response.json(); + return await grpcClient.ping(); } catch (error: any) { console.error('Failed to ping Go engine:', error); throw error; @@ -180,6 +164,7 @@ import { daemonManager } from './daemon'; app.whenReady().then(() => { daemonManager.start(); + grpcClient = new GrpcClient(daemonManager.port); // Set app user model id for windows if (process.platform === 'win32') { From 5bdab6a0251061650916f7642f977d7abc591f26 Mon Sep 17 00:00:00 2001 From: circle33 Date: Mon, 9 Mar 2026 11:45:08 +0800 Subject: [PATCH 3/3] feat: add gen_proto.sh script and document it in AGENTS.md --- AGENTS.md | 2 ++ backend/scripts/gen_proto.sh | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100755 backend/scripts/gen_proto.sh diff --git a/AGENTS.md b/AGENTS.md index b58929b..2e809a2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 @@ -37,6 +38,7 @@ vstable 是一款专为开发者设计的现代数据库管理工具,支持可 - `internal/ast`: 核心 Schema Diff 引擎。提供 AST 类型定义以及特定数据库方言(PostgreSQL/MySQL)的编译器,用于基于状态对齐生成精确的 DDL Diff。 - `internal/db`: 数据库连接管理器和驱动程序抽象。 - `main.go`: 启动 gRPC Server,处理来自 Electron 主进程的远程过程调用。 + - `scripts/gen_proto.sh`: 构建脚本,用于从 `.proto` 文件生成 Go 代码,并同步协议定义到前端资源目录供运行时加载。 - **Electron Main (`frontend/src/main/`)**: - `daemon.ts`: 管理 Go 后端引擎进程的生命周期(启动、日志记录和停止)。 - `grpcClient.ts`: 封装 gRPC 客户端,通过严格类型的 Protobuf 协议与后端引擎通信。 diff --git a/backend/scripts/gen_proto.sh b/backend/scripts/gen_proto.sh new file mode 100755 index 0000000..f98376d --- /dev/null +++ b/backend/scripts/gen_proto.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Get the root of the project +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +BACKEND_DIR="$ROOT_DIR/backend" +FRONTEND_DIR="$ROOT_DIR/frontend" + +echo "Generating Go Protobuf files..." +cd "$BACKEND_DIR" || exit + +# Ensure output directory exists +mkdir -p internal/pb + +# Generate Go code +# Note: This assumes protoc and its Go plugins are installed +protoc --go_out=. --go_opt=module=vstable-engine --go-grpc_out=. --go-grpc_opt=module=vstable-engine api/vstable.proto + +echo "Syncing .proto file to frontend resources..." +# Ensure frontend resource directory exists +mkdir -p "$FRONTEND_DIR/resources/api" + +# Copy the proto file to frontend for runtime loading +cp "$BACKEND_DIR/api/vstable.proto" "$FRONTEND_DIR/resources/api/vstable.proto" + +echo "Protobuf generation and sync complete!"