|
1 | | -# Building Adapters |
| 1 | +# Building Adapters Guide |
2 | 2 |
|
3 | | -The **Adapter SDK** (`nl2sql-adapter-sdk`) allows you to extend the platform to support new databases or APIs. |
| 3 | +The NL2SQL Platform is designed to be extensible. You can build adapters for any datasource, from SQL databases to REST APIs. |
4 | 4 |
|
5 | | -## Implementing an Adapter |
| 5 | +## Implementation Path |
6 | 6 |
|
7 | | -You must implement the `DatasourceAdapter` interface. |
| 7 | +There are two primary ways to build an adapter. Choose the one that fits your target: |
8 | 8 |
|
9 | | -### Mandatory Properties |
10 | | - |
11 | | -* `datasource_id`: Unique identifier (e.g. "postgres_prod"). |
12 | | -* `row_limit`: **Safety Breaker**. Must return `1000` (or config value) to prevent massive result sets. |
13 | | -* `max_bytes`: **Safety Breaker**. limit result size at the network/driver level if possible. |
14 | | - |
15 | | -### Mandatory Methods |
16 | | - |
17 | | -* `fetch_schema()`: Must return `SchemaMetadata` with `tables`, `columns`, `pks`, `fks`. *Crucially, it should also populate `col.statistics` (samples, min/max) for Indexing.* |
18 | | -* `execute(sql)`: Returns `QueryResult`. |
19 | | -* `dry_run(sql)`: Returns validity checks. |
20 | | - |
21 | | -### Optional Optimization |
22 | | - |
23 | | -* `explain(sql)`: Returns query plan. |
24 | | -* `cost_estimate(sql)`: Returns estimated rows/time. used by PhysicalValidator. |
| 9 | +| If you are checking... | Use... | Reference | |
| 10 | +| :--- | :--- | :--- | |
| 11 | +| A standard SQL Database (Postgres, Oracle, Snowflake) | `nl2sql-adapter-sqlalchemy` | **[SQLAlchemy Adapter Reference](sqlalchemy.md)** | |
| 12 | +| A NoSQL DB, REST API, or custom driver | `nl2sql-adapter-sdk` | **[Adapter SDK Reference](sdk.md)** | |
25 | 13 |
|
26 | | -::: nl2sql_adapter_sdk.interfaces.DatasourceAdapter |
| 14 | +## Option 1: The "Fast Lane" (SQLAlchemy) |
27 | 15 |
|
28 | | -## Compliance Testing |
| 16 | +For 95% of use cases, you are connecting to a SQL database that already has a Python SQLAlchemy dialect. |
29 | 17 |
|
30 | | -The SDK provides a compliance test suite. **All Adapters MUST pass this suite.** |
| 18 | +**Use `BaseSQLAlchemyAdapter`**. It handles: |
31 | 19 |
|
32 | | -It verifies: |
| 20 | +* Automatic Schema Introspection (Tables, PKs, FKs) |
| 21 | +* Connection Pooling |
| 22 | +* Statistic Gathering |
| 23 | +* Transaction-based Dry Runs |
33 | 24 |
|
34 | | -* Schema Introspection (PKs/FKs detected?) |
35 | | -* Type Mapping (Date -> Python Date, Numeric -> Python Float) |
36 | | -* Error Handling (Bad SQL -> AdapterError) |
| 25 | +### Example |
37 | 26 |
|
38 | 27 | ```python |
39 | | -# tests/test_my_adapter.py |
40 | | -from nl2sql_adapter_sdk.testing import BaseAdapterTest |
41 | | -from my_adapter import MyAdapter |
| 28 | +from nl2sql_sqlalchemy_adapter import BaseSQLAlchemyAdapter |
42 | 29 |
|
43 | | -class TestMyAdapter(BaseAdapterTest): |
44 | | - @pytest.fixture |
45 | | - def adapter(self): |
46 | | - return MyAdapter(...) |
| 30 | +class PostgresAdapter(BaseSQLAlchemyAdapter): |
| 31 | + def construct_uri(self, args: Dict[str, Any]) -> str: |
| 32 | + # Convert args to connection string |
| 33 | + return f"postgresql://{args['user']}:{args['password']}@{args['host']}/{args['database']}" |
47 | 34 | ``` |
48 | 35 |
|
49 | | -## Choosing a Base Class |
50 | | - |
51 | | -The platform provides two ways to build adapters. Choose the one that fits your target datasource. |
52 | | - |
53 | | -| Feature | `DatasourceAdapter` (Base Interface) | `BaseSQLAlchemyAdapter` (Helper Class) | |
54 | | -| :--- | :--- | :--- | |
55 | | -| **Package** | `nl2sql-adapter-sdk` | `nl2sql-adapter-sqlalchemy` | |
56 | | -| **Best For** | REST APIs, NoSQL, GraphQL, Manual SQL Drivers. | SQL Databases with SQLAlchemy dialects (Postgres, Oracle, Snowflake). | |
57 | | -| **Schema Fetching** | **Manual Implementation Required**. You must map metadata to `SchemaMetadata`. | **Automatic**. Uses `sqlalchemy.inspect` to reflect tables/FKs. | |
58 | | -| **Execution** | **Manual Implementation Required**. You handle connections, cursors, and types. | **Automatic**. Handles pooling, transactions, and result formatting. | |
59 | | -| **Stats Gathering** | **Manual**. You write queries to fetch min/max/nulls. | **Automatic**. Runs optimized generic queries for stats. | |
60 | | -| **Dry Run** | **Manual**. | **Automatic**. Uses transaction rollback pattern. | |
61 | | - |
62 | | -### When to use `DatasourceAdapter`? |
| 36 | +> See the **[SQLAlchemy Adapter Reference](sqlalchemy.md)** for full API details. |
63 | 37 |
|
64 | | -Use the raw interface when: |
| 38 | +## Option 2: The "Custom" Path (SDK) |
65 | 39 |
|
66 | | -1. You are connecting to a non-SQL source (e.g., Elasticsearch, HubSpot API). |
67 | | -2. You are using a customized internal SQL driver that is not compatible with SQLAlchemy. |
68 | | -3. You need complete control over the execution lifecycle (e.g. async-only drivers). |
| 40 | +If you need to connect to something else (e.g., ElasticSearch, a CRM API, or a raw SQL driver), you must implement the raw interface. |
69 | 41 |
|
70 | | -### When to use `BaseSQLAlchemyAdapter`? |
| 42 | +**Implement `DatasourceAdapter`**. You must manually handle: |
71 | 43 |
|
72 | | -Use this helper class when: |
| 44 | +* Fetching and normalizing schema metadata. |
| 45 | +* Executing queries and formatting results. |
| 46 | +* Implementing safety breakers (`row_limit`). |
73 | 47 |
|
74 | | -1. There is an existing SQLAlchemy dialect for your database (this covers 95% of SQL databases). |
75 | | -2. You want to save time on boilerplate (connection pooling, schema reflection). |
76 | | -3. You want consistent behavior with the core supported adapters. |
| 48 | +### Example |
77 | 49 |
|
78 | | -## Building SQL Adapters (The Fast Way) |
79 | | - |
80 | | -For SQL databases supported by SQLAlchemy, you should use the `nl2sql-adapter-sqlalchemy` package as described in the comparison above. |
| 50 | +```python |
| 51 | +from nl2sql_adapter_sdk import DatasourceAdapter |
81 | 52 |
|
82 | | -### `BaseSQLAlchemyAdapter` Features |
| 53 | +class MyRestAdapter(DatasourceAdapter): |
| 54 | + def fetch_schema(self) -> SchemaMetadata: |
| 55 | + # call API, return schema |
| 56 | + pass |
83 | 57 |
|
84 | | -This base class implements ~90% of the required functionality for you: |
| 58 | + def execute(self, query: str) -> QueryResult: |
| 59 | + # run query, return rows |
| 60 | + pass |
| 61 | +``` |
85 | 62 |
|
86 | | -* **Automatic Schema Fetching**: Uses `sqlalchemy.inspect` to get tables, columns, PKs. |
87 | | -* **Automatic Statistics**: Runs optimized queries to fetch `min/max`, `null_percentage`, `distinct_count`, and `sample_values` for text columns. |
88 | | -* **Generic Execution**: Handles connection pooling and result formatting. |
89 | | -* **Safety**: Built-in generic `dry_run` using transaction rollbacks. |
| 63 | +> See the **[Adapter SDK Reference](sdk.md)** for the mandatory method signatures and compliance testing guide. |
90 | 64 |
|
91 | | -### Example Implementation |
| 65 | +## Compliance Testing |
92 | 66 |
|
93 | | -See `packages/adapters/postgres` for a reference implementation. |
| 67 | +Regardless of which path you choose, your adapter **MUST** pass the compliance test suite to ensuring it handles types and errors correctly. |
94 | 68 |
|
95 | 69 | ```python |
96 | | -from nl2sql_sqlalchemy_adapter import BaseSQLAlchemyAdapter |
97 | | - |
98 | | -class PostgresAdapter(BaseSQLAlchemyAdapter): |
99 | | - def construct_uri(self, args: Dict[str, Any]) -> str: |
100 | | - return f"postgresql://{args.get('user')}:{args.get('password')}@{args.get('host')}/{args.get('database')}" |
101 | | - |
102 | | - # Optional: Override dry_run for better performance using EXPLAIN |
103 | | - def dry_run(self, sql: str): |
104 | | - self.execute(f"EXPLAIN {sql}") |
105 | | - return DryRunResult(is_valid=True) |
| 70 | +from nl2sql_adapter_sdk.testing import BaseAdapterTest |
| 71 | +# ... see SDK Reference for test setup |
106 | 72 | ``` |
107 | | - |
108 | | -## Reference Adapters |
109 | | - |
110 | | -For detailed usage configurations of our supported adapters, please see the **[Supported Adapters](index.md)** section. |
111 | | - |
112 | | -Explore the `packages/adapters/` directory for examples: |
113 | | - |
114 | | -* `postgres`: Standard implementation using `sqlalchemy`. |
115 | | -* `sqlite`: Simple, file-based. |
116 | | -* `mssql` / `mysql`: Standard enterprise drivers. |
117 | | - |
118 | | -## Next Steps |
119 | | - |
120 | | -Check out the [Postgres Adapter Source Code](https://github.com/nadeem4/nl2sql/tree/main/packages/adapters/postgres) for a complete, production-grade example. |
0 commit comments