-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added a python DB API driver for GraphQLDriver
- Loading branch information
Showing
9 changed files
with
608 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# Python | ||
__pycache__/ | ||
*.py[cod] | ||
*$py.class | ||
*.so | ||
.Python | ||
build/ | ||
develop-eggs/ | ||
dist/ | ||
downloads/ | ||
eggs/ | ||
.eggs/ | ||
lib/ | ||
lib64/ | ||
parts/ | ||
sdist/ | ||
var/ | ||
wheels/ | ||
*.egg-info/ | ||
.installed.cfg | ||
*.egg | ||
|
||
# Virtual Environment | ||
.env | ||
.venv | ||
env/ | ||
venv/ | ||
ENV/ | ||
|
||
# IDE | ||
.idea/ | ||
.vscode/ | ||
*.swp | ||
*.swo | ||
|
||
# Tests | ||
.coverage | ||
htmlcov/ | ||
.pytest_cache/ | ||
.mypy_cache/ | ||
|
||
# Logs | ||
*.log |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
# Python DB-API for Hasura DDN | ||
|
||
This is a Python DB-API 2.0 compliant implementation for connecting to Hasura DDN endpoints using SQL through the Hasura GraphQL JDBC driver. It allows you to query Hasura DDN endpoints using SQL:2003 syntax through a JDBC bridge. | ||
|
||
## Installation | ||
|
||
```bash | ||
# Using poetry (recommended) | ||
poetry add python-db-api | ||
|
||
# Or using pip | ||
pip install python-db-api | ||
``` | ||
|
||
## Prerequisites | ||
|
||
1. Python 3.9 or higher | ||
2. Java JDK 11 or higher installed and accessible in your system path | ||
3. `graphql-jdbc-driver-1.0.0-jar-with-dependencies.jar` - this single JAR file contains all required dependencies | ||
|
||
## Basic Usage | ||
|
||
Here's a simple example of how to use the DB-API: | ||
|
||
```python | ||
from python_db_api import connect | ||
import os | ||
|
||
# Connection parameters | ||
host = "http://localhost:3000/graphql" # Your Hasura DDN endpoint | ||
jdbc_args = {"role": "admin"} # Connection properties | ||
|
||
# Path to directory containing the all-in-one driver JAR | ||
driver_paths = ["/path/to/jdbc/target"] # Directory containing graphql-jdbc-driver-1.0.0-jar-with-dependencies.jar | ||
|
||
# Create connection using context manager | ||
with connect(host, jdbc_args, driver_paths) as conn: | ||
with conn.cursor() as cur: | ||
# Execute SQL:2003 query | ||
cur.execute("SELECT * FROM Albums") | ||
|
||
# Fetch results | ||
for row in cur.fetchall(): | ||
print(f"Result: {row}") | ||
``` | ||
|
||
## Connection Parameters | ||
|
||
### Required Parameters | ||
|
||
- `host`: The Hasura DDN endpoint URL (e.g., "http://localhost:3000/graphql") | ||
- `driver_paths`: List containing the directory path where `graphql-jdbc-driver-1.0.0-jar-with-dependencies.jar` is located | ||
|
||
### Optional Parameters | ||
|
||
- `jdbc_args`: Dictionary of connection properties | ||
- Supported properties: "role", "user", "auth" | ||
- Example: `{"role": "admin", "auth": "bearer token"}` | ||
|
||
## Connection Properties | ||
|
||
You can pass various connection properties through the `jdbc_args` parameter: | ||
|
||
```python | ||
jdbc_args = { | ||
"role": "admin", # Hasura role | ||
"user": "username", # Optional username | ||
"auth": "token" # Optional auth token | ||
} | ||
``` | ||
|
||
## Directory Structure | ||
|
||
The driver requires a single JAR file. Example structure: | ||
|
||
``` | ||
/path/to/jdbc/ | ||
└── target/ | ||
└── graphql-jdbc-driver-1.0.0-jar-with-dependencies.jar | ||
``` | ||
|
||
## Error Handling | ||
|
||
The implementation provides clear error messages for common issues: | ||
|
||
```python | ||
try: | ||
with connect(host, jdbc_args, driver_paths) as conn: | ||
# ... your code ... | ||
except DatabaseError as e: | ||
print(f"Database error occurred: {e}") | ||
``` | ||
|
||
Common errors: | ||
- Missing driver JAR file | ||
- Invalid driver path | ||
- Connection failures | ||
- Invalid SQL:2003 queries | ||
|
||
## Context Manager Support | ||
|
||
The implementation supports both context manager and traditional connection patterns: | ||
|
||
```python | ||
# Using context manager (recommended) | ||
with connect(host, jdbc_args, driver_paths) as conn: | ||
# ... your code ... | ||
|
||
# Traditional approach | ||
conn = connect(host, jdbc_args, driver_paths) | ||
try: | ||
# ... your code ... | ||
finally: | ||
conn.close() | ||
``` | ||
|
||
## Type Hints | ||
|
||
The implementation includes type hints for better IDE support and code completion: | ||
|
||
```python | ||
from python_db_api import connect | ||
from typing import List | ||
|
||
def get_connection( | ||
host: str, | ||
properties: dict[str, str], | ||
paths: List[str] | ||
) -> None: | ||
with connect(host, properties, paths) as conn: | ||
# Your code here | ||
pass | ||
``` | ||
|
||
## Thread Safety | ||
|
||
The connection is not thread-safe. Each thread should create its own connection instance. | ||
|
||
## Dependencies | ||
|
||
- `jaydebeapi`: Java Database Connectivity (JDBC) bridge | ||
- `jpype1`: Java to Python integration | ||
- Java JDK 11+ | ||
- `graphql-jdbc-driver-1.0.0-jar-with-dependencies.jar` | ||
|
||
## Limitations | ||
|
||
- One JVM per Python process | ||
- Cannot modify classpath after JVM starts | ||
|
||
## Contributing | ||
|
||
Contributions are welcome! Please feel free to submit a Pull Request. | ||
|
||
## License | ||
|
||
This project is licensed under the MIT License. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
"""Example usage of the DB-API implementation.""" | ||
from py_graphql_sql import connect | ||
import os | ||
|
||
def main() -> None: | ||
"""Basic example of connecting to a database and executing queries.""" | ||
# Connection parameters | ||
host = "http://localhost:3000/graphql" | ||
jdbc_args = {"role": "admin"} | ||
|
||
# Get paths to JAR directories | ||
current_dir = os.path.dirname(os.path.abspath(__file__)) | ||
driver_paths = [ | ||
os.path.abspath(os.path.join(current_dir, "../../jdbc/target")) # Add additional paths as needed | ||
] | ||
|
||
# Create connection using context manager | ||
with connect(host, jdbc_args, driver_paths) as conn: | ||
with conn.cursor() as cur: | ||
# Execute a query | ||
cur.execute("SELECT * FROM Albums", []) | ||
|
||
# Fetch all results | ||
rows = cur.fetchall() | ||
|
||
# Display all rows | ||
for row in rows: | ||
print(f"Result: {row}") | ||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
"""DB-API 2.0 compliant JDBC connector.""" | ||
|
||
from .connection import Connection, connect | ||
from .cursor import Cursor | ||
from .exceptions import ( | ||
Error, Warning, InterfaceError, DatabaseError, | ||
DataError, OperationalError, IntegrityError, | ||
InternalError, ProgrammingError, NotSupportedError | ||
) | ||
|
||
# DB-API 2.0 required globals | ||
apilevel = '2.0' | ||
threadsafety = 1 # Threads may share the module but not connections | ||
paramstyle = 'qmark' # Question mark style, e.g. ...WHERE name=? | ||
|
||
__all__ = [ | ||
'Connection', | ||
'Cursor', | ||
'connect', | ||
'apilevel', | ||
'threadsafety', | ||
'paramstyle', | ||
'Error', | ||
'Warning', | ||
'InterfaceError', | ||
'DatabaseError', | ||
'DataError', | ||
'OperationalError', | ||
'IntegrityError', | ||
'InternalError', | ||
'ProgrammingError', | ||
'NotSupportedError', | ||
] |
115 changes: 115 additions & 0 deletions
115
calcite-rs-jni/py_graphql_sql/py_graphql_sql/connection.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
"""DB-API 2.0 Connection implementation.""" | ||
from __future__ import annotations | ||
from contextlib import AbstractContextManager | ||
from typing import Optional, Any, List | ||
import jaydebeapi | ||
import jpype | ||
import os | ||
import glob | ||
|
||
from .exceptions import DatabaseError | ||
from .db_types import JDBCArgs, JDBCPath | ||
|
||
JDBC_DRIVER = "com.hasura.GraphQLDriver" | ||
EXCLUDED_JAR = "graphql-jdbc-driver-1.0.0.jar" | ||
|
||
class Connection(AbstractContextManager['Connection']): | ||
"""DB-API 2.0 Connection class.""" | ||
|
||
def __init__( | ||
self, | ||
host: str, | ||
jdbc_args: JDBCArgs = None, | ||
driver_paths: List[str] = None | ||
) -> None: | ||
"""Initialize connection.""" | ||
try: | ||
# Start JVM if it's not already started | ||
if not jpype.isJVMStarted(): | ||
# Build classpath from all JARs in provided directories | ||
classpath = [] | ||
if driver_paths: | ||
for path in driver_paths: | ||
if not os.path.exists(path): | ||
raise DatabaseError(f"Driver path not found: {path}") | ||
|
||
# Find all JAR files in the directory | ||
jar_files = glob.glob(os.path.join(path, "*.jar")) | ||
|
||
# Add all JARs except the excluded one | ||
for jar in jar_files: | ||
if os.path.basename(jar) != EXCLUDED_JAR: | ||
classpath.append(jar) | ||
|
||
if not classpath: | ||
raise DatabaseError("No JAR files found in provided paths") | ||
|
||
# Join all paths with OS-specific path separator | ||
classpath_str = os.pathsep.join(classpath) | ||
|
||
jpype.startJVM( | ||
jpype.getDefaultJVMPath(), | ||
f"-Djava.class.path={classpath_str}", | ||
convertStrings=True | ||
) | ||
|
||
# Construct JDBC URL | ||
jdbc_url = f"jdbc:graphql:{host}" | ||
|
||
# Create Properties object | ||
props = jpype.JClass('java.util.Properties')() | ||
|
||
# Add any properties from jdbc_args | ||
if jdbc_args: | ||
if isinstance(jdbc_args, dict): | ||
for key, value in jdbc_args.items(): | ||
props.setProperty(key, str(value)) | ||
elif isinstance(jdbc_args, list) and len(jdbc_args) > 0: | ||
props.setProperty("role", jdbc_args[0]) | ||
|
||
# Connect using URL and properties | ||
self._jdbc_connection = jaydebeapi.connect( | ||
jclassname=JDBC_DRIVER, | ||
url=jdbc_url, | ||
driver_args=[props], | ||
jars=None | ||
) | ||
self.closed: bool = False | ||
except Exception as e: | ||
raise DatabaseError(f"Failed to connect: {str(e)}") from e | ||
|
||
def __enter__(self) -> 'Connection': | ||
"""Enter context manager.""" | ||
return self | ||
|
||
def __exit__(self, exc_type: Optional[type], exc_val: Optional[Exception], | ||
exc_tb: Optional[Any]) -> None: | ||
"""Exit context manager.""" | ||
self.close() | ||
|
||
def close(self) -> None: | ||
"""Close the connection.""" | ||
if not self.closed: | ||
self._jdbc_connection.close() | ||
self.closed = True | ||
|
||
def cursor(self): | ||
"""Create a new cursor.""" | ||
if self.closed: | ||
raise DatabaseError("Connection is closed") | ||
return self._jdbc_connection.cursor() | ||
|
||
def connect( | ||
host: str, | ||
jdbc_args: JDBCArgs = None, | ||
driver_paths: List[str] = None, | ||
) -> Connection: | ||
""" | ||
Create a new database connection. | ||
Args: | ||
host: The GraphQL server host (e.g., 'http://localhost:3000/graphql') | ||
jdbc_args: Optional connection arguments (dict or list) | ||
driver_paths: List of paths to directories containing JAR files | ||
""" | ||
return Connection(host, jdbc_args, driver_paths) |
Oops, something went wrong.