外部数据包装器
使用Postgres外部数据包装器连接外部系统。
外接数据包装器(FDW)是Postgres的核心功能,它允许您像访问原生Postgres表一样查询存储在外部数据源中的数据。
Postgres内置了多个外接数据包装器,例如用于访问其他Postgres数据库的postgres_fdw
,以及用于从文件读取数据的file_fdw
。Supabase扩展了这一功能,使其能够查询其他数据库或任何外部系统。我们通过开源的Wrappers框架实现这一功能。在这些指南中,我们会将它们称为"包装器"、外接数据包装器或FDW,它们在概念上是相同的。
核心概念
包装器引入了一些新的术语和工作流程。
远程服务器
远程服务器是指您想要从Postgres数据库查询的外部数据库、API或任何包含数据的系统。例如:
- 外部数据库,如Postgres或Firebase
- 远程数据仓库,如ClickHouse、BigQuery或Snowflake
- API服务,如Stripe或GitHub
可以连接同一类型的多个远程服务器。例如,您可以在同一个Supabase数据库中连接两个不同的Firebase项目。
外部表
外部表是您数据库中映射到远程服务器内部数据的表。
示例:
- 映射到数据仓库内部表的
analytics
表 - 映射到Stripe订阅数据的
subscriptions
表 - 映射到Firebase集合的
collections
表
虽然外部表的行为与其他表类似,但其数据并不存储在您的数据库中。数据始终保留在远程服务器内。
使用包装器进行ETL
ETL代表提取(Extract)、转换(Transform)、加载(Load),是将数据从一个系统迁移到另一个系统的成熟流程。例如,将数据从生产数据库迁移到数据仓库就是常见场景。
市场上有许多流行的ETL工具,如Fivetran和Airbyte。
包装器提供了这些工具的替代方案。您可以使用SQL将数据从一个表移动到另一个表:
123456-- 将过去24小时的数据从生产数据库复制到-- 数据仓库:insert into warehouse.analyticsselect * from public.analyticswhere ts > (now() - interval '1 DAY');
这种方法具有以下优势:
- 简单性:包装器API仅使用SQL,数据工程师无需学习新工具和语言
- 节省时间:避免设置额外的数据管道
- 降低数据工程成本:减少需要管理的基础设施
一个缺点是包装器的功能不如专业ETL工具丰富,同时ETL流程会与您的数据库产生耦合。
按需ETL与Wrappers
Supabase通过实时数据访问扩展了ETL概念。您无需在查询前将海量数据从一个系统迁移到另一个系统,而是可以直接从远程服务器查询数据。这种额外的"查询"选项扩展了ETL流程,被称为QETL(发音为"kettle"):查询(Query)、提取(Extract)、转换(Transform)、加载(Load)。
12345678910-- 从数据仓库获取用户的所有购买记录:select auth.users.id as user_id, warehouse.orders.id as order_idfrom warehouse.ordersjoin auth.users on auth.users.id = warehouse.orders.user_idwhere auth.users.id = '<some_user_id>';
这种方法具有以下优势:
- 按需获取:分析数据无需额外基础设施即可在应用中立即使用
- 始终同步:由于数据直接从远程服务器查询,始终保持最新状态
- 集成性:大型数据集可在应用内使用,并能与运营/交易数据关联
- 节省带宽:仅提取/加载所需数据
使用 Wrappers 进行批量 ETL
Wrappers 的一个常见用例是从生产数据库提取数据并加载到数据仓库中。这可以通过在数据库内使用 pg_cron 来实现。例如,您可以安排一个每天夜间运行的作业,从生产数据库提取数据并加载到数据仓库。
1234567891011-- 每天凌晨3点,将数据从生产数据库-- 复制到数据仓库:select cron.schedule( 'nightly-etl', '0 3 * * *', $$ insert into warehouse.analytics select * from public.analytics where ts > (now() - interval '1 DAY'); $$);
如果您需要移动大量数据,这个过程可能会对数据库造成较大负担。通常,使用外部工具进行批量 ETL 会更合适,例如 Fivetran 或 Airbyte。
WebAssembly Wrappers
WebAssembly (Wasm) 是一种二进制指令格式,能够在网络上高性能地执行代码。Wrappers 现在包含了一个 Wasm 运行时环境,提供沙箱执行环境来运行 Wasm 外部数据包装器。将 Wrappers 与 Wasm 结合后,开发和分发新的 FDW 变得更加容易,您甚至可以构建自己的 Wasm FDW 并在 Supabase 平台上使用。
要了解更多关于 Wasm FDW 的信息,请访问 Wrappers 官方文档。
安全性
外部数据包装器(Foreign Data Wrappers)不提供行级安全(Row Level Security)功能,因此不建议通过API直接暴露它们。包装器应始终存储在私有模式(private schema)中。例如,如果您要连接Stripe账户,应该创建一个stripe
模式来存储所有外部表。此模式不应添加到API部分的"Additional Schemas"设置中。
若需要将外部表的某些列暴露给公共API,可以在public
模式中创建一个具有安全定义者(security definer)的数据库函数,然后通过API与外部表交互。为了更好的访问控制,该函数应对外部表应用适当的过滤条件,根据业务需求实施安全规则。
以下是一个完整示例,请前往SQL编辑器并按步骤操作:
-
创建Stripe产品外部表:
123456789101112131415create foreign table stripe.stripe_products ( id text, name text, active bool, default_price text, description text, created timestamp, updated timestamp, attrs jsonb) server stripe_fdw_server options ( object 'products', rowid_column 'id' ); -
创建安全定义者函数,查询外部表并按名称前缀参数过滤:
1234567891011121314151617181920212223242526create function public.get_stripe_products(name_prefix text)returns table ( id text, name text, active boolean, default_price text, description text)language plpgsqlsecurity definer set search_path = ''as $$begin return query select t.id, t.name, t.active, t.default_price, t.description from stripe.stripe_products t where t.name like name_prefix || '%' ;end;$$; -
限制函数只能由特定角色执行,例如已认证用户:
默认情况下,创建的函数可由任何角色(如
anon
)执行,这意味着外部表是公开可访问的。请始终将函数执行权限限制在适当的角色。123456-- 撤销公共执行权限revoke execute on function public.get_stripe_products from public;revoke execute on function public.get_stripe_products from anon;-- 仅授予特定角色执行权限grant execute on function public.get_stripe_products to authenticated;
完成上述步骤后,即可通过Supabase客户端调用函数查询外部表:
12345const { data, error } = await supabase .rpc('get_stripe_products', { name_prefix: 'Test' }) .select('*')if (error) console.error(error)else console.log(data)