Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: rewrite Notion FDW to Wasm FDW #350

Merged
merged 4 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ exclude = [
"wasm-wrappers/fdw/helloworld_fdw",
"wasm-wrappers/fdw/snowflake_fdw",
"wasm-wrappers/fdw/paddle_fdw",
"wasm-wrappers/fdw/notion_fdw",
]
resolver = "2"

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
| [SQL Server](./wrappers/src/fdw/mssql_fdw) | A FDW for [Microsoft SQL Server](https://www.microsoft.com/en-au/sql-server/) | ✅ | ❌ |
| [Redis](./wrappers/src/fdw/redis_fdw) | A FDW for [Redis](https://redis.io/) | ✅ | ❌ |
| [AWS Cognito](./wrappers/src/fdw/cognito_fdw) | A FDW for [AWS Cognito](https://aws.amazon.com/cognito/) | ✅ | ❌ |
| [Notion](./wrappers/src/fdw/notion_fdw) | A FDW for [Notion](https://www.notion.so/) | ✅ | ❌ |
| [Notion](./wasm-wrappers/fdw/notion_fdw) | A Wasm FDW for [Notion](https://www.notion.so/) | ✅ | ❌ |
| [Snowflake](./wasm-wrappers/fdw/snowflake_fdw) | A Wasm FDW for [Snowflake](https://www.snowflake.com/) | ✅ | ✅ |
| [Paddle](./wasm-wrappers/fdw/paddle_fdw) | A Wasm FDW for [Paddle](https://www.paddle.com/) | ✅ | ✅ |

Expand Down
1 change: 1 addition & 0 deletions docs/catalog/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ See [Developing a Wasm Wrapper](../guides/create-wasm-wrapper.md) for instructio
| ----------- | :------------------------------: | :----------------------------------: | :------------------------------------------------------------------------------------: |
| Paddle | [Supabase](https://supabase.com) | [Link](paddle.md) | [Link](https://github.com/supabase/wrappers/tree/main/wasm-wrappers/fdw/paddle_fdw) |
| Snowflake | [Supabase](https://supabase.com) | [Link](snowflake.md) | [Link](https://github.com/supabase/wrappers/tree/main/wasm-wrappers/fdw/snowflake_fdw) |
| Notion | [Supabase](https://supabase.com) | [Link](notion.md) | [Link](https://github.com/supabase/wrappers/tree/main/wasm-wrappers/fdw/notion_fdw) |
292 changes: 225 additions & 67 deletions docs/catalog/notion.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,144 +4,302 @@ documentation:
author: supabase
tags:
- native
- private alpha
- official
---

# Notion

[Notion](https://notion.so/) provides a versatile, ready-to-use solution for managing your data.

The Notion Wrapper allows you to read data from your Notion workspace for use within your Postgres database. Only the users endpoint is supported at the moment.
The Notion Wrapper is a WebAssembly(Wasm) foreign data wrapper which allows you to read data from your Notion workspace for use within your Postgres database.

!!! warning

Restoring a logical backup of a database with a materialized view using a foreign table can fail. For this reason, either do not use foreign tables in materialized views or use them in databases with physical backups enabled.

## Supported Data Types

| Postgres Data Type | Notion Data Type |
| ------------------ | ---------------- |
| boolean | Boolean |
| text | String |
| timestamp | Time |
| timestamptz | Time |
| jsonb | Json |

The Notion API uses JSON formatted data, please refer to [Notion API docs](https://developers.notion.com/reference/intro) for more details.

## Available Versions

| Version | Wasm Package URL | Checksum |
| ------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| 0.1.0 | `https://github.com/supabase/wrappers/releases/download/wasm_notion_fdw_v0.1.0/notion_fdw.wasm` | `tbd` |

## Preparation

Before you get started, make sure the `wrappers` extension is installed on your database:

```sql
CREATE extension IF NOT EXISTS wrappers SCHEMA extensions;
create extension if not exists wrappers with schema extensions;
```

Then, create the foreign data wrapper:
and then create the Wasm foreign data wrapper:

```sql
CREATE FOREIGN DATA WRAPPER notion_wrapper
HANDLER notion_fdw_handler
VALIDATOR notion_fdw_validator;
create foreign data wrapper wasm_wrapper
handler wasm_fdw_handler
validator wasm_fdw_validator;
```

### Secure your credentials (optional)

By default, Postgres stores FDW credentials in plain text within the `pg_catalog.pg_foreign_server` table, making them visible to anyone with access to this table. To enhance security, it is advisable to use [Vault](https://supabase.com/docs/guides/database/vault) for credential storage. Vault integrates seamlessly with Wrappers to provide a secure storage solution for sensitive information. We strongly recommend utilizing Vault to safeguard your credentials.
By default, Postgres stores FDW credentials inside `pg_catalog.pg_foreign_server` in plain text. Anyone with access to this table will be able to view these credentials. Wrappers is designed to work with [Vault](https://supabase.com/docs/guides/database/vault), which provides an additional level of security for storing credentials. We recommend using Vault to store your credentials.

```sql
-- Save your Notion API key in Vault and retrieve the `key_id`
INSERT INTO vault.secrets (name, secret)
VALUES (
insert into vault.secrets (name, secret)
values (
'notion',
'<Notion API Key>' -- Notion API key
)
RETURNING key_id;
returning key_id;
```

### Connecting to Notion

We need to provide Postgres with the credentials to connect to Notion, and any additional options. We can do this using the `CREATE SERVER` command:

- With Vault (recommended)

```sql
CREATE SERVER notion_server
FOREIGN DATA WRAPPER notion_wrapper
OPTIONS (
api_key_id '<key_ID>', -- The Key ID from the Vault
notion_version '<notion_version>', -- optional, default is '2022-06-28'
api_url '<api_url>' -- optional, default is 'https://api.notion.com/v1/'
);
```

- Without Vault

```sql
CREATE SERVER notion_server
FOREIGN DATA WRAPPER notion_wrapper
OPTIONS (
api_key '<your_api_key>', -- Your Notion API key
notion_version '<notion_version>', -- optional, default is '2022-06-28'
api_url '<api_url>' -- optional, default is 'https://api.notion.com/v1/'
);
```
We need to provide Postgres with the credentials to access Notion, and any additional options. We can do this using the `create server` command:

=== "With Vault"

```sql
create server notion_server
foreign data wrapper wasm_wrapper
options (
fdw_package_url 'https://github.com/supabase/wrappers/releases/download/wasm_notion_fdw_v0.1.0/notion_fdw.wasm',
fdw_package_name 'supabase:notion-fdw',
fdw_package_version '0.1.0',
fdw_package_checksum 'tbd',
api_url 'https://api.notion.com/v1', -- optional
api_version '2022-06-28', -- optional
api_key_id '<key_ID>' -- The Key ID from above.
);
```

=== "Without Vault"

```sql
create server notion_server
foreign data wrapper wasm_wrapper
options (
fdw_package_url 'https://github.com/supabase/wrappers/releases/download/wasm_notion_fdw_v0.1.0/notion_fdw.wasm',
fdw_package_name 'supabase:notion-fdw',
fdw_package_version '0.1.0',
fdw_package_checksum 'tbd',
api_url 'https://api.notion.com/v1', -- optional
api_version '2022-06-28', -- optional
api_key 'secret_xxxxx' -- Notion API key
);
```

Note the `fdw_package_*` options are required, which specify the Wasm package metadata. You can get the available package version list from [above](#available-versions).

### Create a schema

We recommend creating a schema to hold all the foreign tables:

```sql
create schema if not exists notion;
```

## Creating Foreign Tables

The Notion Wrapper supports data reads from the [Notion API](https://developers.notion.com/reference).
The Notion Wrapper supports data reads from below objects in Notion.

| Object | Select | Insert | Update | Delete | Truncate |
| ---------------------------------------------------------- | :----: | :----: | :----: | :----: | :------: |
| [Users](https://developers.notion.com/reference/get-users) | ✅ | ❌ | ❌ | ❌ | ❌ |
| Integration | Select | Insert | Update | Delete | Truncate |
| ----------- | :----: | :----: | :----: | :----: | :------: |
| Block | ✅ | ❌ | ❌ | ❌ | ❌ |
| Page | ✅ | ❌ | ❌ | ❌ | ❌ |
| Database | ✅ | ❌ | ❌ | ❌ | ❌ |
| User | ✅ | ❌ | ❌ | ❌ | ❌ |

For example:

```sql
CREATE FOREIGN TABLE my_foreign_table (
-- Note: the 'page_id' isn't presented in the Notion Block's fields, it is
-- added by the foreign data wrapper for development convenience. All blocks,
-- including nested children blocks, belong to one page will have a same
-- 'page_id' of that page.
create foreign table notion.blocks (
id text,
page_id text,
type text,
created_time timestamp,
last_edited_time timestamp,
archived boolean,
attrs jsonb
)
server notion_server
options (
object 'block'
);

create foreign table notion.pages (
id text,
url text,
created_time timestamp,
last_edited_time timestamp,
archived boolean,
attrs jsonb
)
server notion_server
options (
object 'page'
);

create foreign table notion.databases (
id text,
url text,
created_time timestamp,
last_edited_time timestamp,
archived boolean,
attrs jsonb
)
server notion_server
options (
object 'database'
);

create foreign table notion.users (
id text,
name text,
type text,
person jsonb,
bot jsonb
-- other fields
avatar_url text,
attrs jsonb
)
SERVER notion_server
OPTIONS (
object 'users',
);
server notion_server
options (
object 'user'
);
```

!!! note

- All the supported columns are listed above, other columns are not allowd.
- The `attrs` is a special column which stores all the object attributes in JSON format, you can extract any attributes needed from it. See more examples below.

### Foreign table options

The full list of foreign table options are below:

- `object` - Object name in Notion, required.

Supported objects are listed below:

| Object name |
| --------------------- |
| block |
| page |
| database |
| user |

## Query Pushdown Support

This FDW supports `where` clause pushdown. You can specify a filter in `where` clause and it will be passed to Notion API call.
This FDW supports `where` clause pushdown with `id` as the filter. For example,

```sql
select * from notion.pages
where id = '5a67c86f-d0da-4d0a-9dd7-f4cf164e6247';
```

will be translated Notion API call: `https://api.notion.com/v1/pages/5a67c86f-d0da-4d0a-9dd7-f4cf164e6247`.

For example, this query
In addition to `id` column pushdown, `page_id` column pushdown is also supported for `Block` object. For example,

```sql
SELECT * from notion_users where id = 'xxx';
select * from notion.blocks
where page_id = '5a67c86f-d0da-4d0a-9dd7-f4cf164e6247';
```

will be translated Notion API call: `https://api.notion.com/v1/users/xxx`.
will recursively fetch all children blocks of the Page with id '5a67c86f-d0da-4d0a-9dd7-f4cf164e6247'. This can dramatically reduce number of API calls and improve query speed.

!!! note

Below query will request ALL the blocks of ALL pages recursively, it may take very long time to run if there are many pages in Notion. So it is recommended to always query Block object with an `id` or `page_id` filter like above.

```sql
select * from notion.blocks;
```

## Examples

Some examples on how to use Notion foreign tables.
Below are some examples on how to use Notion foreign tables.

### Basic example

### Users foreign table
This example will create a "foreign table" inside your Postgres database and query its data. First, we can create a schema to hold all the Notion foreign tables.

```sql
create schema if not exists notion;
```

The following command creates a "foreign table" in your Postgres database named `notion_users`:
Then create the foreign table and query it, for example:

```sql
CREATE FOREIGN TABLE notion_users (
create foreign table notion.pages (
id text,
name text,
type text,
person jsonb,
bot jsonb
url text,
created_time timestamp,
last_edited_time timestamp,
archived boolean,
attrs jsonb
)
SERVER notion_server
OPTIONS (
object 'users'
server notion_server
options (
object 'page'
);

-- query all pages
select * from notion.pages;

-- query one page
select * from notion.pages
where id = '5a67c86f-d0da-4d0a-9dd7-f4cf164e6247';
```

You can now fetch your Notion data from within your Postgres database:
`attrs` is a special column which stores all the object attributes in JSON format, you can extract any attributes needed from it. See more examples below.

### Query JSON attributes

```sql
SELECT * FROM notion_users;
create foreign table notion.users (
id text,
name text,
type text,
avatar_url text,
attrs jsonb
)
server notion_server
options (
object 'user'
);

-- extract user's email address
select id, attrs->'person'->>'email' as email
from notion.users
where id = 'fd0ed76c-44bd-413a-9448-18ff4b1d6a5e';
```

You can also query with filters:
### Query Blocks

```sql
SELECT * FROM notion_users WHERE id = 'xxx';
-- query ALL blocks of ALL pages recursively, may take long time!
select * from notion.blocks;

-- query a single block by block id
select * from notion.blocks
where id = 'fc248547-83ef-4069-b7c9-18897edb7150';

-- query all block of a page by page id
select * from notion.blocks
where page_id = '5a67c86f-d0da-4d0a-9dd7-f4cf164e6247';
```
Loading
Loading