sqlmesh-gizmosql 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlmesh_gizmosql-0.1.0/PKG-INFO +134 -0
- sqlmesh_gizmosql-0.1.0/README.md +100 -0
- sqlmesh_gizmosql-0.1.0/pyproject.toml +76 -0
- sqlmesh_gizmosql-0.1.0/setup.cfg +4 -0
- sqlmesh_gizmosql-0.1.0/sqlmesh_gizmosql/__init__.py +65 -0
- sqlmesh_gizmosql-0.1.0/sqlmesh_gizmosql/adapter.py +289 -0
- sqlmesh_gizmosql-0.1.0/sqlmesh_gizmosql/connection.py +134 -0
- sqlmesh_gizmosql-0.1.0/sqlmesh_gizmosql.egg-info/PKG-INFO +134 -0
- sqlmesh_gizmosql-0.1.0/sqlmesh_gizmosql.egg-info/SOURCES.txt +11 -0
- sqlmesh_gizmosql-0.1.0/sqlmesh_gizmosql.egg-info/dependency_links.txt +1 -0
- sqlmesh_gizmosql-0.1.0/sqlmesh_gizmosql.egg-info/requires.txt +9 -0
- sqlmesh_gizmosql-0.1.0/sqlmesh_gizmosql.egg-info/top_level.txt +1 -0
- sqlmesh_gizmosql-0.1.0/tests/test_gizmosql.py +235 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sqlmesh-gizmosql
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: GizmoSQL engine adapter for SQLMesh
|
|
5
|
+
Author: Philip
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/gizmodata/sqlmesh-gizmosql
|
|
8
|
+
Project-URL: Repository, https://github.com/gizmodata/sqlmesh-gizmosql
|
|
9
|
+
Project-URL: Documentation, https://github.com/gizmodata/sqlmesh-gizmosql#readme
|
|
10
|
+
Keywords: sqlmesh,gizmosql,duckdb,arrow,flight-sql,data-engineering
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Database
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
Requires-Dist: sqlmesh>=0.100.0
|
|
27
|
+
Requires-Dist: adbc-driver-flightsql>=1.10.0
|
|
28
|
+
Requires-Dist: pyarrow>=22.0.0
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
33
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
34
|
+
|
|
35
|
+
# SQLMesh GizmoSQL Adapter
|
|
36
|
+
|
|
37
|
+
A [SQLMesh](https://sqlmesh.com) engine adapter for [GizmoSQL](https://github.com/gizmodata/gizmosql) - a database server that uses DuckDB as its execution engine and exposes an Arrow Flight SQL interface for remote connections.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install sqlmesh-gizmosql
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
This will install `sqlmesh`, `adbc-driver-flightsql`, and `pyarrow` as dependencies.
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
### 1. Import the adapter
|
|
50
|
+
|
|
51
|
+
Simply import the package before using SQLMesh. The adapter registers itself automatically:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
import sqlmesh_gizmosql # Registers GizmoSQL adapter
|
|
55
|
+
from sqlmesh import Context
|
|
56
|
+
|
|
57
|
+
context = Context(paths="path/to/project")
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 2. Configure your connection
|
|
61
|
+
|
|
62
|
+
Add a GizmoSQL connection to your `config.yaml`:
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
gateways:
|
|
66
|
+
my_gizmosql:
|
|
67
|
+
connection:
|
|
68
|
+
type: gizmosql
|
|
69
|
+
host: localhost
|
|
70
|
+
port: 31337
|
|
71
|
+
username: your_username
|
|
72
|
+
password: your_password
|
|
73
|
+
database: my_database # optional, default catalog
|
|
74
|
+
use_encryption: true # default: true (uses TLS)
|
|
75
|
+
disable_certificate_verification: false # for self-signed certs
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 3. Use SQLMesh as normal
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
sqlmesh plan
|
|
82
|
+
sqlmesh run
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Configuration Options
|
|
86
|
+
|
|
87
|
+
| Option | Type | Default | Description |
|
|
88
|
+
|--------|------|---------|-------------|
|
|
89
|
+
| `host` | str | `localhost` | GizmoSQL server hostname |
|
|
90
|
+
| `port` | int | `31337` | GizmoSQL server port |
|
|
91
|
+
| `username` | str | *required* | Authentication username |
|
|
92
|
+
| `password` | str | *required* | Authentication password |
|
|
93
|
+
| `database` | str | `None` | Default database/catalog |
|
|
94
|
+
| `use_encryption` | bool | `True` | Use TLS encryption |
|
|
95
|
+
| `disable_certificate_verification` | bool | `False` | Skip TLS cert verification |
|
|
96
|
+
| `concurrent_tasks` | int | `4` | Max concurrent tasks |
|
|
97
|
+
| `register_comments` | bool | `True` | Register model comments |
|
|
98
|
+
| `pre_ping` | bool | `False` | Pre-ping connections |
|
|
99
|
+
|
|
100
|
+
## Features
|
|
101
|
+
|
|
102
|
+
- **Arrow Flight SQL**: Efficient data transfer using Arrow's columnar format
|
|
103
|
+
- **Full Catalog Support**: Create, drop, and switch between databases
|
|
104
|
+
- **Transaction Support**: Full transaction control via SQL statements
|
|
105
|
+
- **ADBC Bulk Ingestion**: Fast data loading using Arrow-native bulk operations
|
|
106
|
+
- **DuckDB Compatibility**: Uses DuckDB SQL dialect for query generation
|
|
107
|
+
|
|
108
|
+
## Requirements
|
|
109
|
+
|
|
110
|
+
- Python >= 3.10
|
|
111
|
+
- SQLMesh >= 0.100.0
|
|
112
|
+
- A running GizmoSQL server with DuckDB backend
|
|
113
|
+
|
|
114
|
+
## Development
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Clone the repository
|
|
118
|
+
git clone https://github.com/philip/sqlmesh-gizmosql.git
|
|
119
|
+
cd sqlmesh-gizmosql
|
|
120
|
+
|
|
121
|
+
# Install dev dependencies
|
|
122
|
+
pip install -e ".[dev]"
|
|
123
|
+
|
|
124
|
+
# Run tests
|
|
125
|
+
pytest
|
|
126
|
+
|
|
127
|
+
# Run linting
|
|
128
|
+
ruff check .
|
|
129
|
+
mypy sqlmesh_gizmosql
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
Apache 2.0
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# SQLMesh GizmoSQL Adapter
|
|
2
|
+
|
|
3
|
+
A [SQLMesh](https://sqlmesh.com) engine adapter for [GizmoSQL](https://github.com/gizmodata/gizmosql) - a database server that uses DuckDB as its execution engine and exposes an Arrow Flight SQL interface for remote connections.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install sqlmesh-gizmosql
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This will install `sqlmesh`, `adbc-driver-flightsql`, and `pyarrow` as dependencies.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### 1. Import the adapter
|
|
16
|
+
|
|
17
|
+
Simply import the package before using SQLMesh. The adapter registers itself automatically:
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import sqlmesh_gizmosql # Registers GizmoSQL adapter
|
|
21
|
+
from sqlmesh import Context
|
|
22
|
+
|
|
23
|
+
context = Context(paths="path/to/project")
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 2. Configure your connection
|
|
27
|
+
|
|
28
|
+
Add a GizmoSQL connection to your `config.yaml`:
|
|
29
|
+
|
|
30
|
+
```yaml
|
|
31
|
+
gateways:
|
|
32
|
+
my_gizmosql:
|
|
33
|
+
connection:
|
|
34
|
+
type: gizmosql
|
|
35
|
+
host: localhost
|
|
36
|
+
port: 31337
|
|
37
|
+
username: your_username
|
|
38
|
+
password: your_password
|
|
39
|
+
database: my_database # optional, default catalog
|
|
40
|
+
use_encryption: true # default: true (uses TLS)
|
|
41
|
+
disable_certificate_verification: false # for self-signed certs
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 3. Use SQLMesh as normal
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
sqlmesh plan
|
|
48
|
+
sqlmesh run
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Configuration Options
|
|
52
|
+
|
|
53
|
+
| Option | Type | Default | Description |
|
|
54
|
+
|--------|------|---------|-------------|
|
|
55
|
+
| `host` | str | `localhost` | GizmoSQL server hostname |
|
|
56
|
+
| `port` | int | `31337` | GizmoSQL server port |
|
|
57
|
+
| `username` | str | *required* | Authentication username |
|
|
58
|
+
| `password` | str | *required* | Authentication password |
|
|
59
|
+
| `database` | str | `None` | Default database/catalog |
|
|
60
|
+
| `use_encryption` | bool | `True` | Use TLS encryption |
|
|
61
|
+
| `disable_certificate_verification` | bool | `False` | Skip TLS cert verification |
|
|
62
|
+
| `concurrent_tasks` | int | `4` | Max concurrent tasks |
|
|
63
|
+
| `register_comments` | bool | `True` | Register model comments |
|
|
64
|
+
| `pre_ping` | bool | `False` | Pre-ping connections |
|
|
65
|
+
|
|
66
|
+
## Features
|
|
67
|
+
|
|
68
|
+
- **Arrow Flight SQL**: Efficient data transfer using Arrow's columnar format
|
|
69
|
+
- **Full Catalog Support**: Create, drop, and switch between databases
|
|
70
|
+
- **Transaction Support**: Full transaction control via SQL statements
|
|
71
|
+
- **ADBC Bulk Ingestion**: Fast data loading using Arrow-native bulk operations
|
|
72
|
+
- **DuckDB Compatibility**: Uses DuckDB SQL dialect for query generation
|
|
73
|
+
|
|
74
|
+
## Requirements
|
|
75
|
+
|
|
76
|
+
- Python >= 3.10
|
|
77
|
+
- SQLMesh >= 0.100.0
|
|
78
|
+
- A running GizmoSQL server with DuckDB backend
|
|
79
|
+
|
|
80
|
+
## Development
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Clone the repository
|
|
84
|
+
git clone https://github.com/philip/sqlmesh-gizmosql.git
|
|
85
|
+
cd sqlmesh-gizmosql
|
|
86
|
+
|
|
87
|
+
# Install dev dependencies
|
|
88
|
+
pip install -e ".[dev]"
|
|
89
|
+
|
|
90
|
+
# Run tests
|
|
91
|
+
pytest
|
|
92
|
+
|
|
93
|
+
# Run linting
|
|
94
|
+
ruff check .
|
|
95
|
+
mypy sqlmesh_gizmosql
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
Apache 2.0
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sqlmesh-gizmosql"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "GizmoSQL engine adapter for SQLMesh"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "Apache-2.0"}
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Philip"}
|
|
14
|
+
]
|
|
15
|
+
keywords = ["sqlmesh", "gizmosql", "duckdb", "arrow", "flight-sql", "data-engineering"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Intended Audience :: Science/Research",
|
|
20
|
+
"License :: OSI Approved :: Apache Software License",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Programming Language :: Python :: 3.14",
|
|
28
|
+
"Topic :: Database",
|
|
29
|
+
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
dependencies = [
|
|
33
|
+
"sqlmesh>=0.100.0",
|
|
34
|
+
"adbc-driver-flightsql>=1.10.0",
|
|
35
|
+
"pyarrow>=22.0.0",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
dev = [
|
|
40
|
+
"pytest>=7.0.0",
|
|
41
|
+
"pytest-cov>=4.0.0",
|
|
42
|
+
"mypy>=1.0.0",
|
|
43
|
+
"ruff>=0.1.0",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[project.urls]
|
|
47
|
+
Homepage = "https://github.com/gizmodata/sqlmesh-gizmosql"
|
|
48
|
+
Repository = "https://github.com/gizmodata/sqlmesh-gizmosql"
|
|
49
|
+
Documentation = "https://github.com/gizmodata/sqlmesh-gizmosql#readme"
|
|
50
|
+
|
|
51
|
+
[tool.setuptools.packages.find]
|
|
52
|
+
include = ["sqlmesh_gizmosql*"]
|
|
53
|
+
|
|
54
|
+
[tool.pytest.ini_options]
|
|
55
|
+
testpaths = ["tests"]
|
|
56
|
+
python_files = ["test_*.py"]
|
|
57
|
+
python_functions = ["test_*"]
|
|
58
|
+
addopts = "-v --tb=short -m 'not integration'"
|
|
59
|
+
markers = [
|
|
60
|
+
"integration: marks tests as integration tests (require Docker)",
|
|
61
|
+
"docker: marks tests that require Docker",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
[tool.mypy]
|
|
65
|
+
python_version = "3.10"
|
|
66
|
+
warn_return_any = true
|
|
67
|
+
warn_unused_configs = true
|
|
68
|
+
ignore_missing_imports = true
|
|
69
|
+
|
|
70
|
+
[tool.ruff]
|
|
71
|
+
line-length = 100
|
|
72
|
+
target-version = "py310"
|
|
73
|
+
|
|
74
|
+
[tool.ruff.lint]
|
|
75
|
+
select = ["E", "F", "I", "W"]
|
|
76
|
+
ignore = ["E501"]
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SQLMesh GizmoSQL Adapter.
|
|
3
|
+
|
|
4
|
+
This package provides GizmoSQL support for SQLMesh. When imported, it automatically
|
|
5
|
+
registers the GizmoSQL engine adapter and connection configuration with SQLMesh.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
# Option 1: Explicit import (recommended for clarity)
|
|
9
|
+
import sqlmesh_gizmosql
|
|
10
|
+
from sqlmesh import Context
|
|
11
|
+
|
|
12
|
+
# Option 2: Just import - registration happens automatically
|
|
13
|
+
import sqlmesh_gizmosql # noqa: F401
|
|
14
|
+
|
|
15
|
+
# Then use SQLMesh normally with type: gizmosql in your config
|
|
16
|
+
|
|
17
|
+
Example config.yaml:
|
|
18
|
+
gateways:
|
|
19
|
+
my_gizmosql:
|
|
20
|
+
connection:
|
|
21
|
+
type: gizmosql
|
|
22
|
+
host: localhost
|
|
23
|
+
port: 31337
|
|
24
|
+
username: user
|
|
25
|
+
password: pass
|
|
26
|
+
database: my_database
|
|
27
|
+
"""
|
|
28
|
+
from sqlmesh_gizmosql.adapter import GizmoSQLEngineAdapter
|
|
29
|
+
from sqlmesh_gizmosql.connection import GizmoSQLConnectionConfig
|
|
30
|
+
|
|
31
|
+
__version__ = "0.1.0"
|
|
32
|
+
__all__ = ["GizmoSQLEngineAdapter", "GizmoSQLConnectionConfig", "register", "__version__"]
|
|
33
|
+
|
|
34
|
+
_registered = False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def register() -> None:
|
|
38
|
+
"""
|
|
39
|
+
Register GizmoSQL adapter and connection config with SQLMesh.
|
|
40
|
+
|
|
41
|
+
This function is called automatically when the module is imported,
|
|
42
|
+
but can also be called explicitly if needed.
|
|
43
|
+
"""
|
|
44
|
+
global _registered
|
|
45
|
+
if _registered:
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
# Register the engine adapter
|
|
49
|
+
from sqlmesh.core import engine_adapter
|
|
50
|
+
if "gizmosql" not in engine_adapter.DIALECT_TO_ENGINE_ADAPTER:
|
|
51
|
+
engine_adapter.DIALECT_TO_ENGINE_ADAPTER["gizmosql"] = GizmoSQLEngineAdapter
|
|
52
|
+
|
|
53
|
+
# Register the connection config
|
|
54
|
+
from sqlmesh.core.config import connection as conn_module
|
|
55
|
+
if "gizmosql" not in conn_module.CONNECTION_CONFIG_TO_TYPE:
|
|
56
|
+
conn_module.CONNECTION_CONFIG_TO_TYPE["gizmosql"] = GizmoSQLConnectionConfig
|
|
57
|
+
|
|
58
|
+
if "gizmosql" not in conn_module.DIALECT_TO_TYPE:
|
|
59
|
+
conn_module.DIALECT_TO_TYPE["gizmosql"] = GizmoSQLConnectionConfig.DIALECT
|
|
60
|
+
|
|
61
|
+
_registered = True
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Auto-register on import
|
|
65
|
+
register()
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GizmoSQL Engine Adapter for SQLMesh.
|
|
3
|
+
|
|
4
|
+
GizmoSQL is a database server that uses DuckDB as its execution engine and
|
|
5
|
+
exposes an Arrow Flight SQL interface for remote connections.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import contextlib
|
|
10
|
+
import typing as t
|
|
11
|
+
|
|
12
|
+
from sqlglot import exp
|
|
13
|
+
from sqlmesh.core.engine_adapter.mixins import (
|
|
14
|
+
GetCurrentCatalogFromFunctionMixin,
|
|
15
|
+
LogicalMergeMixin,
|
|
16
|
+
PandasNativeFetchDFSupportMixin,
|
|
17
|
+
RowDiffMixin,
|
|
18
|
+
)
|
|
19
|
+
from sqlmesh.core.engine_adapter.shared import (
|
|
20
|
+
CatalogSupport,
|
|
21
|
+
CommentCreationTable,
|
|
22
|
+
CommentCreationView,
|
|
23
|
+
DataObject,
|
|
24
|
+
DataObjectType,
|
|
25
|
+
SourceQuery,
|
|
26
|
+
set_catalog,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
if t.TYPE_CHECKING:
|
|
30
|
+
from sqlmesh.core._typing import SchemaName, TableName
|
|
31
|
+
from sqlmesh.core.engine_adapter._typing import DF
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@set_catalog(override_mapping={"_get_data_objects": CatalogSupport.REQUIRES_SET_CATALOG})
|
|
35
|
+
class GizmoSQLEngineAdapter(
|
|
36
|
+
LogicalMergeMixin,
|
|
37
|
+
GetCurrentCatalogFromFunctionMixin,
|
|
38
|
+
PandasNativeFetchDFSupportMixin,
|
|
39
|
+
RowDiffMixin,
|
|
40
|
+
):
|
|
41
|
+
"""
|
|
42
|
+
GizmoSQL Engine Adapter.
|
|
43
|
+
|
|
44
|
+
GizmoSQL is a database server that uses DuckDB as its execution engine and
|
|
45
|
+
exposes an Arrow Flight SQL interface for remote connections. This adapter
|
|
46
|
+
uses ADBC (Arrow Database Connectivity) with the Flight SQL driver to
|
|
47
|
+
communicate with GizmoSQL servers.
|
|
48
|
+
|
|
49
|
+
Key characteristics:
|
|
50
|
+
- Uses DuckDB SQL dialect for query generation
|
|
51
|
+
- Connects remotely via Arrow Flight SQL (ADBC)
|
|
52
|
+
- Supports full catalog operations
|
|
53
|
+
- Returns data as Arrow tables for efficient transfer
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
DIALECT = "duckdb"
|
|
57
|
+
SUPPORTS_TRANSACTIONS = True
|
|
58
|
+
SCHEMA_DIFFER_KWARGS = {
|
|
59
|
+
"parameterized_type_defaults": {
|
|
60
|
+
exp.DataType.build("DECIMAL", dialect="duckdb").this: [(18, 3), (0,)],
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
COMMENT_CREATION_TABLE = CommentCreationTable.COMMENT_COMMAND_ONLY
|
|
64
|
+
COMMENT_CREATION_VIEW = CommentCreationView.COMMENT_COMMAND_ONLY
|
|
65
|
+
SUPPORTS_CREATE_DROP_CATALOG = True
|
|
66
|
+
SUPPORTED_DROP_CASCADE_OBJECT_KINDS = ["SCHEMA", "TABLE", "VIEW"]
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def catalog_support(self) -> CatalogSupport:
|
|
70
|
+
return CatalogSupport.FULL_SUPPORT
|
|
71
|
+
|
|
72
|
+
# DDL/DML keywords that need fetch to trigger GizmoSQL's lazy execution
|
|
73
|
+
_DDL_DML_KEYWORDS = frozenset({
|
|
74
|
+
"CREATE", "DROP", "ALTER", "TRUNCATE", "RENAME", "COMMENT", "USE", "SET",
|
|
75
|
+
"INSERT", "UPDATE", "DELETE", "MERGE", "COPY", "ATTACH", "DETACH",
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
def _execute(self, sql: str, track_rows_processed: bool = False, **kwargs: t.Any) -> None:
|
|
79
|
+
"""
|
|
80
|
+
Execute a SQL statement.
|
|
81
|
+
|
|
82
|
+
GizmoSQL uses lazy execution - statements are not actually executed
|
|
83
|
+
until results are fetched. For DDL/DML statements, we call fetchall()
|
|
84
|
+
to ensure immediate execution. For SELECT queries, we let the caller
|
|
85
|
+
fetch the results.
|
|
86
|
+
"""
|
|
87
|
+
self.cursor.execute(sql, **kwargs)
|
|
88
|
+
|
|
89
|
+
# For DDL/DML, fetch to trigger GizmoSQL's lazy execution
|
|
90
|
+
sql_upper = sql.strip().upper()
|
|
91
|
+
first_word = sql_upper.split()[0] if sql_upper else ""
|
|
92
|
+
if first_word in self._DDL_DML_KEYWORDS:
|
|
93
|
+
self.cursor.fetchall()
|
|
94
|
+
|
|
95
|
+
@contextlib.contextmanager
|
|
96
|
+
def transaction(
|
|
97
|
+
self,
|
|
98
|
+
condition: t.Optional[bool] = None,
|
|
99
|
+
) -> t.Iterator[None]:
|
|
100
|
+
"""
|
|
101
|
+
A transaction context manager using SQL statements.
|
|
102
|
+
|
|
103
|
+
GizmoSQL's ADBC connection doesn't support the standard begin/commit/rollback
|
|
104
|
+
methods, so we use explicit SQL statements (BEGIN TRANSACTION, COMMIT, ROLLBACK)
|
|
105
|
+
for transaction control.
|
|
106
|
+
"""
|
|
107
|
+
if (
|
|
108
|
+
self._connection_pool.is_transaction_active
|
|
109
|
+
or (condition is not None and not condition)
|
|
110
|
+
):
|
|
111
|
+
yield
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
self._connection_pool.begin()
|
|
115
|
+
self.cursor.execute("BEGIN TRANSACTION")
|
|
116
|
+
self.cursor.fetchall()
|
|
117
|
+
try:
|
|
118
|
+
yield
|
|
119
|
+
except Exception as e:
|
|
120
|
+
self.cursor.execute("ROLLBACK")
|
|
121
|
+
self.cursor.fetchall()
|
|
122
|
+
self._connection_pool.rollback()
|
|
123
|
+
raise e
|
|
124
|
+
else:
|
|
125
|
+
self.cursor.execute("COMMIT")
|
|
126
|
+
self.cursor.fetchall()
|
|
127
|
+
self._connection_pool.commit()
|
|
128
|
+
|
|
129
|
+
def set_current_catalog(self, catalog: str) -> None:
|
|
130
|
+
"""Sets the catalog name of the current connection."""
|
|
131
|
+
self.execute(exp.Use(this=exp.to_identifier(catalog)))
|
|
132
|
+
|
|
133
|
+
def _create_catalog(self, catalog_name: exp.Identifier) -> None:
|
|
134
|
+
"""Creates a new catalog (database) in GizmoSQL."""
|
|
135
|
+
self.execute(
|
|
136
|
+
exp.Create(this=exp.Table(this=catalog_name), kind="DATABASE", exists=True)
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def _drop_catalog(self, catalog_name: exp.Identifier) -> None:
|
|
140
|
+
"""Drops a catalog (database) from GizmoSQL."""
|
|
141
|
+
self.execute(
|
|
142
|
+
exp.Drop(
|
|
143
|
+
this=exp.Table(this=catalog_name), kind="DATABASE", cascade=True, exists=True
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def _df_to_source_queries(
|
|
148
|
+
self,
|
|
149
|
+
df: DF,
|
|
150
|
+
target_columns_to_types: t.Dict[str, exp.DataType],
|
|
151
|
+
batch_size: int,
|
|
152
|
+
target_table: TableName,
|
|
153
|
+
source_columns: t.Optional[t.List[str]] = None,
|
|
154
|
+
) -> t.List[SourceQuery]:
|
|
155
|
+
"""
|
|
156
|
+
Convert a DataFrame to source queries for insertion.
|
|
157
|
+
|
|
158
|
+
Uses ADBC bulk ingestion (adbc_ingest) for efficient Arrow-native data transfer
|
|
159
|
+
to GizmoSQL, avoiding row-by-row insertion overhead.
|
|
160
|
+
"""
|
|
161
|
+
import pyarrow as pa
|
|
162
|
+
|
|
163
|
+
temp_table = self._get_temp_table(target_table)
|
|
164
|
+
|
|
165
|
+
# Select only the source columns in the right order
|
|
166
|
+
source_columns_to_types = (
|
|
167
|
+
{col: target_columns_to_types[col] for col in source_columns}
|
|
168
|
+
if source_columns
|
|
169
|
+
else target_columns_to_types
|
|
170
|
+
)
|
|
171
|
+
ordered_df = df[list(source_columns_to_types.keys())]
|
|
172
|
+
|
|
173
|
+
# Convert DataFrame to PyArrow Table for bulk ingestion
|
|
174
|
+
arrow_table = pa.Table.from_pandas(ordered_df)
|
|
175
|
+
|
|
176
|
+
# Use ADBC bulk ingestion with temporary table
|
|
177
|
+
# Note: DuckDB temporary tables cannot have catalog/schema prefixes,
|
|
178
|
+
# so we only pass the table name when temporary=True
|
|
179
|
+
self.cursor.adbc_ingest(
|
|
180
|
+
table_name=temp_table.name,
|
|
181
|
+
data=arrow_table,
|
|
182
|
+
mode="create",
|
|
183
|
+
temporary=True,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Reference temp table by name only (no schema prefix for temporary tables)
|
|
187
|
+
temp_table_name = exp.to_table(temp_table.name)
|
|
188
|
+
|
|
189
|
+
return [
|
|
190
|
+
SourceQuery(
|
|
191
|
+
query_factory=lambda: self._select_columns(target_columns_to_types).from_(
|
|
192
|
+
temp_table_name
|
|
193
|
+
),
|
|
194
|
+
cleanup_func=lambda: self.drop_table(temp_table_name),
|
|
195
|
+
)
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
def _get_data_objects(
|
|
199
|
+
self, schema_name: SchemaName, object_names: t.Optional[t.Set[str]] = None
|
|
200
|
+
) -> t.List[DataObject]:
|
|
201
|
+
"""
|
|
202
|
+
Returns all the data objects that exist in the given schema and optionally catalog.
|
|
203
|
+
"""
|
|
204
|
+
catalog = self.get_current_catalog()
|
|
205
|
+
|
|
206
|
+
if isinstance(schema_name, exp.Table):
|
|
207
|
+
# Ensures we don't generate identifier quotes
|
|
208
|
+
schema_name = ".".join(part.name for part in schema_name.parts)
|
|
209
|
+
|
|
210
|
+
query = (
|
|
211
|
+
exp.select(
|
|
212
|
+
exp.column("table_name").as_("name"),
|
|
213
|
+
exp.column("table_schema").as_("schema"),
|
|
214
|
+
exp.case(exp.column("table_type"))
|
|
215
|
+
.when(
|
|
216
|
+
exp.Literal.string("BASE TABLE"),
|
|
217
|
+
exp.Literal.string("table"),
|
|
218
|
+
)
|
|
219
|
+
.when(
|
|
220
|
+
exp.Literal.string("VIEW"),
|
|
221
|
+
exp.Literal.string("view"),
|
|
222
|
+
)
|
|
223
|
+
.when(
|
|
224
|
+
exp.Literal.string("LOCAL TEMPORARY"),
|
|
225
|
+
exp.Literal.string("table"),
|
|
226
|
+
)
|
|
227
|
+
.as_("type"),
|
|
228
|
+
)
|
|
229
|
+
.from_(exp.to_table("information_schema.tables"))
|
|
230
|
+
.where(
|
|
231
|
+
exp.column("table_catalog").eq(catalog), exp.column("table_schema").eq(schema_name)
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
if object_names:
|
|
235
|
+
query = query.where(exp.column("table_name").isin(*object_names))
|
|
236
|
+
df = self.fetchdf(query)
|
|
237
|
+
return [
|
|
238
|
+
DataObject(
|
|
239
|
+
catalog=catalog, # type: ignore
|
|
240
|
+
schema=row.schema, # type: ignore
|
|
241
|
+
name=row.name, # type: ignore
|
|
242
|
+
type=DataObjectType.from_str(row.type), # type: ignore
|
|
243
|
+
)
|
|
244
|
+
for row in df.itertuples()
|
|
245
|
+
]
|
|
246
|
+
|
|
247
|
+
def _normalize_decimal_value(self, col: exp.Expression, precision: int) -> exp.Expression:
|
|
248
|
+
"""
|
|
249
|
+
GizmoSQL (via DuckDB) truncates instead of rounding when casting to decimal.
|
|
250
|
+
|
|
251
|
+
other databases: select cast(3.14159 as decimal(38,3)) -> 3.142
|
|
252
|
+
duckdb: select cast(3.14159 as decimal(38,3)) -> 3.141
|
|
253
|
+
|
|
254
|
+
however, we can get the behaviour of other databases by casting to double first:
|
|
255
|
+
select cast(cast(3.14159 as double) as decimal(38, 3)) -> 3.142
|
|
256
|
+
"""
|
|
257
|
+
return exp.cast(
|
|
258
|
+
exp.cast(col, "DOUBLE"),
|
|
259
|
+
f"DECIMAL(38, {precision})",
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
def _fetch_native_df(
|
|
263
|
+
self, query: t.Union[exp.Expression, str], quote_identifiers: bool = False
|
|
264
|
+
) -> DF:
|
|
265
|
+
"""
|
|
266
|
+
Fetches a Pandas DataFrame from a SQL query.
|
|
267
|
+
|
|
268
|
+
GizmoSQL returns Arrow tables which can be efficiently converted to Pandas.
|
|
269
|
+
"""
|
|
270
|
+
import pandas as pd
|
|
271
|
+
|
|
272
|
+
sql = (
|
|
273
|
+
self._to_sql(query, quote=quote_identifiers)
|
|
274
|
+
if isinstance(query, exp.Expression)
|
|
275
|
+
else query
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
cursor = self.cursor
|
|
279
|
+
cursor.execute(sql)
|
|
280
|
+
|
|
281
|
+
# ADBC cursors support fetch_arrow_table() for efficient Arrow->Pandas conversion
|
|
282
|
+
if hasattr(cursor, "fetch_arrow_table"):
|
|
283
|
+
arrow_table = cursor.fetch_arrow_table()
|
|
284
|
+
return arrow_table.to_pandas()
|
|
285
|
+
|
|
286
|
+
# Fallback to standard fetchall + DataFrame construction
|
|
287
|
+
result = cursor.fetchall()
|
|
288
|
+
columns = [desc[0] for desc in cursor.description] if cursor.description else []
|
|
289
|
+
return pd.DataFrame(result, columns=columns)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GizmoSQL Connection Configuration for SQLMesh.
|
|
3
|
+
|
|
4
|
+
Provides the connection configuration class for GizmoSQL servers.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import typing as t
|
|
9
|
+
|
|
10
|
+
from pydantic import Field
|
|
11
|
+
from sqlmesh.core.config.common import concurrent_tasks_validator
|
|
12
|
+
from sqlmesh.core.config.connection import ConnectionConfig
|
|
13
|
+
from sqlmesh.core.engine_adapter import EngineAdapter
|
|
14
|
+
from sqlmesh.utils.errors import ConfigError
|
|
15
|
+
from sqlmesh.utils.pydantic import model_validator
|
|
16
|
+
|
|
17
|
+
from sqlmesh_gizmosql.adapter import GizmoSQLEngineAdapter
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _gizmosql_import_validator(cls: t.Any, data: t.Any) -> t.Any:
|
|
21
|
+
"""Validate that ADBC Flight SQL driver is installed."""
|
|
22
|
+
check_import = (
|
|
23
|
+
data.pop("check_import", True) if isinstance(data, dict) else True
|
|
24
|
+
)
|
|
25
|
+
if not check_import:
|
|
26
|
+
return data
|
|
27
|
+
try:
|
|
28
|
+
import adbc_driver_flightsql # noqa: F401
|
|
29
|
+
except ImportError:
|
|
30
|
+
raise ConfigError(
|
|
31
|
+
"Failed to import the 'adbc_driver_flightsql' library. "
|
|
32
|
+
"Please install it with: pip install sqlmesh-gizmosql[gizmosql] "
|
|
33
|
+
"or pip install adbc-driver-flightsql pyarrow"
|
|
34
|
+
)
|
|
35
|
+
return data
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class GizmoSQLConnectionConfig(ConnectionConfig):
|
|
39
|
+
"""
|
|
40
|
+
GizmoSQL connection configuration.
|
|
41
|
+
|
|
42
|
+
GizmoSQL is a database server that uses DuckDB as its execution engine and
|
|
43
|
+
exposes an Arrow Flight SQL interface for remote connections. This configuration
|
|
44
|
+
uses ADBC (Arrow Database Connectivity) with the Flight SQL driver.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
host: The hostname of the GizmoSQL server.
|
|
48
|
+
port: The port of the GizmoSQL server (default: 31337).
|
|
49
|
+
username: The username for authentication.
|
|
50
|
+
password: The password for authentication.
|
|
51
|
+
use_encryption: Whether to use TLS encryption (default: True).
|
|
52
|
+
disable_certificate_verification: Whether to skip TLS certificate verification.
|
|
53
|
+
Useful for self-signed certificates in development (default: False).
|
|
54
|
+
database: The default database/catalog to use.
|
|
55
|
+
concurrent_tasks: The maximum number of concurrent tasks.
|
|
56
|
+
register_comments: Whether to register model comments.
|
|
57
|
+
pre_ping: Whether to pre-ping the connection.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
host: str = "localhost"
|
|
61
|
+
port: int = 31337
|
|
62
|
+
username: str
|
|
63
|
+
password: str
|
|
64
|
+
use_encryption: bool = True
|
|
65
|
+
disable_certificate_verification: bool = False
|
|
66
|
+
database: t.Optional[str] = None
|
|
67
|
+
|
|
68
|
+
concurrent_tasks: int = 4
|
|
69
|
+
register_comments: bool = True
|
|
70
|
+
pre_ping: bool = False
|
|
71
|
+
|
|
72
|
+
type_: t.Literal["gizmosql"] = Field(alias="type", default="gizmosql")
|
|
73
|
+
DIALECT: t.ClassVar[t.Literal["duckdb"]] = "duckdb"
|
|
74
|
+
DISPLAY_NAME: t.ClassVar[t.Literal["GizmoSQL"]] = "GizmoSQL"
|
|
75
|
+
DISPLAY_ORDER: t.ClassVar[t.Literal[17]] = 17
|
|
76
|
+
|
|
77
|
+
_engine_import_validator = model_validator(mode="before")(_gizmosql_import_validator)
|
|
78
|
+
_concurrent_tasks_validator = concurrent_tasks_validator
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def _connection_kwargs_keys(self) -> t.Set[str]:
|
|
82
|
+
# ADBC uses a different connection pattern, so we don't pass these directly
|
|
83
|
+
return set()
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def _engine_adapter(self) -> t.Type[EngineAdapter]:
|
|
87
|
+
return GizmoSQLEngineAdapter
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def _connection_factory(self) -> t.Callable:
|
|
91
|
+
"""
|
|
92
|
+
Create a connection factory for GizmoSQL using ADBC Flight SQL driver.
|
|
93
|
+
|
|
94
|
+
The connection is established using the Arrow Flight SQL protocol over gRPC.
|
|
95
|
+
"""
|
|
96
|
+
import re
|
|
97
|
+
|
|
98
|
+
from adbc_driver_flightsql import DatabaseOptions
|
|
99
|
+
from adbc_driver_flightsql import dbapi as flightsql
|
|
100
|
+
|
|
101
|
+
def connect() -> t.Any:
|
|
102
|
+
# Build the URI for the Flight SQL connection
|
|
103
|
+
protocol = "grpc+tls" if self.use_encryption else "grpc"
|
|
104
|
+
uri = f"{protocol}://{self.host}:{self.port}"
|
|
105
|
+
|
|
106
|
+
# ADBC database-level options (passed to the driver)
|
|
107
|
+
db_kwargs: t.Dict[str, str] = {
|
|
108
|
+
"username": self.username,
|
|
109
|
+
"password": self.password,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# Add TLS skip verify option using the proper DatabaseOptions enum
|
|
113
|
+
if self.use_encryption and self.disable_certificate_verification:
|
|
114
|
+
db_kwargs[DatabaseOptions.TLS_SKIP_VERIFY.value] = "true"
|
|
115
|
+
|
|
116
|
+
# Create the connection - uri is first positional arg, db_kwargs is for driver options
|
|
117
|
+
# Explicit autocommit=True since GizmoSQL doesn't support manual transaction commits
|
|
118
|
+
conn = flightsql.connect(uri, db_kwargs=db_kwargs, autocommit=True)
|
|
119
|
+
|
|
120
|
+
# Verify the backend is DuckDB - this adapter only supports the DuckDB backend
|
|
121
|
+
vendor_version = conn.adbc_get_info().get("vendor_version", "")
|
|
122
|
+
if not re.search(pattern=r"^duckdb ", string=vendor_version):
|
|
123
|
+
conn.close()
|
|
124
|
+
raise ConfigError(
|
|
125
|
+
f"Unsupported GizmoSQL server backend: '{vendor_version}'. "
|
|
126
|
+
"This adapter only supports the DuckDB backend for GizmoSQL."
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
return conn
|
|
130
|
+
|
|
131
|
+
return connect
|
|
132
|
+
|
|
133
|
+
def get_catalog(self) -> t.Optional[str]:
|
|
134
|
+
return self.database
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sqlmesh-gizmosql
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: GizmoSQL engine adapter for SQLMesh
|
|
5
|
+
Author: Philip
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/gizmodata/sqlmesh-gizmosql
|
|
8
|
+
Project-URL: Repository, https://github.com/gizmodata/sqlmesh-gizmosql
|
|
9
|
+
Project-URL: Documentation, https://github.com/gizmodata/sqlmesh-gizmosql#readme
|
|
10
|
+
Keywords: sqlmesh,gizmosql,duckdb,arrow,flight-sql,data-engineering
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Database
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
Requires-Dist: sqlmesh>=0.100.0
|
|
27
|
+
Requires-Dist: adbc-driver-flightsql>=1.10.0
|
|
28
|
+
Requires-Dist: pyarrow>=22.0.0
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
33
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
34
|
+
|
|
35
|
+
# SQLMesh GizmoSQL Adapter
|
|
36
|
+
|
|
37
|
+
A [SQLMesh](https://sqlmesh.com) engine adapter for [GizmoSQL](https://github.com/gizmodata/gizmosql) - a database server that uses DuckDB as its execution engine and exposes an Arrow Flight SQL interface for remote connections.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install sqlmesh-gizmosql
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
This will install `sqlmesh`, `adbc-driver-flightsql`, and `pyarrow` as dependencies.
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
### 1. Import the adapter
|
|
50
|
+
|
|
51
|
+
Simply import the package before using SQLMesh. The adapter registers itself automatically:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
import sqlmesh_gizmosql # Registers GizmoSQL adapter
|
|
55
|
+
from sqlmesh import Context
|
|
56
|
+
|
|
57
|
+
context = Context(paths="path/to/project")
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 2. Configure your connection
|
|
61
|
+
|
|
62
|
+
Add a GizmoSQL connection to your `config.yaml`:
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
gateways:
|
|
66
|
+
my_gizmosql:
|
|
67
|
+
connection:
|
|
68
|
+
type: gizmosql
|
|
69
|
+
host: localhost
|
|
70
|
+
port: 31337
|
|
71
|
+
username: your_username
|
|
72
|
+
password: your_password
|
|
73
|
+
database: my_database # optional, default catalog
|
|
74
|
+
use_encryption: true # default: true (uses TLS)
|
|
75
|
+
disable_certificate_verification: false # for self-signed certs
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 3. Use SQLMesh as normal
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
sqlmesh plan
|
|
82
|
+
sqlmesh run
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Configuration Options
|
|
86
|
+
|
|
87
|
+
| Option | Type | Default | Description |
|
|
88
|
+
|--------|------|---------|-------------|
|
|
89
|
+
| `host` | str | `localhost` | GizmoSQL server hostname |
|
|
90
|
+
| `port` | int | `31337` | GizmoSQL server port |
|
|
91
|
+
| `username` | str | *required* | Authentication username |
|
|
92
|
+
| `password` | str | *required* | Authentication password |
|
|
93
|
+
| `database` | str | `None` | Default database/catalog |
|
|
94
|
+
| `use_encryption` | bool | `True` | Use TLS encryption |
|
|
95
|
+
| `disable_certificate_verification` | bool | `False` | Skip TLS cert verification |
|
|
96
|
+
| `concurrent_tasks` | int | `4` | Max concurrent tasks |
|
|
97
|
+
| `register_comments` | bool | `True` | Register model comments |
|
|
98
|
+
| `pre_ping` | bool | `False` | Pre-ping connections |
|
|
99
|
+
|
|
100
|
+
## Features
|
|
101
|
+
|
|
102
|
+
- **Arrow Flight SQL**: Efficient data transfer using Arrow's columnar format
|
|
103
|
+
- **Full Catalog Support**: Create, drop, and switch between databases
|
|
104
|
+
- **Transaction Support**: Full transaction control via SQL statements
|
|
105
|
+
- **ADBC Bulk Ingestion**: Fast data loading using Arrow-native bulk operations
|
|
106
|
+
- **DuckDB Compatibility**: Uses DuckDB SQL dialect for query generation
|
|
107
|
+
|
|
108
|
+
## Requirements
|
|
109
|
+
|
|
110
|
+
- Python >= 3.10
|
|
111
|
+
- SQLMesh >= 0.100.0
|
|
112
|
+
- A running GizmoSQL server with DuckDB backend
|
|
113
|
+
|
|
114
|
+
## Development
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Clone the repository
|
|
118
|
+
git clone https://github.com/philip/sqlmesh-gizmosql.git
|
|
119
|
+
cd sqlmesh-gizmosql
|
|
120
|
+
|
|
121
|
+
# Install dev dependencies
|
|
122
|
+
pip install -e ".[dev]"
|
|
123
|
+
|
|
124
|
+
# Run tests
|
|
125
|
+
pytest
|
|
126
|
+
|
|
127
|
+
# Run linting
|
|
128
|
+
ruff check .
|
|
129
|
+
mypy sqlmesh_gizmosql
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
Apache 2.0
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
sqlmesh_gizmosql/__init__.py
|
|
4
|
+
sqlmesh_gizmosql/adapter.py
|
|
5
|
+
sqlmesh_gizmosql/connection.py
|
|
6
|
+
sqlmesh_gizmosql.egg-info/PKG-INFO
|
|
7
|
+
sqlmesh_gizmosql.egg-info/SOURCES.txt
|
|
8
|
+
sqlmesh_gizmosql.egg-info/dependency_links.txt
|
|
9
|
+
sqlmesh_gizmosql.egg-info/requires.txt
|
|
10
|
+
sqlmesh_gizmosql.egg-info/top_level.txt
|
|
11
|
+
tests/test_gizmosql.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sqlmesh_gizmosql
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""Unit tests for GizmoSQL engine adapter and connection config.
|
|
2
|
+
|
|
3
|
+
These tests don't require a running GizmoSQL server - they test
|
|
4
|
+
configuration validation and adapter properties.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from unittest.mock import MagicMock, patch
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
from sqlmesh.utils.errors import ConfigError
|
|
11
|
+
|
|
12
|
+
from sqlmesh_gizmosql.adapter import GizmoSQLEngineAdapter
|
|
13
|
+
from sqlmesh_gizmosql.connection import GizmoSQLConnectionConfig
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestGizmoSQLConnectionConfig:
|
|
17
|
+
"""Tests for GizmoSQLConnectionConfig."""
|
|
18
|
+
|
|
19
|
+
def test_default_values(self):
|
|
20
|
+
"""Test default configuration values."""
|
|
21
|
+
config = GizmoSQLConnectionConfig(
|
|
22
|
+
username="user",
|
|
23
|
+
password="pass",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
assert config.host == "localhost"
|
|
27
|
+
assert config.port == 31337
|
|
28
|
+
assert config.use_encryption is True
|
|
29
|
+
assert config.disable_certificate_verification is False
|
|
30
|
+
assert config.database is None
|
|
31
|
+
assert config.concurrent_tasks == 4
|
|
32
|
+
|
|
33
|
+
def test_custom_values(self):
|
|
34
|
+
"""Test custom configuration values."""
|
|
35
|
+
config = GizmoSQLConnectionConfig(
|
|
36
|
+
host="gizmosql.example.com",
|
|
37
|
+
port=12345,
|
|
38
|
+
username="testuser",
|
|
39
|
+
password="testpass",
|
|
40
|
+
use_encryption=False,
|
|
41
|
+
disable_certificate_verification=True,
|
|
42
|
+
database="mydb",
|
|
43
|
+
concurrent_tasks=8,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
assert config.host == "gizmosql.example.com"
|
|
47
|
+
assert config.port == 12345
|
|
48
|
+
assert config.username == "testuser"
|
|
49
|
+
assert config.password == "testpass"
|
|
50
|
+
assert config.use_encryption is False
|
|
51
|
+
assert config.disable_certificate_verification is True
|
|
52
|
+
assert config.database == "mydb"
|
|
53
|
+
assert config.concurrent_tasks == 8
|
|
54
|
+
|
|
55
|
+
def test_dialect_is_duckdb(self):
|
|
56
|
+
"""Test that the dialect is set to duckdb."""
|
|
57
|
+
config = GizmoSQLConnectionConfig(
|
|
58
|
+
username="user",
|
|
59
|
+
password="pass",
|
|
60
|
+
)
|
|
61
|
+
assert config.DIALECT == "duckdb"
|
|
62
|
+
|
|
63
|
+
def test_type_is_gizmosql(self):
|
|
64
|
+
"""Test that the type is set to gizmosql."""
|
|
65
|
+
config = GizmoSQLConnectionConfig(
|
|
66
|
+
username="user",
|
|
67
|
+
password="pass",
|
|
68
|
+
)
|
|
69
|
+
assert config.type_ == "gizmosql"
|
|
70
|
+
|
|
71
|
+
def test_get_catalog(self):
|
|
72
|
+
"""Test get_catalog returns database."""
|
|
73
|
+
config = GizmoSQLConnectionConfig(
|
|
74
|
+
username="user",
|
|
75
|
+
password="pass",
|
|
76
|
+
database="mydb",
|
|
77
|
+
)
|
|
78
|
+
assert config.get_catalog() == "mydb"
|
|
79
|
+
|
|
80
|
+
def test_get_catalog_none(self):
|
|
81
|
+
"""Test get_catalog returns None when database not set."""
|
|
82
|
+
config = GizmoSQLConnectionConfig(
|
|
83
|
+
username="user",
|
|
84
|
+
password="pass",
|
|
85
|
+
)
|
|
86
|
+
assert config.get_catalog() is None
|
|
87
|
+
|
|
88
|
+
def test_connection_factory_builds_correct_uri_with_tls(self):
|
|
89
|
+
"""Test connection factory builds correct URI with TLS."""
|
|
90
|
+
mock_flightsql = MagicMock()
|
|
91
|
+
mock_conn = MagicMock()
|
|
92
|
+
mock_conn.adbc_get_info.return_value = {"vendor_version": "duckdb v1.0.0"}
|
|
93
|
+
mock_flightsql.connect.return_value = mock_conn
|
|
94
|
+
|
|
95
|
+
# Mock DatabaseOptions enum
|
|
96
|
+
mock_db_options = MagicMock()
|
|
97
|
+
mock_db_options.TLS_SKIP_VERIFY.value = "adbc.flight.sql.client_option.tls_skip_verify"
|
|
98
|
+
|
|
99
|
+
config = GizmoSQLConnectionConfig(
|
|
100
|
+
host="example.com",
|
|
101
|
+
port=31337,
|
|
102
|
+
username="user",
|
|
103
|
+
password="pass",
|
|
104
|
+
use_encryption=True,
|
|
105
|
+
disable_certificate_verification=False,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
mock_module = MagicMock()
|
|
109
|
+
mock_module.dbapi = mock_flightsql
|
|
110
|
+
mock_module.DatabaseOptions = mock_db_options
|
|
111
|
+
|
|
112
|
+
with patch.dict("sys.modules", {"adbc_driver_flightsql": mock_module}):
|
|
113
|
+
factory = config._connection_factory
|
|
114
|
+
factory()
|
|
115
|
+
|
|
116
|
+
# Verify connect was called with correct URI (first positional arg)
|
|
117
|
+
call_args = mock_flightsql.connect.call_args
|
|
118
|
+
assert call_args[0][0] == "grpc+tls://example.com:31337"
|
|
119
|
+
# Verify db_kwargs contains credentials
|
|
120
|
+
db_kwargs = call_args[1]["db_kwargs"]
|
|
121
|
+
assert db_kwargs["username"] == "user"
|
|
122
|
+
assert db_kwargs["password"] == "pass"
|
|
123
|
+
|
|
124
|
+
def test_connection_factory_builds_correct_uri_without_tls(self):
|
|
125
|
+
"""Test connection factory builds correct URI without TLS."""
|
|
126
|
+
mock_flightsql = MagicMock()
|
|
127
|
+
mock_conn = MagicMock()
|
|
128
|
+
mock_conn.adbc_get_info.return_value = {"vendor_version": "duckdb v1.0.0"}
|
|
129
|
+
mock_flightsql.connect.return_value = mock_conn
|
|
130
|
+
|
|
131
|
+
# Mock DatabaseOptions enum
|
|
132
|
+
mock_db_options = MagicMock()
|
|
133
|
+
mock_db_options.TLS_SKIP_VERIFY.value = "adbc.flight.sql.client_option.tls_skip_verify"
|
|
134
|
+
|
|
135
|
+
config = GizmoSQLConnectionConfig(
|
|
136
|
+
host="example.com",
|
|
137
|
+
port=31337,
|
|
138
|
+
username="user",
|
|
139
|
+
password="pass",
|
|
140
|
+
use_encryption=False,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
mock_module = MagicMock()
|
|
144
|
+
mock_module.dbapi = mock_flightsql
|
|
145
|
+
mock_module.DatabaseOptions = mock_db_options
|
|
146
|
+
|
|
147
|
+
with patch.dict("sys.modules", {"adbc_driver_flightsql": mock_module}):
|
|
148
|
+
factory = config._connection_factory
|
|
149
|
+
factory()
|
|
150
|
+
|
|
151
|
+
call_args = mock_flightsql.connect.call_args
|
|
152
|
+
assert call_args[0][0] == "grpc://example.com:31337"
|
|
153
|
+
|
|
154
|
+
def test_connection_factory_rejects_non_duckdb_backend(self):
|
|
155
|
+
"""Test connection factory raises error for non-DuckDB backend."""
|
|
156
|
+
mock_flightsql = MagicMock()
|
|
157
|
+
mock_conn = MagicMock()
|
|
158
|
+
mock_conn.adbc_get_info.return_value = {"vendor_version": "sqlite v3.40.0"}
|
|
159
|
+
mock_flightsql.connect.return_value = mock_conn
|
|
160
|
+
|
|
161
|
+
# Mock DatabaseOptions enum
|
|
162
|
+
mock_db_options = MagicMock()
|
|
163
|
+
mock_db_options.TLS_SKIP_VERIFY.value = "adbc.flight.sql.client_option.tls_skip_verify"
|
|
164
|
+
|
|
165
|
+
config = GizmoSQLConnectionConfig(
|
|
166
|
+
host="example.com",
|
|
167
|
+
port=31337,
|
|
168
|
+
username="user",
|
|
169
|
+
password="pass",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
mock_module = MagicMock()
|
|
173
|
+
mock_module.dbapi = mock_flightsql
|
|
174
|
+
mock_module.DatabaseOptions = mock_db_options
|
|
175
|
+
|
|
176
|
+
with patch.dict("sys.modules", {"adbc_driver_flightsql": mock_module}):
|
|
177
|
+
factory = config._connection_factory
|
|
178
|
+
with pytest.raises(ConfigError, match="Unsupported GizmoSQL server backend"):
|
|
179
|
+
factory()
|
|
180
|
+
|
|
181
|
+
# Verify connection was closed after rejection
|
|
182
|
+
mock_conn.close.assert_called_once()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class TestGizmoSQLEngineAdapter:
|
|
186
|
+
"""Tests for GizmoSQLEngineAdapter properties."""
|
|
187
|
+
|
|
188
|
+
def test_dialect(self):
|
|
189
|
+
"""Test that dialect is duckdb."""
|
|
190
|
+
assert GizmoSQLEngineAdapter.DIALECT == "duckdb"
|
|
191
|
+
|
|
192
|
+
def test_supports_transactions(self):
|
|
193
|
+
"""Test transactions are supported via SQL statements."""
|
|
194
|
+
assert GizmoSQLEngineAdapter.SUPPORTS_TRANSACTIONS is True
|
|
195
|
+
|
|
196
|
+
def test_supports_create_drop_catalog(self):
|
|
197
|
+
"""Test catalog operations are supported."""
|
|
198
|
+
assert GizmoSQLEngineAdapter.SUPPORTS_CREATE_DROP_CATALOG is True
|
|
199
|
+
|
|
200
|
+
def test_supported_drop_cascade_object_kinds(self):
|
|
201
|
+
"""Test cascade drop is supported for schemas, tables, and views."""
|
|
202
|
+
assert "SCHEMA" in GizmoSQLEngineAdapter.SUPPORTED_DROP_CASCADE_OBJECT_KINDS
|
|
203
|
+
assert "TABLE" in GizmoSQLEngineAdapter.SUPPORTED_DROP_CASCADE_OBJECT_KINDS
|
|
204
|
+
assert "VIEW" in GizmoSQLEngineAdapter.SUPPORTED_DROP_CASCADE_OBJECT_KINDS
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class TestRegistration:
|
|
208
|
+
"""Tests for automatic registration with SQLMesh."""
|
|
209
|
+
|
|
210
|
+
def test_adapter_registered(self):
|
|
211
|
+
"""Test that adapter is registered in SQLMesh's dialect map."""
|
|
212
|
+
from sqlmesh.core import engine_adapter
|
|
213
|
+
|
|
214
|
+
import sqlmesh_gizmosql # noqa: F401
|
|
215
|
+
|
|
216
|
+
assert "gizmosql" in engine_adapter.DIALECT_TO_ENGINE_ADAPTER
|
|
217
|
+
assert engine_adapter.DIALECT_TO_ENGINE_ADAPTER["gizmosql"] is GizmoSQLEngineAdapter
|
|
218
|
+
|
|
219
|
+
def test_connection_config_registered(self):
|
|
220
|
+
"""Test that connection config is registered in SQLMesh."""
|
|
221
|
+
from sqlmesh.core.config import connection as conn_module
|
|
222
|
+
|
|
223
|
+
import sqlmesh_gizmosql # noqa: F401
|
|
224
|
+
|
|
225
|
+
assert "gizmosql" in conn_module.CONNECTION_CONFIG_TO_TYPE
|
|
226
|
+
assert conn_module.CONNECTION_CONFIG_TO_TYPE["gizmosql"] is GizmoSQLConnectionConfig
|
|
227
|
+
|
|
228
|
+
def test_dialect_type_registered(self):
|
|
229
|
+
"""Test that dialect type is registered."""
|
|
230
|
+
from sqlmesh.core.config import connection as conn_module
|
|
231
|
+
|
|
232
|
+
import sqlmesh_gizmosql # noqa: F401
|
|
233
|
+
|
|
234
|
+
assert "gizmosql" in conn_module.DIALECT_TO_TYPE
|
|
235
|
+
assert conn_module.DIALECT_TO_TYPE["gizmosql"] == "duckdb"
|