Skip to content

Commit 0f4da3e

Browse files
mwc360Copilot
andauthored
feat: test suites, integration tests, resolve python 3.8 support (#70)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e5fdc73 commit 0f4da3e

41 files changed

Lines changed: 3875 additions & 181 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/copilot-instructions.md

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# LakeBench Codebase Reference
2+
3+
> Quick-reference for Copilot and contributors. Keep this in sync when adding major features.
4+
5+
---
6+
7+
## What is LakeBench?
8+
9+
LakeBench is a **Python-native, multi-modal benchmarking framework** for evaluating performance across multiple lakehouse compute engines and ELT scenarios. It supports industry-standard benchmarks (TPC-DS, TPC-H, ClickBench) and a novel ELT-focused benchmark (ELTBench), all installable via `pip`.
10+
11+
---
12+
13+
## Project Layout
14+
15+
```
16+
src/lakebench/
17+
├── __init__.py
18+
19+
├── benchmarks/
20+
│ ├── base.py # BaseBenchmark ABC — result schema, timing, post_results()
21+
│ ├── elt_bench/ # ELTBench: load, transform, merge, maintain, query
22+
│ ├── tpcds/ # TPC-DS: 99 queries, 24 tables
23+
│ ├── tpch/ # TPC-H: 22 queries, 8 tables
24+
│ └── clickbench/ # ClickBench: 43 queries on clickstream data
25+
26+
├── datagen/
27+
│ ├── tpch.py # TPCHDataGenerator (uses tpchgen-rs, ~10x faster than alternatives)
28+
│ ├── tpcds.py # TPCDSDataGenerator (wraps DuckDB TPC-DS extension)
29+
│ └── clickbench.py # Downloads dataset from ClickHouse host
30+
31+
├── engines/
32+
│ ├── base.py # BaseEngine ABC — fsspec, runtime detection, result writing
33+
│ ├── spark.py # Generic Spark engine
34+
│ ├── fabric_spark.py # Microsoft Fabric Spark (auto-authenticates via notebookutils)
35+
│ ├── synapse_spark.py # Azure Synapse Spark
36+
│ ├── hdi_spark.py # HDInsight Spark
37+
│ ├── duckdb.py # DuckDB
38+
│ ├── polars.py # Polars
39+
│ ├── daft.py # Daft
40+
│ ├── sail.py # Sail (PySpark-compatible engine)
41+
│ └── delta_rs.py # Shared DeltaRs write helper (used by non-Spark engines)
42+
43+
└── utils/
44+
├── query_utils.py # transpile_and_qualify_query(), get_table_name_from_ddl()
45+
├── path_utils.py # abfss_to_https(), to_unix_path()
46+
└── timer.py # Context-manager timer; stores results for post_results()
47+
```
48+
49+
---
50+
51+
## Core Abstractions
52+
53+
### `BaseEngine` (`engines/base.py`)
54+
Abstract base for all compute engines.
55+
56+
| Attribute | Description |
57+
|---|---|
58+
| `SQLGLOT_DIALECT` | SQLGlot dialect string for auto-transpilation (e.g. `"duckdb"`) |
59+
| `SUPPORTS_SCHEMA_PREP` | Whether the engine can create an empty schema-defined table before data load |
60+
| `SUPPORTS_MOUNT_PATH` | Whether the engine can use mount-style URIs (`/mnt/...`) |
61+
| `TABLE_FORMAT` | Always `'delta'` |
62+
| `schema_or_working_directory_uri` | Base path where Delta tables are stored |
63+
| `storage_options` | Dict passed through to DeltaRs / fsspec for cloud auth |
64+
| `extended_engine_metadata` | Dict of key/value pairs appended to benchmark results |
65+
66+
Key methods: `get_total_cores()`, `get_compute_size()`, `get_job_cost(duration_ms)`, `create_schema_if_not_exists()`, `_append_results_to_delta()`.
67+
68+
Runtime is auto-detected at init via `_detect_runtime()` — returns `"fabric"`, `"synapse"`, `"databricks"`, `"colab"`, or `"local_unknown"`.
69+
70+
### `BaseBenchmark` (`benchmarks/base.py`)
71+
Abstract base for all benchmarks.
72+
73+
| Attribute | Description |
74+
|---|---|
75+
| `BENCHMARK_IMPL_REGISTRY` | `Dict[EngineClass → ImplClass]` — maps engines to optional engine-specific implementations |
76+
| `RESULT_SCHEMA` | Canonical 21-column result schema (see below) |
77+
| `VERSION` | Benchmark version string |
78+
79+
The result schema includes: `run_id`, `run_datetime`, `lakebench_version`, `engine`, `engine_version`, `benchmark`, `benchmark_version`, `mode`, `scale_factor`, `scenario`, `total_cores`, `compute_size`, `phase`, `test_item`, `start_datetime`, `duration_ms`, `estimated_retail_job_cost`, `iteration`, `success`, `error_message`, `engine_properties` (MAP), `execution_telemetry` (MAP).
80+
81+
`post_results()` collects timer results → builds result rows → optionally appends to a Delta table via `engine._append_results_to_delta()`.
82+
83+
---
84+
85+
## Engine & Benchmark Registration
86+
87+
Benchmarks declare engine support via `BENCHMARK_IMPL_REGISTRY`. If an engine uses only shared `BaseEngine` methods, the value is `None`; otherwise it maps to a specialized implementation class.
88+
89+
```python
90+
# Register a custom engine with an existing benchmark
91+
from lakebench.benchmarks import TPCDS
92+
TPCDS.register_engine(MyNewEngine, None) # use shared methods
93+
TPCDS.register_engine(MyNewEngine, MyTPCDSImpl) # use custom impl class
94+
```
95+
96+
To add a new engine, subclass an existing one:
97+
```python
98+
from lakebench.engines import BaseEngine
99+
100+
class MyEngine(BaseEngine):
101+
SQLGLOT_DIALECT = "duckdb" # or whichever dialect applies
102+
...
103+
104+
from lakebench.benchmarks.elt_bench import ELTBench
105+
ELTBench.register_engine(MyEngine, None)
106+
benchmark = ELTBench(engine=MyEngine(...), ...)
107+
benchmark.run()
108+
```
109+
110+
---
111+
112+
## Query Resolution Strategy (3-Tier Fallback)
113+
114+
For each query, LakeBench resolves in this order:
115+
116+
1. **Engine-specific override**`resources/queries/<engine_name>/q14.sql` (rare; e.g. Daft decimal casting)
117+
2. **Parent engine class override**`resources/queries/<parent_class>/q14.sql` (rare; e.g. Spark family)
118+
3. **Canonical + auto-transpilation**`resources/queries/canonical/q14.sql` transpiled via SQLGlot using the engine's `SQLGLOT_DIALECT`
119+
120+
Tables are automatically qualified with catalog and schema when applicable. To inspect the resolved query:
121+
122+
```python
123+
benchmark = TPCH(engine=MyEngine(...))
124+
print(benchmark._return_query_definition('q14'))
125+
```
126+
127+
---
128+
129+
## Optional Dependency Groups
130+
131+
Install only what you need:
132+
133+
| Extra | Installs |
134+
|---|---|
135+
| `duckdb` | `duckdb`, `deltalake`, `pyarrow` |
136+
| `polars` | `polars`, `deltalake`, `pyarrow` |
137+
| `daft` | `daft`, `deltalake`, `pyarrow` |
138+
| `tpcds_datagen` | `duckdb`, `pyarrow` |
139+
| `tpch_datagen` | `tpchgen-cli` |
140+
| `sparkmeasure` | `sparkmeasure` |
141+
| `sail` | `pysail`, `pyspark[connect]`, `deltalake`, `pyarrow` |
142+
143+
```bash
144+
pip install lakebench[duckdb,polars,tpch_datagen]
145+
```
146+
147+
---
148+
149+
## Supported Runtimes & Storage
150+
151+
**Runtimes**: Local (Windows), Microsoft Fabric, Azure Synapse, HDInsight, Google Colab (experimental)
152+
153+
**Storage**: Local filesystem, OneLake, ADLS Gen2 (Fabric/Synapse/HDInsight), S3 (experimental), GCS (experimental)
154+
155+
**Table format**: Delta Lake only (via `delta-rs` for non-Spark engines)
156+
157+
---
158+
159+
## Timer (`utils/timer.py`)
160+
161+
`timer` is a context-manager function with a `.results` list attached. Use it inside benchmark `run()` implementations to time each phase/test item:
162+
163+
```python
164+
with self.timer(phase="load", test_item="q1", engine=self.engine) as t:
165+
t.execution_telemetry = {"rows": 1000} # optional metadata
166+
do_work()
167+
168+
self.post_results() # flush timer.results → self.results → optionally Delta
169+
```
170+
171+
---
172+
173+
## Key Conventions
174+
175+
- **All Delta writes for non-Spark engines** go through `engines/delta_rs.py` (`DeltaRs().write_deltalake(...)`).
176+
- **SQLGlot transpilation** is the default path; engine-specific SQL files are exceptions, not the rule.
177+
- **`storage_options`** on `BaseEngine` is the single place for cloud auth credentials (bearer token, SAS, etc.).
178+
- **`extended_engine_metadata`** on `BaseEngine` is the right place to attach runtime-specific metadata that ends up in the `engine_properties` MAP column of results.
179+
- **TPC-DS / TPC-H spec compliance**: LakeBench intentionally diverges from `spark-sql-perf` to follow the official specs (see `customer.c_last_review_date_sk` and `store.s_tax_percentage` fixes in README).
180+
- **New benchmarks** should subclass `BaseBenchmark`, define `RESULT_SCHEMA`, `BENCHMARK_IMPL_REGISTRY`, `VERSION`, and implement `run()`.

.github/workflows/publish_to_pypi.yml

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,11 @@ jobs:
88
build-and-publish:
99
runs-on: ubuntu-latest
1010
steps:
11-
- uses: actions/checkout@v2
12-
- name: Set up Python
13-
uses: actions/setup-python@v2
14-
with:
15-
python-version: '3.x'
16-
- name: Install build dependencies
17-
run: python -m pip install build twine
11+
- uses: actions/checkout@v4
12+
- name: Install uv
13+
uses: astral-sh/setup-uv@v5
1814
- name: Build package
19-
run: python -m build
15+
run: uv build
2016
- name: Publish package to PyPI
2117
uses: pypa/gh-action-pypi-publish@v1.4.2
2218
with:

.github/workflows/tests.yml

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
unit-tests:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
16+
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- name: Install uv
21+
uses: astral-sh/setup-uv@v5
22+
with:
23+
python-version: ${{ matrix.python-version }}
24+
25+
- name: Install dependencies
26+
run: uv sync --group dev
27+
28+
- name: Run unit tests
29+
run: uv run pytest tests/ --ignore=tests/integration -v --tb=short
30+
31+
integration-tests:
32+
name: integration (${{ matrix.engine }})
33+
runs-on: ubuntu-latest
34+
strategy:
35+
fail-fast: false
36+
matrix:
37+
include:
38+
- engine: duckdb
39+
extras_flags: "--extra duckdb --extra tpcds_datagen --extra tpch_datagen"
40+
test_file: "tests/integration/test_duckdb.py"
41+
- engine: daft
42+
extras_flags: "--extra daft --extra tpcds_datagen --extra tpch_datagen"
43+
test_file: "tests/integration/test_daft.py"
44+
- engine: polars
45+
extras_flags: "--extra polars --extra tpcds_datagen --extra tpch_datagen"
46+
test_file: "tests/integration/test_polars.py"
47+
- engine: spark
48+
extras_flags: "--extra spark --extra tpcds_datagen --extra tpch_datagen"
49+
test_file: "tests/integration/test_spark.py"
50+
java: "17"
51+
- engine: sail
52+
extras_flags: "--extra sail --extra tpcds_datagen --extra tpch_datagen"
53+
test_file: "tests/integration/test_sail.py"
54+
55+
steps:
56+
- uses: actions/checkout@v4
57+
58+
- name: Set up Java ${{ matrix.java }}
59+
if: matrix.java != ''
60+
uses: actions/setup-java@v4
61+
with:
62+
distribution: temurin
63+
java-version: ${{ matrix.java }}
64+
65+
- name: Install uv
66+
uses: astral-sh/setup-uv@v5
67+
with:
68+
python-version: "3.11"
69+
70+
- name: Install dependencies (${{ matrix.engine }})
71+
run: uv sync --group dev ${{ matrix.extras_flags }}
72+
73+
- name: Run integration tests (${{ matrix.engine }})
74+
run: uv run pytest ${{ matrix.test_file }} -v -s --tb=short -W always

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ __pycache__/
55
*.pyd
66
*.so
77

8+
# Development artifacts
9+
dev/
10+
811
# Virtual environment
912
.venv/
1013
env/
@@ -34,6 +37,10 @@ build/
3437
.DS_Store
3538
Thumbs.db
3639

40+
# Spark metastore (Derby embedded DB)
41+
metastore_db/
42+
derby.log
43+
3744
# Logs
3845
*.log
3946

.python-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.11

README.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,30 @@ LakeBench supports multiple lakehouse compute engines. Each benchmark scenario d
7070
| Synapse Spark |||||
7171
| HDInsight Spark |||||
7272
| DuckDB |||||
73-
| Polars || ⚠️ | ⚠️ | 🔜 |
74-
| Daft || ⚠️ | ⚠️ | 🔜 |
73+
| Polars || ⚠️ | ⚠️ | ⚠️ |
74+
| Daft || ⚠️ | ⚠️ | ⚠️ |
7575
| Sail |||||
7676

7777
> **Legend:**
7878
> ✅ = Supported
7979
> ⚠️ = Some queries fail due to syntax issues (i.e. Polars doesn't support SQL non-equi joins, Daft is missing a lot of standard SQL contructs, i.e. DATE_ADD, CROSS JOIN, Subqueries, non-equi joins, CASE with operand, etc.).
8080
> 🔜 = Coming Soon
81-
> (Blank) = Not currently supported
81+
> (Blank) = Not currently supported
82+
83+
For detailed pass rates and per-query failure analysis, see the [coverage reports](reports/coverage/).
84+
85+
## 📊 Engine Coverage Reports
86+
87+
Per-engine coverage reports are auto-generated by the integration test suite and show pass rates with individual query failure details.
88+
To refresh: run the integration tests for your engine of choice (see [`tests/integration/README.md`](tests/integration/README.md)).
89+
90+
| Engine | Report |
91+
|--------|--------|
92+
| DuckDB | [reports/coverage/duckdb.md](reports/coverage/duckdb.md) |
93+
| Polars | [reports/coverage/polars.md](reports/coverage/polars.md) |
94+
| Daft | [reports/coverage/daft.md](reports/coverage/daft.md) |
95+
| Spark | [reports/coverage/spark.md](reports/coverage/spark.md) |
96+
| Sail | [reports/coverage/sail.md](reports/coverage/sail.md) |
8297

8398
## Where Can I Run LakeBench?
8499
Multiple modalities doesn't end at just benchmarks and engines, LakeBench also supports different runtimes and storage backends:

0 commit comments

Comments
 (0)