外部数据封装器
Connecting to external systems using Postgres Foreign Data Wrappers.
外部数据封装器(FDW)是 Postgres 的一个核心功能,它允许你像访问本地 Postgres 表一样访问和查询存储在外部数据源中的数据。
🌐 Foreign Data Wrappers (FDW) are a core feature of Postgres that allow you to access and query data stored in external data sources as if they were native Postgres tables.
Postgres 包含了几个内置的外部数据封装器(FDW),比如用于访问其他 Postgres 数据库的 postgres_fdw,以及用于从文件中读取数据的 file_fdw。Supabase 扩展了这个功能,可以查询其他数据库或任何外部系统。我们通过开源的 Wrappers 框架来实现这一点。在这些指南中,我们会把它们称作“Wrappers”、外部数据封装器或 FDWs,它们在概念上是一样的。
🌐 Postgres includes several built-in foreign data wrappers, such as postgres_fdw for accessing other Postgres databases, and file_fdw for reading data from files. Supabase extends this feature to query other databases or any other external systems. We do this with our open source Wrappers framework. In these guides we'll refer to them as "Wrappers", Foreign Data Wrappers, or FDWs. They are conceptually the same thing.
概念 #
🌐 Concepts
封装器引入了一些新的术语和不同的工作流程。
🌐 Wrappers introduce some new terminology and different workflows.

远程服务器 #
🌐 Remote servers
远程服务器是一个外部数据库、API,或者任何包含你想从 Postgres 数据库查询的数据的系统。例子包括:
🌐 A Remote Server is an external database, API, or any system containing data that you want to query from your Postgres database. Examples include:
- 一个外部数据库,比如 Postgres 或 Firebase。
- 一个远程数据仓库,比如 ClickHouse、BigQuery 或 Snowflake。
- 一个像 Stripe 或 GitHub 这样的 API。
可以连接到多个相同类型的远程服务器。例如,你可以在同一个 Supabase 数据库中连接到两个不同的 Firebase 项目。
🌐 It's possible to connect to multiple remote servers of the same type. For example, you can connect to two different Firebase projects within the same Supabase database.
外国的桌子 #
🌐 Foreign tables
你数据库里的一个表,用来映射远程服务器里的某些数据。
🌐 A table in your database which maps to some data inside a Remote Server.
示例:
🌐 Examples:
- 一个
analytics表,对应你数据仓库里的表。 - 一个
subscriptions表,用来映射你的 Stripe 订阅。 - 一个
collections表,对应一个 Firebase 集合。
虽然外部表的行为像其他表一样,但数据并不存储在你的数据库里。数据仍然保存在远程服务器上。
🌐 Although a foreign table behaves like any other table, the data is not stored inside your database. The data remains inside the Remote Server.
带封装的ETL #
🌐 ETL with Wrappers
ETL代表提取、转换、加载。这是一个将数据从一个系统移动到另一个系统的常规流程。例如,将数据从生产数据库移动到数据仓库是很常见的。
🌐 ETL stands for Extract, Transform, Load. It's an established process for moving data from one system to another. For example, it's common to move data from a production database to a data warehouse.
有很多流行的 ETL 工具,比如 Fivetran 和 Airbyte。
🌐 There are many popular ETL tools, such as Fivetran and Airbyte.
封装器提供了这些工具的另一种选择。你可以用 SQL 把数据从一个表移动到另一个表:
🌐 Wrappers provide an alternative to these tools. You can use SQL to move data from one table to another:
1-- Copy data from your production database to your2-- data warehouse for the last 24 hours:34insert into warehouse.analytics5select * from public.analytics6where ts > (now() - interval '1 DAY');这种方法有几个好处:
🌐 This approach provides several benefits:
- 简单性: Wrappers API 使用的是 SQL,所以数据工程师不需要学习新的工具和语言。
- **节省时间:**避免设置额外的数据管道。
- **节省数据工程成本:**需要管理的基础设施更少。
一个缺点是,Wrappers 没有 ETL 工具那么多功能。它们还会让 ETL 过程和你的数据库绑定在一起。
🌐 One disadvantage is that Wrappers are not as feature-rich as ETL tools. They also couple the ETL process to your database.
按需 ETL 与封装器 #
🌐 On-demand ETL with Wrappers
Supabase 扩展了 ETL 的概念,实现了实时数据访问。你不必先把几 GB 的数据从一个系统搬到另一个系统再去查询,而是可以直接从远程服务器查询数据。这个额外的选项“查询”(Query)扩展了 ETL 流程,被称为 QETL(发音为“kettle”):查询、抽取、转换、加载。
🌐 Supabase extends the ETL concept with real-time data access. Instead of moving gigabytes of data from one system to another before you can query it, you can instead query the data directly from the remote server. This additional option, "Query", extends the ETL process and is called QETL (pronounced "kettle"): Query, Extract, Transform, Load.
1-- Get all purchases for a user from your data warehouse:2select3 auth.users.id as user_id,4 warehouse.orders.id as order_id5from6 warehouse.orders7join 8 auth.users on auth.users.id = warehouse.orders.user_id9where 10 auth.users.id = '<some_user_id>';这种方法有几个好处:
🌐 This approach has several benefits:
- 按需: 分析数据可以立即在你的应用中获取,无需额外的基础设施。
- 始终同步: 由于数据是直接从远程服务器查询的,所以总是最新的。
- 集成: 大型数据集可以在你的应用中使用,并且可以与你的运营/交易数据结合。
- **节省出口成本:**只提取/加载你需要的内容。
使用封装器的批量ETL #
🌐 Batch ETL with Wrappers
Wrappers 的一个常见用例是从生产数据库中提取数据并加载到数据仓库中。这可以在你的数据库中使用 pg_cron 来完成。例如,你可以安排一个任务每晚运行一次,从你的生产数据库中提取数据并加载到数据仓库中。
🌐 A common use case for Wrappers is to extract data from a production database and load it into a data warehouse. This can be done within your database using pg_cron. For example, you can schedule a job to run every night to extract data from your production database and load it into your data warehouse.
1-- Every day at 3am, copy data from your2-- production database to your data warehouse:3select cron.schedule(4 'nightly-etl',5 '0 3 * * *',6 $$7 insert into warehouse.analytics8 select * from public.analytics9 where ts > (now() - interval '1 DAY');10 $$11);
如果你要移动大量数据,这个过程可能会对你的数据库造成压力。通常,使用像 Fivetran 或 Airbyte 这样的外部工具来进行批量 ETL 会更好。
🌐 This process can be taxing on your database if you are moving large amounts of data. Often, it's better to use an external tool for batch ETL, such as Fivetran or Airbyte.
WebAssembly 封装 #
🌐 WebAssembly Wrappers
WebAssembly(Wasm)是一种二进制指令格式,可以在网页上高性能地执行代码。Wrappers 现在包含了 Wasm 运行时,它提供了一个沙盒执行环境,用来运行 Wasm 外部数据封装器。把 Wrappers 和 Wasm 结合起来,开发和分发新的 FDW 变得更容易,你甚至可以自己构建 Wasm FDW 并在 Supabase 平台上使用。
🌐 WebAssembly (Wasm) is a binary instruction format that enables high-performance execution of code on the web. Wrappers now includes a Wasm runtime, which provides a sandboxed execution environment, to run Wasm foreign data wrappers. Combined Wrappers with Wasm, developing and distributing new FDW becomes much easier and you can even build your own Wasm FDW and use it on Supabase platform.
想了解更多关于 Wasm FDW 的信息,请访问 Wrappers 官方文档。
🌐 To learn more about Wasm FDW, visit Wrappers official documentation.
安全 #
🌐 Security
外部数据封装器不提供行级安全性,因此不建议通过你的 API 暴露它们。封装器应该始终存放在私有 schema 中。例如,如果你要连接到你的 Stripe 账户,你应该创建一个 stripe schema 来存放所有的外部表。这个 schema 不应该添加到 API 部分的“附加 Schemas”设置中。
🌐 Foreign Data Wrappers do not provide Row Level Security, thus it is not advised to expose them via your API. Wrappers should always be stored in a private schema. For example, if you are connecting to your Stripe account, you should create a stripe schema to store all of your foreign tables inside. This schema should not be added to the “Additional Schemas” setting in the API section.
如果你想把任何外部表的列暴露给你的公共 API,你可以在 public 模式下创建一个 带安全定义者的数据库函数,然后就可以通过 API 操作你的外部表。为了更好地控制访问,函数应该在外部表上设置适当的过滤条件,根据你的业务需求应用安全规则。
🌐 If you want to expose any of the foreign table columns to your public API, you can create a Database Function with security definer in the public schema, and then you can interact with your foreign table through API. For better access control, the function should have appropriate filters on the foreign table to apply security rules based on your business needs.
举个例子,打开 SQL 编辑器,然后按照以下步骤操作,
🌐 As an example, go to SQL Editor and then follow below steps,
-
创建一个 Stripe Products 外部表:
1create foreign table stripe.stripe_products (2id text,3name text,4active bool,5default_price text,6description text,7created timestamp,8updated timestamp,9attrs jsonb10)11server stripe_fdw_server12options (13object 'products',14rowid_column 'id'15); -
创建一个安全定义者函数,用于查询外部表并根据名称前缀参数进行过滤:
1create function public.get_stripe_products(name_prefix text)2returns table (3id text,4name text,5active boolean,6default_price text,7description text8)9language plpgsql10security definer set search_path = ''11as $$12begin13return query14select15t.id,16t.name,17t.active,18t.default_price,19t.description20from21stripe.stripe_products t22where23t.name like name_prefix || '%'24;25end;26$$; -
将函数执行限制为特定角色,例如,已认证的用户:
默认情况下,创建的函数可以被像
anon这样的任何角色执行,这意味着外部表是公开可访问的。务必将函数的执行权限限制在适当的角色上。1-- revoke public execute permission2revoke execute on function public.get_stripe_products from public;3revoke execute on function public.get_stripe_products from anon;45-- grant execute permission to a specific role only6grant execute on function public.get_stripe_products to authenticated;
一旦完成前面的步骤,就可以从 Supabase 客户端调用该函数来查询外部表了:
🌐 Once the preceding steps are finished, the function can be invoked from Supabase client to query the foreign table:
1const { data, error } = await supabase2 .rpc('get_stripe_products', { name_prefix: 'Test' })3 .select('*')4if (error) console.error(error)5else console.log(data)资源 #
🌐 Resources