From 92e945dca1efdb4736e4a9ad20f7071ebb4d3518 Mon Sep 17 00:00:00 2001 From: Tyler Abbott Date: Mon, 2 Feb 2026 21:09:37 -0600 Subject: [PATCH 1/3] support IBM DB2 for i with ODBC driver --- IMPLEMENTATION_DB2I.md | 236 ++++++++++++++ docs/QUICKSTART_DB2I.md | 237 ++++++++++++++ pyproject.toml | 2 + .../connections/providers/db2i/README.md | 174 ++++++++++ .../connections/providers/db2i/__init__.py | 1 + .../connections/providers/db2i/adapter.py | 300 ++++++++++++++++++ .../connections/providers/db2i/example.py | 82 +++++ .../connections/providers/db2i/provider.py | 29 ++ .../connections/providers/db2i/schema.py | 38 +++ test_db2i_adapter.py | 42 +++ tests/conftest.py | 1 + tests/fixtures/db2i.py | 45 +++ tests/test_db2i.py | 19 ++ 13 files changed, 1206 insertions(+) create mode 100644 IMPLEMENTATION_DB2I.md create mode 100644 docs/QUICKSTART_DB2I.md create mode 100644 sqlit/domains/connections/providers/db2i/README.md create mode 100644 sqlit/domains/connections/providers/db2i/__init__.py create mode 100644 sqlit/domains/connections/providers/db2i/adapter.py create mode 100644 sqlit/domains/connections/providers/db2i/example.py create mode 100644 sqlit/domains/connections/providers/db2i/provider.py create mode 100644 sqlit/domains/connections/providers/db2i/schema.py create mode 100644 test_db2i_adapter.py create mode 100644 tests/fixtures/db2i.py create mode 100644 tests/test_db2i.py diff --git a/IMPLEMENTATION_DB2I.md b/IMPLEMENTATION_DB2I.md new file mode 100644 index 00000000..a0679d95 --- /dev/null +++ b/IMPLEMENTATION_DB2I.md @@ -0,0 +1,236 @@ +# IBM DB2 for i Support - Implementation Summary + +## Overview + +This implementation adds support for IBM DB2 for i (formerly AS/400, iSeries, System i) using the pyodbc driver with the IBM i Access ODBC Driver. This solution **does not require an IBM license** as it uses the freely available IBM i Access ODBC Driver. + +## What Was Implemented + +### 1. New Database Adapter +**Location:** `sqlit/domains/connections/providers/db2i/` + +#### Files Created: +- `adapter.py` - Main adapter implementation using pyodbc +- `provider.py` - Provider registration +- `schema.py` - Connection schema definition +- `README.md` - Comprehensive documentation +- `example.py` - Usage examples +- `__init__.py` - Package initialization + +### 2. Key Features + +The adapter provides full support for: + +✅ **Connection Management** +- Connect using IBM i Access ODBC Driver (no license required) +- Support for custom ODBC driver names +- Optional port specification +- Default library/schema configuration +- Extra ODBC connection options + +✅ **Schema Operations** +- List libraries (equivalent to databases) +- Browse tables and views +- View column definitions with data types +- Inspect primary keys +- View indexes (with uniqueness indicators) +- View triggers +- View sequences +- List stored procedures + +✅ **Query Execution** +- Execute SQL queries +- Handle result sets +- Row limiting support +- Cross-library queries + +### 3. System Catalog Support + +The adapter queries these IBM i system catalogs: +- `QSYS2.SYSTABLES` - Tables and views metadata +- `QSYS2.SYSVIEWS` - View definitions +- `QSYS2.SYSCOLUMNS` - Column information +- `QSYS2.SYSROUTINES` - Stored procedures/functions +- `QSYS2.SYSINDEXES` - Index metadata +- `QSYS2.SYSTRIGGERS` - Trigger definitions +- `QSYS2.SYSSEQUENCES` - Sequence objects +- `QSYS2.SYSCST` - Constraints (for primary keys) + +### 4. Dependencies + +Added to `pyproject.toml`: +```toml +[project.optional-dependencies] +db2i = ["pyodbc>=5.0.0"] +``` + +Also included in the `all` extras group. + +### 5. Testing Support + +**Test Files Created:** +- `tests/fixtures/db2i.py` - Test fixtures for DB2 for i +- `tests/test_db2i.py` - Integration tests + +**Test Configuration:** +Environment variables for testing: +- `DB2I_HOST` - IBM i server hostname +- `DB2I_PORT` - Optional port (if required) +- `DB2I_USER` - Username for authentication +- `DB2I_PASSWORD` - Password +- `DB2I_LIBRARY` - Default library/schema +- `DB2I_ODBC_DRIVER` - ODBC driver name (optional) + +Run tests with: `pytest -m db2i` + +## Connection Examples + +### Basic Connection +```python +{ + "name": "my_ibm_i", + "db_type": "db2i", + "host": "ibmi.example.com", + "username": "myuser", + "password": "mypass", + "database": "MYLIB" # Default library +} +``` + +### With Custom ODBC Driver +```python +{ + "name": "my_ibm_i", + "db_type": "db2i", + "host": "ibmi.example.com", + "username": "myuser", + "password": "mypass", + "extra_options": { + "odbc_driver": "iSeries Access ODBC Driver" + } +} +``` + +### URL Format +``` +db2i://username:password@hostname/library +``` + +## Installation Instructions + +### 1. Install ODBC Driver + +#### macOS +1. Download IBM i Access Client Solutions +2. Install the ODBC driver component +3. Verify: The driver should be named "IBM i Access ODBC Driver" + +#### Linux +1. Download IBM i Access ODBC Driver for Linux +2. Follow distribution-specific installation +3. Verify: `odbcinst -q -d` + +#### Windows +1. Download and install IBM i Access ODBC Driver +2. Driver is automatically registered + +### 2. Install Python Package +```bash +# Install with DB2 for i support +pip install sqlit-tui[db2i] + +# Or install all database drivers +pip install sqlit-tui[all] + +# Or just install pyodbc separately +pip install pyodbc +``` + +## Technical Details + +### Adapter Class: `Db2iAdapter` + +**Base Class:** `CursorBasedAdapter` + +**Properties:** +- `name`: "IBM DB2 for i" +- `install_extra`: "db2i" +- `install_package`: "pyodbc" +- `driver_import_names`: ("pyodbc",) +- `supports_multiple_databases`: False (uses libraries instead) +- `supports_cross_database_queries`: True +- `supports_stored_procedures`: True +- `supports_sequences`: True + +### ODBC Connection String Format + +``` +DRIVER={IBM i Access ODBC Driver}; +SYSTEM=hostname; +UID=username; +PWD=password; +DBQ=library; +[additional options] +``` + +## Differences from Standard DB2 + +1. **Libraries vs Databases**: IBM i uses libraries as the primary organizational unit +2. **System Catalogs**: Uses QSYS2 schema instead of SYSCAT +3. **ODBC-Based**: Uses standard ODBC instead of DB2 CLI +4. **No License Required**: The ODBC driver is freely available from IBM + +## Architecture Integration + +The DB2 for i provider follows the same pattern as other database providers: + +1. **Provider Registration**: Auto-discovered via `provider.py` +2. **Schema Definition**: Connection fields defined in `schema.py` +3. **Adapter Implementation**: Database operations in `adapter.py` +4. **Testing**: Standard test fixtures and integration tests + +## Next Steps + +To use the new DB2 for i adapter: + +1. Ensure the IBM i Access ODBC Driver is installed on your system +2. Install sqlit with DB2i support: `pip install sqlit-tui[db2i]` +3. Create a connection configuration with `db_type="db2i"` +4. Connect to your IBM i server! + +## Support Resources + +- [IBM i Access ODBC Driver Docs](https://www.ibm.com/docs/en/i/) +- [pyodbc Documentation](https://github.com/mkleehammer/pyodbc/wiki) +- [IBM i SQL Reference](https://www.ibm.com/docs/en/i/7.4?topic=reference-sql) + +## Files Modified + +1. **sqlit/domains/connections/providers/db2i/** (new directory) + - `__init__.py` + - `adapter.py` + - `provider.py` + - `schema.py` + - `README.md` + - `example.py` + +2. **pyproject.toml** + - Added `db2i = ["pyodbc>=5.0.0"]` to optional dependencies + - Added `pyodbc>=5.0.0` to `all` extras + +3. **tests/fixtures/db2i.py** (new file) + - Test fixtures for DB2 for i connections + +4. **tests/test_db2i.py** (new file) + - Integration tests for DB2 for i + +5. **tests/conftest.py** + - Added import for DB2i fixtures + +## License Note + +This implementation uses: +- **pyodbc**: MIT License +- **IBM i Access ODBC Driver**: Freely available from IBM, no license fee required + +The IBM i Access ODBC Driver can be downloaded from IBM at no cost and does not require purchasing an IBM DB2 license. diff --git a/docs/QUICKSTART_DB2I.md b/docs/QUICKSTART_DB2I.md new file mode 100644 index 00000000..d2deb074 --- /dev/null +++ b/docs/QUICKSTART_DB2I.md @@ -0,0 +1,237 @@ +# Quick Start: IBM DB2 for i + +This guide will help you quickly get started with IBM DB2 for i support in sqlit. + +## Prerequisites Checklist + +- [ ] IBM i Access ODBC Driver installed on your system +- [ ] Network access to your IBM i server +- [ ] Valid credentials (username and password) +- [ ] Python 3.10 or higher + +## Installation + +### Step 1: Install sqlit with DB2i support + +```bash +pip install sqlit-tui[db2i] +``` + +Or if you want all database drivers: + +```bash +pip install sqlit-tui[all] +``` + +### Step 2: Verify ODBC Driver Installation + +**macOS/Linux:** +```bash +odbcinst -q -d | grep -i "ibm i" +``` + +**Expected output:** +``` +[IBM i Access ODBC Driver] +``` + +If the driver is not found, download and install it from IBM. + +## Quick Connection Test + +### Using Python + +```python +from sqlit.domains.connections.domain.config import ConnectionConfig, TcpEndpoint +from sqlit.domains.connections.providers.catalog import get_provider + +# Create connection config +config = ConnectionConfig( + name="test_connection", + db_type="db2i", + tcp_endpoint=TcpEndpoint( + host="your.ibmi.server.com", + database="QGPL", # Default library + username="your_username", + password="your_password", + ), +) + +# Get provider and connect +provider = get_provider("db2i") +conn = provider.connection_factory.connect(config) + +# Test query +columns, rows, truncated = provider.query_executor.execute_query( + conn, + "SELECT * FROM QSYS2.SYSTABLES FETCH FIRST 5 ROWS ONLY" +) + +print(f"Successfully connected! Retrieved {len(rows)} rows") +print(f"Columns: {columns}") + +conn.close() +``` + +### Using sqlit CLI + +If sqlit has a CLI interface, you can use: + +```bash +sqlit connect db2i://username:password@hostname/library +``` + +## Common Connection Options + +### Minimal Connection (with defaults) +```python +{ + "db_type": "db2i", + "host": "ibmi.example.com", + "username": "myuser", + "password": "mypass" +} +``` + +### With Default Library +```python +{ + "db_type": "db2i", + "host": "ibmi.example.com", + "database": "MYLIB", # Sets default library for queries + "username": "myuser", + "password": "mypass" +} +``` + +### With Custom ODBC Driver +```python +{ + "db_type": "db2i", + "host": "ibmi.example.com", + "username": "myuser", + "password": "mypass", + "extra_options": { + "odbc_driver": "iSeries Access ODBC Driver" + } +} +``` + +### With Character Encoding +```python +{ + "db_type": "db2i", + "host": "ibmi.example.com", + "username": "myuser", + "password": "mypass", + "extra_options": { + "CHARSET": "UTF-8", + "NAM": "1" # SQL naming convention + } +} +``` + +## Troubleshooting + +### Error: "Data source name not found" + +**Cause:** ODBC driver is not installed or not found. + +**Solution:** +1. Verify ODBC driver installation: `odbcinst -q -d` +2. Install IBM i Access ODBC Driver if missing +3. Specify the exact driver name in `extra_options`: + ```python + "extra_options": {"odbc_driver": "Your Driver Name Here"} + ``` + +### Error: "Connection refused" or "Timeout" + +**Cause:** Network connectivity issues. + +**Solution:** +1. Verify the hostname/IP is correct +2. Check firewall rules +3. Ensure the IBM i server is accessible from your network +4. Try pinging the server: `ping ibmi.example.com` + +### Error: "Login failed" or "Invalid credentials" + +**Cause:** Incorrect username or password. + +**Solution:** +1. Verify credentials with your IBM i administrator +2. Ensure the user has appropriate permissions +3. Check if the password has special characters that need escaping + +### Error: "Library not found" + +**Cause:** The specified default library doesn't exist. + +**Solution:** +1. Verify the library name is correct (case-sensitive on IBM i) +2. Check if you have access to the library +3. Try connecting without specifying a default library first + +## Sample Queries + +### List All Libraries +```sql +SELECT DISTINCT TABLE_SCHEMA +FROM QSYS2.SYSTABLES +WHERE TABLE_SCHEMA NOT LIKE 'Q%' +ORDER BY TABLE_SCHEMA +``` + +### List Tables in a Library +```sql +SELECT TABLE_NAME +FROM QSYS2.SYSTABLES +WHERE TABLE_SCHEMA = 'MYLIB' + AND TABLE_TYPE = 'T' +ORDER BY TABLE_NAME +``` + +### View Table Columns +```sql +SELECT COLUMN_NAME, DATA_TYPE, ORDINAL_POSITION +FROM QSYS2.SYSCOLUMNS +WHERE TABLE_SCHEMA = 'MYLIB' + AND TABLE_NAME = 'MYTABLE' +ORDER BY ORDINAL_POSITION +``` + +### List Stored Procedures +```sql +SELECT ROUTINE_NAME, ROUTINE_SCHEMA +FROM QSYS2.SYSROUTINES +WHERE ROUTINE_TYPE = 'PROCEDURE' + AND ROUTINE_SCHEMA NOT LIKE 'Q%' +ORDER BY ROUTINE_NAME +``` + +## Next Steps + +1. **Read the full documentation**: See [README.md](sqlit/domains/connections/providers/db2i/README.md) +2. **Check out examples**: See [example.py](sqlit/domains/connections/providers/db2i/example.py) +3. **Run tests**: `pytest -m db2i` (requires test environment) + +## Getting Help + +- IBM i SQL Reference: https://www.ibm.com/docs/en/i/7.4?topic=reference-sql +- pyodbc Documentation: https://github.com/mkleehammer/pyodbc/wiki +- IBM i Access Client Solutions: https://www.ibm.com/support/pages/ibm-i-access-client-solutions + +## Environment Variables for Testing + +If you want to run the integration tests: + +```bash +export DB2I_HOST=ibmi.example.com +export DB2I_USER=testuser +export DB2I_PASSWORD=testpass +export DB2I_LIBRARY=TESTLIB +export DB2I_ODBC_DRIVER="IBM i Access ODBC Driver" + +pytest -m db2i +``` diff --git a/pyproject.toml b/pyproject.toml index 3f21e3da..d22d8429 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,7 @@ all = [ "mariadb>=1.1.0", "oracledb>=2.0.0", "ibm_db>=3.2.0", + "pyodbc>=5.0.0", "hdbcli>=2.20.0", "teradatasql>=20.0.0", "trino>=0.329.0", @@ -67,6 +68,7 @@ mysql = ["PyMySQL>=1.1.0"] mariadb = ["mariadb>=1.1.0"] oracle = ["oracledb>=2.0.0"] db2 = ["ibm_db>=3.2.0"] +db2i = ["pyodbc>=5.0.0"] hana = ["hdbcli>=2.20.0"] teradata = ["teradatasql>=20.0.0"] trino = ["trino>=0.329.0"] diff --git a/sqlit/domains/connections/providers/db2i/README.md b/sqlit/domains/connections/providers/db2i/README.md new file mode 100644 index 00000000..e743aec5 --- /dev/null +++ b/sqlit/domains/connections/providers/db2i/README.md @@ -0,0 +1,174 @@ +# IBM DB2 for i Support + +This adapter provides support for IBM DB2 for i (formerly IBM i/AS400/iSeries) using the ODBC driver, which does not require an IBM license. + +## Prerequisites + +### ODBC Driver Installation + +You need to install the IBM i Access ODBC Driver on your system. This driver is freely available and does not require an IBM license. + +#### macOS +1. Download IBM i Access Client Solutions from IBM +2. Install the ODBC driver component +3. The driver will typically be named "IBM i Access ODBC Driver" + +#### Linux +1. Download IBM i Access ODBC Driver for Linux from IBM +2. Follow the installation instructions for your distribution +3. Verify installation with: `odbcinst -q -d` + +#### Windows +1. Download IBM i Access ODBC Driver for Windows from IBM +2. Run the installer +3. The driver will be registered automatically + +### Python Dependencies + +Install the required Python package: + +```bash +pip install sqlit-tui[db2i] +``` + +Or install pyodbc directly: + +```bash +pip install pyodbc +``` + +## Connection Configuration + +### Basic Connection + +```python +{ + "name": "my_db2i", + "db_type": "db2i", + "host": "your.ibmi.server.com", + "username": "your_username", + "password": "your_password", + "database": "MYLIB" # Optional: default library/schema +} +``` + +### Custom ODBC Driver + +If you have a different ODBC driver installed, you can specify it: + +```python +{ + "name": "my_db2i", + "db_type": "db2i", + "host": "your.ibmi.server.com", + "username": "your_username", + "password": "your_password", + "database": "MYLIB", + "extra_options": { + "odbc_driver": "iSeries Access ODBC Driver" + } +} +``` + +### Connection String Format + +The adapter builds an ODBC connection string in the following format: + +``` +DRIVER={IBM i Access ODBC Driver};SYSTEM=hostname;UID=username;PWD=password;DBQ=library; +``` + +## Features + +### Supported Operations + +- ✅ Connect to IBM DB2 for i via ODBC +- ✅ Query execution +- ✅ Browse libraries (databases) +- ✅ Browse tables and views +- ✅ View table columns with data types +- ✅ Stored procedures +- ✅ Sequences +- ✅ Indexes +- ✅ Triggers +- ✅ Cross-library queries + +### System Catalogs + +The adapter queries the following IBM i system catalogs: + +- `QSYS2.SYSTABLES` - Tables and views +- `QSYS2.SYSVIEWS` - View definitions +- `QSYS2.SYSCOLUMNS` - Column information +- `QSYS2.SYSROUTINES` - Stored procedures and functions +- `QSYS2.SYSINDEXES` - Index information +- `QSYS2.SYSTRIGGERS` - Trigger definitions +- `QSYS2.SYSSEQUENCES` - Sequence objects + +### Differences from Standard DB2 + +IBM DB2 for i has some differences from standard DB2: + +1. **Libraries vs Databases**: DB2 for i uses "libraries" instead of traditional databases +2. **System Catalogs**: Uses QSYS2 system catalog instead of SYSCAT +3. **ODBC Driver**: Uses IBM i Access ODBC Driver instead of DB2 CLI driver +4. **No License Required**: The ODBC driver is freely available + +## Troubleshooting + +### Driver Not Found + +If you get an error about the ODBC driver not being found: + +1. Verify the driver is installed: `odbcinst -q -d` (Linux/macOS) +2. Check the exact driver name in your system +3. Specify the correct driver name in the connection configuration + +### Connection Timeout + +If connections are timing out: + +1. Verify network connectivity to the IBM i server +2. Check firewall rules +3. Ensure the ODBC driver is properly configured +4. Try specifying a port if required by your network configuration + +### Character Encoding Issues + +If you encounter character encoding problems: + +1. Add encoding options to `extra_options`: + ```python + "extra_options": { + "CHARSET": "UTF-8" + } + ``` + +## URL Scheme + +You can also use a URL connection string: + +``` +db2i://username:password@hostname/library +``` + +## Testing + +To run tests for the DB2 for i adapter: + +```bash +# Set environment variables +export DB2I_HOST=your.ibmi.server.com +export DB2I_USER=your_username +export DB2I_PASSWORD=your_password +export DB2I_LIBRARY=QGPL + +# Run tests +pytest -m db2i +``` + +## Additional Resources + +- [IBM i Access ODBC Driver Documentation](https://www.ibm.com/docs/en/i/) +- [pyodbc Documentation](https://github.com/mkleehammer/pyodbc/wiki) +- [IBM i SQL Reference](https://www.ibm.com/docs/en/i/7.4?topic=reference-sql) diff --git a/sqlit/domains/connections/providers/db2i/__init__.py b/sqlit/domains/connections/providers/db2i/__init__.py new file mode 100644 index 00000000..47df31fe --- /dev/null +++ b/sqlit/domains/connections/providers/db2i/__init__.py @@ -0,0 +1 @@ +"""IBM DB2 for i provider package.""" diff --git a/sqlit/domains/connections/providers/db2i/adapter.py b/sqlit/domains/connections/providers/db2i/adapter.py new file mode 100644 index 00000000..2cc7956c --- /dev/null +++ b/sqlit/domains/connections/providers/db2i/adapter.py @@ -0,0 +1,300 @@ +"""IBM DB2 for i adapter using pyodbc with ODBC driver.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from sqlit.domains.connections.providers.adapters.base import ( + ColumnInfo, + CursorBasedAdapter, + IndexInfo, + SequenceInfo, + TableInfo, + TriggerInfo, +) +from sqlit.domains.connections.providers.registry import get_default_port + +if TYPE_CHECKING: + from sqlit.domains.connections.domain.config import ConnectionConfig + + +class Db2iAdapter(CursorBasedAdapter): + """Adapter for IBM DB2 for i using pyodbc with IBM i Access ODBC Driver. + + This adapter uses the IBM i Access ODBC Driver which does not require + an IBM license. The driver must be installed separately on the system. + """ + + @property + def name(self) -> str: + return "IBM DB2 for i" + + @property + def install_extra(self) -> str: + return "db2i" + + @property + def install_package(self) -> str: + return "pyodbc" + + @property + def driver_import_names(self) -> tuple[str, ...]: + return ("pyodbc",) + + @property + def supports_multiple_databases(self) -> bool: + return False + + @property + def supports_cross_database_queries(self) -> bool: + return True + + @property + def supports_stored_procedures(self) -> bool: + return True + + @property + def supports_sequences(self) -> bool: + return True + + @property + def default_schema(self) -> str: + return "" + + def connect(self, config: ConnectionConfig) -> Any: + pyodbc = self._import_driver_module( + "pyodbc", + driver_name=self.name, + extra_name=self.install_extra, + package_name=self.install_package, + ) + + endpoint = config.tcp_endpoint + if endpoint is None: + raise ValueError("DB2 for i connections require a TCP-style endpoint.") + + # Get optional driver name from config, default to IBM i Access ODBC Driver + driver_name = config.get_option("odbc_driver", "IBM i Access ODBC Driver") + + # Build ODBC connection string + # Format: DRIVER={driver};SYSTEM=hostname;UID=user;PWD=password; + conn_str_parts = [ + f"DRIVER={{{driver_name}}}", + f"SYSTEM={endpoint.host}", + ] + + # Add port if specified (optional for IBM i Access ODBC Driver) + if endpoint.port: + conn_str_parts.append(f"PORT={endpoint.port}") + + # Add credentials + if endpoint.username: + conn_str_parts.append(f"UID={endpoint.username}") + if endpoint.password: + conn_str_parts.append(f"PWD={endpoint.password}") + + # Add default library (database) if specified + if endpoint.database: + conn_str_parts.append(f"DBQ={endpoint.database}") + + # Add any extra options from config + for key, value in config.extra_options.items(): + conn_str_parts.append(f"{key}={value}") + + conn_str = ";".join(conn_str_parts) + ";" + + conn = pyodbc.connect(conn_str) + # Enable autocommit for DDL operations + conn.autocommit = True + return conn + + def get_databases(self, conn: Any) -> list[str]: + """Get list of libraries (databases) from DB2 for i.""" + # Libraries are the equivalent of databases in DB2 for i + # Query QSYS2.SYSTABLES to get distinct schemas/libraries + cursor = conn.cursor() + cursor.execute( + "SELECT DISTINCT TABLE_SCHEMA FROM QSYS2.SYSTABLES " + "WHERE TABLE_SCHEMA NOT LIKE 'Q%' " + "ORDER BY TABLE_SCHEMA" + ) + return [row[0] for row in cursor.fetchall()] + + def get_tables(self, conn: Any, database: str | None = None) -> list[TableInfo]: + cursor = conn.cursor() + query = ( + "SELECT TABLE_SCHEMA, TABLE_NAME FROM QSYS2.SYSTABLES " + "WHERE TABLE_TYPE = 'T' AND TABLE_SCHEMA NOT LIKE 'Q%' " + ) + if database: + query += f"AND TABLE_SCHEMA = '{database}' " + query += "ORDER BY TABLE_SCHEMA, TABLE_NAME" + + cursor.execute(query) + return [(row[0], row[1]) for row in cursor.fetchall()] + + def get_views(self, conn: Any, database: str | None = None) -> list[TableInfo]: + cursor = conn.cursor() + query = ( + "SELECT TABLE_SCHEMA, TABLE_NAME FROM QSYS2.SYSVIEWS " + "WHERE TABLE_SCHEMA NOT LIKE 'Q%' " + ) + if database: + query += f"AND TABLE_SCHEMA = '{database}' " + query += "ORDER BY TABLE_SCHEMA, TABLE_NAME" + + cursor.execute(query) + return [(row[0], row[1]) for row in cursor.fetchall()] + + def get_columns( + self, conn: Any, table: str, database: str | None = None, schema: str | None = None + ) -> list[ColumnInfo]: + cursor = conn.cursor() + + # Get primary key columns + pk_columns: set[str] = set() + if schema: + cursor.execute( + "SELECT COLUMN_NAME FROM QSYS2.SYSCST " + "WHERE CONSTRAINT_SCHEMA = ? AND CONSTRAINT_NAME IN (" + " SELECT CONSTRAINT_NAME FROM QSYS2.SYSCST " + " WHERE CONSTRAINT_SCHEMA = ? AND CONSTRAINT_TYPE = 'PRIMARY KEY' " + " AND TABLE_SCHEMA = ? AND TABLE_NAME = ?" + ")", + (schema, schema, schema, table), + ) + pk_columns = {row[0] for row in cursor.fetchall()} + + # Get column information + query = ( + "SELECT COLUMN_NAME, DATA_TYPE FROM QSYS2.SYSCOLUMNS " + "WHERE TABLE_NAME = ? " + ) + params: list[Any] = [table] + + if schema: + query += "AND TABLE_SCHEMA = ? " + params.append(schema) + + query += "ORDER BY ORDINAL_POSITION" + + cursor.execute(query, params) + return [ + ColumnInfo(name=row[0], data_type=row[1], is_primary_key=row[0] in pk_columns) + for row in cursor.fetchall() + ] + + def get_procedures(self, conn: Any, database: str | None = None) -> list[str]: + cursor = conn.cursor() + query = ( + "SELECT ROUTINE_NAME FROM QSYS2.SYSROUTINES " + "WHERE ROUTINE_SCHEMA NOT LIKE 'Q%' " + "AND ROUTINE_TYPE = 'PROCEDURE' " + ) + if database: + query += f"AND ROUTINE_SCHEMA = '{database}' " + query += "ORDER BY ROUTINE_NAME" + + cursor.execute(query) + return [row[0] for row in cursor.fetchall()] + + def get_indexes( + self, conn: Any, table: str, database: str | None = None, schema: str | None = None + ) -> list[IndexInfo]: + cursor = conn.cursor() + query = ( + "SELECT INDEX_NAME, IS_UNIQUE FROM QSYS2.SYSINDEXES " + "WHERE TABLE_NAME = ? " + ) + params: list[Any] = [table] + + if schema: + query += "AND TABLE_SCHEMA = ? " + params.append(schema) + + query += "ORDER BY INDEX_NAME" + + cursor.execute(query, params) + return [ + IndexInfo(name=row[0], is_unique=row[1] == 'Y') + for row in cursor.fetchall() + ] + + def get_triggers( + self, conn: Any, table: str, database: str | None = None, schema: str | None = None + ) -> list[TriggerInfo]: + cursor = conn.cursor() + query = ( + "SELECT TRIGGER_NAME, EVENT_MANIPULATION FROM QSYS2.SYSTRIGGERS " + "WHERE EVENT_OBJECT_TABLE = ? " + ) + params: list[Any] = [table] + + if schema: + query += "AND TRIGGER_SCHEMA = ? " + params.append(schema) + + query += "ORDER BY TRIGGER_NAME" + + cursor.execute(query, params) + return [ + TriggerInfo(name=row[0], event=row[1]) + for row in cursor.fetchall() + ] + + def get_sequences(self, conn: Any, database: str | None = None) -> list[SequenceInfo]: + cursor = conn.cursor() + query = ( + "SELECT SEQUENCE_NAME FROM QSYS2.SYSSEQUENCES " + "WHERE SEQUENCE_SCHEMA NOT LIKE 'Q%' " + ) + if database: + query += f"AND SEQUENCE_SCHEMA = '{database}' " + query += "ORDER BY SEQUENCE_NAME" + + cursor.execute(query) + return [SequenceInfo(name=row[0]) for row in cursor.fetchall()] + + def get_sequence_info( + self, conn: Any, sequence_name: str, database: str | None = None + ) -> dict[str, Any]: + """Get detailed information about a DB2 for i sequence.""" + cursor = conn.cursor() + query = ( + "SELECT START_VALUE, INCREMENT, MIN_VALUE, MAX_VALUE, CYCLE_OPTION " + "FROM QSYS2.SYSSEQUENCES " + "WHERE SEQUENCE_NAME = ? " + ) + params: list[Any] = [sequence_name] + + if database: + query += "AND SEQUENCE_SCHEMA = ? " + params.append(database) + + cursor.execute(query, params) + row = cursor.fetchone() + + if not row: + return {} + + return { + "start_value": row[0], + "increment": row[1], + "min_value": row[2], + "max_value": row[3], + "cycle": row[4] == 'YES', + } + + def quote_identifier(self, name: str) -> str: + """Quote an identifier for DB2 for i.""" + escaped = name.replace('"', '""') + return f'"{escaped}"' + + def build_select_query(self, table: str, limit: int, database: str | None = None, schema: str | None = None) -> str: + """Build SELECT query with FETCH FIRST for DB2 for i.""" + if schema: + quoted_table = f'{self.quote_identifier(schema)}.{self.quote_identifier(table)}' + else: + quoted_table = self.quote_identifier(table) + return f'SELECT * FROM {quoted_table} FETCH FIRST {limit} ROWS ONLY' + diff --git a/sqlit/domains/connections/providers/db2i/example.py b/sqlit/domains/connections/providers/db2i/example.py new file mode 100644 index 00000000..3b34b4b3 --- /dev/null +++ b/sqlit/domains/connections/providers/db2i/example.py @@ -0,0 +1,82 @@ +"""Example usage of IBM DB2 for i adapter. + +This example demonstrates how to connect to IBM DB2 for i using the ODBC driver. +""" + +from sqlit.domains.connections.domain.config import ConnectionConfig, TcpEndpoint +from sqlit.domains.connections.providers.catalog import get_provider + + +def example_connection(): + """Example of creating a DB2 for i connection.""" + + # Create a connection configuration + config = ConnectionConfig( + name="my_ibm_i_server", + db_type="db2i", + tcp_endpoint=TcpEndpoint( + host="your.ibmi.server.com", + port=None, # Port is optional for IBM i Access ODBC Driver + database="MYLIB", # Default library/schema + username="your_username", + password="your_password", + ), + extra_options={ + # Optional: specify custom ODBC driver name if different from default + # "odbc_driver": "IBM i Access ODBC Driver", + + # Optional: additional ODBC connection string options + # "CHARSET": "UTF-8", + # "NAM": "1", # Naming convention: 1=SQL, 0=System + }, + ) + + # Get the provider + provider = get_provider("db2i") + + # Create a connection + conn = provider.connection_factory.connect(config) + + # Example: Get list of libraries + libraries = provider.schema_inspector.get_databases(conn) + print(f"Available libraries: {libraries}") + + # Example: Get tables from a library + tables = provider.schema_inspector.get_tables(conn, database="MYLIB") + print(f"Tables in MYLIB: {[t[1] for t in tables]}") + + # Example: Execute a query + columns, rows, truncated = provider.query_executor.execute_query( + conn, + "SELECT * FROM MYLIB.MYTABLE", + max_rows=100 + ) + print(f"Query returned {len(rows)} rows") + print(f"Columns: {columns}") + + # Close connection + conn.close() + + +def example_url_connection(): + """Example of using a URL-style connection string.""" + + # URL format: db2i://username:password@hostname/library + url = "db2i://myuser:mypass@ibmi.example.com/MYLIB" + + # You can parse this URL and create a ConnectionConfig + # (URL parsing functionality would be in the main sqlit application) + print(f"Connection URL: {url}") + + +if __name__ == "__main__": + print("IBM DB2 for i Connection Examples") + print("=" * 50) + print() + print("To use these examples, you need:") + print("1. IBM i Access ODBC Driver installed on your system") + print("2. pyodbc Python package: pip install pyodbc") + print("3. Network access to your IBM i server") + print() + print("Uncomment example_connection() to test") + # example_connection() diff --git a/sqlit/domains/connections/providers/db2i/provider.py b/sqlit/domains/connections/providers/db2i/provider.py new file mode 100644 index 00000000..19d53c45 --- /dev/null +++ b/sqlit/domains/connections/providers/db2i/provider.py @@ -0,0 +1,29 @@ +"""Provider registration for IBM DB2 for i.""" + +from sqlit.domains.connections.providers.adapter_provider import build_adapter_provider +from sqlit.domains.connections.providers.catalog import register_provider +from sqlit.domains.connections.providers.db2i.schema import SCHEMA +from sqlit.domains.connections.providers.model import DatabaseProvider, ProviderSpec + + +def _provider_factory(spec: ProviderSpec) -> DatabaseProvider: + from sqlit.domains.connections.providers.db2i.adapter import Db2iAdapter + + return build_adapter_provider(spec, SCHEMA, Db2iAdapter()) + + +SPEC = ProviderSpec( + db_type="db2i", + display_name="IBM DB2 for i", + schema_path=("sqlit.domains.connections.providers.db2i.schema", "SCHEMA"), + supports_ssh=True, + is_file_based=False, + has_advanced_auth=False, + default_port="", + requires_auth=True, + badge_label="DB2i", + url_schemes=("db2i",), + provider_factory=_provider_factory, +) + +register_provider(SPEC) diff --git a/sqlit/domains/connections/providers/db2i/schema.py b/sqlit/domains/connections/providers/db2i/schema.py new file mode 100644 index 00000000..7e69a5d9 --- /dev/null +++ b/sqlit/domains/connections/providers/db2i/schema.py @@ -0,0 +1,38 @@ +"""Connection schema for IBM DB2 for i.""" + +from sqlit.domains.connections.providers.schema_helpers import ( + SSH_FIELDS, + ConnectionSchema, + SchemaField, + _password_field, + _port_field, + _server_field, + _username_field, +) + +SCHEMA = ConnectionSchema( + db_type="db2i", + display_name="IBM DB2 for i", + fields=( + _server_field(), + _port_field(""), # Port is optional for IBM i Access ODBC Driver + SchemaField( + name="database", + label="Default Library", + placeholder="MYLIB", + required=False, + description="Default library (schema) to use for queries", + ), + _username_field(), + _password_field(), + SchemaField( + name="odbc_driver", + label="ODBC Driver Name", + placeholder="IBM i Access ODBC Driver", + required=False, + description="Name of the ODBC driver to use (default: IBM i Access ODBC Driver)", + ), + ) + + SSH_FIELDS, + default_port="", +) diff --git a/test_db2i_adapter.py b/test_db2i_adapter.py new file mode 100644 index 00000000..060479a2 --- /dev/null +++ b/test_db2i_adapter.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +"""Test script to verify DB2i adapter is working.""" + +import sys +sys.path.insert(0, '.') + +from sqlit.domains.connections.providers.db2i.adapter import Db2iAdapter +from sqlit.domains.connections.providers.catalog import get_supported_db_types, get_provider + +def test_adapter(): + """Test the DB2i adapter directly.""" + print("Testing DB2i Adapter...") + adapter = Db2iAdapter() + print(f"✓ Adapter name: {adapter.name}") + print(f"✓ Install package: {adapter.install_package}") + print(f"✓ Install extra: {adapter.install_extra}") + print(f"✓ Driver imports: {adapter.driver_import_names}") + print(f"✓ Supports multiple databases: {adapter.supports_multiple_databases}") + print(f"✓ Supports stored procedures: {adapter.supports_stored_procedures}") + print(f"✓ Supports sequences: {adapter.supports_sequences}") + print() + +def test_provider_registration(): + """Test that the provider is properly registered.""" + print("Testing Provider Registration...") + db_types = get_supported_db_types() + + if 'db2i' in db_types: + print("✓ DB2i provider is registered") + provider = get_provider('db2i') + print(f"✓ Provider display name: {provider.metadata.display_name}") + print(f"✓ Provider URL schemes: {provider.metadata.url_schemes}") + print(f"✓ Provider badge: {provider.metadata.badge_label}") + else: + print("✗ DB2i provider is NOT registered") + print(f"Available types: {sorted(db_types)}") + print() + +if __name__ == '__main__': + test_adapter() + test_provider_registration() + print("All tests completed!") diff --git a/tests/conftest.py b/tests/conftest.py index ff4c52cc..594c3884 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,6 +8,7 @@ from tests.fixtures.cockroachdb import * from tests.fixtures.db2 import * from tests.fixtures.d1 import * +from tests.fixtures.db2i import * from tests.fixtures.duckdb import * from tests.fixtures.firebird import * from tests.fixtures.flight import * diff --git a/tests/fixtures/db2i.py b/tests/fixtures/db2i.py new file mode 100644 index 00000000..2a372f83 --- /dev/null +++ b/tests/fixtures/db2i.py @@ -0,0 +1,45 @@ +"""IBM DB2 for i fixtures.""" + +from __future__ import annotations + +import os +from typing import TYPE_CHECKING + +import pytest + +if TYPE_CHECKING: + from sqlit.domains.connections.domain.config import ConnectionConfig + +# Environment variables for DB2 for i connection +DB2I_HOST = os.environ.get("DB2I_HOST", "localhost") +DB2I_PORT = os.environ.get("DB2I_PORT", "") # Optional port +DB2I_USER = os.environ.get("DB2I_USER", "QSECOFR") +DB2I_PASSWORD = os.environ.get("DB2I_PASSWORD", "password") +DB2I_LIBRARY = os.environ.get("DB2I_LIBRARY", "QGPL") # Default library +DB2I_ODBC_DRIVER = os.environ.get("DB2I_ODBC_DRIVER", "IBM i Access ODBC Driver") + + +@pytest.fixture +def db2i_connection(cli_runner): + """Create a DB2 for i connection configuration.""" + from sqlit.domains.connections.domain.config import ConnectionConfig, TcpEndpoint + + config = ConnectionConfig( + name="db2i_test", + db_type="db2i", + tcp_endpoint=TcpEndpoint( + host=DB2I_HOST, + port=DB2I_PORT if DB2I_PORT else None, + database=DB2I_LIBRARY, + username=DB2I_USER, + password=DB2I_PASSWORD, + ), + extra_options={"odbc_driver": DB2I_ODBC_DRIVER} if DB2I_ODBC_DRIVER else {}, + ) + return config + + +@pytest.fixture +def db2i_db(db2i_connection): + """Return the DB2 for i database/library name.""" + return DB2I_LIBRARY diff --git a/tests/test_db2i.py b/tests/test_db2i.py new file mode 100644 index 00000000..a999cbfd --- /dev/null +++ b/tests/test_db2i.py @@ -0,0 +1,19 @@ +"""Integration tests for IBM DB2 for i database operations.""" + +import pytest + +from tests.helpers import BaseDatabaseTests + + +@pytest.mark.db2i +class TestDb2iIntegration(BaseDatabaseTests): + """Integration tests for IBM DB2 for i database operations via CLI.""" + + @pytest.fixture(scope="class") + def db_spec(self): + return dict( + db_type="db2i", + display_name="IBM DB2 for i", + connection_fixture="db2i_connection", + db_fixture="db2i_db", + ) From 8b523d45db13a3e35a3ce0365176eb45ebe81d88 Mon Sep 17 00:00:00 2001 From: Tyler Abbott Date: Mon, 2 Feb 2026 21:32:01 -0600 Subject: [PATCH 2/3] fix columns not being shown --- .../connections/providers/db2i/adapter.py | 116 ++++++++++-------- 1 file changed, 63 insertions(+), 53 deletions(-) diff --git a/sqlit/domains/connections/providers/db2i/adapter.py b/sqlit/domains/connections/providers/db2i/adapter.py index 2cc7956c..04f5b818 100644 --- a/sqlit/domains/connections/providers/db2i/adapter.py +++ b/sqlit/domains/connections/providers/db2i/adapter.py @@ -114,21 +114,21 @@ def get_databases(self, conn: Any) -> list[str]: # Query QSYS2.SYSTABLES to get distinct schemas/libraries cursor = conn.cursor() cursor.execute( - "SELECT DISTINCT TABLE_SCHEMA FROM QSYS2.SYSTABLES " - "WHERE TABLE_SCHEMA NOT LIKE 'Q%' " - "ORDER BY TABLE_SCHEMA" + "SELECT DISTINCT table_schema FROM QSYS2.SYSTABLES " + "WHERE table_schema NOT LIKE 'Q%' " + "ORDER BY table_schema" ) return [row[0] for row in cursor.fetchall()] def get_tables(self, conn: Any, database: str | None = None) -> list[TableInfo]: cursor = conn.cursor() query = ( - "SELECT TABLE_SCHEMA, TABLE_NAME FROM QSYS2.SYSTABLES " - "WHERE TABLE_TYPE = 'T' AND TABLE_SCHEMA NOT LIKE 'Q%' " + "SELECT table_schema, table_name FROM QSYS2.SYSTABLES " + "WHERE table_type = 'T' AND table_schema NOT LIKE 'Q%' " ) if database: - query += f"AND TABLE_SCHEMA = '{database}' " - query += "ORDER BY TABLE_SCHEMA, TABLE_NAME" + query += f"AND table_schema = '{database}' " + query += "ORDER BY table_schema, table_name" cursor.execute(query) return [(row[0], row[1]) for row in cursor.fetchall()] @@ -136,12 +136,12 @@ def get_tables(self, conn: Any, database: str | None = None) -> list[TableInfo]: def get_views(self, conn: Any, database: str | None = None) -> list[TableInfo]: cursor = conn.cursor() query = ( - "SELECT TABLE_SCHEMA, TABLE_NAME FROM QSYS2.SYSVIEWS " - "WHERE TABLE_SCHEMA NOT LIKE 'Q%' " + "SELECT table_schema, table_name FROM QSYS2.SYSVIEWS " + "WHERE table_schema NOT LIKE 'Q%' " ) if database: - query += f"AND TABLE_SCHEMA = '{database}' " - query += "ORDER BY TABLE_SCHEMA, TABLE_NAME" + query += f"AND table_schema = '{database}' " + query += "ORDER BY table_schema, table_name" cursor.execute(query) return [(row[0], row[1]) for row in cursor.fetchall()] @@ -151,32 +151,42 @@ def get_columns( ) -> list[ColumnInfo]: cursor = conn.cursor() - # Get primary key columns + # Get primary key columns (try-catch for permissions) pk_columns: set[str] = set() if schema: - cursor.execute( - "SELECT COLUMN_NAME FROM QSYS2.SYSCST " - "WHERE CONSTRAINT_SCHEMA = ? AND CONSTRAINT_NAME IN (" - " SELECT CONSTRAINT_NAME FROM QSYS2.SYSCST " - " WHERE CONSTRAINT_SCHEMA = ? AND CONSTRAINT_TYPE = 'PRIMARY KEY' " - " AND TABLE_SCHEMA = ? AND TABLE_NAME = ?" - ")", - (schema, schema, schema, table), - ) - pk_columns = {row[0] for row in cursor.fetchall()} - - # Get column information - query = ( - "SELECT COLUMN_NAME, DATA_TYPE FROM QSYS2.SYSCOLUMNS " - "WHERE TABLE_NAME = ? " - ) - params: list[Any] = [table] + try: + cursor.execute( + "SELECT COLNAME FROM QSYS2.SYSCST " + "WHERE CONSTRAINT_SCHEMA = ? AND CONSTRAINT_NAME IN (" + " SELECT CONSTRAINT_NAME FROM QSYS2.SYSCST " + " WHERE CONSTRAINT_SCHEMA = ? AND TYPE = 'PRIMARY KEY' " + " AND TABLE_SCHEMA = ? AND TABLE_NAME = ?" + ")", + (schema, schema, schema, table), + ) + pk_columns = {row[0] for row in cursor.fetchall()} + except Exception: + # If primary key query fails, continue without PK info + pass + # Get column information using SYSIBM catalog (more compatible) + # Use columns() table function which is available on IBM i if schema: - query += "AND TABLE_SCHEMA = ? " - params.append(schema) - - query += "ORDER BY ORDINAL_POSITION" + query = ( + "SELECT COLUMN_NAME, TYPE_NAME " + "FROM SYSIBM.SQLCOLUMNS " + "WHERE TABLE_SCHEM = ? AND TABLE_NAME = ? " + "ORDER BY ORDINAL_POSITION" + ) + params = [schema, table] + else: + query = ( + "SELECT COLUMN_NAME, TYPE_NAME " + "FROM SYSIBM.SQLCOLUMNS " + "WHERE TABLE_NAME = ? " + "ORDER BY ORDINAL_POSITION" + ) + params = [table] cursor.execute(query, params) return [ @@ -187,13 +197,13 @@ def get_columns( def get_procedures(self, conn: Any, database: str | None = None) -> list[str]: cursor = conn.cursor() query = ( - "SELECT ROUTINE_NAME FROM QSYS2.SYSROUTINES " - "WHERE ROUTINE_SCHEMA NOT LIKE 'Q%' " - "AND ROUTINE_TYPE = 'PROCEDURE' " + "SELECT routine_name FROM QSYS2.SYSROUTINES " + "WHERE routine_schema NOT LIKE 'Q%' " + "AND routine_type = 'PROCEDURE' " ) if database: - query += f"AND ROUTINE_SCHEMA = '{database}' " - query += "ORDER BY ROUTINE_NAME" + query += f"AND routine_schema = '{database}' " + query += "ORDER BY routine_name" cursor.execute(query) return [row[0] for row in cursor.fetchall()] @@ -203,16 +213,16 @@ def get_indexes( ) -> list[IndexInfo]: cursor = conn.cursor() query = ( - "SELECT INDEX_NAME, IS_UNIQUE FROM QSYS2.SYSINDEXES " - "WHERE TABLE_NAME = ? " + "SELECT index_name, is_unique FROM QSYS2.SYSINDEXES " + "WHERE table_name = ? " ) params: list[Any] = [table] if schema: - query += "AND TABLE_SCHEMA = ? " + query += "AND table_schema = ? " params.append(schema) - query += "ORDER BY INDEX_NAME" + query += "ORDER BY index_name" cursor.execute(query, params) return [ @@ -225,16 +235,16 @@ def get_triggers( ) -> list[TriggerInfo]: cursor = conn.cursor() query = ( - "SELECT TRIGGER_NAME, EVENT_MANIPULATION FROM QSYS2.SYSTRIGGERS " - "WHERE EVENT_OBJECT_TABLE = ? " + "SELECT trigger_name, event_manipulation FROM QSYS2.SYSTRIGGERS " + "WHERE event_object_table = ? " ) params: list[Any] = [table] if schema: - query += "AND TRIGGER_SCHEMA = ? " + query += "AND trigger_schema = ? " params.append(schema) - query += "ORDER BY TRIGGER_NAME" + query += "ORDER BY trigger_name" cursor.execute(query, params) return [ @@ -245,12 +255,12 @@ def get_triggers( def get_sequences(self, conn: Any, database: str | None = None) -> list[SequenceInfo]: cursor = conn.cursor() query = ( - "SELECT SEQUENCE_NAME FROM QSYS2.SYSSEQUENCES " - "WHERE SEQUENCE_SCHEMA NOT LIKE 'Q%' " + "SELECT sequence_name FROM QSYS2.SYSSEQUENCES " + "WHERE sequence_schema NOT LIKE 'Q%' " ) if database: - query += f"AND SEQUENCE_SCHEMA = '{database}' " - query += "ORDER BY SEQUENCE_NAME" + query += f"AND sequence_schema = '{database}' " + query += "ORDER BY sequence_name" cursor.execute(query) return [SequenceInfo(name=row[0]) for row in cursor.fetchall()] @@ -261,14 +271,14 @@ def get_sequence_info( """Get detailed information about a DB2 for i sequence.""" cursor = conn.cursor() query = ( - "SELECT START_VALUE, INCREMENT, MIN_VALUE, MAX_VALUE, CYCLE_OPTION " + "SELECT start_value, increment, minimum_value, maximum_value, cycle_option " "FROM QSYS2.SYSSEQUENCES " - "WHERE SEQUENCE_NAME = ? " + "WHERE sequence_name = ? " ) params: list[Any] = [sequence_name] if database: - query += "AND SEQUENCE_SCHEMA = ? " + query += "AND sequence_schema = ? " params.append(database) cursor.execute(query, params) From 06229787826a7f8c5b851f099953cae88c637010 Mon Sep 17 00:00:00 2001 From: Tyler Abbott Date: Sat, 7 Feb 2026 17:00:37 -0600 Subject: [PATCH 3/3] add ibm db2 for i into the new connection UI --- sqlit/domains/connections/domain/config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sqlit/domains/connections/domain/config.py b/sqlit/domains/connections/domain/config.py index c2eba8f3..7e6e5007 100644 --- a/sqlit/domains/connections/domain/config.py +++ b/sqlit/domains/connections/domain/config.py @@ -16,6 +16,7 @@ class DatabaseType(str, Enum): D1 = "d1" DUCKDB = "duckdb" DB2 = "db2" + DB2I = "db2i" FIREBIRD = "firebird" FLIGHT = "flight" HANA = "hana" @@ -46,6 +47,7 @@ class DatabaseType(str, Enum): DatabaseType.ORACLE, DatabaseType.ORACLE_LEGACY, DatabaseType.DB2, + DatabaseType.DB2I, DatabaseType.HANA, DatabaseType.TERADATA, DatabaseType.SNOWFLAKE,