|
| 1 | +# Building Adapters |
| 2 | + |
| 3 | +The **Adapter SDK** (`nl2sql-adapter-sdk`) allows you to extend the platform to support new databases or APIs. |
| 4 | + |
| 5 | +## Implementing an Adapter |
| 6 | + |
| 7 | +You must implement the `DatasourceAdapter` interface. |
| 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. |
| 25 | + |
| 26 | +::: nl2sql_adapter_sdk.interfaces.DatasourceAdapter |
| 27 | + |
| 28 | +## Compliance Testing |
| 29 | + |
| 30 | +The SDK provides a compliance test suite. **All Adapters MUST pass this suite.** |
| 31 | + |
| 32 | +It verifies: |
| 33 | + |
| 34 | +* Schema Introspection (PKs/FKs detected?) |
| 35 | +* Type Mapping (Date -> Python Date, Numeric -> Python Float) |
| 36 | +* Error Handling (Bad SQL -> AdapterError) |
| 37 | + |
| 38 | +```python |
| 39 | +# tests/test_my_adapter.py |
| 40 | +from nl2sql_adapter_sdk.testing import BaseAdapterTest |
| 41 | +from my_adapter import MyAdapter |
| 42 | + |
| 43 | +class TestMyAdapter(BaseAdapterTest): |
| 44 | + @pytest.fixture |
| 45 | + def adapter(self): |
| 46 | + return MyAdapter(...) |
| 47 | +``` |
| 48 | + |
| 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`? |
| 63 | + |
| 64 | +Use the raw interface when: |
| 65 | + |
| 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). |
| 69 | + |
| 70 | +### When to use `BaseSQLAlchemyAdapter`? |
| 71 | + |
| 72 | +Use this helper class when: |
| 73 | + |
| 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. |
| 77 | + |
| 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. |
| 81 | + |
| 82 | +### `BaseSQLAlchemyAdapter` Features |
| 83 | + |
| 84 | +This base class implements ~90% of the required functionality for you: |
| 85 | + |
| 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. |
| 90 | + |
| 91 | +### Example Implementation |
| 92 | + |
| 93 | +See `packages/adapters/postgres` for a reference implementation. |
| 94 | + |
| 95 | +```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) |
| 106 | +``` |
| 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