├── .cargo └── config.toml ├── .dockerignore ├── .github └── workflows │ ├── coverage.yml │ ├── docs.yml │ ├── release.yml │ ├── release_wasm_fdw.yml │ ├── test_supabase_wrappers.yml │ └── test_wrappers.yml ├── .gitignore ├── .python-version ├── .vscode └── settings.json ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── docs ├── CNAME ├── assets │ ├── airtable_credentials.png │ ├── favicon.ico │ ├── fdw-dark.png │ ├── fdw-light.png │ ├── google-sheet.png │ ├── query-pushdown-dark.png │ ├── wasm-build.png │ ├── wrappers-github.jpg │ ├── wrappers-icon_container-3x.png │ ├── wrappers-icon_container.png │ ├── wrappers-icon_container.svg │ ├── wrappers-wordmark.svg │ └── wrappers.svg ├── catalog │ ├── airtable.md │ ├── auth0.md │ ├── bigquery.md │ ├── cal.md │ ├── calendly.md │ ├── cfd1.md │ ├── clerk.md │ ├── clickhouse.md │ ├── cognito.md │ ├── firebase.md │ ├── hubspot.md │ ├── iceberg.md │ ├── index.md │ ├── logflare.md │ ├── mssql.md │ ├── notion.md │ ├── orb.md │ ├── paddle.md │ ├── redis.md │ ├── s3.md │ ├── slack.md │ ├── snowflake.md │ ├── stripe.md │ └── wasm │ │ └── index.md ├── contributing │ ├── core.md │ ├── documentation.md │ └── native.md ├── guides │ ├── create-wasm-wrapper.md │ ├── installation.md │ ├── limitations.md │ ├── native-wasm.md │ ├── query-pushdown.md │ ├── remote-subqueries.md │ ├── removing-wrappers.md │ ├── security.md │ ├── updating-wrappers.md │ ├── usage-statistics.md │ └── wasm-advanced.md ├── index.md ├── requirements_docs.txt ├── stylesheets │ └── extra.css └── tags.md ├── mkdocs.yaml ├── supabase-wrappers-macros ├── Cargo.toml ├── LICENSE ├── README.md ├── rustfmt.toml └── src │ └── lib.rs ├── supabase-wrappers ├── Cargo.toml ├── LICENSE ├── README.md ├── rustfmt.toml └── src │ ├── import_foreign_schema.rs │ ├── instance.rs │ ├── interface.rs │ ├── lib.rs │ ├── limit.rs │ ├── memctx.rs │ ├── modify.rs │ ├── options.rs │ ├── polyfill.rs │ ├── qual.rs │ ├── scan.rs │ ├── sort.rs │ └── utils.rs ├── wasm-wrappers ├── .cargo │ └── config.toml ├── fdw │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── cal_fdw │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── src │ │ │ └── lib.rs │ │ └── wit │ │ │ └── world.wit │ ├── calendly_fdw │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── src │ │ │ └── lib.rs │ │ └── wit │ │ │ └── world.wit │ ├── cfd1_fdw │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── src │ │ │ └── lib.rs │ │ └── wit │ │ │ └── world.wit │ ├── clerk_fdw │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── src │ │ │ └── lib.rs │ │ └── wit │ │ │ └── world.wit │ ├── helloworld_fdw │ │ ├── .cargo │ │ │ └── config.toml │ │ ├── .vscode │ │ │ └── settings.json │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── src │ │ │ └── lib.rs │ │ └── wit │ │ │ └── world.wit │ ├── hubspot_fdw │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── src │ │ │ └── lib.rs │ │ └── wit │ │ │ └── world.wit │ ├── notion_fdw │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── src │ │ │ └── lib.rs │ │ └── wit │ │ │ └── world.wit │ ├── orb_fdw │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── src │ │ │ └── lib.rs │ │ └── wit │ │ │ └── world.wit │ ├── paddle_fdw │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── src │ │ │ └── lib.rs │ │ └── wit │ │ │ └── world.wit │ ├── slack_fdw │ │ ├── .cargo │ │ │ └── config.toml │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── src │ │ │ ├── api.rs │ │ │ ├── lib.rs │ │ │ └── models.rs │ │ └── wit │ │ │ └── world.wit │ └── snowflake_fdw │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── src │ │ └── lib.rs │ │ └── wit │ │ └── world.wit └── wit │ ├── v1 │ ├── http.wit │ ├── jwt.wit │ ├── routines.wit │ ├── stats.wit │ ├── time.wit │ ├── types.wit │ ├── utils.wit │ └── world.wit │ └── v2 │ ├── http.wit │ ├── jwt.wit │ ├── routines.wit │ ├── stats.wit │ ├── time.wit │ ├── types.wit │ ├── utils.wit │ └── world.wit └── wrappers ├── .ci ├── docker-compose-native.yaml ├── docker-compose-wasm.yaml └── docker-compose.yaml ├── Cargo.toml ├── README.md ├── dockerfiles ├── airtable │ ├── Dockerfile │ └── server.py ├── auth0 │ ├── Dockerfile │ └── server.py ├── bigquery │ ├── Dockerfile │ └── data.yaml ├── cognito │ └── .cognito │ │ ├── config.json │ │ └── db │ │ ├── clients.json │ │ └── local_6QNVVZIN.json ├── firebase │ ├── README.md │ ├── baseline-data │ │ ├── auth_export │ │ │ ├── accounts.json │ │ │ └── config.json │ │ ├── firebase-export-metadata.json │ │ └── firestore_export │ │ │ ├── all_namespaces │ │ │ └── all_kinds │ │ │ │ ├── all_namespaces_all_kinds.export_metadata │ │ │ │ └── output-0 │ │ │ └── firestore_export.overall_export_metadata │ ├── firebase.json │ └── storage.rules ├── logflare │ ├── Dockerfile │ ├── data.json │ ├── requirements.txt │ └── server.py ├── notion │ ├── Dockerfile │ ├── data.json │ ├── requirements.txt │ └── server.py ├── s3 │ ├── Dockerfile │ ├── iceberg_seed.py │ └── test_data │ │ ├── test_data.csv │ │ ├── test_data.csv.gz │ │ ├── test_data.jsonl │ │ ├── test_data.jsonl.bz2 │ │ ├── test_data.parquet │ │ └── test_data.parquet.gz └── wasm │ ├── Dockerfile │ └── server.py ├── rustfmt.toml ├── sql ├── bootstrap.sql └── finalize.sql ├── src ├── bin │ └── pgrx_embed.rs ├── fdw │ ├── airtable_fdw │ │ ├── README.md │ │ ├── airtable_fdw.rs │ │ ├── mod.rs │ │ ├── result.rs │ │ └── tests.rs │ ├── auth0_fdw │ │ ├── README.md │ │ ├── auth0_client │ │ │ ├── mod.rs │ │ │ ├── row.rs │ │ │ └── rows_iterator.rs │ │ ├── auth0_fdw.rs │ │ ├── mod.rs │ │ └── tests.rs │ ├── bigquery_fdw │ │ ├── README.md │ │ ├── bigquery_fdw.rs │ │ ├── mod.rs │ │ └── tests.rs │ ├── clickhouse_fdw │ │ ├── README.md │ │ ├── clickhouse_fdw.rs │ │ ├── mod.rs │ │ └── tests.rs │ ├── cognito_fdw │ │ ├── README.md │ │ ├── cognito_client │ │ │ ├── mod.rs │ │ │ ├── row.rs │ │ │ └── rows_iterator.rs │ │ ├── cognito_fdw.rs │ │ ├── mod.rs │ │ └── tests.rs │ ├── firebase_fdw │ │ ├── README.md │ │ ├── firebase_fdw.rs │ │ ├── mod.rs │ │ └── tests.rs │ ├── helloworld_fdw │ │ ├── README.md │ │ ├── helloworld_fdw.rs │ │ └── mod.rs │ ├── iceberg_fdw │ │ ├── README.md │ │ ├── iceberg_fdw.rs │ │ ├── mapper.rs │ │ ├── mod.rs │ │ ├── pushdown.rs │ │ └── tests.rs │ ├── logflare_fdw │ │ ├── README.md │ │ ├── logflare_fdw.rs │ │ ├── mod.rs │ │ └── tests.rs │ ├── mod.rs │ ├── mssql_fdw │ │ ├── README.md │ │ ├── mod.rs │ │ ├── mssql_fdw.rs │ │ └── tests.rs │ ├── redis_fdw │ │ ├── README.md │ │ ├── mod.rs │ │ ├── redis_fdw.rs │ │ └── tests.rs │ ├── s3_fdw │ │ ├── README.md │ │ ├── mod.rs │ │ ├── parquet.rs │ │ ├── s3_fdw.rs │ │ └── tests.rs │ ├── stripe_fdw │ │ ├── README.md │ │ ├── mod.rs │ │ ├── stripe_fdw.rs │ │ └── tests.rs │ └── wasm_fdw │ │ ├── README.md │ │ ├── bindings │ │ ├── mod.rs │ │ ├── v1.rs │ │ └── v2.rs │ │ ├── host │ │ ├── http.rs │ │ ├── jwt.rs │ │ ├── mod.rs │ │ ├── stats.rs │ │ ├── time.rs │ │ └── utils.rs │ │ ├── mod.rs │ │ ├── tests.rs │ │ └── wasm_fdw.rs ├── lib.rs └── stats.rs └── wrappers.control /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(target_os="macos")'] 2 | # Postgres symbols won't be available until runtime 3 | rustflags = ["-Clink-arg=-Wl,-undefined,dynamic_lookup"] 4 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .benchmarks 2 | .git/ 3 | .github/ 4 | .pytest_cache/ 5 | dockerfiles/ 6 | docs/ 7 | pg_graphql.egg-info/ 8 | target/ 9 | wrappers/target/ 10 | supabase-wrappers/target/ 11 | supabase-wrappers-macros/target/ 12 | nix/ 13 | node_modules/ 14 | results/ 15 | venv/ 16 | *.md 17 | *.py 18 | *.yaml 19 | *.yml 20 | *.graphql 21 | *.nix 22 | .python-version 23 | .gitignore 24 | .clang-format 25 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Code Coverage 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | 9 | permissions: 10 | contents: write 11 | 12 | jobs: 13 | code-coverage: 14 | name: Code Coverage 15 | runs-on: [larger-runner-4cpu] 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v4 19 | 20 | - name: Install Rust toolchain 21 | uses: actions-rs/toolchain@v1 22 | with: 23 | toolchain: 1.85.1 24 | components: llvm-tools-preview, rustfmt, clippy 25 | default: true 26 | override: true 27 | 28 | - name: Install cargo-llvm-cov 29 | uses: taiki-e/install-action@cargo-llvm-cov 30 | 31 | - run: | 32 | sudo apt remove -y postgres* 33 | sudo apt -y install curl ca-certificates build-essential pkg-config libssl-dev 34 | sudo install -d /usr/share/postgresql-common/pgdg 35 | sudo curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc 36 | . /etc/os-release 37 | sudo sh -c "echo 'deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt $VERSION_CODENAME-pgdg main' > /etc/apt/sources.list.d/pgdg.list" 38 | sudo apt update -y -qq --fix-missing 39 | sudo apt -y install postgresql-client-15 postgresql-15 postgresql-server-dev-15 40 | sudo apt -y autoremove && sudo apt -y clean 41 | sudo chmod a+rwx `/usr/lib/postgresql/15/bin/pg_config --pkglibdir` `/usr/lib/postgresql/15/bin/pg_config --sharedir`/extension /var/run/postgresql/ 42 | 43 | - run: cargo install cargo-pgrx --version 0.14.3 44 | - run: cargo pgrx init --pg15 /usr/lib/postgresql/15/bin/pg_config 45 | 46 | - name: Build docker images 47 | run: | 48 | docker compose -f wrappers/.ci/docker-compose-native.yaml up -d 49 | 50 | - name: Generate code coverage 51 | id: coverage 52 | run: | 53 | source <(cargo llvm-cov show-env --export-prefix --no-cfg-coverage) 54 | cargo llvm-cov clean --workspace 55 | cargo pgrx test --features "native_fdws" --manifest-path wrappers/Cargo.toml pg15 56 | cargo llvm-cov report --lcov --output-path lcov.info 57 | 58 | - name: Coveralls upload 59 | uses: coverallsapp/github-action@v2 60 | with: 61 | github-token: ${{ secrets.GITHUB_TOKEN }} 62 | path-to-lcov: lcov.info 63 | debug: true 64 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy docs 2 | 3 | on: 4 | push: 5 | tags: 6 | - docs_v* 7 | 8 | permissions: {} 9 | 10 | jobs: 11 | deploy-docs: 12 | runs-on: ubuntu-latest 13 | env: 14 | DOCS_REVALIDATION_KEY: ${{ secrets.DOCS_REVALIDATION_KEY }} 15 | steps: 16 | - name: Request docs revalidation 17 | run: | 18 | curl -X POST https://supabase.com/docs/api/revalidate \ 19 | -H 'Content-Type: application/json' \ 20 | -H 'Authorization: Bearer ${{ secrets.DOCS_REVALIDATION_KEY }}' \ 21 | -d '{"tags": ["wrappers"]}' 22 | -------------------------------------------------------------------------------- /.github/workflows/test_supabase_wrappers.yml: -------------------------------------------------------------------------------- 1 | name: Test Wrappers 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | test: 13 | name: Run supabase_wrappers tests 14 | runs-on: ubuntu-24.04 15 | 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v4 19 | 20 | - uses: actions-rs/toolchain@v1 21 | with: 22 | toolchain: 1.85.1 23 | default: true 24 | override: true 25 | components: rustfmt, clippy 26 | 27 | - run: | 28 | sudo apt remove -y postgres* 29 | sudo apt -y install curl ca-certificates build-essential pkg-config libssl-dev 30 | sudo install -d /usr/share/postgresql-common/pgdg 31 | sudo curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc 32 | . /etc/os-release 33 | sudo sh -c "echo 'deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt $VERSION_CODENAME-pgdg main' > /etc/apt/sources.list.d/pgdg.list" 34 | sudo apt update -y -qq --fix-missing 35 | sudo apt -y install postgresql-client-15 postgresql-15 postgresql-server-dev-15 36 | sudo apt -y autoremove && sudo apt -y clean 37 | sudo chmod a+rwx `/usr/lib/postgresql/15/bin/pg_config --pkglibdir` `/usr/lib/postgresql/15/bin/pg_config --sharedir`/extension /var/run/postgresql/ 38 | 39 | - run: cargo install cargo-pgrx --version 0.14.3 40 | - run: cargo pgrx init --pg15 /usr/lib/postgresql/15/bin/pg_config 41 | 42 | - name: Format code 43 | run: | 44 | cd supabase-wrappers && cargo fmt --check 45 | 46 | - name: Run clippy 47 | run: | 48 | cd supabase-wrappers && RUSTFLAGS="-D warnings" cargo clippy --all --tests --no-deps 49 | 50 | - run: cd supabase-wrappers && cargo test 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | target/ 4 | *.iml 5 | **/*.rs.bk 6 | *.o 7 | *.so 8 | *.a 9 | *.swp 10 | *.log 11 | site/ 12 | .bash_history 13 | .config/ 14 | cmake*/ 15 | .direnv 16 | results/ 17 | wrappers/results/ 18 | wrappers/regression.* 19 | .venv/ 20 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.10.3 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.features": ["all_fdws,helloworld_fdw"] 3 | } 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Any contributions to wrappers are welcome, please visit https://fdw.dev/contributing/core/ for more details. 4 | 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "supabase-wrappers", 4 | "supabase-wrappers-macros", 5 | "wrappers", 6 | ] 7 | exclude = [ 8 | "wasm-wrappers", 9 | ] 10 | resolver = "2" 11 | 12 | [workspace.package] 13 | edition = "2021" 14 | rust-version = "1.85.1" 15 | homepage = "https://github.com/supabase/wrappers" 16 | repository = "https://github.com/supabase/wrappers" 17 | 18 | [profile.dev] 19 | panic = "unwind" 20 | lto = "thin" 21 | 22 | [profile.release] 23 | panic = "unwind" 24 | opt-level = 3 25 | lto = "fat" 26 | codegen-units = 1 27 | 28 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | fdw.dev 2 | -------------------------------------------------------------------------------- /docs/assets/airtable_credentials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase/wrappers/0da8b7131243e2a1696980703acd9b363c9c49d1/docs/assets/airtable_credentials.png -------------------------------------------------------------------------------- /docs/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase/wrappers/0da8b7131243e2a1696980703acd9b363c9c49d1/docs/assets/favicon.ico -------------------------------------------------------------------------------- /docs/assets/fdw-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase/wrappers/0da8b7131243e2a1696980703acd9b363c9c49d1/docs/assets/fdw-dark.png -------------------------------------------------------------------------------- /docs/assets/fdw-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase/wrappers/0da8b7131243e2a1696980703acd9b363c9c49d1/docs/assets/fdw-light.png -------------------------------------------------------------------------------- /docs/assets/google-sheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase/wrappers/0da8b7131243e2a1696980703acd9b363c9c49d1/docs/assets/google-sheet.png -------------------------------------------------------------------------------- /docs/assets/query-pushdown-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase/wrappers/0da8b7131243e2a1696980703acd9b363c9c49d1/docs/assets/query-pushdown-dark.png -------------------------------------------------------------------------------- /docs/assets/wasm-build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase/wrappers/0da8b7131243e2a1696980703acd9b363c9c49d1/docs/assets/wasm-build.png -------------------------------------------------------------------------------- /docs/assets/wrappers-github.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase/wrappers/0da8b7131243e2a1696980703acd9b363c9c49d1/docs/assets/wrappers-github.jpg -------------------------------------------------------------------------------- /docs/assets/wrappers-icon_container-3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase/wrappers/0da8b7131243e2a1696980703acd9b363c9c49d1/docs/assets/wrappers-icon_container-3x.png -------------------------------------------------------------------------------- /docs/assets/wrappers-icon_container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase/wrappers/0da8b7131243e2a1696980703acd9b363c9c49d1/docs/assets/wrappers-icon_container.png -------------------------------------------------------------------------------- /docs/assets/wrappers-icon_container.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/assets/wrappers.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/catalog/auth0.md: -------------------------------------------------------------------------------- 1 | --- 2 | source: 3 | documentation: 4 | author: supabase 5 | tags: 6 | - native 7 | - official 8 | --- 9 | 10 | # Auth0 11 | 12 | [Auth0](https://auth0.com/) is a flexible, drop-in solution to add authentication and authorization services to your applications 13 | 14 | The Auth0 Wrapper allows you to read data from your Auth0 tenant for use within your Postgres database. 15 | 16 | ## Preparation 17 | 18 | Before you can query Auth0, you need to enable the Wrappers extension and store your credentials in Postgres. 19 | 20 | ### Enable Wrappers 21 | 22 | Make sure the `wrappers` extension is installed on your database: 23 | 24 | ```sql 25 | create extension if not exists wrappers with schema extensions; 26 | ``` 27 | 28 | ### Enable the Auth0 Wrapper 29 | 30 | Enable the `auth0_wrapper` FDW: 31 | 32 | ```sql 33 | create foreign data wrapper auth0_wrapper 34 | handler auth0_fdw_handler 35 | validator auth0_fdw_validator; 36 | ``` 37 | 38 | ### Store your credentials (optional) 39 | 40 | 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. 41 | 42 | ```sql 43 | -- Save your Auth0 API key in Vault and retrieve the created `key_id` 44 | select vault.create_secret( 45 | '', -- Auth0 API key or Personal Access Token (PAT) 46 | 'auth0', 47 | 'Auth0 API key for Wrappers' 48 | ); 49 | ``` 50 | 51 | ### Connecting to Auth0 52 | 53 | We need to provide Postgres with the credentials to connect to Auth0, and any additional options. We can do this using the `create server` command: 54 | 55 | === "With Vault" 56 | 57 | ```sql 58 | create server auth0_server 59 | foreign data wrapper auth0_wrapper 60 | options ( 61 | url 'https://dev-.us.auth0.com/api/v2/users', 62 | api_key_id '' -- The Key ID from above. 63 | ); 64 | ``` 65 | 66 | === "Without Vault" 67 | 68 | ```sql 69 | -- create server and specify custom options 70 | create server auth0_server 71 | foreign data wrapper auth0_wrapper 72 | options ( 73 | url 'https://dev-.us.auth0.com/api/v2/users', 74 | api_key '' 75 | ); 76 | ``` 77 | 78 | ### Create a schema 79 | 80 | We recommend creating a schema to hold all the foreign tables: 81 | 82 | ```sql 83 | create schema if not exists auth0; 84 | ``` 85 | 86 | ## Entities 87 | 88 | The Auth0 Wrapper supports data reads from Auth0 API. 89 | 90 | ### Users 91 | 92 | The Auth0 Wrapper supports data reads from Auth0's [Management API List users endpoint](https://auth0.com/docs/api/management/v2/users/get-users) endpoint (_read only_). 93 | 94 | #### Operations 95 | 96 | | Object | Select | Insert | Update | Delete | Truncate | 97 | | ------ | :----: | :----: | :----: | :----: | :------: | 98 | | Users | ✅ | ❌ | ❌ | ❌ | ❌ | 99 | 100 | #### Usage 101 | 102 | ```sql 103 | create foreign table auth0.my_foreign_table ( 104 | name text 105 | -- other fields 106 | ) 107 | server auth0_server 108 | options ( 109 | object 'users' 110 | ); 111 | ``` 112 | 113 | #### Notes 114 | 115 | - Currently only supports the `users` object 116 | 117 | ## Query Pushdown Support 118 | 119 | This FDW doesn't support query pushdown. 120 | 121 | ## Limitations 122 | 123 | This section describes important limitations and considerations when using this FDW: 124 | 125 | - No query pushdown support, all filtering must be done locally 126 | - Large result sets may experience slower performance due to full data transfer requirement 127 | - Only supports the `users` object from Auth0 Management API 128 | - Cannot modify Auth0 user properties via FDW 129 | - Materialized views using these foreign tables may fail during logical backups 130 | 131 | ## Examples 132 | 133 | ### Basic Auth0 Users Query 134 | 135 | This example demonstrates querying Auth0 users data. 136 | 137 | ```sql 138 | create foreign table auth0.auth0_table ( 139 | created_at text, 140 | email text, 141 | email_verified bool, 142 | identities jsonb 143 | ) 144 | server auth0_server 145 | options ( 146 | object 'users' 147 | ); 148 | ``` 149 | 150 | You can now fetch your Auth0 data from within your Postgres database: 151 | 152 | ```sql 153 | select * from auth0.auth0_table; 154 | ``` 155 | -------------------------------------------------------------------------------- /docs/catalog/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | hide: 3 | - toc 4 | --- 5 | 6 | # Catalog 7 | 8 | Each FDW documentation includes a detailed "Limitations" section that describes important considerations and potential pitfalls when using that specific FDW. 9 | 10 | ## Official 11 | 12 | | Integration | Select | Insert | Update | Delete | Truncate | Push Down | 13 | | ------------- | :----: | :----: | :----: | :----: | :------: | :-------: | 14 | | Airtable | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | 15 | | Auth0 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | 16 | | AWS Cognito | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | 17 | | BigQuery | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | 18 | | Cal.com | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | 19 | | Calendly | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | 20 | | Clerk | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | 21 | | ClickHouse | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | 22 | | Cloudflare D1 | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | 23 | | Firebase | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | 24 | | HubSpot | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | 25 | | Iceberg | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | 26 | | Logflare | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | 27 | | Notion | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | 28 | | Orb | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | 29 | | Paddle | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | 30 | | Redis | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | 31 | | S3 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | 32 | | Snowflake | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | 33 | | Stripe | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | 34 | | SQL Server | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | 35 | 36 | ## Community 37 | 38 | Wasm wrappers can be installed directly from GitHub or any external source. 39 | 40 | See [Developing a Wasm Wrapper](../guides/create-wasm-wrapper.md) for instructions on how to build and develop your own. 41 | 42 | | Integration | Developer | Docs | Source | 43 | | :-----------: | :------------------------------: | :------------------: | :------------------------------------------------------------------------------------: | 44 | | Cal.com | [Supabase](https://supabase.com) | [Link](cal.md) | [Link](https://github.com/supabase/wrappers/tree/main/wasm-wrappers/fdw/cal_fdw) | 45 | | Calendly | [Supabase](https://supabase.com) | [Link](calendly.md) | [Link](https://github.com/supabase/wrappers/tree/main/wasm-wrappers/fdw/calendly_fdw) | 46 | | Clerk | [Supabase](https://supabase.com) | [Link](clerk.md) | [Link](https://github.com/supabase/wrappers/tree/main/wasm-wrappers/fdw/clerk_fdw) | 47 | | Cloudflare D1 | [Supabase](https://supabase.com) | [Link](cfd1.md) | [Link](https://github.com/supabase/wrappers/tree/main/wasm-wrappers/fdw/cfd1_fdw) | 48 | | HubSpot | [Supabase](https://supabase.com) | [Link](hubspot.md) | [Link](https://github.com/supabase/wrappers/tree/main/wasm-wrappers/fdw/hubspot_fdw) | 49 | | Notion | [Supabase](https://supabase.com) | [Link](notion.md) | [Link](https://github.com/supabase/wrappers/tree/main/wasm-wrappers/fdw/notion_fdw) | 50 | | Orb | [Supabase](https://supabase.com) | [Link](orb.md) | [Link](https://github.com/supabase/wrappers/tree/main/wasm-wrappers/fdw/orb_fdw) | 51 | | Paddle | [Supabase](https://supabase.com) | [Link](paddle.md) | [Link](https://github.com/supabase/wrappers/tree/main/wasm-wrappers/fdw/paddle_fdw) | 52 | | Snowflake | [Supabase](https://supabase.com) | [Link](snowflake.md) | [Link](https://github.com/supabase/wrappers/tree/main/wasm-wrappers/fdw/snowflake_fdw) | 53 | -------------------------------------------------------------------------------- /docs/contributing/core.md: -------------------------------------------------------------------------------- 1 | [`supabase/wrappers`](https://github.com/supabase/wrappers) is open source. PRs and issues are welcome. 2 | 3 | ## Development 4 | 5 | Requirements: 6 | 7 | - [Rust](https://www.rust-lang.org/) 8 | - [Cargo](https://doc.rust-lang.org/cargo/) 9 | - [Docker](https://www.docker.com/) 10 | - [pgrx](https://github.com/pgcentralfoundation/pgrx) 11 | 12 | ### Testing 13 | 14 | Tests are located in each FDW's `tests.rs` file. For example, Stripe FDW tests are in `wrappers/src/fdw/stripe_fdw/tests.rs` file. 15 | 16 | To run the tests locally, we need to start the mock containers first: 17 | 18 | ```bash 19 | cd wrappers 20 | docker-compose -f .ci/docker-compose.yaml up -d 21 | ``` 22 | 23 | And then build all Wasm FDW packages: 24 | 25 | ```bash 26 | find ../wasm-wrappers/fdw/ -name "Cargo.toml" -exec cargo component build --release --target wasm32-unknown-unknown --manifest-path {} \; 27 | ``` 28 | 29 | Now we can run all the tests: 30 | 31 | ```bash 32 | cargo pgrx test --features "all_fdws pg15" 33 | ``` 34 | 35 | You can also run the native or Wasm FDW tests individually: 36 | 37 | ```base 38 | # run native FDW tests only 39 | cargo pgrx test --features "native_fdws pg15" 40 | 41 | # or run Wasm FDW tests only 42 | cargo pgrx test --features "wasm_fdw pg15" 43 | ``` 44 | 45 | ### Interactive PSQL Development 46 | 47 | To reduce the iteration cycle, you may want to launch a psql prompt with `wrappers` installed to experiment a single FDW like below: 48 | 49 | ```bash 50 | cd wrappers 51 | cargo pgrx run pg15 --features clickhouse_fdw 52 | ``` 53 | 54 | Try out the SQLs in the psql prompt with the `wrappers` extension installed like below: 55 | 56 | ```sql 57 | create extension if not exists wrappers; 58 | 59 | create foreign data wrapper wasm_wrapper 60 | handler wasm_fdw_handler 61 | validator wasm_fdw_validator; 62 | ``` 63 | 64 | For debugging, you can make use of [`notice!` macros](https://docs.rs/pgrx/latest/pgrx/macro.notice.html) to print out statements while using your wrapper in `psql`. 65 | -------------------------------------------------------------------------------- /docs/contributing/documentation.md: -------------------------------------------------------------------------------- 1 | Building documentation requires Python 3.6+. 2 | 3 | ### Install Dependencies 4 | 5 | Install mkdocs, themes, and extensions. 6 | 7 | ```shell 8 | pip install -r docs/requirements_docs.txt 9 | ``` 10 | 11 | ### Serving 12 | 13 | To serve the documentation locally run 14 | 15 | ```shell 16 | mkdocs serve 17 | ``` 18 | 19 | and visit the docs at [http://127.0.0.1:8000/wrappers/](http://127.0.0.1:8000/wrappers/) 20 | 21 | ### Deploying 22 | 23 | If you have write access to the repo, docs can be updated using 24 | 25 | ``` 26 | mkdocs gh-deploy 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/contributing/native.md: -------------------------------------------------------------------------------- 1 | # Developing a Native Wrapper 2 | 3 | !!! warning 4 | 5 | We are not accepting native community Wrappers this repo until the Wrappers API is stabilized. If you have an idea for a Wrapper, you can [vote your favorite Wrapper](https://github.com/orgs/supabase/discussions/26627). Once we release Wrappers 1.0, we will support native community Wrappers within the Wrappers repo. 6 | 7 | In the meantime you can develop a Wasm wrapper, which can be installed on any Postgres instance with `wrappers v0.4.0+`. 8 | 9 | To develop a FDW using `Wrappers`, you only need to implement the [ForeignDataWrapper](https://github.com/supabase/wrappers/blob/main/supabase-wrappers/src/interface.rs) trait. 10 | 11 | ```rust 12 | pub trait ForeignDataWrapper { 13 | // create a FDW instance 14 | fn new(...) -> Self; 15 | 16 | // functions for data scan, e.g. select 17 | fn begin_scan(...); 18 | fn iter_scan(...) -> Option; 19 | fn end_scan(...); 20 | 21 | // functions for data modify, e.g. insert, update and delete 22 | fn begin_modify(...); 23 | fn insert(...); 24 | fn update(...); 25 | fn delete(...); 26 | fn end_modify(...); 27 | 28 | // other optional functions 29 | ... 30 | } 31 | ``` 32 | 33 | In a minimum FDW, which supports data scan only, `new()`, `begin_scan()`, `iter_scan()` and `end_scan()` are required, all the other functions are optional. 34 | 35 | To know more about FDW development, please visit the [Wrappers documentation](https://docs.rs/supabase-wrappers/latest/supabase_wrappers/). 36 | 37 | ## Basic usage 38 | 39 | These steps outline how to use the a demo FDW [HelloWorldFdw](https://github.com/supabase/wrappers/tree/main/wrappers/src/fdw/helloworld_fdw), which only outputs a single line of fake data: 40 | 41 | 1. Clone this repo 42 | 43 | ```bash 44 | git clone https://github.com/supabase/wrappers.git 45 | ``` 46 | 47 | 2. Run it using pgrx with feature: 48 | 49 | ```bash 50 | cd wrappers/wrappers 51 | cargo pgrx run pg15 --features helloworld_fdw 52 | ``` 53 | 54 | 3. Create the extension, foreign data wrapper and related objects: 55 | 56 | ```sql 57 | -- create extension 58 | create extension wrappers; 59 | 60 | -- create foreign data wrapper and enable 'HelloWorldFdw' 61 | create foreign data wrapper helloworld_wrapper 62 | handler hello_world_fdw_handler 63 | validator hello_world_fdw_validator; 64 | 65 | -- create server and specify custom options 66 | create server my_helloworld_server 67 | foreign data wrapper helloworld_wrapper 68 | options ( 69 | foo 'bar' 70 | ); 71 | 72 | -- create an example foreign table 73 | create foreign table hello ( 74 | id bigint, 75 | col text 76 | ) 77 | server my_helloworld_server 78 | options ( 79 | foo 'bar' 80 | ); 81 | ``` 82 | 83 | 4. Run a query to check if it is working: 84 | 85 | ```sql 86 | wrappers=# select * from hello; 87 | id | col 88 | ----+------------- 89 | 0 | Hello world 90 | (1 row) 91 | ``` 92 | 93 | ## Running tests 94 | 95 | In order to run tests in `wrappers`: 96 | 97 | ```bash 98 | docker-compose -f .ci/docker-compose.yaml up -d 99 | cargo pgrx test --features all_fdws,pg15 100 | ``` 101 | 102 | ## Contribution 103 | 104 | All contributions, feature requests, bug report or ideas are welcomed. 105 | -------------------------------------------------------------------------------- /docs/guides/installation.md: -------------------------------------------------------------------------------- 1 | First, install [pgrx](https://github.com/tcdi/pgrx) 2 | 3 | Then clone the repo and install using 4 | 5 | ```bash 6 | git clone https://github.com/supabase/wrappers.git 7 | cd wrappers/wrappers 8 | cargo pgrx install --no-default-features --features pg14,_fdw --release 9 | ``` 10 | 11 | To enable the extension in PostgreSQL we must execute a `create extension` statement. 12 | 13 | ```psql 14 | create extension wrappers; 15 | ``` 16 | -------------------------------------------------------------------------------- /docs/guides/limitations.md: -------------------------------------------------------------------------------- 1 | ## Limitations 2 | 3 | - Windows is not supported, that limitation inherits from [pgrx](https://github.com/tcdi/pgrx). 4 | - Currently only supports PostgreSQL v14, v15 and v16. 5 | - Generated columns are not supported. 6 | -------------------------------------------------------------------------------- /docs/guides/native-wasm.md: -------------------------------------------------------------------------------- 1 | # Native vs Wasm Wrappers 2 | 3 | ## Wasm Wrappers 4 | 5 | Since v0.4.0, Wrappers supports WebAssembly (Wasm) FDWs. Anyone can develop a Wasm Wrapper. Wasm foreign data wrappers are dynamically loaded on the first query and then cached locally, they can be installed directly from remotes like GitHub and S3. 6 | 7 | Check out [Developing a Wrapper](create-wasm-wrapper.md) to develop your own Wasm Wrapper. 8 | 9 | ## Native Wrappers 10 | 11 | Native Wrappers are developed and supported by Supabase. These have better performance than Wasm wrappers, but they must be pre-installed on the Postgres database before a developer can use it. 12 | -------------------------------------------------------------------------------- /docs/guides/query-pushdown.md: -------------------------------------------------------------------------------- 1 | # Query Pushdown 2 | 3 | Query pushdown is a technique that enhances query performance by executing parts of the query directly on the data source. It reduces data transfer between the database and the application, enabling faster execution and improved performance. 4 | 5 | ### What is Query Pushdown? 6 | 7 | Query pushdown is a technique that enhances query performance by executing parts of the query directly on the data source. It reduces data transfer between the database and the application, enabling faster execution and improved performance. 8 | 9 | ![assets/query-pushdown-dark.png](../assets/query-pushdown-dark.png) 10 | 11 | ### Using Query Pushdown 12 | 13 | In Wrappers, the pushdown logic is integrated into each FDW. You don’t need to modify your queries to benefit from this feature. For example, the [Stripe FDW](https://supabase.com/docs/guides/database/extensions/wrappers/stripe) automatically applies query pushdown for `id` within the `customer` object: 14 | 15 | ```sql 16 | select * 17 | from stripe.customers 18 | where id = 'cus_N5WMk7pvQPkY3B'; 19 | ``` 20 | 21 | This approach contrasts with fetching and filtering all customers locally, which is less efficient. Query pushdown translates this into a single API call, significantly speeding up the process: 22 | 23 | ```bash 24 | https://api.stripe.com/v1/customers/cus_N5WMk7pvQPkY3B 25 | ``` 26 | 27 | We can use push down criteria and other query parameters too. For example, [ClickHouse FDW](https://supabase.com/docs/guides/database/extensions/wrappers/clickhouse) supports `order by` and `limit` pushdown: 28 | 29 | ```sql 30 | select * 31 | from clickhouse.people 32 | order by name 33 | limit 20; 34 | ``` 35 | 36 | This query executes `order by name limit 20` on ClickHouse before transferring the result to Postgres. 37 | -------------------------------------------------------------------------------- /docs/guides/remote-subqueries.md: -------------------------------------------------------------------------------- 1 | # Remote Subqueries 2 | 3 | Remote subqueries enable the use of prepared data on a remote server, which is beneficial for complex queries or sensitive data protection. 4 | 5 | ### Static Subqueries 6 | 7 | In its most basic form, you can map a query on the remote server into a foreign table in Postgres. For instance: 8 | 9 | ```sql 10 | create foreign table clickhouse.people ( 11 | id bigint, 12 | name text, 13 | age bigint 14 | ) 15 | server clickhouse_server 16 | options ( 17 | table '(select * from people where age < 25)' 18 | ); 19 | ``` 20 | 21 | In this example, the foreign table `clickhouse.people` data is read from the result of the subquery `select * from people where age < 25` which runs on ClickHouse server. 22 | 23 | ### Dynamic Subqueries 24 | 25 | What if the query is not fixed and needs to be dynamic? For example, ClickHouse provides [Parameterized Views](https://clickhouse.com/docs/en/sql-reference/statements/create/view#parameterized-view) which can accept parameters for a view. Wrappers supports this by defining a column for each parameter. 26 | 27 | Let's take a look at an example: 28 | 29 | ```sql 30 | create foreign table clickhouse.my_table ( 31 | id bigint, 32 | col1 text, 33 | col2 bigint, 34 | _param1 text, 35 | _param2 bigint 36 | ) 37 | server clickhouse_server 38 | options ( 39 | table '(select * from my_view(column1=${_param1}, column2=${_param2}))' 40 | ); 41 | ``` 42 | 43 | You can then pass values to these parameters in your query: 44 | 45 | ```sql 46 | select id, col1, col2 47 | from clickhouse.my_table 48 | where _param1 = 'abc' and _param2 = 42; 49 | ``` 50 | 51 | Currently, this feature is supported by [ClickHouse FDW](https://supabase.com/docs/guides/database/extensions/wrappers/clickhouse) and [BigQuery FDW](https://supabase.com/docs/guides/database/extensions/wrappers/bigquery), with plans to expand support in the future. 52 | -------------------------------------------------------------------------------- /docs/guides/removing-wrappers.md: -------------------------------------------------------------------------------- 1 | # Removing Foreign Data Wrappers 2 | 3 | This guide explains how to fully remove all foreign data wrappers from your PostgreSQL database. 4 | 5 | 6 | ## Components to Remove 7 | 8 | When removing a foreign data wrapper, you need to remove several components in the correct order: 9 | 10 | 1. Foreign Tables 11 | 2. Foreign Servers 12 | 3. Wrappers Extension 13 | 14 | ## Step-by-Step Removal Process 15 | 16 | ### 1. List and Remove Foreign Tables 17 | 18 | First, list all foreign tables associated with your wrapper: 19 | 20 | ```sql 21 | select c.relname as foreign_table_name, 22 | n.nspname as schema_name 23 | from pg_catalog.pg_foreign_table ft 24 | join pg_catalog.pg_class c on c.oid = ft.ftrelid 25 | join pg_catalog.pg_namespace n on n.oid = c.relnamespace; 26 | ``` 27 | 28 | Remove each foreign table: 29 | 30 | ```sql 31 | drop foreign table if exists schema_name.table_name; 32 | ``` 33 | 34 | ### 2. Remove Foreign Servers 35 | 36 | List servers: 37 | 38 | ```sql 39 | select fs.srvname as server_name, 40 | fdw.fdwname as wrapper_name 41 | from pg_catalog.pg_foreign_server fs 42 | join pg_catalog.pg_foreign_data_wrapper fdw on fdw.oid = fs.srvfdw; 43 | ``` 44 | 45 | Remove each server: 46 | 47 | ```sql 48 | drop server if exists server_name cascade; 49 | ``` 50 | 51 | !!! note 52 | 53 | With `cascade` option, this will also drop all foreign tables using that foreign server. 54 | 55 | ### 3. Remove the Extension 56 | 57 | ```sql 58 | drop extension if exists wrappers cascade; 59 | ``` 60 | 61 | !!! note 62 | 63 | With `cascade` option, this will also drop all foreign servers and foreign tables using Wrappers extension. 64 | 65 | -------------------------------------------------------------------------------- /docs/guides/security.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | ## Remote Servers 4 | 5 | Your FDWs will inherit the security model of the credentials provided when you create the remote server. For example, take this Stripe Wrapper: 6 | 7 | ```sql 8 | create server stripe_server 9 | foreign data wrapper stripe_wrapper 10 | options ( 11 | api_key '', -- Stripe API key, required 12 | api_url 'https://api.stripe.com/v1/', 13 | api_version '2024-06-20' 14 | ); 15 | ``` 16 | 17 | The Wrapper will be able to access any data that the `api_key` has permission to access. 18 | 19 | ## Row Level Security 20 | 21 | Foreign Data Wrappers do not provide Row Level Security. 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 have a restrictive set of grants. 22 | 23 | ## Exposing foreign data 24 | 25 | If you want to expose any of the foreign table columns through a public API, we recommend using a [Postgres Function with `security definer`](https://supabase.com/docs/guides/database/functions#security-definer-vs-invoker). For better access control, the function should have appropriate filters on the foreign table to apply security rules based on your business needs. 26 | 27 | For example, if you wanted to expose all of your Stripe Products through an API: 28 | 29 | Create a Stripe Products foreign table: 30 | 31 | ```sql 32 | create foreign table stripe.stripe_products ( 33 | id text, 34 | name text, 35 | active bool, 36 | default_price text, 37 | description text 38 | ) 39 | server stripe_fdw_server 40 | options ( 41 | object 'products', 42 | rowid_column 'id' 43 | ); 44 | ``` 45 | 46 | Create a `security definer` function that queries the foreign table and filters on the name prefix parameter: 47 | 48 | ```sql 49 | create function public.get_stripe_products(name_prefix text) 50 | returns table ( 51 | id text, 52 | name text, 53 | active boolean, 54 | default_price text, 55 | description text 56 | ) 57 | language plpgsql 58 | security definer set search_path = '' 59 | as $$ 60 | begin 61 | return query 62 | select 63 | id, 64 | name, 65 | active, 66 | default_price, 67 | description 68 | from 69 | stripe.stripe_products 70 | where 71 | name like name_prefix || '%' 72 | ; 73 | end; 74 | $$; 75 | ``` 76 | 77 | Restrict the function execution to a specific role only. For example, if you have an `authenticated` role in Postgres, revoke access to everything except that one role: 78 | 79 | ```sql 80 | -- revoke public execute permission 81 | revoke execute on function public.get_stripe_products from public; 82 | revoke execute on function public.get_stripe_products from anon; 83 | 84 | -- grant execute permission to a specific role only 85 | grant execute on function public.get_stripe_products to authenticated; 86 | ``` 87 | -------------------------------------------------------------------------------- /docs/guides/updating-wrappers.md: -------------------------------------------------------------------------------- 1 | # Updating Foreign Data Wrappers 2 | 3 | This guide explains how to update foreign data wrappers in your PostgreSQL database, with special focus on WebAssembly (WASM) wrappers. 4 | 5 | ## Types of Updates 6 | 7 | There are two main types of updates for foreign data wrappers: 8 | 9 | 1. **Native wrapper updates**: Updates to native Postgres extensions 10 | 2. **WASM wrapper updates**: Updates to WebAssembly-based wrappers 11 | 12 | ## Updating WASM Foreign Data Wrappers 13 | 14 | WASM wrappers can be updated without reinstalling the entire extension, by modifying the foreign server configuration. 15 | 16 | ### Step-by-Step Update Process 17 | 18 | #### 1. Update the Foreign Server 19 | 20 | To update a WASM wrapper, you need to update the URL, version, and checksum: 21 | 22 | ```sql 23 | alter server slack_server options ( 24 | set fdw_package_url 'https://github.com/supabase/wrappers/releases/download/....', 25 | set fdw_package_version '0.0.1', 26 | set fdw_package_checksum 'new-checksum-here' 27 | ); 28 | ``` 29 | 30 | **Important options:** 31 | 32 | - `fdw_package_url`: The URL to the new version of the WASM package 33 | - `fdw_package_version`: The new version number 34 | - `fdw_package_checksum`: The checksum of the new version for verification 35 | 36 | #### 3. Verify the Update 37 | 38 | You can verify that the server has been updated by querying it again: 39 | 40 | ```sql 41 | select option_name, option_value 42 | from pg_options_to_table( 43 | (select srvoptions from pg_foreign_server where srvname = 'slack_server') 44 | ); 45 | ``` 46 | 47 | You can change `srvname` to the name of the FDW when you installed it. 48 | 49 | ## Updating Native Foreign Data Wrappers 50 | 51 | This is valid if your version of Postgres has multiple versions of the FDW installed. 52 | 53 | ```sql 54 | alter extension wrappers update; 55 | ``` 56 | 57 | ## Backward Compatibility Considerations 58 | 59 | When updating wrappers, consider the following: 60 | 61 | 1. **Schema changes**: Some updates might change the schema of foreign tables, requiring you to recreate them 62 | 2. **API changes**: The wrapper might interact with a different version of an external API 63 | 3. **Connection parameters**: New parameters might be required or old ones deprecated 64 | 65 | Always check the release notes for any breaking changes before updating. 66 | 67 | ## Troubleshooting 68 | 69 | If you encounter issues after updating: 70 | 71 | 1. **Check server logs**: PostgreSQL logs may contain error messages 72 | 2. **Verify options**: Make sure all required options are set correctly 73 | 3. **Test with a simple query**: Start with basic queries to isolate problems -------------------------------------------------------------------------------- /docs/guides/usage-statistics.md: -------------------------------------------------------------------------------- 1 | # FDW Usage Statistics 2 | 3 | Quantitative metrics are useful when working with Postgres FDWs because of their impact on performance optimization, monitoring, and query planning across distributed databases. 4 | 5 | You can use the FDW statistics to identify bottlenecks, latency issues, and inefficiencies in data retrieval. 6 | 7 | ## Querying FDW Statistics 8 | 9 | ```sql 10 | select * 11 | from extensions.wrappers_fdw_stats; 12 | ``` 13 | 14 | ## Statistics Reference 15 | 16 | - `create_times` - number of times the FDW instance has been created 17 | - `rows_in` - number of rows transferred from source 18 | - `rows_out` - number of rows transferred to source 19 | - `bytes_in` - number of bytes transferred from source 20 | - `bytes_out` - number of bytes transferred to source 21 | - `metadata` - additional usage statistics specific to a FDW 22 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | hide: 3 | - navigation 4 | --- 5 | 6 | # Postgres Wrappers 7 | 8 | Wrappers is a Rust framework for developing PostgreSQL Foreign Data Wrappers. 9 | 10 | ## What is a Foreign Data Wrapper? 11 | 12 | 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. 13 | 14 | Postgres includes several built-in foreign data wrappers, such as `postgres_fdw` for accessing other PostgreSQL databases, and `file_fdw` for reading data from files. 15 | 16 | ## The Wrappers Framework 17 | 18 | The Wrappers framework extends the Postgres FDW feature. You can use it to query other databases or any other external systems. For example, developers can use the Stripe wrapper to query Stripe data and join the data with customer data inside Postgres: 19 | 20 | ```sql 21 | select 22 | customer_id, 23 | name 24 | from 25 | stripe.customers; 26 | ``` 27 | 28 | returns 29 | 30 | ``` 31 | customer_id | name 32 | --------------------+----------- 33 | cus_NffrFeUfNV2Hib | Jenny Rosen 34 | (1 row) 35 | ``` 36 | 37 | ## Concepts 38 | 39 | Postgres FDWs introduce the concept of a "remote server" and "foreign table": 40 | 41 | ![FDW](assets/fdw-dark.png) 42 | 43 | ### Remote servers 44 | 45 | A Remote Server is an external database, API, or any system containing data that you want to query from your Postgres database. Examples include: 46 | 47 | - An external database, like Postgres or Firebase. 48 | - A remote data warehouse, like ClickHouse, BigQuery, or Snowflake. 49 | - An API, like Stripe or GitHub. 50 | 51 | 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 Postgres database. 52 | 53 | ### Foreign tables 54 | 55 | A table in your database which maps to some data inside a Remote Server. 56 | 57 | Examples: 58 | 59 | - An `analytics` table which maps to a table inside your data warehouse. 60 | - A `subscriptions` table which maps to your Stripe subscriptions. 61 | - A `collections` table which maps to a Firebase collection. 62 | 63 | Although a foreign table behaves like any other table, the data is not stored inside your database. The data remains inside the Remote Server. 64 | 65 | ## Supported platforms 66 | 67 | The following Postgres providers support Wrappers: 68 | 69 | - [supabase.com](https://supabase.com) 70 | -------------------------------------------------------------------------------- /docs/requirements_docs.txt: -------------------------------------------------------------------------------- 1 | mkdocs 2 | mkdocs-material 3 | -------------------------------------------------------------------------------- /docs/stylesheets/extra.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap'); 2 | 3 | :root { 4 | --md-text-font: "IBM Plex Sans", "Roboto", sans-serif; 5 | /* color: red; */ 6 | } 7 | 8 | [data-md-color-scheme="slate"] { 9 | --md-default-bg-color:#121212; 10 | --md-default-fg-color--light: white; 11 | --md-code-bg-color: #2a2929; 12 | --md-code-hl-keyword-color: #569cd6; 13 | } 14 | 15 | .md-header, .md-tabs { 16 | background-color: var(--md-default-bg-color); 17 | color: var(--md-default-fg-color--light); 18 | font-family: var(--md-text-font); 19 | } -------------------------------------------------------------------------------- /docs/tags.md: -------------------------------------------------------------------------------- 1 | --- 2 | hide: 3 | - navigation 4 | --- 5 | 6 | # Catalog 7 | 8 | Following is a list of relevant tags: 9 | 10 | 11 | -------------------------------------------------------------------------------- /mkdocs.yaml: -------------------------------------------------------------------------------- 1 | site_name: Wrappers 2 | site_url: https://supabase.github.io/wrappers 3 | site_description: A PostgreSQL extension for connecting to external data sources 4 | copyright: Copyright © Supabase 5 | repo_name: supabase/wrappers 6 | repo_url: https://github.com/supabase/wrappers 7 | edit_uri: edit/main/docs/ 8 | 9 | not_in_nav: | 10 | tags.md 11 | 12 | nav: 13 | - Home: 14 | - "index.md" 15 | - Catalog: 16 | - catalog/index.md 17 | - Native: 18 | - Airtable: "catalog/airtable.md" 19 | - Auth0: "catalog/auth0.md" 20 | - AWS Cognito: "catalog/cognito.md" 21 | - BigQuery: "catalog/bigquery.md" 22 | - ClickHouse: "catalog/clickhouse.md" 23 | - Firebase: "catalog/firebase.md" 24 | - Iceberg : "catalog/iceberg.md" 25 | - Logflare: "catalog/logflare.md" 26 | - Redis: "catalog/redis.md" 27 | - S3 (CSV, JSON, Parquet): "catalog/s3.md" 28 | - Stripe: "catalog/stripe.md" 29 | - SQL Server: "catalog/mssql.md" 30 | - Wasm: 31 | - catalog/wasm/index.md 32 | - Cal.com: "catalog/cal.md" 33 | - Calendly: "catalog/calendly.md" 34 | - Clerk: "catalog/clerk.md" 35 | - Cloudflare D1: "catalog/cfd1.md" 36 | - HubSpot: "catalog/hubspot.md" 37 | - Notion: "catalog/notion.md" 38 | - Orb: "catalog/orb.md" 39 | - Paddle: "catalog/paddle.md" 40 | - Slack: "catalog/slack.md" 41 | - Snowflake: "catalog/snowflake.md" 42 | - Guides: 43 | - Native vs Wasm Wrappers: "guides/native-wasm.md" 44 | - Query Pushdown: "guides/query-pushdown.md" 45 | - Remote Subqueries: "guides/remote-subqueries.md" 46 | - Security: "guides/security.md" 47 | - FDW Statistics: "guides/usage-statistics.md" 48 | - Installing Wrappers in Postgres: "guides/installation.md" 49 | - Updating Foreign Data Wrappers: "guides/updating-wrappers.md" 50 | - Removing Foreign Data Wrappers: "guides/removing-wrappers.md" 51 | - Limitations: "guides/limitations.md" 52 | - Contributing: 53 | - Building the Docs: "contributing/documentation.md" 54 | - Core Development: "contributing/core.md" 55 | - Developing a Native Wrapper: 56 | - Quick Start: "contributing/native.md" 57 | - Developing a Wasm Wrapper: 58 | - Quick Start: "guides/create-wasm-wrapper.md" 59 | - Advanced guide: "guides/wasm-advanced.md" 60 | 61 | plugins: 62 | - tags: 63 | tags_file: tags.md 64 | 65 | theme: 66 | name: "material" 67 | favicon: "assets/favicon.ico" 68 | logo: "assets/wrappers.svg" 69 | homepage: https://supabase.github.io/wrappers 70 | features: 71 | - content.code.annotate 72 | - content.code.copy 73 | - navigation.expand 74 | - navigation.tabs 75 | - navigation.tabs.sticky 76 | - navigation.sections 77 | - navigation.indexes 78 | - navigation.footer 79 | palette: 80 | scheme: slate 81 | primary: green 82 | accent: green 83 | 84 | extra_css: 85 | - stylesheets/extra.css 86 | 87 | extra: 88 | social: 89 | - icon: fontawesome/brands/twitter 90 | link: https://x.com/supabase 91 | name: Supabase on Twitter 92 | - icon: fontawesome/brands/reddit 93 | link: https://reddit.com/r/supabase 94 | name: Supabase on Reddit 95 | - icon: fontawesome/brands/github 96 | link: https://github.com/supabase/wrappers 97 | name: Wrappers on GitHub 98 | 99 | markdown_extensions: 100 | - attr_list 101 | - md_in_html 102 | - pymdownx.highlight: 103 | linenums: true 104 | guess_lang: false 105 | use_pygments: true 106 | pygments_style: default 107 | - pymdownx.superfences 108 | - pymdownx.tabbed: 109 | alternate_style: true 110 | - pymdownx.snippets 111 | - pymdownx.tasklist 112 | - admonition 113 | - pymdownx.emoji: 114 | emoji_index: !!python/name:material.extensions.emoji.twemoji 115 | emoji_generator: !!python/name:material.extensions.emoji.to_svg 116 | -------------------------------------------------------------------------------- /supabase-wrappers-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "supabase-wrappers-macros" 3 | version = "0.1.18" 4 | authors = ["Supabase Inc. https://supabase.com/"] 5 | license = "Apache-2.0" 6 | description = "Postgres Foreign Data Wrapper development framework macros for supabase-wrappers" 7 | homepage = "https://github.com/supabase/wrappers/tree/main/supabase-wrappers-macros" 8 | repository = "https://github.com/supabase/wrappers/tree/main/supabase-wrappers-macros" 9 | categories = ["database"] 10 | keywords = ["database", "postgres", "postgresql", "extension"] 11 | edition = { workspace = true } 12 | rust-version = { workspace = true } 13 | 14 | [dependencies] 15 | syn = { version = "1.0", features = ["full"] } 16 | quote = "1.0" 17 | proc-macro2 = "1.0" 18 | 19 | [lib] 20 | proc-macro = true 21 | -------------------------------------------------------------------------------- /supabase-wrappers-macros/README.md: -------------------------------------------------------------------------------- 1 | # supabase-wrappers-macros 2 | 3 | This crate is to provide helper macros for `Wrappers` and NOT supposed to be used directly, please use [supabase-wrappers](https://github.com/supabase/wrappers/tree/main/supabase-wrappers) instead. 4 | -------------------------------------------------------------------------------- /supabase-wrappers-macros/rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 100 2 | -------------------------------------------------------------------------------- /supabase-wrappers/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "supabase-wrappers" 3 | version = "0.1.22" 4 | authors = ["Supabase Inc. https://supabase.com/"] 5 | license = "Apache-2.0" 6 | description = "Postgres Foreign Data Wrapper development framework in Rust." 7 | homepage = "https://github.com/supabase/wrappers/tree/main/supabase-wrappers" 8 | repository = "https://github.com/supabase/wrappers/tree/main/supabase-wrappers" 9 | categories = ["database"] 10 | keywords = ["database", "postgres", "postgresql", "extension"] 11 | edition = { workspace = true } 12 | rust-version = { workspace = true } 13 | 14 | [lints.rust] 15 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(pgrx_embed)'] } 16 | 17 | [features] 18 | default = ["pg15"] 19 | pg13 = ["pgrx/pg13", "pgrx-tests/pg13"] 20 | pg14 = ["pgrx/pg14", "pgrx-tests/pg14"] 21 | pg15 = ["pgrx/pg15", "pgrx-tests/pg15"] 22 | pg16 = ["pgrx/pg16", "pgrx-tests/pg16"] 23 | pg17 = ["pgrx/pg17", "pgrx-tests/pg17"] 24 | pg_test = [] 25 | 26 | [dependencies] 27 | pgrx = { version = "=0.14.3", default-features = false } 28 | thiserror = "1.0.63" 29 | tokio = { version = "1.43", features = ["rt", "net"] } 30 | uuid = { version = "1.10.0" } 31 | supabase-wrappers-macros = { version = "0.1", path = "../supabase-wrappers-macros" } 32 | 33 | [dev-dependencies] 34 | pgrx-tests = "=0.14.3" 35 | 36 | [package.metadata.docs.rs] 37 | features = ["pg15"] 38 | no-default-features = true 39 | # Enable `#[cfg(docsrs)]` (https://docs.rs/about/builds#cross-compiling) 40 | rustc-args = ["--cfg", "docsrs"] 41 | -------------------------------------------------------------------------------- /supabase-wrappers/rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 100 2 | -------------------------------------------------------------------------------- /supabase-wrappers/src/instance.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::ffi::CStr; 3 | 4 | use crate::prelude::*; 5 | use pgrx::pg_sys::panic::ErrorReport; 6 | use pgrx::prelude::*; 7 | 8 | #[derive(Debug, Clone, Default)] 9 | pub struct ForeignServer { 10 | pub server_name: String, 11 | pub server_type: Option, 12 | pub server_version: Option, 13 | pub options: HashMap, 14 | } 15 | 16 | // create a fdw instance from its id 17 | pub(super) unsafe fn create_fdw_instance_from_server_id< 18 | E: Into, 19 | W: ForeignDataWrapper, 20 | >( 21 | fserver_id: pg_sys::Oid, 22 | ) -> W { 23 | let to_string = |raw: *mut std::ffi::c_char| -> Option { 24 | if raw.is_null() { 25 | return None; 26 | } 27 | let c_str = CStr::from_ptr(raw); 28 | let value = c_str 29 | .to_str() 30 | .map_err(|_| { 31 | OptionsError::OptionValueIsInvalidUtf8( 32 | String::from_utf8_lossy(c_str.to_bytes()).to_string(), 33 | ) 34 | }) 35 | .report_unwrap() 36 | .to_string(); 37 | Some(value) 38 | }; 39 | let fserver = pg_sys::GetForeignServer(fserver_id); 40 | let server = ForeignServer { 41 | server_name: to_string((*fserver).servername).unwrap(), 42 | server_type: to_string((*fserver).servertype), 43 | server_version: to_string((*fserver).serverversion), 44 | options: options_to_hashmap((*fserver).options).report_unwrap(), 45 | }; 46 | let wrapper = W::new(server); 47 | wrapper.report_unwrap() 48 | } 49 | 50 | // create a fdw instance from a foreign table id 51 | pub(super) unsafe fn create_fdw_instance_from_table_id< 52 | E: Into, 53 | W: ForeignDataWrapper, 54 | >( 55 | ftable_id: pg_sys::Oid, 56 | ) -> W { 57 | let ftable = pg_sys::GetForeignTable(ftable_id); 58 | create_fdw_instance_from_server_id((*ftable).serverid) 59 | } 60 | -------------------------------------------------------------------------------- /supabase-wrappers/src/limit.rs: -------------------------------------------------------------------------------- 1 | use crate::interface::Limit; 2 | use pgrx::{is_a, pg_sys, FromDatum}; 3 | 4 | // extract limit 5 | pub(crate) unsafe fn extract_limit( 6 | root: *mut pg_sys::PlannerInfo, 7 | _baserel: *mut pg_sys::RelOptInfo, 8 | _baserel_id: pg_sys::Oid, 9 | ) -> Option { 10 | let parse = (*root).parse; 11 | 12 | // don't push down LIMIT if the query has a GROUP BY clause or aggregates 13 | if !(*parse).groupClause.is_null() || (*parse).hasAggs { 14 | return None; 15 | } 16 | 17 | // only push down constant LIMITs that are not NULL 18 | let limit_count = (*parse).limitCount as *mut pg_sys::Const; 19 | if limit_count.is_null() || !is_a(limit_count as *mut pg_sys::Node, pg_sys::NodeTag::T_Const) { 20 | return None; 21 | } 22 | 23 | let mut limit = Limit::default(); 24 | 25 | if let Some(count) = i64::from_polymorphic_datum( 26 | (*limit_count).constvalue, 27 | (*limit_count).constisnull, 28 | (*limit_count).consttype, 29 | ) { 30 | limit.count = count; 31 | } else { 32 | return None; 33 | } 34 | 35 | // only consider OFFSETS that are non-NULL constants 36 | let limit_offset = (*parse).limitOffset as *mut pg_sys::Const; 37 | if !limit_offset.is_null() && is_a(limit_offset as *mut pg_sys::Node, pg_sys::NodeTag::T_Const) 38 | { 39 | if let Some(offset) = i64::from_polymorphic_datum( 40 | (*limit_offset).constvalue, 41 | (*limit_offset).constisnull, 42 | (*limit_offset).consttype, 43 | ) { 44 | limit.offset = offset; 45 | } 46 | } 47 | 48 | Some(limit) 49 | } 50 | -------------------------------------------------------------------------------- /supabase-wrappers/src/memctx.rs: -------------------------------------------------------------------------------- 1 | //! Helper functions for Wrappers Memory Context management 2 | //! 3 | 4 | use pgrx::{memcxt::PgMemoryContexts, pg_sys::AsPgCStr, prelude::*}; 5 | 6 | // Wrappers root memory context name 7 | const ROOT_MEMCTX_NAME: &str = "WrappersRootMemCtx"; 8 | 9 | // search memory context by name under specified MemoryContext 10 | unsafe fn find_memctx_under(name: &str, under: PgMemoryContexts) -> Option { 11 | let mut ctx = (*under.value()).firstchild; 12 | while !ctx.is_null() { 13 | if let Ok(ctx_name) = std::ffi::CStr::from_ptr((*ctx).name).to_str() { 14 | if ctx_name == name { 15 | return Some(PgMemoryContexts::For(ctx)); 16 | } 17 | } 18 | ctx = (*ctx).nextchild; 19 | } 20 | None 21 | } 22 | 23 | // search for root memory context under CacheMemoryContext, create a new one if not exists 24 | unsafe fn ensure_root_wrappers_memctx() -> PgMemoryContexts { 25 | find_memctx_under(ROOT_MEMCTX_NAME, PgMemoryContexts::CacheMemoryContext).unwrap_or_else(|| { 26 | let name = PgMemoryContexts::CacheMemoryContext.pstrdup(ROOT_MEMCTX_NAME); 27 | let ctx = pg_sys::AllocSetContextCreateExtended( 28 | PgMemoryContexts::CacheMemoryContext.value(), 29 | name, 30 | pg_sys::ALLOCSET_DEFAULT_MINSIZE as usize, 31 | pg_sys::ALLOCSET_DEFAULT_INITSIZE as usize, 32 | pg_sys::ALLOCSET_DEFAULT_MAXSIZE as usize, 33 | ); 34 | PgMemoryContexts::For(ctx) 35 | }) 36 | } 37 | 38 | // search Wrappers memory context by name, reset it if exists otherwise create a new one 39 | pub(super) unsafe fn refresh_wrappers_memctx(name: &str) -> PgMemoryContexts { 40 | let mut root = ensure_root_wrappers_memctx(); 41 | find_memctx_under(name, PgMemoryContexts::For(root.value())) 42 | .map(|mut ctx| { 43 | ctx.reset(); 44 | ctx 45 | }) 46 | .unwrap_or_else(|| { 47 | let name = root.switch_to(|_| name.as_pg_cstr()); 48 | let ctx = pg_sys::AllocSetContextCreateExtended( 49 | root.value(), 50 | name, 51 | pg_sys::ALLOCSET_DEFAULT_MINSIZE as usize, 52 | pg_sys::ALLOCSET_DEFAULT_INITSIZE as usize, 53 | pg_sys::ALLOCSET_DEFAULT_MAXSIZE as usize, 54 | ); 55 | PgMemoryContexts::For(ctx) 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /supabase-wrappers/src/polyfill.rs: -------------------------------------------------------------------------------- 1 | use pgrx::pg_sys::Datum; 2 | use pgrx::prelude::*; 3 | use std::os::raw::c_int; 4 | use std::slice; 5 | 6 | // ExecClearTuple 7 | pub(super) unsafe fn exec_clear_tuple(slot: *mut pg_sys::TupleTableSlot) { 8 | if let Some(clear) = (*(*slot).tts_ops).clear { 9 | clear(slot); 10 | } 11 | } 12 | 13 | // fetch one attribute of the slot's contents. 14 | pub(super) unsafe fn slot_getattr( 15 | slot: *mut pg_sys::TupleTableSlot, 16 | attnum: c_int, 17 | isnull: *mut bool, 18 | ) -> Datum { 19 | assert!(attnum > 0); 20 | 21 | if attnum > (*slot).tts_nvalid.into() { 22 | pg_sys::slot_getsomeattrs_int(slot, attnum); 23 | } 24 | 25 | let attnum = attnum as usize; 26 | let values = slice::from_raw_parts((*slot).tts_values, attnum); 27 | let nulls = slice::from_raw_parts((*slot).tts_isnull, attnum); 28 | 29 | *isnull = nulls[attnum - 1]; 30 | values[attnum - 1] 31 | } 32 | 33 | #[cfg(not(feature = "pg13"))] 34 | #[inline] 35 | pub(super) unsafe fn outer_plan_state(node: *mut pg_sys::PlanState) -> *mut pg_sys::PlanState { 36 | (*node).lefttree 37 | } 38 | -------------------------------------------------------------------------------- /supabase-wrappers/src/sort.rs: -------------------------------------------------------------------------------- 1 | use crate::interface::Sort; 2 | use pgrx::list::List; 3 | use pgrx::{is_a, pg_sys}; 4 | use std::ffi::c_void; 5 | use std::ffi::CStr; 6 | 7 | pub(crate) unsafe fn create_sort( 8 | pathkey: *mut pg_sys::PathKey, 9 | var: *mut pg_sys::Var, 10 | baserel_id: pg_sys::Oid, 11 | ) -> Option { 12 | let attno = (*var).varattno; 13 | let attname = pg_sys::get_attname(baserel_id, attno, true); 14 | if !attname.is_null() { 15 | let sort = Sort { 16 | field: CStr::from_ptr(attname).to_str().unwrap().to_owned(), 17 | field_no: attno as usize, 18 | reversed: (*pathkey).pk_strategy as u32 == pg_sys::BTGreaterStrategyNumber, 19 | nulls_first: (*pathkey).pk_nulls_first, 20 | ..Default::default() 21 | }; 22 | return Some(sort); 23 | } 24 | None 25 | } 26 | 27 | // extract sorts 28 | pub(crate) unsafe fn extract_sorts( 29 | root: *mut pg_sys::PlannerInfo, 30 | baserel: *mut pg_sys::RelOptInfo, 31 | baserel_id: pg_sys::Oid, 32 | ) -> Vec { 33 | pgrx::memcx::current_context(|mcx| { 34 | let mut ret = Vec::new(); 35 | 36 | if let Some(pathkeys) = 37 | List::<*mut c_void>::downcast_ptr_in_memcx((*root).query_pathkeys, mcx) 38 | { 39 | for pathkey in pathkeys.iter() { 40 | let ec = (*(*pathkey as *mut pg_sys::PathKey)).pk_eclass; 41 | 42 | if (*ec).ec_has_volatile { 43 | continue; 44 | } 45 | 46 | if let Some(ems) = List::<*mut c_void>::downcast_ptr_in_memcx((*ec).ec_members, mcx) 47 | { 48 | ems.iter() 49 | .find(|em| { 50 | pg_sys::bms_equal( 51 | (*(**em as *mut pg_sys::EquivalenceMember)).em_relids, 52 | (*baserel).relids, 53 | ) 54 | }) 55 | .and_then(|em| { 56 | let expr = (*(*em as *mut pg_sys::EquivalenceMember)).em_expr 57 | as *mut pg_sys::Node; 58 | 59 | if is_a(expr, pg_sys::NodeTag::T_Var) { 60 | let var = expr as *mut pg_sys::Var; 61 | let sort = create_sort(*pathkey as _, var, baserel_id); 62 | if let Some(sort) = sort { 63 | ret.push(sort); 64 | } 65 | } else if is_a(expr, pg_sys::NodeTag::T_RelabelType) { 66 | // ORDER BY clauses having a COLLATE option will be RelabelType 67 | let expr = expr as *mut pg_sys::RelabelType; 68 | let var = (*expr).arg as *mut pg_sys::Var; 69 | if is_a(var as *mut pg_sys::Node, pg_sys::NodeTag::T_Var) { 70 | let sort = create_sort(*pathkey as _, var, baserel_id); 71 | if let Some(mut sort) = sort { 72 | let coll_id = (*expr).resultcollid; 73 | sort.collate = Some( 74 | CStr::from_ptr(pg_sys::get_collation_name(coll_id)) 75 | .to_str() 76 | .unwrap() 77 | .to_owned(), 78 | ); 79 | ret.push(sort); 80 | } 81 | } 82 | } 83 | 84 | None::<()> 85 | }); 86 | } 87 | } 88 | } 89 | 90 | ret 91 | }) 92 | } 93 | -------------------------------------------------------------------------------- /wasm-wrappers/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-unknown-unknown" 3 | 4 | [profile.release] 5 | strip = "debuginfo" 6 | lto = true 7 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/.gitignore: -------------------------------------------------------------------------------- 1 | **/src/bindings.rs 2 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "cal_fdw", 4 | "calendly_fdw", 5 | "cfd1_fdw", 6 | "clerk_fdw", 7 | "helloworld_fdw", 8 | "hubspot_fdw", 9 | "notion_fdw", 10 | "orb_fdw", 11 | "paddle_fdw", 12 | "slack_fdw", 13 | "snowflake_fdw", 14 | ] 15 | resolver = "2" 16 | 17 | [workspace.package] 18 | edition = "2021" 19 | rust-version = "1.85" 20 | homepage = "https://github.com/supabase/wrappers/tree/main/wasm-wrappers" 21 | repository = "https://github.com/supabase/wrappers/tree/main/wasm-wrappers" 22 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/cal_fdw/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cal_fdw" 3 | version = "0.2.0" 4 | edition = { workspace = true } 5 | homepage = { workspace = true } 6 | rust-version = { workspace = true } 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | wit-bindgen-rt = "0.41.0" 13 | serde_json = "1.0" 14 | chrono = "0.4.38" 15 | 16 | [package.metadata.component] 17 | package = "supabase:cal-fdw" 18 | 19 | [package.metadata.component.dependencies] 20 | 21 | [package.metadata.component.target] 22 | path = "wit" 23 | 24 | [package.metadata.component.target.dependencies] 25 | "supabase:wrappers" = { path = "../../wit/v2" } 26 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/cal_fdw/wit/world.wit: -------------------------------------------------------------------------------- 1 | package supabase:cal-fdw@0.2.0; 2 | 3 | world cal { 4 | import supabase:wrappers/http@0.2.0; 5 | import supabase:wrappers/jwt@0.2.0; 6 | import supabase:wrappers/stats@0.2.0; 7 | import supabase:wrappers/time@0.2.0; 8 | import supabase:wrappers/utils@0.2.0; 9 | export supabase:wrappers/routines@0.2.0; 10 | } 11 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/calendly_fdw/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calendly_fdw" 3 | version = "0.2.0" 4 | edition = { workspace = true } 5 | homepage = { workspace = true } 6 | rust-version = { workspace = true } 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | wit-bindgen-rt = "0.41.0" 13 | serde_json = "1.0" 14 | chrono = "0.4.38" 15 | 16 | [package.metadata.component] 17 | package = "supabase:calendly-fdw" 18 | 19 | [package.metadata.component.dependencies] 20 | 21 | [package.metadata.component.target] 22 | path = "wit" 23 | 24 | [package.metadata.component.target.dependencies] 25 | "supabase:wrappers" = { path = "../../wit/v2" } 26 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/calendly_fdw/wit/world.wit: -------------------------------------------------------------------------------- 1 | package supabase:calendly-fdw@0.2.0; 2 | 3 | world calendly { 4 | import supabase:wrappers/http@0.2.0; 5 | import supabase:wrappers/jwt@0.2.0; 6 | import supabase:wrappers/stats@0.2.0; 7 | import supabase:wrappers/time@0.2.0; 8 | import supabase:wrappers/utils@0.2.0; 9 | export supabase:wrappers/routines@0.2.0; 10 | } 11 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/cfd1_fdw/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "cfd1_fdw" 7 | version = "0.2.0" 8 | dependencies = [ 9 | "serde_json", 10 | "wit-bindgen-rt", 11 | ] 12 | 13 | [[package]] 14 | name = "itoa" 15 | version = "1.0.14" 16 | source = "registry+https://github.com/rust-lang/crates.io-index" 17 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 18 | 19 | [[package]] 20 | name = "memchr" 21 | version = "2.7.4" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 24 | 25 | [[package]] 26 | name = "proc-macro2" 27 | version = "1.0.92" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 30 | dependencies = [ 31 | "unicode-ident", 32 | ] 33 | 34 | [[package]] 35 | name = "quote" 36 | version = "1.0.37" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 39 | dependencies = [ 40 | "proc-macro2", 41 | ] 42 | 43 | [[package]] 44 | name = "ryu" 45 | version = "1.0.18" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 48 | 49 | [[package]] 50 | name = "serde" 51 | version = "1.0.216" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" 54 | dependencies = [ 55 | "serde_derive", 56 | ] 57 | 58 | [[package]] 59 | name = "serde_derive" 60 | version = "1.0.216" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" 63 | dependencies = [ 64 | "proc-macro2", 65 | "quote", 66 | "syn", 67 | ] 68 | 69 | [[package]] 70 | name = "serde_json" 71 | version = "1.0.133" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" 74 | dependencies = [ 75 | "itoa", 76 | "memchr", 77 | "ryu", 78 | "serde", 79 | ] 80 | 81 | [[package]] 82 | name = "syn" 83 | version = "2.0.90" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" 86 | dependencies = [ 87 | "proc-macro2", 88 | "quote", 89 | "unicode-ident", 90 | ] 91 | 92 | [[package]] 93 | name = "unicode-ident" 94 | version = "1.0.14" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 97 | 98 | [[package]] 99 | name = "wit-bindgen-rt" 100 | version = "0.41.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "c4db52a11d4dfb0a59f194c064055794ee6564eb1ced88c25da2cf76e50c5621" 103 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/cfd1_fdw/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cfd1_fdw" 3 | version = "0.2.0" 4 | edition = { workspace = true } 5 | homepage = { workspace = true } 6 | rust-version = { workspace = true } 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | wit-bindgen-rt = "0.41.0" 13 | serde_json = "1.0" 14 | 15 | [package.metadata.component] 16 | package = "supabase:cfd1-fdw" 17 | 18 | [package.metadata.component.dependencies] 19 | 20 | [package.metadata.component.target] 21 | path = "wit" 22 | 23 | [package.metadata.component.target.dependencies] 24 | "supabase:wrappers" = { path = "../../wit/v2" } 25 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/cfd1_fdw/wit/world.wit: -------------------------------------------------------------------------------- 1 | package supabase:cfd1-fdw@0.2.0; 2 | 3 | world cfd1 { 4 | import supabase:wrappers/http@0.2.0; 5 | import supabase:wrappers/jwt@0.2.0; 6 | import supabase:wrappers/stats@0.2.0; 7 | import supabase:wrappers/time@0.2.0; 8 | import supabase:wrappers/utils@0.2.0; 9 | export supabase:wrappers/routines@0.2.0; 10 | } 11 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/clerk_fdw/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "clerk_fdw" 7 | version = "0.2.0" 8 | dependencies = [ 9 | "serde_json", 10 | "wit-bindgen-rt", 11 | ] 12 | 13 | [[package]] 14 | name = "itoa" 15 | version = "1.0.14" 16 | source = "registry+https://github.com/rust-lang/crates.io-index" 17 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 18 | 19 | [[package]] 20 | name = "memchr" 21 | version = "2.7.4" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 24 | 25 | [[package]] 26 | name = "proc-macro2" 27 | version = "1.0.93" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" 30 | dependencies = [ 31 | "unicode-ident", 32 | ] 33 | 34 | [[package]] 35 | name = "quote" 36 | version = "1.0.38" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 39 | dependencies = [ 40 | "proc-macro2", 41 | ] 42 | 43 | [[package]] 44 | name = "ryu" 45 | version = "1.0.18" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 48 | 49 | [[package]] 50 | name = "serde" 51 | version = "1.0.217" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" 54 | dependencies = [ 55 | "serde_derive", 56 | ] 57 | 58 | [[package]] 59 | name = "serde_derive" 60 | version = "1.0.217" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" 63 | dependencies = [ 64 | "proc-macro2", 65 | "quote", 66 | "syn", 67 | ] 68 | 69 | [[package]] 70 | name = "serde_json" 71 | version = "1.0.137" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" 74 | dependencies = [ 75 | "itoa", 76 | "memchr", 77 | "ryu", 78 | "serde", 79 | ] 80 | 81 | [[package]] 82 | name = "syn" 83 | version = "2.0.96" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" 86 | dependencies = [ 87 | "proc-macro2", 88 | "quote", 89 | "unicode-ident", 90 | ] 91 | 92 | [[package]] 93 | name = "unicode-ident" 94 | version = "1.0.14" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 97 | 98 | [[package]] 99 | name = "wit-bindgen-rt" 100 | version = "0.41.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "c4db52a11d4dfb0a59f194c064055794ee6564eb1ced88c25da2cf76e50c5621" 103 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/clerk_fdw/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "clerk_fdw" 3 | version = "0.2.0" 4 | edition = { workspace = true } 5 | homepage = { workspace = true } 6 | rust-version = { workspace = true } 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | wit-bindgen-rt = "0.41.0" 13 | serde_json = "1.0" 14 | 15 | [package.metadata.component] 16 | package = "supabase:clerk-fdw" 17 | 18 | [package.metadata.component.dependencies] 19 | 20 | [package.metadata.component.target] 21 | path = "wit" 22 | 23 | [package.metadata.component.target.dependencies] 24 | "supabase:wrappers" = { path = "../../wit/v2" } 25 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/clerk_fdw/wit/world.wit: -------------------------------------------------------------------------------- 1 | package supabase:clerk-fdw@0.2.0; 2 | 3 | world clerk { 4 | import supabase:wrappers/http@0.2.0; 5 | import supabase:wrappers/jwt@0.2.0; 6 | import supabase:wrappers/stats@0.2.0; 7 | import supabase:wrappers/time@0.2.0; 8 | import supabase:wrappers/utils@0.2.0; 9 | export supabase:wrappers/routines@0.2.0; 10 | } 11 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/helloworld_fdw/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-unknown-unknown" 3 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/helloworld_fdw/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.check.overrideCommand": [ 3 | "cargo", 4 | "component", 5 | "check", 6 | "--workspace", 7 | "--all-targets", 8 | "--message-format=json" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/helloworld_fdw/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "helloworld_fdw" 7 | version = "0.2.0" 8 | dependencies = [ 9 | "wit-bindgen-rt", 10 | ] 11 | 12 | [[package]] 13 | name = "wit-bindgen-rt" 14 | version = "0.41.0" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "c4db52a11d4dfb0a59f194c064055794ee6564eb1ced88c25da2cf76e50c5621" 17 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/helloworld_fdw/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "helloworld_fdw" 3 | version = "0.2.0" 4 | edition = { workspace = true } 5 | homepage = { workspace = true } 6 | rust-version = { workspace = true } 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | wit-bindgen-rt = "0.41.0" 13 | 14 | [package.metadata.component] 15 | package = "supabase:helloworld-fdw" 16 | 17 | [package.metadata.component.dependencies] 18 | 19 | [package.metadata.component.target] 20 | path = "wit" 21 | 22 | [package.metadata.component.target.dependencies] 23 | "supabase:wrappers" = { path = "../../wit/v2" } 24 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/helloworld_fdw/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[allow(warnings)] 2 | mod bindings; 3 | 4 | use bindings::{ 5 | exports::supabase::wrappers::routines::Guest, 6 | supabase::wrappers::types::{Cell, Context, FdwError, FdwResult, ImportForeignSchemaStmt, Row}, 7 | }; 8 | 9 | #[derive(Debug, Default)] 10 | struct HelloWorldFdw { 11 | // row counter 12 | row_cnt: i32, 13 | } 14 | 15 | static mut INSTANCE: *mut HelloWorldFdw = std::ptr::null_mut::(); 16 | 17 | impl HelloWorldFdw { 18 | fn init() { 19 | let instance = Self::default(); 20 | unsafe { 21 | INSTANCE = Box::leak(Box::new(instance)); 22 | } 23 | } 24 | 25 | fn this_mut() -> &'static mut Self { 26 | unsafe { &mut (*INSTANCE) } 27 | } 28 | } 29 | 30 | impl Guest for HelloWorldFdw { 31 | fn host_version_requirement() -> String { 32 | // semver ref: https://docs.rs/semver/latest/semver/enum.Op.html 33 | "^0.1.0".to_string() 34 | } 35 | 36 | fn init(_ctx: &Context) -> FdwResult { 37 | Self::init(); 38 | Ok(()) 39 | } 40 | 41 | fn begin_scan(_ctx: &Context) -> FdwResult { 42 | let this = Self::this_mut(); 43 | 44 | // reset row counter 45 | this.row_cnt = 0; 46 | 47 | Ok(()) 48 | } 49 | 50 | fn iter_scan(ctx: &Context, row: &Row) -> Result, FdwError> { 51 | let this = Self::this_mut(); 52 | 53 | if this.row_cnt >= 1 { 54 | // return 'None' to stop data scan 55 | return Ok(None); 56 | } 57 | 58 | for tgt_col in &ctx.get_columns() { 59 | match tgt_col.name().as_str() { 60 | "id" => { 61 | row.push(Some(&Cell::I64(42))); 62 | } 63 | "col" => { 64 | row.push(Some(&Cell::String("Hello world".to_string()))); 65 | } 66 | _ => unreachable!(), 67 | } 68 | } 69 | 70 | this.row_cnt += 1; 71 | 72 | // return Some(_) to Postgres and continue data scan 73 | Ok(Some(0)) 74 | } 75 | 76 | fn re_scan(_ctx: &Context) -> FdwResult { 77 | // reset row counter 78 | let this = Self::this_mut(); 79 | this.row_cnt = 0; 80 | Ok(()) 81 | } 82 | 83 | fn end_scan(_ctx: &Context) -> FdwResult { 84 | Ok(()) 85 | } 86 | 87 | fn begin_modify(_ctx: &Context) -> FdwResult { 88 | Err("modify on foreign table is not supported".to_owned()) 89 | } 90 | 91 | fn insert(_ctx: &Context, _row: &Row) -> FdwResult { 92 | Ok(()) 93 | } 94 | 95 | fn update(_ctx: &Context, _rowid: Cell, _row: &Row) -> FdwResult { 96 | Ok(()) 97 | } 98 | 99 | fn delete(_ctx: &Context, _rowid: Cell) -> FdwResult { 100 | Ok(()) 101 | } 102 | 103 | fn end_modify(_ctx: &Context) -> FdwResult { 104 | Ok(()) 105 | } 106 | 107 | fn import_foreign_schema( 108 | _ctx: &Context, 109 | stmt: ImportForeignSchemaStmt, 110 | ) -> Result, FdwError> { 111 | let ret = vec![format!( 112 | r#"create foreign table if not exists helloworld ( 113 | id bigint, 114 | col text 115 | ) 116 | server {} options ( 117 | foo 'bar' 118 | )"#, 119 | stmt.server_name, 120 | )]; 121 | Ok(ret) 122 | } 123 | } 124 | 125 | bindings::export!(HelloWorldFdw with_types_in bindings); 126 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/helloworld_fdw/wit/world.wit: -------------------------------------------------------------------------------- 1 | package supabase:helloworld-fdw@0.2.0; 2 | 3 | world helloworld { 4 | export supabase:wrappers/routines@0.2.0; 5 | } 6 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/hubspot_fdw/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "hubspot_fdw" 7 | version = "0.2.0" 8 | dependencies = [ 9 | "serde_json", 10 | "wit-bindgen-rt", 11 | ] 12 | 13 | [[package]] 14 | name = "itoa" 15 | version = "1.0.14" 16 | source = "registry+https://github.com/rust-lang/crates.io-index" 17 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 18 | 19 | [[package]] 20 | name = "memchr" 21 | version = "2.7.4" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 24 | 25 | [[package]] 26 | name = "proc-macro2" 27 | version = "1.0.93" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" 30 | dependencies = [ 31 | "unicode-ident", 32 | ] 33 | 34 | [[package]] 35 | name = "quote" 36 | version = "1.0.38" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 39 | dependencies = [ 40 | "proc-macro2", 41 | ] 42 | 43 | [[package]] 44 | name = "ryu" 45 | version = "1.0.19" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" 48 | 49 | [[package]] 50 | name = "serde" 51 | version = "1.0.218" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" 54 | dependencies = [ 55 | "serde_derive", 56 | ] 57 | 58 | [[package]] 59 | name = "serde_derive" 60 | version = "1.0.218" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" 63 | dependencies = [ 64 | "proc-macro2", 65 | "quote", 66 | "syn", 67 | ] 68 | 69 | [[package]] 70 | name = "serde_json" 71 | version = "1.0.139" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" 74 | dependencies = [ 75 | "itoa", 76 | "memchr", 77 | "ryu", 78 | "serde", 79 | ] 80 | 81 | [[package]] 82 | name = "syn" 83 | version = "2.0.98" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" 86 | dependencies = [ 87 | "proc-macro2", 88 | "quote", 89 | "unicode-ident", 90 | ] 91 | 92 | [[package]] 93 | name = "unicode-ident" 94 | version = "1.0.17" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" 97 | 98 | [[package]] 99 | name = "wit-bindgen-rt" 100 | version = "0.41.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "c4db52a11d4dfb0a59f194c064055794ee6564eb1ced88c25da2cf76e50c5621" 103 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/hubspot_fdw/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hubspot_fdw" 3 | version = "0.2.0" 4 | edition = { workspace = true } 5 | homepage = { workspace = true } 6 | rust-version = { workspace = true } 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | wit-bindgen-rt = "0.41.0" 13 | serde_json = "1.0" 14 | 15 | [package.metadata.component] 16 | package = "supabase:hubspot-fdw" 17 | 18 | [package.metadata.component.dependencies] 19 | 20 | [package.metadata.component.target] 21 | path = "wit" 22 | 23 | [package.metadata.component.target.dependencies] 24 | "supabase:wrappers" = { path = "../../wit/v2" } 25 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/hubspot_fdw/wit/world.wit: -------------------------------------------------------------------------------- 1 | package supabase:hubspot-fdw@0.2.0; 2 | 3 | world hubspot { 4 | import supabase:wrappers/http@0.2.0; 5 | import supabase:wrappers/jwt@0.2.0; 6 | import supabase:wrappers/stats@0.2.0; 7 | import supabase:wrappers/time@0.2.0; 8 | import supabase:wrappers/utils@0.2.0; 9 | export supabase:wrappers/routines@0.2.0; 10 | } 11 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/notion_fdw/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notion_fdw" 3 | version = "0.2.0" 4 | edition = { workspace = true } 5 | homepage = { workspace = true } 6 | rust-version = { workspace = true } 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | wit-bindgen-rt = "0.41.0" 13 | serde_json = "1.0" 14 | chrono = "0.4.38" 15 | 16 | [package.metadata.component] 17 | package = "supabase:notion-fdw" 18 | 19 | [package.metadata.component.dependencies] 20 | 21 | [package.metadata.component.target] 22 | path = "wit" 23 | 24 | [package.metadata.component.target.dependencies] 25 | "supabase:wrappers" = { path = "../../wit/v2" } 26 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/notion_fdw/wit/world.wit: -------------------------------------------------------------------------------- 1 | package supabase:notion-fdw@0.2.0; 2 | 3 | world notion { 4 | import supabase:wrappers/http@0.2.0; 5 | import supabase:wrappers/jwt@0.2.0; 6 | import supabase:wrappers/stats@0.2.0; 7 | import supabase:wrappers/time@0.2.0; 8 | import supabase:wrappers/utils@0.2.0; 9 | export supabase:wrappers/routines@0.2.0; 10 | } 11 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/orb_fdw/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "itoa" 7 | version = "1.0.14" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 10 | 11 | [[package]] 12 | name = "memchr" 13 | version = "2.7.4" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 16 | 17 | [[package]] 18 | name = "orb_fdw" 19 | version = "0.2.0" 20 | dependencies = [ 21 | "serde_json", 22 | "wit-bindgen-rt", 23 | ] 24 | 25 | [[package]] 26 | name = "proc-macro2" 27 | version = "1.0.93" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" 30 | dependencies = [ 31 | "unicode-ident", 32 | ] 33 | 34 | [[package]] 35 | name = "quote" 36 | version = "1.0.38" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 39 | dependencies = [ 40 | "proc-macro2", 41 | ] 42 | 43 | [[package]] 44 | name = "ryu" 45 | version = "1.0.19" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" 48 | 49 | [[package]] 50 | name = "serde" 51 | version = "1.0.217" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" 54 | dependencies = [ 55 | "serde_derive", 56 | ] 57 | 58 | [[package]] 59 | name = "serde_derive" 60 | version = "1.0.217" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" 63 | dependencies = [ 64 | "proc-macro2", 65 | "quote", 66 | "syn", 67 | ] 68 | 69 | [[package]] 70 | name = "serde_json" 71 | version = "1.0.138" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" 74 | dependencies = [ 75 | "itoa", 76 | "memchr", 77 | "ryu", 78 | "serde", 79 | ] 80 | 81 | [[package]] 82 | name = "syn" 83 | version = "2.0.98" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" 86 | dependencies = [ 87 | "proc-macro2", 88 | "quote", 89 | "unicode-ident", 90 | ] 91 | 92 | [[package]] 93 | name = "unicode-ident" 94 | version = "1.0.16" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" 97 | 98 | [[package]] 99 | name = "wit-bindgen-rt" 100 | version = "0.41.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "c4db52a11d4dfb0a59f194c064055794ee6564eb1ced88c25da2cf76e50c5621" 103 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/orb_fdw/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "orb_fdw" 3 | version = "0.2.0" 4 | edition = { workspace = true } 5 | homepage = { workspace = true } 6 | rust-version = { workspace = true } 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | wit-bindgen-rt = "0.41.0" 13 | serde_json = "1.0" 14 | 15 | [package.metadata.component] 16 | package = "supabase:orb-fdw" 17 | 18 | [package.metadata.component.dependencies] 19 | 20 | [package.metadata.component.target] 21 | path = "wit" 22 | 23 | [package.metadata.component.target.dependencies] 24 | "supabase:wrappers" = { path = "../../wit/v2" } 25 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/orb_fdw/wit/world.wit: -------------------------------------------------------------------------------- 1 | package supabase:orb-fdw@0.2.0; 2 | 3 | world orb { 4 | import supabase:wrappers/http@0.2.0; 5 | import supabase:wrappers/jwt@0.2.0; 6 | import supabase:wrappers/stats@0.2.0; 7 | import supabase:wrappers/time@0.2.0; 8 | import supabase:wrappers/utils@0.2.0; 9 | export supabase:wrappers/routines@0.2.0; 10 | } 11 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/paddle_fdw/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "paddle_fdw" 3 | version = "0.2.0" 4 | edition = { workspace = true } 5 | homepage = { workspace = true } 6 | rust-version = { workspace = true } 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | wit-bindgen-rt = "0.41.0" 13 | serde_json = "1.0" 14 | chrono = "0.4.38" 15 | 16 | [package.metadata.component] 17 | package = "supabase:paddle-fdw" 18 | 19 | [package.metadata.component.dependencies] 20 | 21 | [package.metadata.component.target] 22 | path = "wit" 23 | 24 | [package.metadata.component.target.dependencies] 25 | "supabase:wrappers" = { path = "../../wit/v2" } 26 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/paddle_fdw/wit/world.wit: -------------------------------------------------------------------------------- 1 | package supabase:paddle-fdw@0.2.0; 2 | 3 | world paddle { 4 | import supabase:wrappers/http@0.2.0; 5 | import supabase:wrappers/jwt@0.2.0; 6 | import supabase:wrappers/stats@0.2.0; 7 | import supabase:wrappers/time@0.2.0; 8 | import supabase:wrappers/utils@0.2.0; 9 | export supabase:wrappers/routines@0.2.0; 10 | } 11 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/slack_fdw/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [dependencies] 2 | litemap = { version = "=0.6.0" } 3 | zerofrom = { version = "=0.1.5" } 4 | 5 | [build] 6 | target = "wasm32-wasip1" -------------------------------------------------------------------------------- /wasm-wrappers/fdw/slack_fdw/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "itoa" 7 | version = "1.0.15" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 10 | 11 | [[package]] 12 | name = "memchr" 13 | version = "2.7.4" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 16 | 17 | [[package]] 18 | name = "proc-macro2" 19 | version = "1.0.94" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 22 | dependencies = [ 23 | "unicode-ident", 24 | ] 25 | 26 | [[package]] 27 | name = "quote" 28 | version = "1.0.40" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 31 | dependencies = [ 32 | "proc-macro2", 33 | ] 34 | 35 | [[package]] 36 | name = "ryu" 37 | version = "1.0.20" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 40 | 41 | [[package]] 42 | name = "serde" 43 | version = "1.0.219" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 46 | dependencies = [ 47 | "serde_derive", 48 | ] 49 | 50 | [[package]] 51 | name = "serde_derive" 52 | version = "1.0.219" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 55 | dependencies = [ 56 | "proc-macro2", 57 | "quote", 58 | "syn", 59 | ] 60 | 61 | [[package]] 62 | name = "serde_json" 63 | version = "1.0.140" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 66 | dependencies = [ 67 | "itoa", 68 | "memchr", 69 | "ryu", 70 | "serde", 71 | ] 72 | 73 | [[package]] 74 | name = "slack_fdw" 75 | version = "0.2.0" 76 | dependencies = [ 77 | "serde", 78 | "serde_json", 79 | "wit-bindgen-rt", 80 | ] 81 | 82 | [[package]] 83 | name = "syn" 84 | version = "2.0.100" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 87 | dependencies = [ 88 | "proc-macro2", 89 | "quote", 90 | "unicode-ident", 91 | ] 92 | 93 | [[package]] 94 | name = "unicode-ident" 95 | version = "1.0.18" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 98 | 99 | [[package]] 100 | name = "wit-bindgen-rt" 101 | version = "0.41.0" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "c4db52a11d4dfb0a59f194c064055794ee6564eb1ced88c25da2cf76e50c5621" 104 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/slack_fdw/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "slack_fdw" 3 | version = "0.2.0" 4 | edition = { workspace = true } 5 | homepage = { workspace = true } 6 | rust-version = { workspace = true } 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | wit-bindgen-rt = "0.41.0" 13 | serde = { version = "1.0", features = ["derive"] } 14 | serde_json = "1.0" 15 | 16 | [package.metadata.component] 17 | package = "supabase:slack-fdw" 18 | 19 | [package.metadata.component.dependencies] 20 | 21 | [package.metadata.component.target] 22 | path = "wit" 23 | 24 | [package.metadata.component.target.dependencies] 25 | "supabase:wrappers" = { path = "../../wit/v2" } 26 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/slack_fdw/src/api.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | #[cfg(test)] 4 | pub fn apply_conditions_test( 5 | endpoint: &str, 6 | conditions: &[(String, String, String)], // (column, operator, value) 7 | ) -> HashMap { 8 | let mut params = HashMap::new(); 9 | 10 | for (column, operator, value) in conditions { 11 | match endpoint { 12 | "conversations.history" => { 13 | if column == "channel_id" && operator == "eq" { 14 | params.insert("channel".to_string(), value.clone()); 15 | } else if column == "oldest" && operator == "ge" { 16 | params.insert("oldest".to_string(), value.clone()); 17 | } else if column == "latest" && operator == "le" { 18 | params.insert("latest".to_string(), value.clone()); 19 | } 20 | } 21 | "users.list" => { 22 | // Users API doesn't support many filters 23 | } 24 | "conversations.list" => { 25 | if column == "types" && operator == "eq" { 26 | params.insert("types".to_string(), value.clone()); 27 | } 28 | } 29 | "files.list" => { 30 | if column == "channel_id" && operator == "eq" { 31 | params.insert("channel".to_string(), value.clone()); 32 | } else if column == "user_id" && operator == "eq" { 33 | params.insert("user".to_string(), value.clone()); 34 | } else if column == "ts_from" && operator == "ge" { 35 | params.insert("ts_from".to_string(), value.clone()); 36 | } else if column == "ts_to" && operator == "le" { 37 | params.insert("ts_to".to_string(), value.clone()); 38 | } 39 | } 40 | _ => {} 41 | } 42 | } 43 | 44 | params 45 | } 46 | 47 | #[cfg(test)] 48 | mod tests { 49 | use super::*; 50 | 51 | #[test] 52 | fn test_apply_conditions_conversations_history() { 53 | let conditions = vec![ 54 | ( 55 | "channel_id".to_string(), 56 | "eq".to_string(), 57 | "C12345".to_string(), 58 | ), 59 | ( 60 | "oldest".to_string(), 61 | "ge".to_string(), 62 | "1234567890.123456".to_string(), 63 | ), 64 | ]; 65 | 66 | let params = apply_conditions_test("conversations.history", &conditions); 67 | 68 | assert_eq!(params.get("channel"), Some(&"C12345".to_string())); 69 | assert_eq!(params.get("oldest"), Some(&"1234567890.123456".to_string())); 70 | } 71 | 72 | #[test] 73 | fn test_apply_conditions_files_list() { 74 | let conditions = vec![ 75 | ( 76 | "channel_id".to_string(), 77 | "eq".to_string(), 78 | "C12345".to_string(), 79 | ), 80 | ( 81 | "user_id".to_string(), 82 | "eq".to_string(), 83 | "U12345".to_string(), 84 | ), 85 | ( 86 | "ts_from".to_string(), 87 | "ge".to_string(), 88 | "1234567890.123456".to_string(), 89 | ), 90 | ]; 91 | 92 | let params = apply_conditions_test("files.list", &conditions); 93 | 94 | assert_eq!(params.get("channel"), Some(&"C12345".to_string())); 95 | assert_eq!(params.get("user"), Some(&"U12345".to_string())); 96 | assert_eq!( 97 | params.get("ts_from"), 98 | Some(&"1234567890.123456".to_string()) 99 | ); 100 | } 101 | 102 | #[test] 103 | fn test_apply_conditions_conversations_list() { 104 | let conditions = vec![( 105 | "types".to_string(), 106 | "eq".to_string(), 107 | "public_channel".to_string(), 108 | )]; 109 | 110 | let params = apply_conditions_test("conversations.list", &conditions); 111 | 112 | assert_eq!(params.get("types"), Some(&"public_channel".to_string())); 113 | } 114 | 115 | #[test] 116 | fn test_apply_conditions_unsupported_endpoint() { 117 | let conditions = vec![("test".to_string(), "eq".to_string(), "test".to_string())]; 118 | 119 | let params = apply_conditions_test("unsupported.endpoint", &conditions); 120 | 121 | // Should return empty params for unsupported endpoint 122 | assert!(params.is_empty()); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/slack_fdw/wit/world.wit: -------------------------------------------------------------------------------- 1 | package supabase:slack-fdw@0.2.0; 2 | 3 | world slack { 4 | import supabase:wrappers/http@0.2.0; 5 | import supabase:wrappers/jwt@0.2.0; 6 | import supabase:wrappers/stats@0.2.0; 7 | import supabase:wrappers/time@0.2.0; 8 | import supabase:wrappers/utils@0.2.0; 9 | export supabase:wrappers/routines@0.2.0; 10 | } 11 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/snowflake_fdw/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "itoa" 7 | version = "1.0.11" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 10 | 11 | [[package]] 12 | name = "proc-macro2" 13 | version = "1.0.81" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" 16 | dependencies = [ 17 | "unicode-ident", 18 | ] 19 | 20 | [[package]] 21 | name = "quote" 22 | version = "1.0.36" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 25 | dependencies = [ 26 | "proc-macro2", 27 | ] 28 | 29 | [[package]] 30 | name = "ryu" 31 | version = "1.0.17" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" 34 | 35 | [[package]] 36 | name = "serde" 37 | version = "1.0.199" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" 40 | dependencies = [ 41 | "serde_derive", 42 | ] 43 | 44 | [[package]] 45 | name = "serde_derive" 46 | version = "1.0.199" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" 49 | dependencies = [ 50 | "proc-macro2", 51 | "quote", 52 | "syn", 53 | ] 54 | 55 | [[package]] 56 | name = "serde_json" 57 | version = "1.0.116" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" 60 | dependencies = [ 61 | "itoa", 62 | "ryu", 63 | "serde", 64 | ] 65 | 66 | [[package]] 67 | name = "snowflake_fdw" 68 | version = "0.2.0" 69 | dependencies = [ 70 | "serde_json", 71 | "wit-bindgen-rt", 72 | ] 73 | 74 | [[package]] 75 | name = "syn" 76 | version = "2.0.60" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" 79 | dependencies = [ 80 | "proc-macro2", 81 | "quote", 82 | "unicode-ident", 83 | ] 84 | 85 | [[package]] 86 | name = "unicode-ident" 87 | version = "1.0.12" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 90 | 91 | [[package]] 92 | name = "wit-bindgen-rt" 93 | version = "0.41.0" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "c4db52a11d4dfb0a59f194c064055794ee6564eb1ced88c25da2cf76e50c5621" 96 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/snowflake_fdw/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "snowflake_fdw" 3 | version = "0.2.0" 4 | edition = { workspace = true } 5 | homepage = { workspace = true } 6 | rust-version = { workspace = true } 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | wit-bindgen-rt = "0.41.0" 13 | serde_json = "1.0" 14 | 15 | [package.metadata.component] 16 | package = "supabase:snowflake-fdw" 17 | 18 | [package.metadata.component.dependencies] 19 | 20 | [package.metadata.component.target] 21 | path = "wit" 22 | 23 | [package.metadata.component.target.dependencies] 24 | "supabase:wrappers" = { path = "../../wit/v2" } 25 | -------------------------------------------------------------------------------- /wasm-wrappers/fdw/snowflake_fdw/wit/world.wit: -------------------------------------------------------------------------------- 1 | package supabase:snowflake-fdw@0.2.0; 2 | 3 | world snowflake { 4 | import supabase:wrappers/http@0.2.0; 5 | import supabase:wrappers/jwt@0.2.0; 6 | import supabase:wrappers/stats@0.2.0; 7 | import supabase:wrappers/time@0.2.0; 8 | import supabase:wrappers/utils@0.2.0; 9 | export supabase:wrappers/routines@0.2.0; 10 | } 11 | -------------------------------------------------------------------------------- /wasm-wrappers/wit/v1/http.wit: -------------------------------------------------------------------------------- 1 | interface http { 2 | type headers = list>; 3 | 4 | variant method { 5 | get, 6 | post, 7 | put, 8 | patch, 9 | delete, 10 | } 11 | 12 | record request { 13 | method: method, 14 | url: string, 15 | headers: headers, 16 | body: string, 17 | } 18 | 19 | record response { 20 | url: string, 21 | status-code: u16, 22 | headers: headers, 23 | body: string, 24 | } 25 | 26 | type http-error = string; 27 | type http-result = result; 28 | 29 | get: func(req: request) -> http-result; 30 | post: func(req: request) -> http-result; 31 | put: func(req: request) -> http-result; 32 | patch: func(req: request) -> http-result; 33 | delete: func(req: request) -> http-result; 34 | 35 | error-for-status: func(resp: response) -> result<_, http-error>; 36 | } 37 | -------------------------------------------------------------------------------- /wasm-wrappers/wit/v1/jwt.wit: -------------------------------------------------------------------------------- 1 | interface jwt { 2 | type jwt-error = string; 3 | type jwt-result = result; 4 | 5 | encode: func( 6 | payload: list>, 7 | algo: string, 8 | key: string, 9 | ttl-hours: u32 10 | ) -> jwt-result; 11 | } 12 | -------------------------------------------------------------------------------- /wasm-wrappers/wit/v1/routines.wit: -------------------------------------------------------------------------------- 1 | interface routines { 2 | use types.{ 3 | cell, row, context, fdw-error, fdw-result, 4 | }; 5 | 6 | // ---------------------------------------------- 7 | // foreign data wrapper interface functions 8 | // ---------------------------------------------- 9 | 10 | // define host version requirement, e.g, "^1.2.3" 11 | host-version-requirement: func() -> string; 12 | 13 | // fdw initialization 14 | init: func(ctx: borrow) -> fdw-result; 15 | 16 | // data scan 17 | begin-scan: func(ctx: borrow) -> fdw-result; 18 | iter-scan: func( 19 | ctx: borrow, 20 | row: borrow, 21 | ) -> result, fdw-error>; 22 | re-scan: func(ctx: borrow) -> fdw-result; 23 | end-scan: func(ctx: borrow) -> fdw-result; 24 | 25 | // data modify 26 | begin-modify: func(ctx: borrow) -> fdw-result; 27 | insert: func(ctx: borrow, row: borrow) -> fdw-result; 28 | update: func( 29 | ctx: borrow, 30 | rowid: cell, 31 | new-row: borrow, 32 | ) -> fdw-result; 33 | delete: func(ctx: borrow, rowid: cell) -> fdw-result; 34 | end-modify: func(ctx: borrow) -> fdw-result; 35 | } 36 | -------------------------------------------------------------------------------- /wasm-wrappers/wit/v1/stats.wit: -------------------------------------------------------------------------------- 1 | interface stats { 2 | type metadata = option; 3 | 4 | variant metric { 5 | create-times, 6 | rows-in, 7 | rows-out, 8 | bytes-in, 9 | bytes-out, 10 | } 11 | 12 | inc-stats: func(fdw-name: string, metric: metric, inc: s64); 13 | get-metadata: func(fdw-name: string) -> metadata; 14 | set-metadata: func(fdw-name: string, metadata: metadata); 15 | } 16 | -------------------------------------------------------------------------------- /wasm-wrappers/wit/v1/time.wit: -------------------------------------------------------------------------------- 1 | interface time { 2 | type time-error = string; 3 | type time-result = result; 4 | 5 | // get seconds since Unix epoch 6 | epoch-secs: func() -> s64; 7 | 8 | // parse RFC3339 string to microseconds since Unix epoch 9 | parse-from-rfc3339: func(s: string) -> time-result; 10 | 11 | // parse string from an user-specified format to microseconds since Unix epoch 12 | parse-from-str: func(s: string, fmt: string) -> time-result; 13 | 14 | // convert microseconds since Unix epoch to RFC3339 string 15 | epoch-ms-to-rfc3339: func(msecs: s64) -> result; 16 | 17 | // sleep for a while 18 | sleep: func(millis: u64); 19 | } 20 | -------------------------------------------------------------------------------- /wasm-wrappers/wit/v1/types.wit: -------------------------------------------------------------------------------- 1 | interface types { 2 | variant type-oid { 3 | %bool, 4 | i8, 5 | i16, 6 | %f32, 7 | i32, 8 | %f64, 9 | i64, 10 | numeric, 11 | %string, 12 | date, 13 | timestamp, 14 | timestamptz, 15 | json, 16 | } 17 | 18 | variant cell { 19 | %bool(bool), 20 | i8(s8), 21 | i16(s16), 22 | %f32(f32), 23 | i32(s32), 24 | %f64(f64), 25 | i64(s64), 26 | numeric(f64), 27 | %string(string), 28 | // seconds since Unix epoch 29 | date(s64), 30 | // microseconds since Unix epoch 31 | timestamp(s64), 32 | timestamptz(s64), 33 | json(string), 34 | } 35 | 36 | resource row { 37 | constructor(); 38 | 39 | cols: func() -> list; 40 | cells: func() -> list>; 41 | 42 | push: func(cell: option); 43 | } 44 | 45 | resource column { 46 | constructor(index: u32); 47 | 48 | name: func() -> string; 49 | num: func() -> u32; 50 | type-oid: func() -> type-oid; 51 | } 52 | 53 | variant value { 54 | cell(cell), 55 | array(list), 56 | } 57 | 58 | record param { 59 | id: u32, 60 | type-oid: u32, 61 | } 62 | 63 | resource qual { 64 | constructor(index: u32); 65 | 66 | field: func() -> string; 67 | operator: func() -> string; 68 | value: func() -> value; 69 | use-or: func() -> bool; 70 | param: func() -> option; 71 | 72 | deparse: func() -> string; 73 | } 74 | 75 | resource sort { 76 | constructor(index: u32); 77 | 78 | field: func() -> string; 79 | field-no: func() -> u32; 80 | reversed: func() -> bool; 81 | nulls-first: func() -> bool; 82 | collate: func() -> option; 83 | 84 | deparse: func() -> string; 85 | deparse-with-collate: func() -> string; 86 | } 87 | 88 | resource limit { 89 | constructor(); 90 | 91 | count: func() -> s64; 92 | offset: func() -> s64; 93 | 94 | deparse: func() -> string; 95 | } 96 | 97 | variant options-type { 98 | server, 99 | table, 100 | } 101 | 102 | resource options { 103 | constructor(options-type: options-type); 104 | 105 | get: func(key: string) -> option; 106 | require: func(key: string) -> result; 107 | require-or: func(key: string, default: string) -> string; 108 | } 109 | 110 | resource context { 111 | constructor(); 112 | 113 | get-options: func(options-type: options-type) -> options; 114 | get-quals: func() -> list; 115 | get-columns: func() -> list; 116 | get-sorts: func() -> list; 117 | get-limit: func() -> option; 118 | } 119 | 120 | type fdw-error = string; 121 | type fdw-result = result<_, fdw-error>; 122 | } 123 | 124 | 125 | -------------------------------------------------------------------------------- /wasm-wrappers/wit/v1/utils.wit: -------------------------------------------------------------------------------- 1 | interface utils { 2 | use types.{cell}; 3 | 4 | report-info: func(msg: string); 5 | report-notice: func(msg: string); 6 | report-warning: func(msg: string); 7 | report-error: func(msg: string); 8 | 9 | cell-to-string: func(cell: option) -> string; 10 | get-vault-secret: func(secret-id: string) -> option; 11 | } 12 | -------------------------------------------------------------------------------- /wasm-wrappers/wit/v1/world.wit: -------------------------------------------------------------------------------- 1 | package supabase:wrappers@0.1.0; 2 | 3 | world wrappers { 4 | import http; 5 | import jwt; 6 | import stats; 7 | import time; 8 | import types; 9 | import utils; 10 | export routines; 11 | } 12 | -------------------------------------------------------------------------------- /wasm-wrappers/wit/v2/http.wit: -------------------------------------------------------------------------------- 1 | interface http { 2 | type headers = list>; 3 | 4 | variant method { 5 | get, 6 | post, 7 | put, 8 | patch, 9 | delete, 10 | } 11 | 12 | record request { 13 | method: method, 14 | url: string, 15 | headers: headers, 16 | body: string, 17 | } 18 | 19 | record response { 20 | url: string, 21 | status-code: u16, 22 | headers: headers, 23 | body: string, 24 | } 25 | 26 | type http-error = string; 27 | type http-result = result; 28 | 29 | get: func(req: request) -> http-result; 30 | post: func(req: request) -> http-result; 31 | put: func(req: request) -> http-result; 32 | patch: func(req: request) -> http-result; 33 | delete: func(req: request) -> http-result; 34 | 35 | error-for-status: func(resp: response) -> result<_, http-error>; 36 | } 37 | -------------------------------------------------------------------------------- /wasm-wrappers/wit/v2/jwt.wit: -------------------------------------------------------------------------------- 1 | interface jwt { 2 | type jwt-error = string; 3 | type jwt-result = result; 4 | 5 | encode: func( 6 | payload: list>, 7 | algo: string, 8 | key: string, 9 | ttl-hours: u32 10 | ) -> jwt-result; 11 | } 12 | -------------------------------------------------------------------------------- /wasm-wrappers/wit/v2/routines.wit: -------------------------------------------------------------------------------- 1 | interface routines { 2 | use types.{ 3 | cell, row, import-foreign-schema-stmt, context, fdw-error, fdw-result, 4 | }; 5 | 6 | // ---------------------------------------------- 7 | // foreign data wrapper interface functions 8 | // ---------------------------------------------- 9 | 10 | // define host version requirement, e.g, "^1.2.3" 11 | host-version-requirement: func() -> string; 12 | 13 | // fdw initialization 14 | init: func(ctx: borrow) -> fdw-result; 15 | 16 | // data scan 17 | begin-scan: func(ctx: borrow) -> fdw-result; 18 | iter-scan: func( 19 | ctx: borrow, 20 | row: borrow, 21 | ) -> result, fdw-error>; 22 | re-scan: func(ctx: borrow) -> fdw-result; 23 | end-scan: func(ctx: borrow) -> fdw-result; 24 | 25 | // data modify 26 | begin-modify: func(ctx: borrow) -> fdw-result; 27 | insert: func(ctx: borrow, row: borrow) -> fdw-result; 28 | update: func( 29 | ctx: borrow, 30 | rowid: cell, 31 | new-row: borrow, 32 | ) -> fdw-result; 33 | delete: func(ctx: borrow, rowid: cell) -> fdw-result; 34 | end-modify: func(ctx: borrow) -> fdw-result; 35 | 36 | // import foreign schema 37 | import-foreign-schema: func( 38 | ctx: borrow, 39 | stmt: import-foreign-schema-stmt, 40 | ) -> result, fdw-error>; 41 | } 42 | -------------------------------------------------------------------------------- /wasm-wrappers/wit/v2/stats.wit: -------------------------------------------------------------------------------- 1 | interface stats { 2 | type metadata = option; 3 | 4 | variant metric { 5 | create-times, 6 | rows-in, 7 | rows-out, 8 | bytes-in, 9 | bytes-out, 10 | } 11 | 12 | inc-stats: func(fdw-name: string, metric: metric, inc: s64); 13 | get-metadata: func(fdw-name: string) -> metadata; 14 | set-metadata: func(fdw-name: string, metadata: metadata); 15 | } 16 | -------------------------------------------------------------------------------- /wasm-wrappers/wit/v2/time.wit: -------------------------------------------------------------------------------- 1 | interface time { 2 | type time-error = string; 3 | type time-result = result; 4 | 5 | // get seconds since Unix epoch 6 | epoch-secs: func() -> s64; 7 | 8 | // parse RFC3339 string to microseconds since Unix epoch 9 | parse-from-rfc3339: func(s: string) -> time-result; 10 | 11 | // parse string from an user-specified format to microseconds since Unix epoch 12 | parse-from-str: func(s: string, fmt: string) -> time-result; 13 | 14 | // convert microseconds since Unix epoch to RFC3339 string 15 | epoch-ms-to-rfc3339: func(msecs: s64) -> result; 16 | 17 | // sleep for a while 18 | sleep: func(millis: u64); 19 | } 20 | -------------------------------------------------------------------------------- /wasm-wrappers/wit/v2/types.wit: -------------------------------------------------------------------------------- 1 | interface types { 2 | variant type-oid { 3 | %bool, 4 | i8, 5 | i16, 6 | %f32, 7 | i32, 8 | %f64, 9 | i64, 10 | numeric, 11 | %string, 12 | date, 13 | timestamp, 14 | timestamptz, 15 | json, 16 | uuid, 17 | other(string), 18 | } 19 | 20 | variant cell { 21 | %bool(bool), 22 | i8(s8), 23 | i16(s16), 24 | %f32(f32), 25 | i32(s32), 26 | %f64(f64), 27 | i64(s64), 28 | numeric(f64), 29 | %string(string), 30 | // seconds since Unix epoch 31 | date(s64), 32 | // microseconds since Unix epoch 33 | timestamp(s64), 34 | timestamptz(s64), 35 | json(string), 36 | uuid(string), 37 | other(string), 38 | } 39 | 40 | resource row { 41 | constructor(); 42 | 43 | cols: func() -> list; 44 | cells: func() -> list>; 45 | 46 | push: func(cell: option); 47 | } 48 | 49 | resource column { 50 | constructor(index: u32); 51 | 52 | name: func() -> string; 53 | num: func() -> u32; 54 | type-oid: func() -> type-oid; 55 | } 56 | 57 | variant value { 58 | cell(cell), 59 | array(list), 60 | } 61 | 62 | record param { 63 | id: u32, 64 | type-oid: u32, 65 | } 66 | 67 | resource qual { 68 | constructor(index: u32); 69 | 70 | field: func() -> string; 71 | operator: func() -> string; 72 | value: func() -> value; 73 | use-or: func() -> bool; 74 | param: func() -> option; 75 | 76 | deparse: func() -> string; 77 | } 78 | 79 | resource sort { 80 | constructor(index: u32); 81 | 82 | field: func() -> string; 83 | field-no: func() -> u32; 84 | reversed: func() -> bool; 85 | nulls-first: func() -> bool; 86 | collate: func() -> option; 87 | 88 | deparse: func() -> string; 89 | deparse-with-collate: func() -> string; 90 | } 91 | 92 | resource limit { 93 | constructor(); 94 | 95 | count: func() -> s64; 96 | offset: func() -> s64; 97 | 98 | deparse: func() -> string; 99 | } 100 | 101 | variant options-type { 102 | server, 103 | table, 104 | import-schema, 105 | other(string), 106 | } 107 | 108 | resource options { 109 | constructor(options-type: options-type); 110 | 111 | get: func(key: string) -> option; 112 | require: func(key: string) -> result; 113 | require-or: func(key: string, default: string) -> string; 114 | } 115 | 116 | variant import-schema-type { 117 | all, 118 | limit-to, 119 | except, 120 | } 121 | 122 | record import-foreign-schema-stmt { 123 | server-name: string, 124 | remote-schema: string, 125 | local-schema: string, 126 | list-type: import-schema-type, 127 | table-list: list, 128 | } 129 | 130 | resource context { 131 | constructor(); 132 | 133 | get-options: func(options-type: options-type) -> options; 134 | get-quals: func() -> list; 135 | get-columns: func() -> list; 136 | get-sorts: func() -> list; 137 | get-limit: func() -> option; 138 | } 139 | 140 | type fdw-error = string; 141 | type fdw-result = result<_, fdw-error>; 142 | } 143 | 144 | 145 | -------------------------------------------------------------------------------- /wasm-wrappers/wit/v2/utils.wit: -------------------------------------------------------------------------------- 1 | interface utils { 2 | use types.{cell}; 3 | 4 | report-info: func(msg: string); 5 | report-notice: func(msg: string); 6 | report-warning: func(msg: string); 7 | report-error: func(msg: string); 8 | 9 | cell-to-string: func(cell: option) -> string; 10 | get-vault-secret: func(secret-id: string) -> option; 11 | } 12 | -------------------------------------------------------------------------------- /wasm-wrappers/wit/v2/world.wit: -------------------------------------------------------------------------------- 1 | package supabase:wrappers@0.2.0; 2 | 3 | world wrappers { 4 | import http; 5 | import jwt; 6 | import stats; 7 | import time; 8 | import types; 9 | import utils; 10 | export routines; 11 | } 12 | -------------------------------------------------------------------------------- /wrappers/.ci/docker-compose-wasm.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | 3 | wasm: 4 | container_name: wasm-local 5 | build: 6 | context: ../dockerfiles/wasm 7 | ports: 8 | - "8096:8096" 9 | healthcheck: 10 | test: curl --fail http://0.0.0.0:8096/paddle || exit 1 11 | interval: 10s 12 | timeout: 5s 13 | retries: 3 14 | -------------------------------------------------------------------------------- /wrappers/.ci/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | include: 2 | - docker-compose-native.yaml 3 | - docker-compose-wasm.yaml 4 | -------------------------------------------------------------------------------- /wrappers/README.md: -------------------------------------------------------------------------------- 1 | # Postgres Foreign Data Wrappers by Supabase 2 | 3 | This is a collection of FDWs built by [Supabase](https://www.supabase.com). We currently support the following FDWs, with more are under development: 4 | 5 | - [HelloWorld](./src/fdw/helloworld_fdw): A demo FDW to show how to develop a basic FDW. 6 | - [BigQuery](./src/fdw/bigquery_fdw): A FDW for Google [BigQuery](https://cloud.google.com/bigquery) which supports data read and modify. 7 | - [Clickhouse](./src/fdw/clickhouse_fdw): A FDW for [ClickHouse](https://clickhouse.com/) which supports data read and modify. 8 | - [Stripe](./src/fdw/stripe_fdw): A FDW for [Stripe](https://stripe.com/) API which supports data read and modify. 9 | - [Firebase](./src/fdw/firebase_fdw): A FDW for Google [Firebase](https://firebase.google.com/) which supports data read only. 10 | - [Airtable](./src/fdw/airtable_fdw): A FDW for [Airtable](https://airtable.com/) API which supports data read only. 11 | - [S3](./src/fdw/s3_fdw): A FDW for [AWS S3](https://aws.amazon.com/s3/). Currently read-only. 12 | - [Logflare](./src/fdw/logflare_fdw): A FDW for [Logflare](https://logflare.app/) which supports data read only. 13 | - [Auth0](./src/fdw/auth0_fdw): A FDW for [Auth0](https://auth0.com/). 14 | - [Cognito](./src/fdw/cognito_fdw): A FDW for [AWS Cogntio](https://aws.amazon.com/pm/cognito/). 15 | - [SQL Server](./src/fdw/mssql_fdw): A FDW for [Microsoft SQL Server](https://www.microsoft.com/en-au/sql-server/) which supports data read only. 16 | - [Redis](./src/fdw/redis_fdw): A FDW for [Redis](https://redis.io/) which supports data read only. 17 | -------------------------------------------------------------------------------- /wrappers/dockerfiles/airtable/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.13.2-slim 2 | 3 | WORKDIR /usr/src/app 4 | 5 | RUN apt-get update \ 6 | && apt-get install -y --no-install-recommends curl \ 7 | && pip install airtablemock 8 | 9 | COPY ./server.py . 10 | 11 | CMD [ "python", "./server.py" ] 12 | -------------------------------------------------------------------------------- /wrappers/dockerfiles/airtable/server.py: -------------------------------------------------------------------------------- 1 | from http.server import BaseHTTPRequestHandler, HTTPServer 2 | import datetime, json 3 | from urllib.parse import urlparse, parse_qs 4 | import airtablemock 5 | 6 | hostName = "0.0.0.0" 7 | serverPort = 8086 8 | base_id = 'baseID' 9 | test_table = 'table-foo' 10 | test_view = 'view-bar' 11 | 12 | # This is a client for the base "baseID", it will not access the real 13 | # Airtable service but only the mock one which keeps data in RAM. 14 | client = airtablemock.Airtable(base_id, 'apiKey') 15 | 16 | class AirtableMockServer(BaseHTTPRequestHandler): 17 | def do_GET(self): 18 | path = urlparse(self.path) 19 | [_, base_id, table_id] = path.path.split('/') 20 | views = parse_qs(path.query).get('view') 21 | view = views[0] if views else None 22 | 23 | records = client.get(table_id, view=view) 24 | if records is None: 25 | self.send_response(404) 26 | return 27 | 28 | self.send_response(200) 29 | self.send_header("Content-type", "application/json") 30 | self.end_headers() 31 | 32 | for rec in records['records']: 33 | rec['createdTime'] = datetime.datetime.now().isoformat() 34 | 35 | self.wfile.write(bytes(json.dumps(records), "utf-8")) 36 | return 37 | 38 | 39 | if __name__ == "__main__": 40 | # Populate a test table 41 | client.create( 42 | test_table, 43 | { 44 | "bool_field": True, 45 | "numeric_field": 1, 46 | "string_field": "two", 47 | "timestamp_field": "2023-07-19T06:39:15.000Z", 48 | "object_field": {"foo": "bar"}, 49 | "strings_array_field": ["foo", "bar"], 50 | "numerics_array_field": [1, 2], 51 | "bools_array_field": [False], 52 | "objects_array_field": [{"foo": "bar"}, {"foo": "baz"}] 53 | }, 54 | ) 55 | client.create( 56 | test_table, 57 | { 58 | "bool_field": False, 59 | "numeric_field": 2, 60 | "string_field": "three", 61 | "timestamp_field": "2023-07-20T06:39:15.000Z", 62 | "object_field": {"foo": "baz"}, 63 | "strings_array_field": ["baz", "qux"], 64 | "numerics_array_field": [3, 4], 65 | "bools_array_field": [True, False, True], 66 | "objects_array_field": [{"foo": "qux"}] 67 | }, 68 | ) 69 | 70 | # Create a test view 71 | airtablemock.create_view(base_id, test_table, test_view, 'string_field = "three"') 72 | 73 | # Create web server 74 | webServer = HTTPServer((hostName, serverPort), AirtableMockServer) 75 | print("Airtable Mock Server started at http://%s:%s" % (hostName, serverPort)) 76 | 77 | try: 78 | webServer.serve_forever() 79 | except KeyboardInterrupt: 80 | pass 81 | 82 | webServer.server_close() 83 | print("Server stopped.") 84 | -------------------------------------------------------------------------------- /wrappers/dockerfiles/auth0/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.13.2-slim 2 | 3 | WORKDIR /usr/src/app 4 | 5 | RUN apt-get update \ 6 | && apt-get install -y --no-install-recommends curl 7 | 8 | COPY ./server.py . 9 | 10 | CMD [ "python", "./server.py" ] 11 | -------------------------------------------------------------------------------- /wrappers/dockerfiles/auth0/server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import http.server 4 | import socketserver 5 | import json 6 | 7 | class MockServerHandler(http.server.SimpleHTTPRequestHandler): 8 | def do_GET(self): 9 | response_data = {"start":0,"limit":50,"length":1, "users": [{ 10 | "email": "john@doe.com", 11 | "email_verified": True, 12 | "user_id": "auth0|1234567890abcdef", 13 | "username": "userexample", 14 | "phone_number": "123-456-7890", 15 | "phone_verified": True, 16 | "created_at": "2023-05-16T07:41:08.028Z", 17 | "updated_at": "2023-05-16T08:41:08.028Z", 18 | "identities": [{ 19 | "connection": "Username-Password-Authentication", 20 | "user_id": "1234567890abcdef", 21 | "provider": "auth0", 22 | "isSocial": False 23 | }], 24 | "app_metadata": {}, 25 | "user_metadata": {}, 26 | "picture": "https://example.com/avatar.jpg", 27 | "name": "John Doe", 28 | "nickname": "Johnny", 29 | "multifactor": [], 30 | "last_ip": "192.168.1.1", 31 | "last_login": "2023-05-16T08:41:08.028Z", 32 | "logins_count": 1, 33 | "blocked": False, 34 | "given_name": "John", 35 | "family_name": "Doe" 36 | }]} 37 | 38 | # Set response code and headers 39 | self.send_response(200) 40 | self.send_header('Content-type', 'application/json') 41 | self.end_headers() 42 | 43 | # Send the response data 44 | self.wfile.write(json.dumps(response_data).encode()) 45 | 46 | 47 | 48 | if __name__ == "__main__": 49 | # Define server address and port 50 | port = 3796 51 | server_address = ('', port) 52 | # Create an HTTP server instance 53 | httpd = socketserver.TCPServer(server_address, MockServerHandler) 54 | print(f"Auth0 mock Server running on port {port}...") 55 | try: 56 | httpd.serve_forever() 57 | except KeyboardInterrupt: 58 | pass 59 | httpd.server_close() 60 | print("Server stopped") 61 | -------------------------------------------------------------------------------- /wrappers/dockerfiles/bigquery/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/goccy/bigquery-emulator:latest 2 | 3 | RUN apt-get update 4 | RUN apt-get install -y --no-install-recommends curl 5 | -------------------------------------------------------------------------------- /wrappers/dockerfiles/bigquery/data.yaml: -------------------------------------------------------------------------------- 1 | projects: 2 | - id: test_project 3 | datasets: 4 | - id: test_dataset 5 | tables: 6 | - id: test_table 7 | columns: 8 | - name: id 9 | type: INTEGER 10 | mode: REQUIRED 11 | - name: name 12 | type: STRING 13 | mode: REQUIRED 14 | - name: num 15 | type: NUMERIC 16 | - name: is_active 17 | type: BOOL 18 | - name: col_int8 19 | type: INTEGER 20 | - name: col_int16 21 | type: INTEGER 22 | - name: col_int32 23 | type: INTEGER 24 | - name: col_float32 25 | type: FLOAT64 26 | - name: col_float64 27 | type: FLOAT64 28 | - name: attrs 29 | type: JSON 30 | - name: signup_dt 31 | type: DATE 32 | - name: created_at 33 | type: TIMESTAMP 34 | data: 35 | - id: 1 36 | name: foo 37 | num: 0.123 38 | is_active: true 39 | col_int8: 42 40 | col_int16: 12345 41 | col_int32: 65538 42 | col_float32: 123.456 43 | col_float64: 123.456 44 | attrs: '{"aa": "bb"}' 45 | signup_dt: "2022-10-21" 46 | created_at: "2022-10-21T00:00:00" 47 | - id: 2 48 | name: bar 49 | num: 1234.56789 50 | is_active: false 51 | col_int8: 44 52 | col_int16: 12348 53 | col_int32: 65540 54 | col_float32: 123.456 55 | col_float64: 654.321 56 | attrs: '{"aa": 123.45}' 57 | signup_dt: "2023-10-21" 58 | created_at: "2023-10-21T00:00:00" 59 | -------------------------------------------------------------------------------- /wrappers/dockerfiles/cognito/.cognito/config.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /wrappers/dockerfiles/cognito/.cognito/db/clients.json: -------------------------------------------------------------------------------- 1 | { 2 | "Clients": {} 3 | } -------------------------------------------------------------------------------- /wrappers/dockerfiles/firebase/README.md: -------------------------------------------------------------------------------- 1 | ## Start Firebase Emulator 2 | 3 | ref: https://github.com/AndreySenov/firebase-tools-docker 4 | 5 | ```bash 6 | cd wrappers 7 | 8 | docker run \ 9 | --name "firebase-emu" \ 10 | --rm \ 11 | -p=9000:9000 \ 12 | -p=8080:8080 \ 13 | -p=4000:4000 \ 14 | -p=9099:9099 \ 15 | -p=8085:8085 \ 16 | -p=5001:5001 \ 17 | -p=9199:9199 \ 18 | -v $PWD/dockerfiles/firebase:/home/node \ 19 | andreysenov/firebase-tools:11.24.1-node-14-alpine \ 20 | firebase emulators:start --project supa --only auth,firestore --import=/home/node/baseline-data 21 | ``` 22 | 23 | ## Export Firebase Data 24 | 25 | ```bash 26 | docker exec -it "firebase-emu" bash 27 | 28 | firebase emulators:export ./baseline-data --project supa 29 | ``` 30 | -------------------------------------------------------------------------------- /wrappers/dockerfiles/firebase/baseline-data/auth_export/accounts.json: -------------------------------------------------------------------------------- 1 | {"kind":"identitytoolkit#DownloadAccountResponse","users":[{"localId":"JeXJ2gUHCpYSQV6zXlRYfRYHUfMl","createdAt":"1668488830314","lastLoginAt":"1668488830310","displayName":"Yoda","photoUrl":"","passwordHash":"fakeHash:salt=fakeSaltNZhRYxXIjWaiXB75SD8z:password=adsf123","salt":"fakeSaltNZhRYxXIjWaiXB75SD8z","passwordUpdatedAt":1701484518847,"providerUserInfo":[{"providerId":"password","email":"foo@example.com","federatedId":"foo@example.com","rawId":"foo@example.com","displayName":"Yoda","photoUrl":""}],"validSince":"1701484518","email":"foo@example.com","emailVerified":false,"disabled":false},{"localId":"eUHS9EaQS2lZO0zbyMq28m02SUGP","createdAt":"1668488839113","lastLoginAt":"1668488839111","displayName":"Luck","photoUrl":"","passwordHash":"fakeHash:salt=fakeSaltL62cBYW58puy9DarO9TO:password=asdf123","salt":"fakeSaltL62cBYW58puy9DarO9TO","passwordUpdatedAt":1701484518848,"providerUserInfo":[{"providerId":"password","email":"bar@example.com","federatedId":"bar@example.com","rawId":"bar@example.com","displayName":"Luke","photoUrl":""}],"validSince":"1701484518","email":"bar@example.com","emailVerified":false,"disabled":false}]} 2 | -------------------------------------------------------------------------------- /wrappers/dockerfiles/firebase/baseline-data/auth_export/config.json: -------------------------------------------------------------------------------- 1 | {"signIn":{"allowDuplicateEmails":false}} -------------------------------------------------------------------------------- /wrappers/dockerfiles/firebase/baseline-data/firebase-export-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "11.16.0", 3 | "firestore": { 4 | "version": "1.15.1", 5 | "path": "firestore_export", 6 | "metadata_file": "firestore_export/firestore_export.overall_export_metadata" 7 | }, 8 | "auth": { 9 | "version": "11.16.0", 10 | "path": "auth_export" 11 | } 12 | } -------------------------------------------------------------------------------- /wrappers/dockerfiles/firebase/baseline-data/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase/wrappers/0da8b7131243e2a1696980703acd9b363c9c49d1/wrappers/dockerfiles/firebase/baseline-data/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata -------------------------------------------------------------------------------- /wrappers/dockerfiles/firebase/baseline-data/firestore_export/all_namespaces/all_kinds/output-0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase/wrappers/0da8b7131243e2a1696980703acd9b363c9c49d1/wrappers/dockerfiles/firebase/baseline-data/firestore_export/all_namespaces/all_kinds/output-0 -------------------------------------------------------------------------------- /wrappers/dockerfiles/firebase/baseline-data/firestore_export/firestore_export.overall_export_metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase/wrappers/0da8b7131243e2a1696980703acd9b363c9c49d1/wrappers/dockerfiles/firebase/baseline-data/firestore_export/firestore_export.overall_export_metadata -------------------------------------------------------------------------------- /wrappers/dockerfiles/firebase/firebase.json: -------------------------------------------------------------------------------- 1 | {"emulators":{"firestore":{"port":8080,"host":"0.0.0.0"},"ui":{"enabled":true,"port":4000,"host":"0.0.0.0"},"auth":{"port":9099,"host":"0.0.0.0"}}} 2 | -------------------------------------------------------------------------------- /wrappers/dockerfiles/firebase/storage.rules: -------------------------------------------------------------------------------- 1 | rules_version = '2'; 2 | service firebase.storage { 3 | match /b/{bucket}/o { 4 | match /{allPaths=**} { 5 | allow read, write: if true; 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /wrappers/dockerfiles/logflare/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.13.2-slim 2 | 3 | WORKDIR /usr/src/app 4 | 5 | RUN apt-get update \ 6 | && apt-get install -y --no-install-recommends curl 7 | 8 | COPY . . 9 | RUN pip install -r requirements.txt 10 | 11 | CMD [ "uvicorn", "server:app", "--host", "0.0.0.0", "--port", "4343" ] 12 | -------------------------------------------------------------------------------- /wrappers/dockerfiles/logflare/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi 2 | uvicorn 3 | -------------------------------------------------------------------------------- /wrappers/dockerfiles/logflare/server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import http.server 4 | import socketserver 5 | import json 6 | 7 | from typing import Union 8 | from fastapi import APIRouter, FastAPI, HTTPException 9 | 10 | router = APIRouter(prefix="/v1") 11 | data = json.loads(open("data.json").read()) 12 | 13 | @router.get("/{endpoint:path}") 14 | def endpoint(): 15 | return data 16 | 17 | app = FastAPI() 18 | app.include_router(router) 19 | -------------------------------------------------------------------------------- /wrappers/dockerfiles/notion/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.13.2-slim 2 | 3 | WORKDIR /usr/src/app 4 | 5 | RUN apt-get update \ 6 | && apt-get install -y --no-install-recommends curl 7 | 8 | COPY . . 9 | RUN pip install -r requirements.txt 10 | 11 | CMD [ "uvicorn", "server:app", "--host", "0.0.0.0", "--port", "4242" ] 12 | -------------------------------------------------------------------------------- /wrappers/dockerfiles/notion/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "users": [ 3 | { 4 | "object": "user", 5 | "id": "d40e767c-d7af-4b18-a86d-55c61f1e39a4", 6 | "name": "John Doe", 7 | "avatar_url": "https://s3-us-west-2.amazonaws.com/public.notion-static.com/e6a352a8-8381-44d0-a1dc-9ed80e62b53d.jpg", 8 | "type": "person", 9 | "person": { 10 | "email": "john@doe.com" 11 | } 12 | }, 13 | { 14 | "object": "user", 15 | "id": "9a3b5ae0-c6e6-482d-b0e1-ed315ee6dc57", 16 | "name": "Beep Boop", 17 | "type": "bot", 18 | "avatar_url": null, 19 | "bot": { 20 | "owner": { 21 | "type": "workspace", 22 | "workspace": true 23 | }, 24 | "workspace_name": "John's Workspace" 25 | } 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /wrappers/dockerfiles/notion/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi 2 | uvicorn 3 | -------------------------------------------------------------------------------- /wrappers/dockerfiles/notion/server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import http.server 4 | import socketserver 5 | import json 6 | 7 | from typing import Union 8 | from fastapi import APIRouter, FastAPI, HTTPException 9 | 10 | router = APIRouter(prefix="/v1") 11 | data = json.loads(open("data.json").read()) 12 | 13 | 14 | @router.get("/users") 15 | def users(): 16 | return { 17 | "object": "list", 18 | "next_cursor": None, 19 | "has_more": False, 20 | "type": "user", 21 | "request_id": "829ea7f8-97b4-4a05-a1f3-df6ef3c467b4", 22 | "results": data.get("users"), 23 | } 24 | 25 | 26 | @router.get("/users/{user_id}") 27 | def user(user_id: str): 28 | user = next((user for user in data.get("users") if user.get("id") == user_id), None) 29 | if user: 30 | return { 31 | "object": "user", 32 | "request_id": "829ea7f8-97b4-4a05-a1f3-df6ef3c467b4", 33 | **user, 34 | } 35 | 36 | raise HTTPException(404, "User not found") 37 | 38 | app = FastAPI() 39 | app.include_router(router) 40 | -------------------------------------------------------------------------------- /wrappers/dockerfiles/s3/Dockerfile: -------------------------------------------------------------------------------- 1 | # This docker file is s3-init image 2 | 3 | FROM python:3.13-slim 4 | 5 | RUN apt-get update && apt-get install -y --no-install-recommends wget 6 | RUN pip install "pyiceberg[s3fs,pyarrow]" \ 7 | && wget https://dl.min.io/client/mc/release/linux-amd64/mc \ 8 | && chmod +x mc 9 | -------------------------------------------------------------------------------- /wrappers/dockerfiles/s3/test_data/test_data.csv: -------------------------------------------------------------------------------- 1 | "name","sex","age","height","weight" 2 | "Bert","M",42,68,166 3 | "Alex","M",41,74,170 4 | "Carl","M",32,,155 5 | -------------------------------------------------------------------------------- /wrappers/dockerfiles/s3/test_data/test_data.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase/wrappers/0da8b7131243e2a1696980703acd9b363c9c49d1/wrappers/dockerfiles/s3/test_data/test_data.csv.gz -------------------------------------------------------------------------------- /wrappers/dockerfiles/s3/test_data/test_data.jsonl: -------------------------------------------------------------------------------- 1 | {"name":"Alex","sex":"M","age":41,"height":74,"weight":170} 2 | {"name":"Bert","sex":"M","age":42,"height":68,"weight":166} 3 | {"name":"Carl","sex":"M","age":32,"height":null,"weight":155} 4 | -------------------------------------------------------------------------------- /wrappers/dockerfiles/s3/test_data/test_data.jsonl.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase/wrappers/0da8b7131243e2a1696980703acd9b363c9c49d1/wrappers/dockerfiles/s3/test_data/test_data.jsonl.bz2 -------------------------------------------------------------------------------- /wrappers/dockerfiles/s3/test_data/test_data.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase/wrappers/0da8b7131243e2a1696980703acd9b363c9c49d1/wrappers/dockerfiles/s3/test_data/test_data.parquet -------------------------------------------------------------------------------- /wrappers/dockerfiles/s3/test_data/test_data.parquet.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supabase/wrappers/0da8b7131243e2a1696980703acd9b363c9c49d1/wrappers/dockerfiles/s3/test_data/test_data.parquet.gz -------------------------------------------------------------------------------- /wrappers/dockerfiles/wasm/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.13.2-slim 2 | 3 | WORKDIR /usr/src/app 4 | 5 | RUN apt-get update \ 6 | && apt-get install -y --no-install-recommends curl 7 | 8 | COPY ./server.py . 9 | 10 | CMD [ "python", "./server.py" ] 11 | -------------------------------------------------------------------------------- /wrappers/rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 100 2 | -------------------------------------------------------------------------------- /wrappers/sql/bootstrap.sql: -------------------------------------------------------------------------------- 1 | -- SQL statements are intended to go before all other generated SQL. 2 | 3 | DROP TABLE IF EXISTS wrappers_fdw_stats; 4 | 5 | CREATE TABLE wrappers_fdw_stats ( 6 | fdw_name text NOT NULL PRIMARY KEY, 7 | create_times bigint NULL, 8 | rows_in bigint NULL, 9 | rows_out bigint NULL, 10 | bytes_in bigint NULL, 11 | bytes_out bigint NULL, 12 | metadata jsonb NULL, 13 | created_at timestamptz NOT NULL DEFAULT timezone('utc'::text, now()), 14 | updated_at timestamptz NOT NULL DEFAULT timezone('utc'::text, now()) 15 | ); 16 | 17 | COMMENT ON TABLE wrappers_fdw_stats IS 'Wrappers Foreign Data Wrapper statistics'; 18 | COMMENT ON COLUMN wrappers_fdw_stats.create_times IS 'Total number of times the FDW instacne has been created'; 19 | COMMENT ON COLUMN wrappers_fdw_stats.rows_in IS 'Total rows input from origin'; 20 | COMMENT ON COLUMN wrappers_fdw_stats.rows_out IS 'Total rows output to Postgres'; 21 | COMMENT ON COLUMN wrappers_fdw_stats.bytes_in IS 'Total bytes input from origin'; 22 | COMMENT ON COLUMN wrappers_fdw_stats.bytes_out IS 'Total bytes output to Postgres'; 23 | COMMENT ON COLUMN wrappers_fdw_stats.metadata IS 'Metadata specific for the FDW'; 24 | 25 | -------------------------------------------------------------------------------- /wrappers/sql/finalize.sql: -------------------------------------------------------------------------------- 1 | -- SQL statements are intended to go after all other generated SQL. 2 | 3 | -------------------------------------------------------------------------------- /wrappers/src/bin/pgrx_embed.rs: -------------------------------------------------------------------------------- 1 | ::pgrx::pgrx_embed!(); 2 | -------------------------------------------------------------------------------- /wrappers/src/fdw/airtable_fdw/README.md: -------------------------------------------------------------------------------- 1 | # Airtable Foreign Data Wrapper 2 | 3 | This is a foreign data wrapper for [Airtable](https://www.airtable.com). It is developed using [Wrappers](https://github.com/supabase/wrappers) and only supports data read from [Records](https://airtable.com/developers/web/api/list-records) endpoint. 4 | 5 | ## Documentation 6 | 7 | [https://fdw.dev/catalog/airtable/](https://fdw.dev/catalog/airtable/) 8 | 9 | 10 | ## Changelog 11 | 12 | | Version | Date | Notes | 13 | | ------- | ---------- | ---------------------------------------------------- | 14 | | 0.1.4 | 2024-09-30 | Support for pgrx 0.12.6 | 15 | | 0.1.3 | 2023-10-20 | Added jsonb data types support | 16 | | 0.1.2 | 2023-07-19 | Added more data types support | 17 | | 0.1.1 | 2023-07-13 | Added fdw stats collection | 18 | | 0.1.0 | 2022-11-30 | Initial version | 19 | -------------------------------------------------------------------------------- /wrappers/src/fdw/airtable_fdw/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::module_inception)] 2 | mod airtable_fdw; 3 | mod result; 4 | mod tests; 5 | 6 | use pgrx::pg_sys::panic::ErrorReport; 7 | use pgrx::prelude::PgSqlErrorCode; 8 | use thiserror::Error; 9 | 10 | use supabase_wrappers::prelude::{CreateRuntimeError, OptionsError}; 11 | 12 | #[derive(Error, Debug)] 13 | enum AirtableFdwError { 14 | #[error("column '{0}' data type is not supported")] 15 | UnsupportedColumnType(String), 16 | 17 | #[error("column '{0}' data type not match")] 18 | ColumnTypeNotMatch(String), 19 | 20 | #[error("{0}")] 21 | CreateRuntimeError(#[from] CreateRuntimeError), 22 | 23 | #[error("parse url failed: {0}")] 24 | UrlParseError(#[from] url::ParseError), 25 | 26 | #[error("invalid api_key header")] 27 | InvalidApiKeyHeader, 28 | 29 | #[error("request failed: {0}")] 30 | RequestError(#[from] reqwest::Error), 31 | 32 | #[error("request middleware failed: {0}")] 33 | RequestMiddlewareError(#[from] reqwest_middleware::Error), 34 | 35 | #[error("invalid json response: {0}")] 36 | SerdeError(#[from] serde_json::Error), 37 | 38 | #[error("{0}")] 39 | OptionsError(#[from] OptionsError), 40 | 41 | #[error("{0}")] 42 | NumericConversionError(#[from] pgrx::numeric::Error), 43 | } 44 | 45 | impl From for ErrorReport { 46 | fn from(value: AirtableFdwError) -> Self { 47 | match value { 48 | AirtableFdwError::CreateRuntimeError(e) => e.into(), 49 | AirtableFdwError::OptionsError(e) => e.into(), 50 | _ => ErrorReport::new(PgSqlErrorCode::ERRCODE_FDW_ERROR, format!("{value}"), ""), 51 | } 52 | } 53 | } 54 | 55 | type AirtableFdwResult = Result; 56 | -------------------------------------------------------------------------------- /wrappers/src/fdw/auth0_fdw/README.md: -------------------------------------------------------------------------------- 1 | # Auth0 Foreign Data Wrapper 2 | 3 | This is a demo foreign data wrapper which is developed using [Wrappers](https://github.com/supabase/wrappers). 4 | 5 | ## Basic usage 6 | 7 | These steps outline how to use the this FDW: 8 | 9 | 1. Clone this repo 10 | 11 | ```bash 12 | git clone https://github.com/supabase/wrappers.git 13 | ``` 14 | 15 | 2. Run it using pgrx with feature: 16 | 17 | ```bash 18 | cd wrappers/wrappers 19 | cargo pgrx run --features auth0_fdw 20 | ``` 21 | 22 | 3. Create the extension, foreign data wrapper and related objects: 23 | 24 | 25 | 26 | ``` sql 27 | -- create extension 28 | create extension wrappers; 29 | ``` 30 | 31 | ``` sql 32 | -- create foreign data wrapper and enable 'Auth0Fdw' 33 | create foreign data wrapper auth0_wrapper 34 | handler auth0_fdw_handler 35 | validator auth0_fdw_validator; 36 | ``` 37 | 38 | 39 | 40 | ``` sql 41 | -- create server and specify custom options 42 | create server auth0_server 43 | foreign data wrapper auth0_wrapper 44 | options ( 45 | url 'https://dev-.us.auth0.com/api/v2/users', 46 | api_key '' 47 | ); 48 | ``` 49 | 50 | 51 | ``` sql 52 | -- create an example foreign table 53 | -- Number of fields are illustrative 54 | create foreign table auth0 ( 55 | created_at text, 56 | email text, 57 | email_verified bool, 58 | identities jsonb 59 | ) 60 | server auth0_server 61 | options ( 62 | object 'users' 63 | ); 64 | ``` 65 | 66 | 67 | ``` 68 | 69 | 4. Run a query to check if it is working: 70 | 71 | ```sql 72 | wrappers=# select * from auth0; 73 | 74 | created_at | 2023-11-22T09:52:17.326Z 75 | email | myname@supabase.io 76 | email_verified | t 77 | identities | [{"user_id": "", "isSocial": false, "provider": "auth0", "connection": "Username-Password-Authentication"}] 78 | 79 | ``` 80 | 81 | ## Changelog 82 | 83 | | Version | Date | Notes | 84 | | ------- | ---------- | ---------------------------------------------------- | 85 | | 0.1.2 | 2024-09-23 | Make User object fields nullable | 86 | | 0.1.1 | 2023-09-20 | Error reporting refactoring | 87 | | 0.1.0 | 2022-11-30 | Initial version | 88 | -------------------------------------------------------------------------------- /wrappers/src/fdw/auth0_fdw/auth0_client/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::fdw::auth0_fdw::auth0_client::row::ResultPayload; 2 | use http::{HeaderMap, HeaderName, HeaderValue}; 3 | use pgrx::pg_sys::panic::ErrorReport; 4 | use pgrx::PgSqlErrorCode; 5 | use reqwest::Url; 6 | use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; 7 | use reqwest_retry::policies::ExponentialBackoff; 8 | use reqwest_retry::RetryTransientMiddleware; 9 | use supabase_wrappers::prelude::*; 10 | use thiserror::Error; 11 | use url::ParseError; 12 | 13 | pub(crate) mod row; 14 | 15 | pub(crate) struct Auth0Client { 16 | url: Url, 17 | client: ClientWithMiddleware, 18 | } 19 | 20 | pub(crate) mod rows_iterator; 21 | 22 | impl Auth0Client { 23 | pub(crate) fn new(url: &str, api_key: &str) -> Result { 24 | Ok(Self { 25 | url: Url::parse(url)?, 26 | client: Self::create_client(api_key)?, 27 | }) 28 | } 29 | 30 | fn create_client(api_key: &str) -> Result { 31 | let mut headers = HeaderMap::new(); 32 | let header_name = HeaderName::from_static("authorization"); // Use 'authorization' instead of 'api-key' 33 | // Format the API key as a Bearer token 34 | let api_key_value = format!("Bearer {}", api_key); 35 | let mut api_key_value = HeaderValue::from_str(&api_key_value) 36 | .map_err(|_| Auth0ClientError::InvalidApiKeyHeader)?; 37 | api_key_value.set_sensitive(true); 38 | headers.insert(header_name, api_key_value); 39 | let client = reqwest::Client::builder() 40 | .default_headers(headers) 41 | .build()?; 42 | let retry_policy = ExponentialBackoff::builder().build_with_max_retries(3); 43 | Ok(ClientBuilder::new(client) 44 | .with(RetryTransientMiddleware::new_with_policy(retry_policy)) 45 | .build()) 46 | } 47 | 48 | pub fn get_client(&self) -> &ClientWithMiddleware { 49 | &self.client 50 | } 51 | 52 | pub(crate) fn fetch_users( 53 | &self, 54 | page: u64, 55 | per_page: Option, 56 | ) -> Result { 57 | let rt = create_async_runtime()?; 58 | 59 | rt.block_on(async { 60 | let mut url = self.url.clone(); 61 | url.query_pairs_mut().append_pair("page", &page.to_string()); 62 | if let Some(per_page) = per_page { 63 | url.query_pairs_mut() 64 | .append_pair("per_page", &per_page.to_string()); 65 | } 66 | 67 | url.query_pairs_mut().append_pair("include_totals", "true"); 68 | 69 | let response = self.get_client().get(url.as_str()).send().await?; 70 | let response = response.error_for_status()?; 71 | let payload = response.json::().await?; 72 | 73 | Ok(payload) 74 | }) 75 | } 76 | } 77 | #[derive(Error, Debug)] 78 | pub(crate) enum Auth0ClientError { 79 | #[error("{0}")] 80 | CreateRuntimeError(#[from] CreateRuntimeError), 81 | 82 | #[error("invalid api_key header")] 83 | InvalidApiKeyHeader, 84 | 85 | #[error("reqwest error: {0}")] 86 | ReqwestError(#[from] reqwest::Error), 87 | 88 | #[error("reqwest middleware error: {0}")] 89 | ReqwestMiddlewareError(#[from] reqwest_middleware::Error), 90 | 91 | #[error("invalid json response: {0}")] 92 | SerdeError(#[from] serde_json::Error), 93 | 94 | #[error("failed to parse url: {0}")] 95 | UrlParseError(#[from] ParseError), 96 | } 97 | 98 | impl From for ErrorReport { 99 | fn from(value: Auth0ClientError) -> Self { 100 | match value { 101 | Auth0ClientError::CreateRuntimeError(e) => e.into(), 102 | Auth0ClientError::UrlParseError(_) 103 | | Auth0ClientError::InvalidApiKeyHeader 104 | | Auth0ClientError::ReqwestError(_) 105 | | Auth0ClientError::ReqwestMiddlewareError(_) 106 | | Auth0ClientError::SerdeError(_) => { 107 | ErrorReport::new(PgSqlErrorCode::ERRCODE_FDW_ERROR, format!("{value}"), "") 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /wrappers/src/fdw/auth0_fdw/auth0_client/rows_iterator.rs: -------------------------------------------------------------------------------- 1 | use crate::fdw::auth0_fdw::auth0_client::{Auth0Client, Auth0ClientError}; 2 | use std::collections::VecDeque; 3 | use supabase_wrappers::prelude::{Column, Row}; 4 | 5 | pub(crate) struct RowsIterator { 6 | auth0_client: Auth0Client, 7 | per_page: u64, 8 | columns: Vec, 9 | rows: VecDeque, 10 | have_more_rows: bool, 11 | page_offset: u64, 12 | } 13 | 14 | impl RowsIterator { 15 | pub(crate) fn new(columns: Vec, per_page: u64, auth0_client: Auth0Client) -> Self { 16 | Self { 17 | columns, 18 | auth0_client, 19 | per_page, 20 | rows: VecDeque::new(), 21 | have_more_rows: true, 22 | page_offset: 0, 23 | } 24 | } 25 | 26 | fn get_page_offset(&self) -> u64 { 27 | self.page_offset 28 | } 29 | 30 | fn get_per_page(&self) -> Option { 31 | Some(self.per_page) 32 | } 33 | 34 | fn fetch_rows_batch(&mut self) -> Result, Auth0ClientError> { 35 | let result_payload = self 36 | .auth0_client 37 | .fetch_users(self.get_page_offset(), self.get_per_page())?; 38 | let total = result_payload.get_total().unwrap_or(0); 39 | self.rows = result_payload 40 | .into_users() 41 | .into_iter() 42 | .map(|u| u.into_row(&self.columns)) 43 | .collect(); 44 | self.page_offset += 1; 45 | self.have_more_rows = total > self.per_page * self.page_offset; 46 | Ok(self.get_next_row()) 47 | } 48 | 49 | fn get_next_row(&mut self) -> Option { 50 | self.rows.pop_front() 51 | } 52 | } 53 | 54 | impl Iterator for RowsIterator { 55 | type Item = Result; 56 | 57 | fn next(&mut self) -> Option { 58 | if let Some(row) = self.get_next_row() { 59 | Some(Ok(row)) 60 | } else if self.have_more_rows { 61 | self.fetch_rows_batch().transpose() 62 | } else { 63 | None 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /wrappers/src/fdw/auth0_fdw/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::module_inception)] 2 | mod auth0_client; 3 | mod auth0_fdw; 4 | mod tests; 5 | -------------------------------------------------------------------------------- /wrappers/src/fdw/bigquery_fdw/README.md: -------------------------------------------------------------------------------- 1 | # BigQuery Foreign Data Wrapper 2 | 3 | This is a foreign data wrapper for [BigQuery](https://cloud.google.com/bigquery). It is developed using [Wrappers](https://github.com/supabase/wrappers) and only supports data scan at this moment. 4 | 5 | ## Documentation 6 | 7 | [https://fdw.dev/catalog/bigquery/](https://fdw.dev/catalog/bigquery/) 8 | 9 | 10 | ## Changelog 11 | 12 | | Version | Date | Notes | 13 | | ------- | ---------- | ---------------------------------------------------- | 14 | | 0.1.6 | 2025-02-04 | Upgrade bq client lib to v0.25.1, support JSON type | 15 | | 0.1.5 | 2024-09-30 | Support for pgrx 0.12.6 | 16 | | 0.1.4 | 2023-07-13 | Added fdw stats collection | 17 | | 0.1.3 | 2023-04-03 | Added support for `NUMERIC` type | 18 | | 0.1.2 | 2023-03-15 | Added subquery support for `table` option | 19 | | 0.1.1 | 2023-02-15 | Upgrade bq client lib to v0.16.5, code improvement | 20 | | 0.1.0 | 2022-11-30 | Initial version | 21 | -------------------------------------------------------------------------------- /wrappers/src/fdw/bigquery_fdw/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::module_inception)] 2 | 3 | use gcp_bigquery_client::error::BQError; 4 | use pgrx::pg_sys::panic::ErrorReport; 5 | use pgrx::{prelude::DateTimeConversionError, PgSqlErrorCode}; 6 | use supabase_wrappers::prelude::{CreateRuntimeError, OptionsError}; 7 | use thiserror::Error; 8 | 9 | mod bigquery_fdw; 10 | mod tests; 11 | 12 | #[derive(Error, Debug)] 13 | enum BigQueryFdwError { 14 | #[error("{0}")] 15 | CreateRuntimeError(#[from] CreateRuntimeError), 16 | 17 | #[error("{0}")] 18 | OptionsError(#[from] OptionsError), 19 | 20 | #[error("big query error: {0}")] 21 | BigQueryError(#[from] BQError), 22 | 23 | #[error("field {0} type not supported")] 24 | UnsupportedFieldType(String), 25 | 26 | #[error("{0}")] 27 | NumericConversionError(#[from] pgrx::numeric::Error), 28 | 29 | #[error("{0}")] 30 | DateTimeConversionError(#[from] DateTimeConversionError), 31 | } 32 | 33 | impl From for ErrorReport { 34 | fn from(value: BigQueryFdwError) -> Self { 35 | match value { 36 | BigQueryFdwError::CreateRuntimeError(e) => e.into(), 37 | BigQueryFdwError::OptionsError(e) => e.into(), 38 | BigQueryFdwError::BigQueryError(e) => ErrorReport::new( 39 | PgSqlErrorCode::ERRCODE_FDW_INVALID_DATA_TYPE, 40 | format!("{e}"), 41 | "", 42 | ), 43 | _ => ErrorReport::new(PgSqlErrorCode::ERRCODE_FDW_ERROR, format!("{value}"), ""), 44 | } 45 | } 46 | } 47 | 48 | type BigQueryFdwResult = Result; 49 | -------------------------------------------------------------------------------- /wrappers/src/fdw/clickhouse_fdw/README.md: -------------------------------------------------------------------------------- 1 | # ClickHouse Foreign Data Wrapper 2 | 3 | This is a foreign data wrapper for [ClickHouse](https://clickhouse.com/). It is developed using [Wrappers](https://github.com/supabase/wrappers) and supports both data scan and modify. 4 | 5 | ## Documentation 6 | 7 | [https://fdw.dev/catalog/clickhouse/](https://fdw.dev/catalog/clickhouse/) 8 | 9 | 10 | ## Changelog 11 | 12 | | Version | Date | Notes | 13 | | ------- | ---------- | ---------------------------------------------------- | 14 | | 0.1.7 | 2025-05-22 | Added more data types support | 15 | | 0.1.6 | 2025-05-06 | Added UUID data type support | 16 | | 0.1.5 | 2024-09-30 | Support for pgrx 0.12.6 | 17 | | 0.1.4 | 2024-09-10 | Added Nullable type suppport | 18 | | 0.1.3 | 2023-07-17 | Added sort and limit pushdown suppport | 19 | | 0.1.2 | 2023-07-13 | Added fdw stats collection | 20 | | 0.1.1 | 2023-05-19 | Added custom sql support | 21 | | 0.1.0 | 2022-11-30 | Initial version | 22 | -------------------------------------------------------------------------------- /wrappers/src/fdw/clickhouse_fdw/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::module_inception)] 2 | mod clickhouse_fdw; 3 | mod tests; 4 | 5 | use pgrx::datum::datetime_support::DateTimeConversionError; 6 | use pgrx::pg_sys::panic::ErrorReport; 7 | use pgrx::prelude::PgSqlErrorCode; 8 | use thiserror::Error; 9 | 10 | use supabase_wrappers::prelude::{CreateRuntimeError, OptionsError}; 11 | 12 | #[derive(Error, Debug)] 13 | enum ClickHouseFdwError { 14 | #[error("parameter '{0}' doesn't supports array value")] 15 | NoArrayParameter(String), 16 | 17 | #[error("unmatched query parameter: {0}")] 18 | UnmatchedParameter(String), 19 | 20 | #[error("column data type '{0}' is not supported")] 21 | UnsupportedColumnType(String), 22 | 23 | #[error("parse integer from string error")] 24 | ParseIntError(#[from] std::num::ParseIntError), 25 | 26 | #[error("numeric conversion error: {0}")] 27 | NumericConversionError(#[from] pgrx::numeric::Error), 28 | 29 | #[error("datetime conversion error: {0}")] 30 | DatetimeConversionError(#[from] DateTimeConversionError), 31 | 32 | #[error("datetime parse error: {0}")] 33 | DatetimeParseError(#[from] chrono::format::ParseError), 34 | 35 | #[error("uuid parse error: {0}")] 36 | UuidParseError(#[from] uuid::Error), 37 | 38 | #[error("{0}")] 39 | OptionsError(#[from] OptionsError), 40 | 41 | #[error("{0}")] 42 | CreateRuntimeError(#[from] CreateRuntimeError), 43 | 44 | #[error("{0}")] 45 | ClickHouseError(#[from] clickhouse_rs::errors::Error), 46 | } 47 | 48 | impl From for ErrorReport { 49 | fn from(value: ClickHouseFdwError) -> Self { 50 | ErrorReport::new(PgSqlErrorCode::ERRCODE_FDW_ERROR, format!("{value}"), "") 51 | } 52 | } 53 | 54 | type ClickHouseFdwResult = Result; 55 | -------------------------------------------------------------------------------- /wrappers/src/fdw/cognito_fdw/README.md: -------------------------------------------------------------------------------- 1 | # Cognito (AWS) Foreign Data Wrapper 2 | 3 | This is a demo foreign data wrapper which is developed using [Wrappers](https://github.com/supabase/wrappers). 4 | 5 | ## Documentation 6 | 7 | [https://fdw.dev/catalog/cognito/](https://fdw.dev/catalog/cognito/) 8 | 9 | ## Changelog 10 | 11 | | Version | Date | Notes | 12 | | ------- | ---------- | ---------------------------------------------------- | 13 | | 0.1.4 | 2025-03-21 | Added import foreign schema support | 14 | | 0.1.3 | 2024-12-11 | Code quality improvment | 15 | | 0.1.2 | 2024-09-30 | Support for pgrx 0.12.6 | 16 | | 0.1.0 | 2024-01-25 | Initial version | 17 | -------------------------------------------------------------------------------- /wrappers/src/fdw/cognito_fdw/cognito_client/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod row; 2 | pub(crate) mod rows_iterator; 3 | -------------------------------------------------------------------------------- /wrappers/src/fdw/cognito_fdw/cognito_client/row.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::result_large_err)] 2 | use aws_sdk_cognitoidentityprovider::primitives::DateTime; 3 | use aws_sdk_cognitoidentityprovider::types::{AttributeType, UserType}; 4 | use serde_json::{json, Value}; 5 | use supabase_wrappers::prelude::{Cell, Column, Row}; 6 | 7 | use super::super::CognitoFdwError; 8 | 9 | pub(in super::super) trait IntoRow { 10 | fn into_row(self, columns: &[Column]) -> Result; 11 | } 12 | 13 | fn serialize_attributes(attributes: &Vec) -> Value { 14 | let mut attrs = vec![]; 15 | 16 | for attr in attributes { 17 | // Convert each AttributeType to a serde_json::Value 18 | let attr_json = json!({ attr.name.clone(): attr.value }); 19 | attrs.push(attr_json); 20 | } 21 | 22 | json!(attrs) 23 | } 24 | 25 | fn convert_to_timestamp(dt: DateTime) -> Cell { 26 | let millis = dt.to_millis().expect("timestamp should be valid"); 27 | // convert Unix epoch to Postgres epoch 28 | let ts = pgrx::prelude::Timestamp::try_from(millis * 1000 - 946_684_800_000_000) 29 | .expect("timestamp should be converted Postgres epoch"); 30 | Cell::Timestamp(ts) 31 | } 32 | 33 | impl IntoRow for UserType { 34 | fn into_row(self, columns: &[Column]) -> Result { 35 | let mut row = Row::new(); 36 | 37 | for column in columns { 38 | match column.name.as_str() { 39 | "username" => { 40 | row.push("username", self.username.clone().map(Cell::String)); 41 | } 42 | "attributes" => { 43 | if let Some(ref attributes) = self.attributes { 44 | let serialized_attributes = serialize_attributes(attributes); 45 | let attributes_json_b = pgrx::JsonB(serialized_attributes); 46 | row.push("attributes", Some(Cell::Json(attributes_json_b))); 47 | } 48 | } 49 | "created_at" => { 50 | row.push( 51 | "created_at", 52 | self.user_create_date.map(convert_to_timestamp), 53 | ); 54 | } 55 | "updated_at" => { 56 | row.push( 57 | "updated_at", 58 | self.user_last_modified_date.map(convert_to_timestamp), 59 | ); 60 | } 61 | "email" => { 62 | let value = self.extract_attribute_value("email").map(Cell::String); 63 | row.push("email", value); 64 | } 65 | "enabled" => { 66 | row.push("enabled", Some(Cell::Bool(self.enabled))); 67 | } 68 | "status" => { 69 | row.push( 70 | "status", 71 | self.user_status 72 | .clone() 73 | .map(|s| Cell::String(s.as_str().to_owned())), 74 | ); 75 | } 76 | _ => { 77 | return Err(CognitoFdwError::UnsupportedColumn(column.name.clone())); 78 | } 79 | } 80 | } 81 | 82 | Ok(row) 83 | } 84 | } 85 | 86 | pub(in super::super) trait UserTypeExt { 87 | fn extract_attribute_value(&self, attr_name: &str) -> Option; 88 | } 89 | 90 | impl UserTypeExt for UserType { 91 | fn extract_attribute_value(&self, attr_name: &str) -> Option { 92 | self.attributes 93 | .iter() 94 | .flat_map(|vec| vec.iter()) // Iterate over each AttributeType in the Vec 95 | .find(|attr| attr.name == attr_name) 96 | .and_then(|attr| attr.value.clone()) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /wrappers/src/fdw/cognito_fdw/cognito_client/rows_iterator.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::result_large_err)] 2 | use aws_sdk_cognitoidentityprovider::Client; 3 | use std::collections::VecDeque; 4 | use std::sync::Arc; 5 | 6 | use supabase_wrappers::prelude::{Column, Row, Runtime}; 7 | 8 | use super::super::CognitoFdwResult; 9 | use super::row::IntoRow; 10 | 11 | pub(in super::super) struct RowsIterator { 12 | rt: Arc, 13 | cognito_client: Client, 14 | columns: Vec, 15 | rows: VecDeque, 16 | user_pool_id: String, 17 | have_more_rows: bool, 18 | pagination_token: Option, 19 | } 20 | 21 | impl RowsIterator { 22 | pub(in super::super) fn new( 23 | rt: Arc, 24 | columns: Vec, 25 | user_pool_id: String, 26 | cognito_client: Client, 27 | ) -> Self { 28 | Self { 29 | rt, 30 | columns, 31 | cognito_client, 32 | user_pool_id, 33 | rows: VecDeque::new(), 34 | have_more_rows: true, 35 | pagination_token: None, 36 | } 37 | } 38 | 39 | fn fetch_rows_batch(&mut self) -> CognitoFdwResult> { 40 | self.have_more_rows = false; 41 | 42 | let mut request = self 43 | .cognito_client 44 | .list_users() 45 | .user_pool_id(self.user_pool_id.clone()); 46 | 47 | if let Some(ref token) = self.pagination_token { 48 | request = request.pagination_token(token.clone()); 49 | } 50 | 51 | let resp = self 52 | .rt 53 | .block_on(request.send()) 54 | .map_err(aws_sdk_cognitoidentityprovider::Error::from)?; 55 | self.pagination_token.clone_from(&resp.pagination_token); 56 | self.rows = resp 57 | .users 58 | .unwrap_or_default() 59 | .into_iter() 60 | .map(|u| u.into_row(&self.columns)) 61 | .collect::, _>>()?; 62 | 63 | self.have_more_rows = self.pagination_token.is_some(); 64 | 65 | Ok(self.get_next_row()) 66 | } 67 | 68 | fn get_next_row(&mut self) -> Option { 69 | self.rows.pop_front() 70 | } 71 | } 72 | 73 | impl Iterator for RowsIterator { 74 | type Item = CognitoFdwResult; 75 | 76 | fn next(&mut self) -> Option { 77 | if let Some(row) = self.get_next_row() { 78 | Some(Ok(row)) 79 | } else if self.have_more_rows { 80 | self.fetch_rows_batch().transpose() 81 | } else { 82 | None 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /wrappers/src/fdw/cognito_fdw/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::module_inception)] 2 | mod cognito_client; 3 | mod cognito_fdw; 4 | mod tests; 5 | 6 | use pgrx::pg_sys::panic::ErrorReport; 7 | use pgrx::PgSqlErrorCode; 8 | use thiserror::Error; 9 | 10 | use supabase_wrappers::prelude::{CreateRuntimeError, OptionsError}; 11 | 12 | #[derive(Error, Debug)] 13 | enum CognitoFdwError { 14 | #[error("{0}")] 15 | CognitoClientError(#[from] aws_sdk_cognitoidentityprovider::Error), 16 | 17 | #[error("column not supported: {0}")] 18 | UnsupportedColumn(String), 19 | 20 | #[error("{0}")] 21 | CreateRuntimeError(#[from] CreateRuntimeError), 22 | 23 | #[error("parse url failed: {0}")] 24 | UrlParseError(#[from] url::ParseError), 25 | 26 | #[error("request failed: {0}")] 27 | RequestError(#[from] reqwest::Error), 28 | 29 | #[error("request middleware failed: {0}")] 30 | RequestMiddlewareError(#[from] reqwest_middleware::Error), 31 | 32 | #[error("invalid json response: {0}")] 33 | SerdeError(#[from] serde_json::Error), 34 | 35 | #[error("{0}")] 36 | OptionsError(#[from] OptionsError), 37 | 38 | #[error("{0}")] 39 | NumericConversionError(#[from] pgrx::numeric::Error), 40 | 41 | #[error("no secret found in vault with id {0}")] 42 | SecretNotFound(String), 43 | 44 | #[error("both `api_key` and `api_secret_key` options must be set")] 45 | ApiKeyAndSecretKeySet, 46 | 47 | #[error("exactly one of `aws_secret_access_key` or `api_key_id` options must be set")] 48 | SetOneOfSecretKeyAndApiKeyIdSet, 49 | } 50 | 51 | impl From for ErrorReport { 52 | fn from(value: CognitoFdwError) -> Self { 53 | ErrorReport::new(PgSqlErrorCode::ERRCODE_FDW_ERROR, format!("{value}"), "") 54 | } 55 | } 56 | 57 | type CognitoFdwResult = Result; 58 | -------------------------------------------------------------------------------- /wrappers/src/fdw/cognito_fdw/tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(test, feature = "pg_test"))] 2 | #[pgrx::pg_schema] 3 | mod tests { 4 | use pgrx::pg_test; 5 | use pgrx::prelude::*; 6 | 7 | #[pg_test] 8 | fn cognito_smoketest() { 9 | Spi::connect_mut(|c| { 10 | c.update( 11 | r#"create foreign data wrapper cognito_wrapper 12 | handler cognito_fdw_handler validator cognito_fdw_validator"#, 13 | None, 14 | &[], 15 | ) 16 | .expect("Failed to create foreign data wrapper"); 17 | c.update( 18 | r#"CREATE SERVER cognito_server 19 | FOREIGN DATA WRAPPER cognito_wrapper 20 | OPTIONS ( 21 | aws_access_key_id 'mysecretaccesskey', 22 | aws_secret_access_key 'apiKey', 23 | endpoint_url 'http://localhost:9229/', 24 | user_pool_id 'local_6QNVVZIN', 25 | region 'ap-southeast-1' 26 | )"#, 27 | None, 28 | &[], 29 | ) 30 | .unwrap(); 31 | c.update(r#"CREATE SCHEMA IF NOT EXISTS cognito"#, None, &[]) 32 | .unwrap(); 33 | c.update( 34 | r#"IMPORT FOREIGN SCHEMA cognito FROM SERVER cognito_server INTO cognito"#, 35 | None, 36 | &[], 37 | ) 38 | .unwrap(); 39 | 40 | let results = c 41 | .select( 42 | "SELECT * FROM cognito.users WHERE email = 'test1'", 43 | None, 44 | &[], 45 | ) 46 | .expect("One record for the query") 47 | .collect::>(); 48 | assert_eq!(results.len(), 1); 49 | }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /wrappers/src/fdw/firebase_fdw/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::module_inception)] 2 | mod firebase_fdw; 3 | mod tests; 4 | 5 | use pgrx::pg_sys::panic::ErrorReport; 6 | use pgrx::prelude::PgSqlErrorCode; 7 | use std::num::ParseIntError; 8 | use thiserror::Error; 9 | 10 | use supabase_wrappers::prelude::{CreateRuntimeError, OptionsError}; 11 | 12 | #[derive(Error, Debug)] 13 | enum FirebaseFdwError { 14 | #[error("invalid service account key: {0}")] 15 | InvalidServiceAccount(#[from] std::io::Error), 16 | 17 | #[error("no token found in '{0:?}'")] 18 | NoTokenFound(yup_oauth2::AccessToken), 19 | 20 | #[error("get oauth2 token failed: {0}")] 21 | OAuthTokenError(#[from] yup_oauth2::Error), 22 | 23 | #[error("Firebase object '{0}' not implemented")] 24 | ObjectNotImplemented(String), 25 | 26 | #[error("column '{0}' data type is not supported")] 27 | UnsupportedColumnType(String), 28 | 29 | #[error("invalid timestamp format: {0}")] 30 | InvalidTimestampFormat(String), 31 | 32 | #[error("invalid Firebase response: {0}")] 33 | InvalidResponse(String), 34 | 35 | #[error("{0}")] 36 | CreateRuntimeError(#[from] CreateRuntimeError), 37 | 38 | #[error("{0}")] 39 | OptionsError(#[from] OptionsError), 40 | 41 | #[error("invalid api_key header")] 42 | InvalidApiKeyHeader, 43 | 44 | #[error("request failed: {0}")] 45 | RequestError(#[from] reqwest::Error), 46 | 47 | #[error("request middleware failed: {0}")] 48 | RequestMiddlewareError(#[from] reqwest_middleware::Error), 49 | 50 | #[error("`limit` option must be an integer: {0}")] 51 | LimitOptionParseError(#[from] ParseIntError), 52 | 53 | #[error("parse JSON response failed: {0}")] 54 | JsonParseError(#[from] serde_json::Error), 55 | } 56 | 57 | impl From for ErrorReport { 58 | fn from(value: FirebaseFdwError) -> Self { 59 | ErrorReport::new(PgSqlErrorCode::ERRCODE_FDW_ERROR, format!("{value}"), "") 60 | } 61 | } 62 | 63 | type FirebaseFdwResult = Result; 64 | -------------------------------------------------------------------------------- /wrappers/src/fdw/helloworld_fdw/README.md: -------------------------------------------------------------------------------- 1 | # HelloWorld Foreign Data Wrapper 2 | 3 | This is a demo foreign data wrapper which is developed using [Wrappers](https://github.com/supabase/wrappers). 4 | 5 | ## Basic usage 6 | 7 | These steps outline how to use the this FDW: 8 | 9 | 1. Clone this repo 10 | 11 | ```bash 12 | git clone https://github.com/supabase/wrappers.git 13 | ``` 14 | 15 | 2. Run it using pgrx with feature: 16 | 17 | ```bash 18 | cd wrappers/wrappers 19 | cargo pgrx run --features helloworld_fdw 20 | ``` 21 | 22 | 3. Create the extension, foreign data wrapper and related objects: 23 | 24 | ```sql 25 | -- create extension 26 | create extension wrappers; 27 | 28 | -- create foreign data wrapper and enable 'HelloWorldFdw' 29 | create foreign data wrapper helloworld_wrapper 30 | handler hello_world_fdw_handler 31 | validator hello_world_fdw_validator; 32 | 33 | -- create server and specify custom options 34 | create server my_helloworld_server 35 | foreign data wrapper helloworld_wrapper 36 | options ( 37 | foo 'bar' 38 | ); 39 | 40 | -- create an example foreign table 41 | create foreign table hello ( 42 | id bigint, 43 | col text 44 | ) 45 | server my_helloworld_server 46 | options ( 47 | foo 'bar' 48 | ); 49 | ``` 50 | 51 | 4. Run a query to check if it is working: 52 | 53 | ```sql 54 | wrappers=# select * from hello; 55 | id | col 56 | ----+------------- 57 | 0 | Hello world 58 | (1 row) 59 | ``` 60 | 61 | ## Changelog 62 | 63 | | Version | Date | Notes | 64 | | ------- | ---------- | ---------------------------------------------------- | 65 | | 0.1.1 | 2023-09-20 | Error reporting refactoring | 66 | | 0.1.0 | 2022-11-30 | Initial version | 67 | -------------------------------------------------------------------------------- /wrappers/src/fdw/helloworld_fdw/helloworld_fdw.rs: -------------------------------------------------------------------------------- 1 | use pgrx::pg_sys::panic::ErrorReport; 2 | use pgrx::PgSqlErrorCode; 3 | use std::collections::HashMap; 4 | use supabase_wrappers::prelude::*; 5 | 6 | // A simple demo FDW 7 | #[wrappers_fdw( 8 | version = "0.1.1", 9 | author = "Supabase", 10 | website = "https://github.com/supabase/wrappers/tree/main/wrappers/src/fdw/helloworld_fdw", 11 | error_type = "HelloWorldFdwError" 12 | )] 13 | pub(crate) struct HelloWorldFdw { 14 | // row counter 15 | row_cnt: i64, 16 | 17 | // target column list 18 | tgt_cols: Vec, 19 | } 20 | 21 | enum HelloWorldFdwError {} 22 | 23 | impl From for ErrorReport { 24 | fn from(_value: HelloWorldFdwError) -> Self { 25 | ErrorReport::new(PgSqlErrorCode::ERRCODE_FDW_ERROR, "", "") 26 | } 27 | } 28 | 29 | type HelloWorldFdwResult = Result; 30 | 31 | impl ForeignDataWrapper for HelloWorldFdw { 32 | // 'options' is the key-value pairs defined in `CREATE SERVER` SQL, for example, 33 | // 34 | // create server my_helloworld_server 35 | // foreign data wrapper wrappers_helloworld 36 | // options ( 37 | // foo 'bar' 38 | // ); 39 | // 40 | // 'options' passed here will be a hashmap { 'foo' -> 'bar' }. 41 | // 42 | // You can do any initalization in this new() function, like saving connection 43 | // info or API url in an variable, but don't do any heavy works like making a 44 | // database connection or API call. 45 | fn new(_server: ForeignServer) -> HelloWorldFdwResult { 46 | Ok(Self { 47 | row_cnt: 0, 48 | tgt_cols: Vec::new(), 49 | }) 50 | } 51 | 52 | fn begin_scan( 53 | &mut self, 54 | _quals: &[Qual], 55 | columns: &[Column], 56 | _sorts: &[Sort], 57 | _limit: &Option, 58 | _options: &HashMap, 59 | ) -> HelloWorldFdwResult<()> { 60 | // reset row counter 61 | self.row_cnt = 0; 62 | 63 | // save a copy of target columns 64 | self.tgt_cols = columns.to_vec(); 65 | 66 | Ok(()) 67 | } 68 | 69 | fn iter_scan(&mut self, row: &mut Row) -> HelloWorldFdwResult> { 70 | // this is called on each row and we only return one row here 71 | if self.row_cnt < 1 { 72 | // add values to row if they are in target column list 73 | for tgt_col in &self.tgt_cols { 74 | match tgt_col.name.as_str() { 75 | "id" => row.push("id", Some(Cell::I64(self.row_cnt))), 76 | "col" => row.push("col", Some(Cell::String("Hello world".to_string()))), 77 | _ => {} 78 | } 79 | } 80 | 81 | self.row_cnt += 1; 82 | 83 | // return Some(()) to Postgres and continue data scan 84 | return Ok(Some(())); 85 | } 86 | 87 | // return 'None' to stop data scan 88 | Ok(None) 89 | } 90 | 91 | fn end_scan(&mut self) -> HelloWorldFdwResult<()> { 92 | // we do nothing here, but you can do things like resource cleanup and etc. 93 | Ok(()) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /wrappers/src/fdw/helloworld_fdw/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::module_inception)] 2 | mod helloworld_fdw; 3 | -------------------------------------------------------------------------------- /wrappers/src/fdw/iceberg_fdw/README.md: -------------------------------------------------------------------------------- 1 | # Apache Iceberg Foreign Data Wrapper 2 | 3 | This is a foreign data wrapper for [Apache Iceberg](https://iceberg.apache.org/). It is developed using [Wrappers](https://github.com/supabase/wrappers) and only supports data scan at this moment. 4 | 5 | ## Documentation 6 | 7 | [https://fdw.dev/catalog/iceberg/](https://fdw.dev/catalog/iceberg/) 8 | 9 | ## Changelog 10 | 11 | | Version | Date | Notes | 12 | | ------- | ---------- | ---------------------------------------------------- | 13 | | 0.1.0 | 2025-05-07 | Initial version | 14 | 15 | -------------------------------------------------------------------------------- /wrappers/src/fdw/iceberg_fdw/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::module_inception)] 2 | mod iceberg_fdw; 3 | mod mapper; 4 | mod pushdown; 5 | mod tests; 6 | 7 | use pgrx::pg_sys::panic::ErrorReport; 8 | use pgrx::prelude::PgSqlErrorCode; 9 | use thiserror::Error; 10 | 11 | use supabase_wrappers::prelude::{CreateRuntimeError, OptionsError}; 12 | 13 | #[derive(Error, Debug)] 14 | enum IcebergFdwError { 15 | #[error("column {0} is not found in source")] 16 | ColumnNotFound(String), 17 | 18 | #[error("column '{0}' data type is not supported")] 19 | UnsupportedColumnType(String), 20 | 21 | #[error("column '{0}' data type '{1}' is incompatible")] 22 | IncompatibleColumnType(String, String), 23 | 24 | #[error("cannot import column '{0}' data type '{1}'")] 25 | ImportColumnError(String, String), 26 | 27 | #[error("vault error: '{0}'")] 28 | VaultError(String), 29 | 30 | #[error("decimal conversion error: {0}")] 31 | DecimalConversionError(#[from] rust_decimal::Error), 32 | 33 | #[error("parse float error: {0}")] 34 | ParseFloatError(#[from] std::num::ParseFloatError), 35 | 36 | #[error("datetime conversion error: {0}")] 37 | DatetimeConversionError(#[from] pgrx::datum::datetime_support::DateTimeConversionError), 38 | 39 | #[error("datum conversion error: {0}")] 40 | DatumConversionError(String), 41 | 42 | #[error("uuid error: {0}")] 43 | UuidConversionError(#[from] uuid::Error), 44 | 45 | #[error("numeric error: {0}")] 46 | NumericError(#[from] pgrx::datum::numeric_support::error::Error), 47 | 48 | #[error("iceberg error: {0}")] 49 | IcebergError(#[from] iceberg::Error), 50 | 51 | #[error("arrow error: {0}")] 52 | ArrowError(#[from] arrow_schema::ArrowError), 53 | 54 | #[error("json error: {0}")] 55 | JsonError(#[from] serde_json::Error), 56 | 57 | #[error("{0}")] 58 | CreateRuntimeError(#[from] CreateRuntimeError), 59 | 60 | #[error("{0}")] 61 | OptionsError(#[from] OptionsError), 62 | 63 | #[error("{0}")] 64 | IoError(#[from] std::io::Error), 65 | } 66 | 67 | impl From for ErrorReport { 68 | fn from(value: IcebergFdwError) -> Self { 69 | ErrorReport::new(PgSqlErrorCode::ERRCODE_FDW_ERROR, format!("{value}"), "") 70 | } 71 | } 72 | 73 | type IcebergFdwResult = Result; 74 | -------------------------------------------------------------------------------- /wrappers/src/fdw/logflare_fdw/README.md: -------------------------------------------------------------------------------- 1 | # Logflare Foreign Data Wrapper 2 | 3 | This is a foreign data wrapper for [Logflare](https://logflare.app/). It is developed using [Wrappers](https://github.com/supabase/wrappers) and only supports data scan at this moment. 4 | 5 | ## Documentation 6 | 7 | [https://fdw.dev/catalog/logflare/](https://fdw.dev/catalog/logflare/) 8 | 9 | ## Changelog 10 | 11 | | Version | Date | Notes | 12 | | ------- | ---------- | ---------------------------------------------------- | 13 | | 0.1.0 | 2023-06-27 | Initial version | 14 | -------------------------------------------------------------------------------- /wrappers/src/fdw/logflare_fdw/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::module_inception)] 2 | mod logflare_fdw; 3 | mod tests; 4 | 5 | use http::header::InvalidHeaderValue; 6 | use pgrx::pg_sys::panic::ErrorReport; 7 | use pgrx::prelude::PgSqlErrorCode; 8 | use thiserror::Error; 9 | 10 | use supabase_wrappers::prelude::{CreateRuntimeError, OptionsError}; 11 | 12 | #[derive(Error, Debug)] 13 | enum LogflareFdwError { 14 | #[error("parameter '{0}' only supports '=' operator")] 15 | NoEqualParameter(String), 16 | 17 | #[error("parameter '{0}' doesn't supports array value")] 18 | NoArrayParameter(String), 19 | 20 | #[error("column '{0}' data type is not supported")] 21 | UnsupportedColumnType(String), 22 | 23 | #[error("column '{0}' data type not match")] 24 | ColumnTypeNotMatch(String), 25 | 26 | #[error("invalid Logflare response: {0}")] 27 | InvalidResponse(String), 28 | 29 | #[error("{0}")] 30 | OptionsError(#[from] OptionsError), 31 | 32 | #[error("{0}")] 33 | CreateRuntimeError(#[from] CreateRuntimeError), 34 | 35 | #[error("parse url failed: {0}")] 36 | UrlParseError(#[from] url::ParseError), 37 | 38 | #[error("invalid api_key header: {0}")] 39 | InvalidApiKeyHeader(#[from] InvalidHeaderValue), 40 | 41 | #[error("request failed: {0}")] 42 | RequestError(#[from] reqwest::Error), 43 | 44 | #[error("request middleware failed: {0}")] 45 | RequestMiddlewareError(#[from] reqwest_middleware::Error), 46 | 47 | #[error("parse JSON response failed: {0}")] 48 | JsonParseError(#[from] serde_json::Error), 49 | } 50 | 51 | impl From for ErrorReport { 52 | fn from(value: LogflareFdwError) -> Self { 53 | ErrorReport::new(PgSqlErrorCode::ERRCODE_FDW_ERROR, format!("{value}"), "") 54 | } 55 | } 56 | 57 | type LogflareFdwResult = Result; 58 | -------------------------------------------------------------------------------- /wrappers/src/fdw/logflare_fdw/tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(test, feature = "pg_test"))] 2 | #[pgrx::pg_schema] 3 | mod tests { 4 | use pgrx::prelude::*; 5 | 6 | #[pg_test] 7 | fn logflare_smoketest() { 8 | Spi::connect_mut(|c| { 9 | c.update( 10 | r#"CREATE FOREIGN DATA WRAPPER logflare_wrapper 11 | HANDLER logflare_fdw_handler VALIDATOR logflare_fdw_validator"#, 12 | None, 13 | &[], 14 | ) 15 | .unwrap(); 16 | c.update( 17 | r#"CREATE SERVER logflare_server 18 | FOREIGN DATA WRAPPER logflare_wrapper 19 | OPTIONS ( 20 | api_url 'http://localhost:4343/v1/endpoint', 21 | api_key 'apiKey' 22 | )"#, 23 | None, 24 | &[], 25 | ) 26 | .unwrap(); 27 | c.update( 28 | r#" 29 | CREATE FOREIGN TABLE logflare_table ( 30 | id text, 31 | timestamp bigint, 32 | event_message text, 33 | _result text 34 | ) 35 | SERVER logflare_server 36 | OPTIONS ( 37 | endpoint '3d48cbfe-5d65-494f-be06-910479aed6c1' 38 | ) 39 | "#, 40 | None, 41 | &[], 42 | ) 43 | .unwrap(); 44 | 45 | let results = c 46 | .select("SELECT * FROM logflare_table", None, &[]) 47 | .unwrap() 48 | .filter_map(|r| r.get_by_name::<&str, _>("id").unwrap()) 49 | .collect::>(); 50 | 51 | assert_eq!( 52 | results, 53 | vec![ 54 | "84e1ed2a-3627-4d70-b311-c0e7c0bed313", 55 | "f45121ea-1738-46c9-a506-9ee52ff8220f" 56 | ] 57 | ); 58 | }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /wrappers/src/fdw/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "helloworld_fdw")] 2 | mod helloworld_fdw; 3 | 4 | #[cfg(feature = "bigquery_fdw")] 5 | mod bigquery_fdw; 6 | 7 | #[cfg(feature = "clickhouse_fdw")] 8 | mod clickhouse_fdw; 9 | 10 | #[cfg(feature = "stripe_fdw")] 11 | mod stripe_fdw; 12 | 13 | #[cfg(feature = "firebase_fdw")] 14 | mod firebase_fdw; 15 | 16 | #[cfg(feature = "airtable_fdw")] 17 | mod airtable_fdw; 18 | 19 | #[cfg(feature = "s3_fdw")] 20 | mod s3_fdw; 21 | 22 | #[cfg(feature = "logflare_fdw")] 23 | mod logflare_fdw; 24 | 25 | #[cfg(feature = "auth0_fdw")] 26 | mod auth0_fdw; 27 | 28 | #[cfg(feature = "mssql_fdw")] 29 | mod mssql_fdw; 30 | 31 | #[cfg(feature = "redis_fdw")] 32 | mod redis_fdw; 33 | 34 | #[cfg(feature = "cognito_fdw")] 35 | mod cognito_fdw; 36 | 37 | #[cfg(feature = "wasm_fdw")] 38 | mod wasm_fdw; 39 | 40 | #[cfg(feature = "iceberg_fdw")] 41 | mod iceberg_fdw; 42 | -------------------------------------------------------------------------------- /wrappers/src/fdw/mssql_fdw/README.md: -------------------------------------------------------------------------------- 1 | # SQL Server Foreign Data Wrapper 2 | 3 | This is a foreign data wrapper for [Microsoft SQL Server](https://www.microsoft.com/en-au/sql-server/). It is developed using [Wrappers](https://github.com/supabase/wrappers) and only supports data scan at this moment. 4 | 5 | ## Documentation 6 | 7 | [https://fdw.dev/catalog/mssql/](https://fdw.dev/catalog/mssql/) 8 | 9 | ## Changelog 10 | 11 | | Version | Date | Notes | 12 | | ------- | ---------- | ---------------------------------------------------- | 13 | | 0.1.3 | 2025-02-12 | Fix Numeric type conversion error | 14 | | 0.1.2 | 2024-09-30 | Support for pgrx 0.12.6 | 15 | | 0.1.1 | 2024-09-09 | Add boolean test qual support | 16 | | 0.1.0 | 2023-12-27 | Initial version | 17 | 18 | -------------------------------------------------------------------------------- /wrappers/src/fdw/mssql_fdw/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::module_inception)] 2 | mod mssql_fdw; 3 | mod tests; 4 | 5 | use pgrx::pg_sys::panic::ErrorReport; 6 | use pgrx::prelude::PgSqlErrorCode; 7 | use thiserror::Error; 8 | 9 | use supabase_wrappers::prelude::{CreateRuntimeError, OptionsError}; 10 | 11 | #[derive(Error, Debug)] 12 | enum MssqlFdwError { 13 | #[error("syntax error: {0}")] 14 | SyntaxError(String), 15 | 16 | #[error("column '{0}' data type is not supported")] 17 | UnsupportedColumnType(String), 18 | 19 | #[error("column conversion failure: {0}")] 20 | ConversionError(#[from] std::num::TryFromIntError), 21 | 22 | #[error("{0}")] 23 | TiberiusError(#[from] tiberius::error::Error), 24 | 25 | #[error("{0}")] 26 | PgrxNumericError(#[from] pgrx::datum::numeric_support::error::Error), 27 | 28 | #[error("{0}")] 29 | CreateRuntimeError(#[from] CreateRuntimeError), 30 | 31 | #[error("{0}")] 32 | OptionsError(#[from] OptionsError), 33 | 34 | #[error("{0}")] 35 | IoError(#[from] std::io::Error), 36 | } 37 | 38 | impl From for ErrorReport { 39 | fn from(value: MssqlFdwError) -> Self { 40 | ErrorReport::new(PgSqlErrorCode::ERRCODE_FDW_ERROR, format!("{value}"), "") 41 | } 42 | } 43 | 44 | type MssqlFdwResult = Result; 45 | -------------------------------------------------------------------------------- /wrappers/src/fdw/redis_fdw/README.md: -------------------------------------------------------------------------------- 1 | # Redis Foreign Data Wrapper 2 | 3 | This is a foreign data wrapper for [Redis](https://redis.io/). It is developed using [Wrappers](https://github.com/supabase/wrappers) and only supports data scan at this moment. 4 | 5 | ## Documentation 6 | 7 | [https://fdw.dev/catalog/redis/](https://fdw.dev/catalog/redis/) 8 | 9 | ## Changelog 10 | 11 | | Version | Date | Notes | 12 | | ------- | ---------- | ---------------------------------------------------- | 13 | | 0.1.1 | 2024-11-28 | Added TLS support | 14 | | 0.1.0 | 2023-12-29 | Initial version | 15 | -------------------------------------------------------------------------------- /wrappers/src/fdw/redis_fdw/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::module_inception)] 2 | mod redis_fdw; 3 | mod tests; 4 | 5 | use pgrx::pg_sys::panic::ErrorReport; 6 | use pgrx::prelude::PgSqlErrorCode; 7 | use redis::RedisError; 8 | use thiserror::Error; 9 | 10 | use supabase_wrappers::prelude::OptionsError; 11 | 12 | #[derive(Error, Debug)] 13 | enum RedisFdwError { 14 | #[error("'{0}' source type is not supported")] 15 | UnsupportedSourceType(String), 16 | 17 | #[error("'{0}' foreign table can only have one column")] 18 | OnlyOneColumn(String), 19 | 20 | #[error("'{0}' foreign table can only have two columns")] 21 | OnlyTwoColumn(String), 22 | 23 | #[error("column '{0}' name is not supported")] 24 | UnsupportedColumnName(String), 25 | 26 | #[error("column '{0}' data type is not supported")] 27 | UnsupportedColumnType(String), 28 | 29 | #[error("{0}")] 30 | RedisError(#[from] RedisError), 31 | 32 | #[error("{0}")] 33 | OptionsError(#[from] OptionsError), 34 | 35 | #[error("{0}")] 36 | IoError(#[from] std::io::Error), 37 | } 38 | 39 | impl From for ErrorReport { 40 | fn from(value: RedisFdwError) -> Self { 41 | ErrorReport::new(PgSqlErrorCode::ERRCODE_FDW_ERROR, format!("{value}"), "") 42 | } 43 | } 44 | 45 | type RedisFdwResult = Result; 46 | -------------------------------------------------------------------------------- /wrappers/src/fdw/s3_fdw/README.md: -------------------------------------------------------------------------------- 1 | # AWS S3 Foreign Data Wrapper 2 | 3 | This is a foreign data wrapper for [AWS S3](https://aws.amazon.com/s3/). It is developed using [Wrappers](https://github.com/supabase/wrappers) and only supports data scan at this moment. 4 | 5 | ## Documentation 6 | 7 | [https://fdw.dev/catalog/s3/](https://fdw.dev/catalog/s3/) 8 | 9 | ## Changelog 10 | 11 | | Version | Date | Notes | 12 | | ------- | ---------- | ---------------------------------------------------- | 13 | | 0.1.4 | 2024-08-20 | Added `path_style_url` server option | 14 | | 0.1.2 | 2023-07-13 | Added fdw stats collection | 15 | | 0.1.1 | 2023-06-05 | Added Parquet file support | 16 | | 0.1.0 | 2023-03-01 | Initial version | 17 | -------------------------------------------------------------------------------- /wrappers/src/fdw/s3_fdw/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::module_inception)] 2 | mod parquet; 3 | mod s3_fdw; 4 | mod tests; 5 | 6 | use aws_sdk_s3::config::http::HttpResponse; 7 | use aws_sdk_s3::error::SdkError; 8 | use aws_sdk_s3::operation::get_object::GetObjectError; 9 | use pgrx::pg_sys::panic::ErrorReport; 10 | use pgrx::prelude::PgSqlErrorCode; 11 | use thiserror::Error; 12 | 13 | use supabase_wrappers::prelude::{CreateRuntimeError, OptionsError}; 14 | 15 | #[derive(Error, Debug)] 16 | enum S3FdwError { 17 | #[error("invalid s3 uri: {0}")] 18 | InvalidS3Uri(String), 19 | 20 | #[error("invalid format option: '{0}', it can only be 'csv', 'jsonl' or 'parquet'")] 21 | InvalidFormatOption(String), 22 | 23 | #[error("invalid compression option: {0}")] 24 | InvalidCompressOption(String), 25 | 26 | #[error("read line failed: {0}")] 27 | ReadLineError(#[from] std::io::Error), 28 | 29 | #[error("read csv record failed: {0}")] 30 | ReadCsvError(#[from] csv::Error), 31 | 32 | #[error("read jsonl record failed: {0}")] 33 | ReadJsonlError(String), 34 | 35 | #[error("read parquet failed: {0}")] 36 | ReadParquetError(#[from] ::parquet::errors::ParquetError), 37 | 38 | #[error("column '{0}' data type is not supported")] 39 | UnsupportedColumnType(String), 40 | 41 | #[error("column '{0}' data type not match")] 42 | ColumnTypeNotMatch(String), 43 | 44 | #[error("column {0} not found in parquet file")] 45 | ColumnNotFound(String), 46 | 47 | #[error("{0}")] 48 | OptionsError(#[from] OptionsError), 49 | 50 | #[error("{0}")] 51 | CreateRuntimeError(#[from] CreateRuntimeError), 52 | 53 | #[error("parse uri failed: {0}")] 54 | UriParseError(#[from] http::uri::InvalidUri), 55 | 56 | #[error("request failed: {0}")] 57 | RequestError(#[from] SdkError), 58 | 59 | #[error("parse JSON response failed: {0}")] 60 | JsonParseError(#[from] serde_json::Error), 61 | 62 | #[error("{0}")] 63 | NumericConversionError(#[from] pgrx::numeric::Error), 64 | } 65 | 66 | impl From for ErrorReport { 67 | fn from(value: S3FdwError) -> Self { 68 | ErrorReport::new(PgSqlErrorCode::ERRCODE_FDW_ERROR, format!("{value}"), "") 69 | } 70 | } 71 | 72 | type S3FdwResult = Result; 73 | -------------------------------------------------------------------------------- /wrappers/src/fdw/stripe_fdw/README.md: -------------------------------------------------------------------------------- 1 | # Stripe Foreign Data Wrapper 2 | 3 | This is a foreign data wrapper for [Stripe](https://stripe.com/) developed using [Wrappers](https://github.com/supabase/wrappers). 4 | 5 | ## Documentation 6 | 7 | [https://fdw.dev/catalog/stripe/](https://fdw.dev/catalog/stripe/) 8 | 9 | ## Changelog 10 | 11 | | Version | Date | Notes | 12 | | ------- | ---------- | ---------------------------------------------------- | 13 | | 0.1.12 | 2025-03-06 | Added import foreign schema support | 14 | | 0.1.11 | 2024-09-20 | Added Meter object | 15 | | 0.1.10 | 2024-08-26 | Added 'api_key_name' server option | 16 | | 0.1.9 | 2024-07-01 | Added 'api_version' server option | 17 | | 0.1.7 | 2023-07-13 | Added fdw stats collection | 18 | | 0.1.6 | 2023-05-30 | Added Checkout Session object | 19 | | 0.1.5 | 2023-05-01 | Added 'prices' object and empty result improvement | 20 | | 0.1.4 | 2023-02-21 | Added Connect objects | 21 | | 0.1.3 | 2022-12-21 | Added more core objects | 22 | | 0.1.2 | 2022-12-04 | Added 'products' objects support | 23 | | 0.1.1 | 2022-12-03 | Added quals pushdown support | 24 | | 0.1.0 | 2022-12-01 | Initial version | 25 | -------------------------------------------------------------------------------- /wrappers/src/fdw/stripe_fdw/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::module_inception)] 2 | mod stripe_fdw; 3 | mod tests; 4 | 5 | use http::header::InvalidHeaderValue; 6 | use pgrx::pg_sys::panic::ErrorReport; 7 | use pgrx::prelude::PgSqlErrorCode; 8 | use thiserror::Error; 9 | 10 | use supabase_wrappers::prelude::{CreateRuntimeError, OptionsError}; 11 | 12 | #[derive(Error, Debug)] 13 | enum StripeFdwError { 14 | #[error("column '{0}' data type is not supported")] 15 | UnsupportedColumnType(String), 16 | 17 | #[error("Stripe object '{0}' not implemented")] 18 | ObjectNotImplemented(String), 19 | 20 | #[error("{0}")] 21 | OptionsError(#[from] OptionsError), 22 | 23 | #[error("{0}")] 24 | CreateRuntimeError(#[from] CreateRuntimeError), 25 | 26 | #[error("parse url failed: {0}")] 27 | UrlParseError(#[from] url::ParseError), 28 | 29 | #[error("request failed: {0}")] 30 | RequestError(#[from] reqwest::Error), 31 | 32 | #[error("request middleware failed: {0}")] 33 | RequestMiddlewareError(#[from] reqwest_middleware::Error), 34 | 35 | #[error("parse JSON response failed: {0}")] 36 | JsonParseError(#[from] serde_json::Error), 37 | 38 | #[error("invalid api_key header: {0}")] 39 | InvalidApiKeyHeader(#[from] InvalidHeaderValue), 40 | 41 | #[error("invalid response")] 42 | InvalidResponse, 43 | 44 | #[error("invalid stats: {0}")] 45 | InvalidStats(String), 46 | } 47 | 48 | impl From for ErrorReport { 49 | fn from(value: StripeFdwError) -> Self { 50 | ErrorReport::new(PgSqlErrorCode::ERRCODE_FDW_ERROR, format!("{value}"), "") 51 | } 52 | } 53 | 54 | type StripeFdwResult = Result; 55 | -------------------------------------------------------------------------------- /wrappers/src/fdw/wasm_fdw/README.md: -------------------------------------------------------------------------------- 1 | # Wasm Foreign Data Wrapper 2 | 3 | This is Wasm foreign data wrapper host, please visit each Wasm foreign data wrapper documentation. 4 | 5 | ## Changelog 6 | 7 | | Version | Date | Notes | 8 | | ------- | ---------- | ---------------------------------------------------- | 9 | | 0.1.5 | 2025-04-30 | Add 'import foreign schema' support | 10 | | 0.1.4 | 2024-12-09 | Improve remote wasm downloading and caching | 11 | | 0.1.3 | 2024-09-30 | Support for pgrx 0.12.6 | 12 | | 0.1.2 | 2024-07-07 | Add fdw_package_checksum server option | 13 | | 0.1.1 | 2024-07-05 | Fix missing wasm package cache dir issue | 14 | | 0.1.0 | 2024-07-03 | Initial version | 15 | 16 | -------------------------------------------------------------------------------- /wrappers/src/fdw/wasm_fdw/bindings/mod.rs: -------------------------------------------------------------------------------- 1 | pub(super) mod v1; 2 | pub(super) mod v2; 3 | 4 | // 'pg epoch' (2000-01-01 00:00:00) in macroseconds and seconds 5 | const PG_EPOCH_MS: i64 = 946_684_800_000_000; 6 | const PG_EPOCH_SEC: i64 = 946_684_800; 7 | -------------------------------------------------------------------------------- /wrappers/src/fdw/wasm_fdw/host/jwt.rs: -------------------------------------------------------------------------------- 1 | use jwt_simple::prelude::*; 2 | 3 | use super::FdwHost; 4 | 5 | const _: () = { 6 | use super::super::bindings::v1::supabase::wrappers::jwt; 7 | 8 | impl jwt::Host for FdwHost { 9 | fn encode( 10 | &mut self, 11 | payload: Vec<(String, String)>, 12 | algo: String, 13 | key: String, 14 | ttl_hours: u32, 15 | ) -> jwt::JwtResult { 16 | let mut claims = Claims::create(Duration::from_hours(ttl_hours as u64)); 17 | for (claim, value) in payload { 18 | match claim.as_str() { 19 | "iss" => { 20 | claims = claims.with_issuer(value); 21 | } 22 | "sub" => { 23 | claims = claims.with_subject(value); 24 | } 25 | _ => return Err(format!("claim {} not implemented", claim)), 26 | } 27 | } 28 | 29 | match algo.as_str() { 30 | "RS256" => RS256KeyPair::from_pem(&key), 31 | _ => return Err(format!("algorithm {} not implemented", algo)), 32 | } 33 | .and_then(|keypair| keypair.sign(claims)) 34 | .map_err(|e| e.to_string()) 35 | } 36 | } 37 | }; 38 | 39 | const _: () = { 40 | use super::super::bindings::v1::supabase::wrappers::jwt as jwt_v1; 41 | use super::super::bindings::v2::supabase::wrappers::jwt; 42 | 43 | impl jwt::Host for FdwHost { 44 | fn encode( 45 | &mut self, 46 | payload: Vec<(String, String)>, 47 | algo: String, 48 | key: String, 49 | ttl_hours: u32, 50 | ) -> jwt::JwtResult { 51 | jwt_v1::Host::encode(self, payload, algo, key, ttl_hours) 52 | } 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /wrappers/src/fdw/wasm_fdw/host/stats.rs: -------------------------------------------------------------------------------- 1 | use crate::stats as host_stats; 2 | use pgrx::JsonB; 3 | 4 | use super::FdwHost; 5 | 6 | const _: () = { 7 | use super::super::bindings::v1::supabase::wrappers::stats; 8 | 9 | impl stats::Host for FdwHost { 10 | fn inc_stats(&mut self, fdw_name: String, metric: stats::Metric, inc: i64) { 11 | host_stats::inc_stats(&fdw_name, host_stats::Metric::from(metric), inc); 12 | } 13 | 14 | fn get_metadata(&mut self, fdw_name: String) -> Option { 15 | host_stats::get_metadata(&fdw_name).map(|m| m.0.to_string()) 16 | } 17 | 18 | fn set_metadata(&mut self, fdw_name: String, metadata: Option) { 19 | let jsonb = metadata.map(|m| JsonB(serde_json::from_str(&m).unwrap_or_default())); 20 | host_stats::set_metadata(&fdw_name, jsonb); 21 | } 22 | } 23 | }; 24 | 25 | const _: () = { 26 | use super::super::bindings::v1::supabase::wrappers::stats as stats_v1; 27 | use super::super::bindings::v2::supabase::wrappers::stats; 28 | 29 | impl From for stats_v1::Metric { 30 | fn from(m: stats::Metric) -> Self { 31 | match m { 32 | stats::Metric::CreateTimes => stats_v1::Metric::CreateTimes, 33 | stats::Metric::RowsIn => stats_v1::Metric::RowsIn, 34 | stats::Metric::RowsOut => stats_v1::Metric::RowsOut, 35 | stats::Metric::BytesIn => stats_v1::Metric::BytesIn, 36 | stats::Metric::BytesOut => stats_v1::Metric::BytesOut, 37 | } 38 | } 39 | } 40 | 41 | impl stats::Host for FdwHost { 42 | fn inc_stats(&mut self, fdw_name: String, metric: stats::Metric, inc: i64) { 43 | stats_v1::Host::inc_stats(self, fdw_name, metric.into(), inc); 44 | } 45 | 46 | fn get_metadata(&mut self, fdw_name: String) -> Option { 47 | stats_v1::Host::get_metadata(self, fdw_name) 48 | } 49 | 50 | fn set_metadata(&mut self, fdw_name: String, metadata: Option) { 51 | stats_v1::Host::set_metadata(self, fdw_name, metadata); 52 | } 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /wrappers/src/fdw/wasm_fdw/host/time.rs: -------------------------------------------------------------------------------- 1 | use chrono::DateTime; 2 | use std::time::{SystemTime, UNIX_EPOCH}; 3 | 4 | use super::FdwHost; 5 | 6 | const _: () = { 7 | use super::super::bindings::v1::supabase::wrappers::time; 8 | 9 | impl time::Host for FdwHost { 10 | fn epoch_secs(&mut self) -> i64 { 11 | SystemTime::now() 12 | .duration_since(UNIX_EPOCH) 13 | .expect("SystemTime before UNIX EPOCH!") 14 | .as_secs() as i64 15 | } 16 | 17 | fn parse_from_rfc3339(&mut self, s: String) -> time::TimeResult { 18 | DateTime::parse_from_rfc3339(&s) 19 | .map(|ts| ts.timestamp_micros()) 20 | .map_err(|e| e.to_string()) 21 | } 22 | 23 | fn parse_from_str(&mut self, s: String, fmt: String) -> time::TimeResult { 24 | DateTime::parse_from_str(&s, &fmt) 25 | .map(|ts| ts.timestamp_micros()) 26 | .map_err(|e| e.to_string()) 27 | } 28 | 29 | fn epoch_ms_to_rfc3339(&mut self, msecs: i64) -> Result { 30 | DateTime::from_timestamp_micros(msecs) 31 | .map(|ts| ts.to_rfc3339()) 32 | .ok_or("invalid microseconds since Unix Epoch".to_string()) 33 | } 34 | 35 | fn sleep(&mut self, millis: u64) { 36 | std::thread::sleep(std::time::Duration::from_millis(millis)); 37 | } 38 | } 39 | }; 40 | 41 | const _: () = { 42 | use super::super::bindings::v1::supabase::wrappers::time as time_v1; 43 | use super::super::bindings::v2::supabase::wrappers::time; 44 | 45 | impl time::Host for FdwHost { 46 | fn epoch_secs(&mut self) -> i64 { 47 | time_v1::Host::epoch_secs(self) 48 | } 49 | 50 | fn parse_from_rfc3339(&mut self, s: String) -> time::TimeResult { 51 | time_v1::Host::parse_from_rfc3339(self, s) 52 | } 53 | 54 | fn parse_from_str(&mut self, s: String, fmt: String) -> time::TimeResult { 55 | time_v1::Host::parse_from_str(self, s, fmt) 56 | } 57 | 58 | fn epoch_ms_to_rfc3339(&mut self, msecs: i64) -> Result { 59 | time_v1::Host::epoch_ms_to_rfc3339(self, msecs) 60 | } 61 | 62 | fn sleep(&mut self, millis: u64) { 63 | time_v1::Host::sleep(self, millis) 64 | } 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /wrappers/src/fdw/wasm_fdw/host/utils.rs: -------------------------------------------------------------------------------- 1 | use pgrx::prelude::PgSqlErrorCode; 2 | 3 | use supabase_wrappers::prelude::*; 4 | 5 | use super::FdwHost; 6 | 7 | const _: () = { 8 | use super::super::bindings::v1::supabase::wrappers::{types::Cell as GuestCell, utils}; 9 | 10 | impl utils::Host for FdwHost { 11 | fn report_info(&mut self, msg: String) { 12 | report_info(&msg); 13 | } 14 | 15 | fn report_notice(&mut self, msg: String) { 16 | report_notice(&msg); 17 | } 18 | 19 | fn report_warning(&mut self, msg: String) { 20 | report_warning(&msg); 21 | } 22 | 23 | fn report_error(&mut self, msg: String) { 24 | report_error(PgSqlErrorCode::ERRCODE_FDW_ERROR, &msg); 25 | } 26 | 27 | fn cell_to_string(&mut self, cell: Option) -> String { 28 | match cell { 29 | Some(c) => Cell::try_from(c) 30 | .map(|a| a.to_string()) 31 | .expect("convert cell failed"), 32 | None => "null".to_string(), 33 | } 34 | } 35 | 36 | fn get_vault_secret(&mut self, secret_id: String) -> Option { 37 | get_vault_secret(&secret_id) 38 | } 39 | } 40 | }; 41 | 42 | const _: () = { 43 | use super::super::bindings::v2::supabase::wrappers::{types::Cell as GuestCell, utils}; 44 | 45 | impl utils::Host for FdwHost { 46 | fn report_info(&mut self, msg: String) { 47 | report_info(&msg); 48 | } 49 | 50 | fn report_notice(&mut self, msg: String) { 51 | report_notice(&msg); 52 | } 53 | 54 | fn report_warning(&mut self, msg: String) { 55 | report_warning(&msg); 56 | } 57 | 58 | fn report_error(&mut self, msg: String) { 59 | report_error(PgSqlErrorCode::ERRCODE_FDW_ERROR, &msg); 60 | } 61 | 62 | fn cell_to_string(&mut self, cell: Option) -> String { 63 | match cell { 64 | Some(c) => Cell::try_from(c) 65 | .map(|a| a.to_string()) 66 | .expect("convert cell failed"), 67 | None => "null".to_string(), 68 | } 69 | } 70 | 71 | fn get_vault_secret(&mut self, secret_id: String) -> Option { 72 | get_vault_secret(&secret_id) 73 | } 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /wrappers/src/fdw/wasm_fdw/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::module_inception)] 2 | mod bindings; 3 | mod host; 4 | mod tests; 5 | mod wasm_fdw; 6 | 7 | use pgrx::pg_sys::panic::ErrorReport; 8 | use pgrx::prelude::PgSqlErrorCode; 9 | use thiserror::Error; 10 | 11 | use supabase_wrappers::prelude::{CreateRuntimeError, OptionsError}; 12 | 13 | use self::bindings::v1::supabase::wrappers::types::FdwError as GuestFdwError; 14 | 15 | #[derive(Error, Debug)] 16 | enum WasmFdwError { 17 | #[error("invalid WebAssembly component")] 18 | InvalidWasmComponent, 19 | 20 | #[error("guest fdw error: {0}")] 21 | GuestFdw(GuestFdwError), 22 | 23 | #[error("semver error: {0}")] 24 | Semver(#[from] semver::Error), 25 | 26 | #[error("wasmtime error: {0}")] 27 | Wasmtime(#[from] wasmtime::Error), 28 | 29 | #[error("uuid error: {0}")] 30 | Uuid(#[from] uuid::Error), 31 | 32 | #[error("warg error: {0}")] 33 | WargClient(#[from] warg_client::ClientError), 34 | 35 | #[error("request failed: {0}")] 36 | Request(#[from] reqwest::Error), 37 | 38 | #[error("io error: {0}")] 39 | Io(#[from] std::io::Error), 40 | 41 | #[error("{0}")] 42 | CreateRuntime(#[from] CreateRuntimeError), 43 | 44 | #[error("{0}")] 45 | Options(#[from] OptionsError), 46 | } 47 | 48 | impl From for WasmFdwError { 49 | fn from(value: GuestFdwError) -> Self { 50 | Self::GuestFdw(value) 51 | } 52 | } 53 | 54 | impl From for ErrorReport { 55 | fn from(value: WasmFdwError) -> Self { 56 | ErrorReport::new(PgSqlErrorCode::ERRCODE_FDW_ERROR, format!("{value}"), "") 57 | } 58 | } 59 | 60 | type WasmFdwResult = Result; 61 | -------------------------------------------------------------------------------- /wrappers/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Wrappers is a development framework for Postgres Foreign Data Wrappers (FDW). 2 | //! This crate provides the core functionality and implementations for various FDWs. 3 | //! 4 | //! # Features 5 | //! - Easy to implement FDW interface 6 | //! - Support for rich data types 7 | //! - Support both sync and async backends 8 | //! - Built on top of pgrx 9 | 10 | use pgrx::prelude::*; 11 | use std::sync::OnceLock; 12 | 13 | // crypto primitive provider initialization required by rustls > v0.22. 14 | // It is not required by every FDW, but only call it when needed. 15 | // ref: https://docs.rs/rustls/latest/rustls/index.html#cryptography-providers 16 | static RUSTLS_CRYPTO_PROVIDER_LOCK: OnceLock<()> = OnceLock::new(); 17 | 18 | #[allow(dead_code)] 19 | fn setup_rustls_default_crypto_provider() { 20 | RUSTLS_CRYPTO_PROVIDER_LOCK.get_or_init(|| { 21 | rustls::crypto::aws_lc_rs::default_provider() 22 | .install_default() 23 | .unwrap() 24 | }); 25 | } 26 | 27 | pg_module_magic!(); 28 | 29 | extension_sql_file!("../sql/bootstrap.sql", bootstrap); 30 | extension_sql_file!("../sql/finalize.sql", finalize); 31 | 32 | /// FDW implementations for various data sources 33 | pub mod fdw; 34 | /// Statistics collection and reporting utilities 35 | pub mod stats; 36 | 37 | #[cfg(test)] 38 | pub mod pg_test { 39 | pub fn setup(_options: Vec<&str>) { 40 | // noop 41 | } 42 | 43 | pub fn postgresql_conf_options() -> Vec<&'static str> { 44 | vec![] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /wrappers/wrappers.control: -------------------------------------------------------------------------------- 1 | comment = 'Foreign data wrappers developed by Supabase' 2 | default_version = '@CARGO_VERSION@' 3 | # commenting-out module_pathname results in this extension being built/run/tested in "versioned shared-object mode" 4 | #module_pathname = '$libdir/wrappers' 5 | relocatable = true 6 | superuser = false 7 | --------------------------------------------------------------------------------