tauri-plugin-pg-sync 0.1.11

Offline-first PostgreSQL sync plugin for Tauri apps
# 服务端 PostgreSQL 配置指南

本文档说明如何配置 PostgreSQL 服务端以支持实时数据同步。

## 概述

使用 PostgreSQL 内置的 `LISTEN/NOTIFY` 机制实现实时变更通知,无需额外的 WebSocket 服务。

```
┌─────────────┐     INSERT/UPDATE/DELETE     ┌─────────────┐
│  客户端 A   │ ─────────────────────────────▶│             │
└─────────────┘                               │             │
                                              │ PostgreSQL  │
┌─────────────┐     LISTEN 'data_changes'    │             │
│  客户端 B   │ ◀─────────────────────────────│             │
└─────────────┘     pg_notify()              └─────────────┘
```

## 第一步:创建通知函数

在 PostgreSQL 中执行以下 SQL:

```sql
-- 创建通知函数
CREATE OR REPLACE FUNCTION notify_table_change()
RETURNS trigger AS $$
DECLARE
    payload JSON;
    record_id TEXT;
BEGIN
    -- 获取记录 ID
    IF TG_OP = 'DELETE' THEN
        record_id := OLD.id::TEXT;
    ELSE
        record_id := NEW.id::TEXT;
    END IF;
    
    -- 构建通知载荷
    payload := json_build_object(
        'table', TG_TABLE_SCHEMA || '.' || TG_TABLE_NAME,
        'action', TG_OP,
        'id', record_id,
        'timestamp', CURRENT_TIMESTAMP
    );
    
    -- 发送通知
    PERFORM pg_notify('data_changes', payload::TEXT);
    
    -- 返回结果
    IF TG_OP = 'DELETE' THEN
        RETURN OLD;
    ELSE
        RETURN NEW;
    END IF;
END;
$$ LANGUAGE plpgsql;
```

## 第二步:为同步表添加触发器

为每个需要同步的表添加触发器:

```sql
-- 为 users 表添加触发器
CREATE TRIGGER users_sync_trigger
AFTER INSERT OR UPDATE OR DELETE ON users
FOR EACH ROW EXECUTE FUNCTION notify_table_change();

-- 为 orders 表添加触发器
CREATE TRIGGER orders_sync_trigger
AFTER INSERT OR UPDATE OR DELETE ON orders
FOR EACH ROW EXECUTE FUNCTION notify_table_change();

-- 通用模板(替换 TABLE_NAME)
CREATE TRIGGER TABLE_NAME_sync_trigger
AFTER INSERT OR UPDATE OR DELETE ON TABLE_NAME
FOR EACH ROW EXECUTE FUNCTION notify_table_change();
```

## 第三步:批量添加触发器脚本

使用此脚本为所有带前缀的表添加触发器:

```sql
-- 为所有 app_ 前缀的表添加触发器
DO $$
DECLARE
    tbl RECORD;
BEGIN
    FOR tbl IN 
        SELECT table_name 
        FROM information_schema.tables 
        WHERE table_schema = 'public' 
          AND table_type = 'BASE TABLE'
          AND table_name LIKE 'app_%'  -- 修改为你的表前缀
    LOOP
        EXECUTE format(
            'DROP TRIGGER IF EXISTS %I_sync_trigger ON %I',
            tbl.table_name, tbl.table_name
        );
        EXECUTE format(
            'CREATE TRIGGER %I_sync_trigger 
             AFTER INSERT OR UPDATE OR DELETE ON %I
             FOR EACH ROW EXECUTE FUNCTION notify_table_change()',
            tbl.table_name, tbl.table_name
        );
        RAISE NOTICE 'Created trigger for table: %', tbl.table_name;
    END LOOP;
END $$;
```

## 第四步:验证配置

### 测试通知是否工作

打开两个 PostgreSQL 终端:

**终端 1(监听):**
```sql
LISTEN data_changes;
```

**终端 2(触发):**
```sql
INSERT INTO users (id, name, _hlc, _node_id) 
VALUES (gen_random_uuid(), 'Test', '2024-01-01T00:00:00Z', 'test-node');
```

**终端 1 应收到通知:**
```
Asynchronous notification "data_changes" with payload 
"{"table":"public.users","action":"INSERT","id":"xxx-xxx","timestamp":"2024-01-01T12:00:00"}" 
received from server process with PID 12345.
```

## 第五步:确保同步元字段存在

每个同步表必须包含以下字段:

```sql
-- 检查并添加同步元字段
ALTER TABLE users ADD COLUMN IF NOT EXISTS _hlc TEXT NOT NULL DEFAULT '';
ALTER TABLE users ADD COLUMN IF NOT EXISTS _node_id TEXT NOT NULL DEFAULT '';
ALTER TABLE users ADD COLUMN IF NOT EXISTS _version INTEGER DEFAULT 1;
ALTER TABLE users ADD COLUMN IF NOT EXISTS _deleted BOOLEAN DEFAULT FALSE;

-- 创建索引
CREATE INDEX IF NOT EXISTS idx_users_hlc ON users(_hlc);
CREATE INDEX IF NOT EXISTS idx_users_deleted ON users(_deleted);
```

## 完整建表示例

```sql
-- 创建用户表(完整示例)
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name TEXT NOT NULL,
    email TEXT,
    age INTEGER,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    
    -- 同步元字段(必需)
    _hlc TEXT NOT NULL,
    _node_id TEXT NOT NULL,
    _version INTEGER DEFAULT 1,
    _deleted BOOLEAN DEFAULT FALSE
);

-- 创建索引
CREATE INDEX idx_users_hlc ON users(_hlc);
CREATE INDEX idx_users_deleted ON users(_deleted);

-- 添加同步触发器
CREATE TRIGGER users_sync_trigger
AFTER INSERT OR UPDATE OR DELETE ON users
FOR EACH ROW EXECUTE FUNCTION notify_table_change();
```

## 第六步:服务端直接修改时自动更新同步元数据(推荐)

当直接在 PostgreSQL 中修改数据时(如通过管理工具或后端服务),需要自动更新 `_hlc` 和 `_node_id`,否则客户端可能无法检测到这些变更。

```sql
-- 创建自动更新同步元数据的触发器函数
CREATE OR REPLACE FUNCTION auto_update_sync_meta()
RETURNS trigger AS $$
BEGIN
    -- 只在服务端直接修改时更新(_node_id 未被客户端改变)
    IF TG_OP = 'UPDATE' THEN
        IF OLD."_node_id" = NEW."_node_id" THEN
            NEW."_hlc" := (EXTRACT(EPOCH FROM NOW()) * 1000)::BIGINT::TEXT || ':0:server';
            NEW."_node_id" := 'server';
        END IF;
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- 为表添加 BEFORE UPDATE 触发器
CREATE TRIGGER users_auto_sync_meta
BEFORE UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION auto_update_sync_meta();
```

> **注意**: 此触发器是 BEFORE UPDATE,与 notify_table_change 的 AFTER 触发器互不冲突。

## PostgreSQL 配置优化(可选)

### 增加通知队列大小

编辑 `postgresql.conf`:

```ini
# 增加异步通知队列大小(默认 8MB)
notify_queue_pages = 1000
```

### 连接池配置

如果使用连接池(如 PgBouncer),需要配置为 session 模式以支持 LISTEN:

```ini
# pgbouncer.ini
pool_mode = session
```

## 使用 Schema 隔离(可选)

如果多个应用共用同一个数据库,建议使用 PostgreSQL schema 隔离:

```sql
-- 创建应用专用 schema
CREATE SCHEMA IF NOT EXISTS mobile_app;

-- 在指定 schema 创建表
CREATE TABLE mobile_app.users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name TEXT NOT NULL,
    -- ...其他字段
    _hlc TEXT NOT NULL,
    _node_id TEXT NOT NULL,
    _version INTEGER DEFAULT 1,
    _deleted BOOLEAN DEFAULT FALSE
);

-- 为指定 schema 的表添加触发器
CREATE TRIGGER users_sync_trigger
AFTER INSERT OR UPDATE OR DELETE ON mobile_app.users
FOR EACH ROW EXECUTE FUNCTION notify_table_change();
```

## 权限配置

确保应用数据库用户有以下权限:

```sql
-- 创建应用用户
CREATE USER app_user WITH PASSWORD 'your_password';

-- 授权
GRANT CONNECT ON DATABASE your_db TO app_user;
GRANT USAGE ON SCHEMA public TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_user;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app_user;

-- 如果使用自定义 schema
GRANT USAGE ON SCHEMA mobile_app TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA mobile_app TO app_user;
```

## 云数据库支持

### Supabase

Supabase 基于 PostgreSQL,完全支持 LISTEN/NOTIFY:

```javascript
// 连接字符串
const url = 'postgres://postgres:[PASSWORD]@db.[PROJECT_ID].supabase.co:5432/postgres';
```

### AWS RDS

AWS RDS PostgreSQL 支持 LISTEN/NOTIFY,无需额外配置。

### Azure Database for PostgreSQL

Azure PostgreSQL 支持 LISTEN/NOTIFY,但需要使用 SSL 连接。

### 阿里云 RDS PostgreSQL

支持 LISTEN/NOTIFY,确保安全组开放 5432 端口。

## 监控和调试

### 查看当前监听

```sql
SELECT * FROM pg_stat_activity WHERE state = 'idle' AND query LIKE '%LISTEN%';
```

### 查看通知统计

```sql
SELECT * FROM pg_stat_database WHERE datname = 'your_db';
```

### 手动发送测试通知

```sql
SELECT pg_notify('data_changes', '{"table":"test","action":"TEST","id":"123"}');
```

## 故障排查

### 客户端收不到通知

1. 检查触发器是否创建成功:
   ```sql
   SELECT * FROM information_schema.triggers WHERE trigger_name LIKE '%sync%';
   ```

2. 检查连接是否保持(LISTEN 需要持久连接)

3. 检查防火墙/安全组是否允许长连接

### 通知延迟

1. 检查数据库负载
2. 考虑增加 `notify_queue_pages` 配置
3. 检查网络延迟

## 下一步

服务端配置完成后,客户端会自动监听变更通知并触发同步。

详见 [API.md](./API.md) 中的实时同步章节。