Skip to content
Database

外部数据封装器

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.

Foreign Data Wrappers (FDW)

远程服务器 #

🌐 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 工具,比如 FivetranAirbyte

🌐 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 your
2
-- data warehouse for the last 24 hours:
3
4
insert into warehouse.analytics
5
select * from public.analytics
6
where ts > (now() - interval '1 DAY');

这种方法有几个好处:

🌐 This approach provides several benefits:

  1. 简单性: Wrappers API 使用的是 SQL,所以数据工程师不需要学习新的工具和语言。
  2. **节省时间:**避免设置额外的数据管道。
  3. **节省数据工程成本:**需要管理的基础设施更少。

一个缺点是,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:
2
select
3
auth.users.id as user_id,
4
warehouse.orders.id as order_id
5
from
6
warehouse.orders
7
join
8
auth.users on auth.users.id = warehouse.orders.user_id
9
where
10
auth.users.id = '<some_user_id>';

这种方法有几个好处:

🌐 This approach has several benefits:

  1. 按需: 分析数据可以立即在你的应用中获取,无需额外的基础设施。
  2. 始终同步: 由于数据是直接从远程服务器查询的,所以总是最新的。
  3. 集成: 大型数据集可以在你的应用中使用,并且可以与你的运营/交易数据结合。
  4. **节省出口成本:**只提取/加载你需要的内容。

使用封装器的批量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 your
2
-- production database to your data warehouse:
3
select cron.schedule(
4
'nightly-etl',
5
'0 3 * * *',
6
$$
7
insert into warehouse.analytics
8
select * from public.analytics
9
where ts > (now() - interval '1 DAY');
10
$$
11
);
FDW with pg_cron

如果你要移动大量数据,这个过程可能会对你的数据库造成压力。通常,使用像 FivetranAirbyte 这样的外部工具来进行批量 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,

  1. 创建一个 Stripe Products 外部表:

    1
    create foreign table stripe.stripe_products (
    2
    id text,
    3
    name text,
    4
    active bool,
    5
    default_price text,
    6
    description text,
    7
    created timestamp,
    8
    updated timestamp,
    9
    attrs jsonb
    10
    )
    11
    server stripe_fdw_server
    12
    options (
    13
    object 'products',
    14
    rowid_column 'id'
    15
    );
  2. 创建一个安全定义者函数,用于查询外部表并根据名称前缀参数进行过滤:

    1
    create function public.get_stripe_products(name_prefix text)
    2
    returns table (
    3
    id text,
    4
    name text,
    5
    active boolean,
    6
    default_price text,
    7
    description text
    8
    )
    9
    language plpgsql
    10
    security definer set search_path = ''
    11
    as $$
    12
    begin
    13
    return query
    14
    select
    15
    t.id,
    16
    t.name,
    17
    t.active,
    18
    t.default_price,
    19
    t.description
    20
    from
    21
    stripe.stripe_products t
    22
    where
    23
    t.name like name_prefix || '%'
    24
    ;
    25
    end;
    26
    $$;
  3. 将函数执行限制为特定角色,例如,已认证的用户:

    1
    -- revoke public execute permission
    2
    revoke execute on function public.get_stripe_products from public;
    3
    revoke execute on function public.get_stripe_products from anon;
    4
    5
    -- grant execute permission to a specific role only
    6
    grant 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:

1
const { data, error } = await supabase
2
.rpc('get_stripe_products', { name_prefix: 'Test' })
3
.select('*')
4
if (error) console.error(error)
5
else console.log(data)

资源 #

🌐 Resources