PGlite函数编程:自定义函数与存储过程的开发指南
【免费下载链接】pglite 项目地址: https://gitcode.com/GitHub_Trending/pg/pglite
引言:PostgreSQL函数编程的Web革命
你还在为前端应用缺乏强大的数据处理能力而烦恼吗?还在为前后端数据一致性问题奔波于API调试吗?PGlite(PostgreSQL Lite)带来了颠覆性的解决方案——在浏览器环境中直接运行完整的PostgreSQL数据库引擎,让你能够编写高性能的自定义函数与存储过程,实现真正的前端数据自治。
本文将带你深入探索PGlite函数编程的世界,从基础语法到高级应用,从性能优化到实战案例,全方位掌握在浏览器中开发PostgreSQL函数的核心技能。读完本文,你将能够:
- 使用PL/pgSQL编写复杂业务逻辑的自定义函数
- 开发事务安全的存储过程处理多步骤数据操作
- 掌握函数调试与性能优化的关键技巧
- 在React等前端框架中无缝集成数据库函数
- 解决前端数据处理中的并发与一致性问题
一、PGlite函数编程基础
1.1 环境准备与初始化
PGlite提供了简洁的API来初始化数据库环境,所有函数和存储过程都运行在这个嵌入式数据库实例中:
import { PGlite } from '@electric-sql/pglite';
// 初始化内存数据库(浏览器环境)
const pg = await PGlite.create({
dataDir: 'memory://my-functions-db',
debug: 1
});
// 验证数据库连接
const res = await pg.query('SELECT version();');
console.log('PGlite版本:', res.rows[0].version);
1.2 自定义函数的基本结构
PGlite完全支持PostgreSQL的函数创建语法,最常用的是PL/pgSQL语言:
-- 创建基本数学运算函数
CREATE OR REPLACE FUNCTION calculate_factorial(n INT)
RETURNS INT AS $$
DECLARE
result INT := 1;
BEGIN
-- 异常处理
IF n < 0 THEN
RAISE EXCEPTION '输入不能为负数: %', n;
ELSIF n = 0 OR n = 1 THEN
RETURN result;
ELSE
-- 循环计算阶乘
FOR i IN 2..n LOOP
result := result * i;
END LOOP;
RETURN result;
END IF;
END;
$$ LANGUAGE plpgsql;
在PGlite中调用函数与标准PostgreSQL完全一致:
// 调用自定义函数
const result = await pg.query(
'SELECT calculate_factorial($1) AS result;',
[5]
);
console.log('5的阶乘:', result.rows[0].result); // 输出: 120
// 处理函数抛出的异常
try {
await pg.query('SELECT calculate_factorial($1);', [-3]);
} catch (e) {
console.error('函数执行错误:', e.message); // 输出: 输入不能为负数: -3
}
二、PL/pgSQL函数高级特性
2.1 参数类型与返回值
PGlite支持PostgreSQL的所有数据类型,包括数组、JSON和自定义类型:
-- 创建处理JSON数据的函数
CREATE OR REPLACE FUNCTION process_user_data(user_data JSONB)
RETURNS TEXT AS $$
DECLARE
user_name TEXT;
user_age INT;
BEGIN
-- 提取JSON字段
user_name := user_data->>'name';
user_age := (user_data->>'age')::INT;
-- 条件逻辑
IF user_age >= 18 THEN
RETURN format('已成年用户: %s (年龄: %s)', user_name, user_age);
ELSE
RETURN format('青少年用户: %s (年龄: %s)', user_name, user_age);
END IF;
END;
$$ LANGUAGE plpgsql;
调用带复杂参数的函数:
// 调用JSON处理函数
const userResult = await pg.query(
'SELECT process_user_data($1) AS result;',
[{ name: '张三', age: 25, email: 'zhangsan@example.com' }]
);
console.log(userResult.rows[0].result); // 输出: 已成年用户: 张三 (年龄: 25)
2.2 表值函数与集合返回
创建返回多行结果的表值函数,实现复杂数据查询逻辑封装:
-- 创建表值函数
CREATE OR REPLACE FUNCTION get_users_by_age_range(min_age INT, max_age INT)
RETURNS TABLE (id INT, name TEXT, age INT) AS $$
BEGIN
RETURN QUERY
SELECT u.id, u.name, u.age
FROM users u
WHERE u.age BETWEEN min_age AND max_age
ORDER BY u.age;
END;
$$ LANGUAGE plpgsql;
使用表值函数:
// 创建测试表
await pg.exec(`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
age INT NOT NULL
);
INSERT INTO users (name, age) VALUES
('张三', 22), ('李四', 28), ('王五', 35),
('赵六', 19), ('钱七', 42);
`);
// 调用表值函数
const usersResult = await pg.query(
'SELECT * FROM get_users_by_age_range($1, $2);',
[20, 30]
);
console.log('符合条件的用户:', usersResult.rows);
2.3 函数重载与多态
PostgreSQL支持函数重载,允许创建同名但参数不同的函数:
-- 函数重载示例 - 计算矩形面积
CREATE OR REPLACE FUNCTION calculate_area(width NUMERIC, height NUMERIC)
RETURNS NUMERIC AS $$
BEGIN
RETURN width * height;
END;
$$ LANGUAGE plpgsql;
-- 函数重载示例 - 计算圆面积
CREATE OR REPLACE FUNCTION calculate_area(radius NUMERIC)
RETURNS NUMERIC AS $$
BEGIN
RETURN PI() * radius * radius;
END;
$$ LANGUAGE plpgsql;
调用重载函数时,PostgreSQL会根据参数类型自动选择正确的实现:
// 调用不同版本的重载函数
const rectArea = await pg.query('SELECT calculate_area(5, 10) AS area;');
const circleArea = await pg.query('SELECT calculate_area(7) AS area;');
console.log('矩形面积:', rectArea.rows[0].area); // 输出: 50
console.log('圆形面积:', circleArea.rows[0].area); // 输出: 153.938...
三、存储过程开发与事务管理
3.1 创建与调用存储过程
存储过程(PROCEDURE)支持事务控制,适用于多步骤的数据操作:
-- 创建转账存储过程
CREATE OR REPLACE PROCEDURE transfer_funds(
from_account_id INT,
to_account_id INT,
amount NUMERIC,
OUT transaction_result TEXT
)
LANGUAGE plpgsql
AS $$
DECLARE
from_balance NUMERIC;
BEGIN
-- 检查源账户余额
SELECT balance INTO from_balance
FROM accounts
WHERE id = from_account_id;
IF from_balance < amount THEN
transaction_result := '转账失败: 余额不足';
RETURN;
END IF;
-- 执行转账(使用事务)
UPDATE accounts SET balance = balance - amount
WHERE id = from_account_id;
UPDATE accounts SET balance = balance + amount
WHERE id = to_account_id;
-- 记录交易
INSERT INTO transactions (from_account, to_account, amount, transaction_date)
VALUES (from_account_id, to_account_id, amount, NOW());
transaction_result := '转账成功';
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
transaction_result := format('转账失败: %s', SQLERRM);
END;
$$;
在PGlite中调用存储过程:
// 准备测试数据
await pg.exec(`
CREATE TABLE IF NOT EXISTS accounts (
id SERIAL PRIMARY KEY,
balance NUMERIC NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS transactions (
id SERIAL PRIMARY KEY,
from_account INT,
to_account INT,
amount NUMERIC,
transaction_date TIMESTAMP
);
INSERT INTO accounts (balance) VALUES (1000), (500);
`);
// 调用存储过程
const transferResult = await pg.query(
'CALL transfer_funds($1, $2, $3, $4);',
[1, 2, 300, null] // 最后一个参数是OUT参数
);
console.log('转账结果:', transferResult.rows[0].transaction_result);
// 验证结果
const accounts = await pg.query('SELECT * FROM accounts;');
console.log('账户余额:', accounts.rows);
3.2 事务管理与异常处理
存储过程中的事务控制是其与函数的主要区别,PGlite完全支持PostgreSQL的事务隔离级别:
-- 带事务控制的存储过程
CREATE OR REPLACE PROCEDURE batch_update_prices(
percentage_change NUMERIC,
OUT updated_count INT,
OUT error_message TEXT
)
LANGUAGE plpgsql
AS $$
BEGIN
updated_count := 0;
error_message := '';
-- 设置事务隔离级别
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- 批量更新产品价格
UPDATE products
SET price = price * (1 + percentage_change / 100)
WHERE active = true;
GET DIAGNOSTICS updated_count = ROW_COUNT;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
error_message := SQLERRM;
updated_count := 0;
END;
$$;
四、PGlite函数与前端框架集成
4.1 React组件中使用自定义函数
结合PGlite-React钩子,在React组件中无缝集成数据库函数:
// MyPGliteComponent.tsx
import { usePGlite } from '@electric-sql/pglite-react';
import { useState } from 'react';
function ProductPriceCalculator() {
const db = usePGlite();
const [price, setPrice] = useState<number>(0);
const [result, setResult] = useState<string>('');
const calculateDiscountedPrice = async () => {
try {
// 调用自定义函数计算折扣价格
const res = await db.query(
'SELECT calculate_discounted_price($1, 15) AS discounted_price;',
[price]
);
setResult(`折扣后价格: ¥${res.rows[0].discounted_price.toFixed(2)}`);
} catch (e) {
setResult(`计算错误: ${(e as Error).message}`);
}
};
return (
<div className="price-calculator">
<h3>产品价格折扣计算器</h3>
<input
type="number"
value={price}
onChange={(e) => setPrice(parseFloat(e.target.value))}
placeholder="输入原价"
/>
<button onClick={calculateDiscountedPrice}>计算15%折扣</button>
<p>{result}</p>
</div>
);
}
export default ProductPriceCalculator;
4.2 实时数据处理与函数结合
使用PGlite的实时查询功能,结合自定义函数实现动态数据处理:
// LiveDataDashboard.tsx
import { useLiveQuery } from '@electric-sql/pglite-react';
function SalesDashboard() {
// 使用实时查询调用聚合函数
const dailySales = useLiveQuery(`
SELECT
date_trunc('day', sale_date) AS sale_day,
calculate_total_sales_by_day(date_trunc('day', sale_date)) AS total_sales,
calculate_average_order_value(date_trunc('day', sale_date)) AS avg_order_value
FROM sales
GROUP BY sale_day
ORDER BY sale_day DESC
LIMIT 7;
`);
if (!dailySales.rows.length) {
return <div>加载中...</div>;
}
return (
<div className="sales-dashboard">
<h2>最近7天销售数据</h2>
<table>
<thead>
<tr>
<th>日期</th>
<th>总销售额</th>
<th>平均订单价值</th>
</tr>
</thead>
<tbody>
{dailySales.rows.map((row) => (
<tr key={row.sale_day}>
<td>{new Date(row.sale_day).toLocaleDateString()}</td>
<td>¥{row.total_sales.toFixed(2)}</td>
<td>¥{row.avg_order_value.toFixed(2)}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
五、性能优化与最佳实践
5.1 函数性能优化技巧
-- 优化前: 未使用索引的函数
CREATE OR REPLACE FUNCTION get_orders_by_customer(customer_id INT)
RETURNS SETOF orders AS $$
BEGIN
RETURN QUERY
SELECT * FROM orders
WHERE customer = customer_id
ORDER BY order_date DESC;
END;
$$ LANGUAGE plpgsql;
-- 优化后: 使用物化视图和索引
CREATE MATERIALIZED VIEW mv_customer_orders AS
SELECT
o.*,
ROW_NUMBER() OVER (PARTITION BY o.customer ORDER BY o.order_date DESC) AS rn
FROM orders o;
CREATE UNIQUE INDEX idx_mv_customer_orders ON mv_customer_orders(customer, rn);
-- 优化后的函数
CREATE OR REPLACE FUNCTION get_orders_by_customer_optimized(customer_id INT, limit_count INT DEFAULT 10)
RETURNS SETOF orders AS $$
BEGIN
RETURN QUERY
SELECT * FROM mv_customer_orders
WHERE customer = customer_id AND rn <= limit_count;
END;
$$ LANGUAGE plpgsql STABLE;
5.2 函数安全与权限控制
PGlite支持PostgreSQL的权限系统,可以控制函数的执行权限:
-- 创建安全的函数
CREATE OR REPLACE FUNCTION admin_only_function()
RETURNS TEXT AS $$
BEGIN
IF CURRENT_USER <> 'admin' THEN
RAISE EXCEPTION '仅管理员可执行此函数';
END IF;
RETURN '执行管理员操作';
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 创建用户并授予权限
CREATE USER app_user WITH PASSWORD 'password';
GRANT EXECUTE ON FUNCTION calculate_factorial TO app_user;
-- 不授予admin_only_function的执行权限
在应用中使用不同用户执行函数:
// 使用普通用户连接
const userPg = await PGlite.create({
database: 'appdb',
username: 'app_user',
password: 'password'
});
try {
// 尝试执行管理员函数
await userPg.query('SELECT admin_only_function();');
} catch (e) {
console.error('权限错误:', e.message); // 输出: 仅管理员可执行此函数
}
六、高级应用场景
6.1 结合向量扩展实现AI功能
PGlite支持pgvector扩展,可以在函数中实现向量计算:
-- 启用向量扩展
CREATE EXTENSION IF NOT EXISTS vector;
-- 创建向量相似度计算函数
CREATE OR REPLACE FUNCTION find_similar_products(
target_vector vector(3),
threshold FLOAT DEFAULT 0.8
)
RETURNS TABLE (id INT, name TEXT, similarity FLOAT) AS $$
BEGIN
RETURN QUERY
SELECT
id,
name,
1 - (embedding <-> target_vector) AS similarity
FROM products
WHERE 1 - (embedding <-> target_vector) > threshold
ORDER BY similarity DESC;
END;
$$ LANGUAGE plpgsql;
使用向量函数实现相似产品推荐:
// 插入产品向量数据
await pg.exec(`
CREATE TABLE IF NOT EXISTS products (
id SERIAL PRIMARY KEY,
name TEXT,
embedding vector(3)
);
INSERT INTO products (name, embedding) VALUES
('笔记本电脑', '[0.1, 0.3, 0.5]'),
('游戏鼠标', '[0.2, 0.8, 0.4]'),
('机械键盘', '[0.3, 0.7, 0.5]'),
('显示器', '[0.4, 0.2, 0.6]');
`);
// 查找相似产品
const similarProducts = await pg.query(
'SELECT * FROM find_similar_products($1, $2);',
[[0.25, 0.75, 0.45], 0.7]
);
console.log('相似产品:', similarProducts.rows);
6.2 实现事件驱动架构
结合触发器和通知机制,实现基于函数的事件处理:
-- 创建事件通知函数
CREATE OR REPLACE FUNCTION notify_order_created()
RETURNS TRIGGER AS $$
DECLARE
payload JSONB;
BEGIN
-- 构建通知 payload
payload := jsonb_build_object(
'order_id', NEW.id,
'customer_id', NEW.customer_id,
'amount', NEW.amount,
'created_at', NOW()
);
-- 发送通知
PERFORM pg_notify('order_events', payload::TEXT);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 创建触发器
CREATE TRIGGER order_created_trigger
AFTER INSERT ON orders
FOR EACH ROW
EXECUTE FUNCTION notify_order_created();
在前端应用中监听数据库事件:
// 监听数据库事件
const unsub = await pg.listen('order_events', (payload) => {
const eventData = JSON.parse(payload);
console.log('新订单事件:', eventData);
// 处理订单事件(更新UI、发送推送等)
updateOrderUI(eventData);
});
// 插入订单触发事件
await pg.query(
'INSERT INTO orders (customer_id, amount) VALUES ($1, $2);',
[1, 299.99]
);
// 不再需要时取消订阅
// unsub();
结语:前端数据处理的新范式
PGlite将PostgreSQL的强大功能
【免费下载链接】pglite 项目地址: https://gitcode.com/GitHub_Trending/pg/pglite
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



