Skip to content

Commit

Permalink
Merge pull request #134 from supabase/feat/airtable-view
Browse files Browse the repository at this point in the history
feat: add view support for airtable fdw
  • Loading branch information
burmecia authored Aug 30, 2023
2 parents 182b165 + 69c712f commit 2878ec4
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 10 deletions.
21 changes: 21 additions & 0 deletions docs/airtable.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ The full list of foreign table options are below:

- `base_id` - Airtable Base ID the table belongs to, required.
- `table_id` - Airtable table ID, required.
- `view_id` - Airtable view ID, optional.

## Examples

Expand Down Expand Up @@ -114,3 +115,23 @@ You can now fetch your Airtable data from within your Postgres database:
```sql
select * from airtable_table;
```

We can also create a foreign table from an Airtable View called `airtable_view`:

```sql
create foreign table airtable_view (
name text,
notes text,
content text,
amount numeric,
updated_at timestamp
)
server airtable_server
options (
base_id 'appTc3yI68KN6ukZc',
table_id 'tbltiLinE56l3YKfn',
view_id 'viwY8si0zcEzw3ntZ'
);

select * from airtable_view;
```
16 changes: 12 additions & 4 deletions wrappers/dockerfiles/airtable/server.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
from http.server import BaseHTTPRequestHandler, HTTPServer
import datetime, json
from urllib.parse import urlparse
from urllib.parse import urlparse, parse_qs
import airtablemock

hostName = "0.0.0.0"
serverPort = 8086
base_id = 'baseID'
test_table = 'table-foo'
test_view = 'view-bar'

# This is a client for the base "baseID", it will not access the real
# Airtable service but only the mock one which keeps data in RAM.
client = airtablemock.Airtable('baseID', 'apiKey')
test_table = 'table-foo'
client = airtablemock.Airtable(base_id, 'apiKey')

class AirtableMockServer(BaseHTTPRequestHandler):
def do_GET(self):
path = urlparse(self.path)
[_, base_id, table_id] = path.path.split('/')
views = parse_qs(path.query).get('view')
view = views[0] if views else None

records = client.get(table_id)
records = client.get(table_id, view=view)
if records is None:
self.send_response(404)
return
Expand All @@ -35,6 +39,10 @@ def do_GET(self):
if __name__ == "__main__":
# Populate a test table
client.create(test_table, {'field1': 1, 'field2': 'two', 'field3': '2023-07-19T06:39:15.000Z'})
client.create(test_table, {'field1': 2, 'field2': 'three', 'field3': '2023-07-20T06:39:15.000Z'})

# Create a test view
airtablemock.create_view(base_id, test_table, test_view, 'field2 = "three"')

# Create web server
webServer = HTTPServer((hostName, serverPort), AirtableMockServer)
Expand Down
14 changes: 10 additions & 4 deletions wrappers/src/fdw/airtable_fdw/airtable_fdw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,14 @@ impl AirtableFdw {
const FDW_NAME: &str = "AirtableFdw";

#[inline]
fn build_url(&self, base_id: &str, table_id: &str) -> String {
format!("{}/{}/{}", &self.base_url, base_id, table_id)
fn build_url(&self, base_id: &str, table_id: &str, view_id: Option<&String>) -> String {
match view_id {
Some(view_id) => format!(
"{}/{}/{}?view={}",
&self.base_url, base_id, table_id, view_id
),
None => format!("{}/{}/{}", &self.base_url, base_id, table_id),
}
}

#[inline]
Expand Down Expand Up @@ -120,9 +126,9 @@ impl ForeignDataWrapper for AirtableFdw {
_limit: &Option<Limit>, // TODO: maxRecords
options: &HashMap<String, String>,
) {
// TODO: Support specifying other options (view)
let url = if let Some(url) = require_option("base_id", options).and_then(|base_id| {
require_option("table_id", options).map(|table_id| self.build_url(&base_id, &table_id))
require_option("table_id", options)
.map(|table_id| self.build_url(&base_id, &table_id, options.get("view_id")))
}) {
url
} else {
Expand Down
29 changes: 27 additions & 2 deletions wrappers/src/fdw/airtable_fdw/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,43 @@ mod tests {
None,
)
.unwrap();
c.update(
r#"
CREATE FOREIGN TABLE airtable_view (
field1 numeric,
field2 text,
field3 timestamp
)
SERVER airtable_server
OPTIONS (
base_id 'baseID',
table_id 'table-foo',
view_id 'view-bar'
)
"#,
None,
None,
)
.unwrap();


/*
The table data below comes from the code in wrappers/dockerfiles/airtable/server.py
*/

let results = c
.select("SELECT field2 FROM airtable_table", None, None)
.select("SELECT field2 FROM airtable_table WHERE field = 1", None, None)
.unwrap()
.filter_map(|r| r.get_by_name::<&str, _>("field2").unwrap())
.collect::<Vec<_>>();

assert_eq!(results, vec!["two"]);

let results = c
.select("SELECT field2 FROM airtable_view", None, None)
.unwrap()
.filter_map(|r| r.get_by_name::<&str, _>("field2").unwrap())
.collect::<Vec<_>>();
assert_eq!(results, vec!["three"]);
});
}
}

0 comments on commit 2878ec4

Please sign in to comment.