pytest-exasol-benchmark 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Exasol
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,62 @@
1
+ Metadata-Version: 2.4
2
+ Name: pytest-exasol-benchmark
3
+ Version: 0.1.0
4
+ Summary:
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Author: Exasol AG
8
+ Author-email: opensource@exasol.com
9
+ Requires-Python: >=3.10,<3.14
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Requires-Dist: pytest (>=9.1.1,<10.0.0)
16
+ Requires-Dist: pytest-benchmark (>=5.2.3,<6.0.0)
17
+ Requires-Dist: sqlglot (>=30.13.0,<31.0.0)
18
+ Project-URL: Homepage, https://github.com/exasol/pytest-exasol-benchmark
19
+ Project-URL: Repository, https://github.com/exasol/pytest-exasol-benchmark
20
+ Description-Content-Type: text/x-rst
21
+
22
+ pytest-exasol-benchmark Plugin
23
+ ==============================
24
+
25
+
26
+
27
+ .. image:: https://img.shields.io/pypi/l/pytest_exasol_benchmark
28
+ :target: https://opensource.org/licenses/MIT
29
+ :alt: License
30
+
31
+ .. image:: https://img.shields.io/pypi/dm/pytest_exasol_benchmark
32
+ :target: https://pypi.org/project/pytest_exasol_benchmark/
33
+ :alt: Downloads
34
+
35
+ .. image:: https://img.shields.io/pypi/pyversions/pytest_exasol_benchmark
36
+ :target: https://pypi.org/project/pytest_exasol_benchmark/
37
+ :alt: Supported Python Versions
38
+
39
+ .. image:: https://img.shields.io/pypi/v/pytest_exasol_benchmark
40
+ :target: https://pypi.org/project/pytest_exasol_benchmark/
41
+ :alt: PyPi Package
42
+
43
+ 🚀 Features
44
+ -----------
45
+
46
+ 🔌️ Prerequisites
47
+ -----------------
48
+
49
+ - `Python <https://www.python.org/>`__ >= 3.10
50
+
51
+ 💾 Installation
52
+ ---------------
53
+
54
+ .. code-block:: shell
55
+
56
+ pip install pytest_exasol_benchmark
57
+
58
+ .. 📚 Documentation
59
+ ----------------
60
+
61
+ For further details, check out the latest `documentation <https://exasol.github.io/pytest-exasol-benchmark/>`_.
62
+
@@ -0,0 +1,40 @@
1
+ pytest-exasol-benchmark Plugin
2
+ ==============================
3
+
4
+
5
+
6
+ .. image:: https://img.shields.io/pypi/l/pytest_exasol_benchmark
7
+ :target: https://opensource.org/licenses/MIT
8
+ :alt: License
9
+
10
+ .. image:: https://img.shields.io/pypi/dm/pytest_exasol_benchmark
11
+ :target: https://pypi.org/project/pytest_exasol_benchmark/
12
+ :alt: Downloads
13
+
14
+ .. image:: https://img.shields.io/pypi/pyversions/pytest_exasol_benchmark
15
+ :target: https://pypi.org/project/pytest_exasol_benchmark/
16
+ :alt: Supported Python Versions
17
+
18
+ .. image:: https://img.shields.io/pypi/v/pytest_exasol_benchmark
19
+ :target: https://pypi.org/project/pytest_exasol_benchmark/
20
+ :alt: PyPi Package
21
+
22
+ 🚀 Features
23
+ -----------
24
+
25
+ 🔌️ Prerequisites
26
+ -----------------
27
+
28
+ - `Python <https://www.python.org/>`__ >= 3.10
29
+
30
+ 💾 Installation
31
+ ---------------
32
+
33
+ .. code-block:: shell
34
+
35
+ pip install pytest_exasol_benchmark
36
+
37
+ .. 📚 Documentation
38
+ ----------------
39
+
40
+ For further details, check out the latest `documentation <https://exasol.github.io/pytest-exasol-benchmark/>`_.
@@ -0,0 +1,12 @@
1
+ # Changes
2
+
3
+ * [unreleased](unreleased.md)
4
+ * [0.1.0](changes_0.1.0.md)
5
+
6
+ ```{toctree}
7
+ ---
8
+ hidden:
9
+ ---
10
+ unreleased
11
+ changes_0.1.0
12
+ ```
@@ -0,0 +1,154 @@
1
+ from contextlib import contextmanager
2
+ from importlib.metadata import version
3
+
4
+ from sqlglot import (
5
+ Dialects,
6
+ exp,
7
+ )
8
+
9
+ __version__ = version("pytest_exasol_benchmark")
10
+ import logging
11
+ from collections.abc import Callable
12
+ from typing import (
13
+ Any,
14
+ TypeAlias,
15
+ )
16
+
17
+ import pytest
18
+ from sqlglot.expressions import (
19
+ Query,
20
+ Select,
21
+ )
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ QueryResult: TypeAlias = Any
26
+ QueryFunc: TypeAlias = Callable[[str], QueryResult]
27
+
28
+ MAX_UNIONS: int = 100
29
+
30
+
31
+ def linear_row_sql_data_generator(
32
+ schema_name: str,
33
+ output_table_name: str,
34
+ input_table_name: str,
35
+ factor: int,
36
+ max_unions: int = MAX_UNIONS,
37
+ ) -> list[str]:
38
+ """
39
+ Generate SQL statements that copy rows from `schema.input_table_name` to `schema.output_table_name`
40
+ `factor` times using UNION ALL. For each `max_unions` a new SQL statement is added to the list.
41
+
42
+ Returns a list of SQL statements.
43
+ Example:
44
+ factor=3, max_unions=100 -> 1 SQL statement
45
+ factor=150, max_unions=100 -> 2 SQL statements: 150 + 50
46
+ """
47
+ if factor < 1:
48
+ raise ValueError("factor must be greater than 0")
49
+ if max_unions < 1 or max_unions > MAX_UNIONS:
50
+ raise ValueError(f"max_unions must be between 1 and {MAX_UNIONS}")
51
+
52
+ sql_statements: list[str] = []
53
+ remaining = factor
54
+
55
+ input_table = exp.table_(table=input_table_name, db=schema_name)
56
+ output_table = exp.table_(table=output_table_name, db=schema_name)
57
+
58
+ while remaining > 0:
59
+ batch_size: int = min(remaining, max_unions)
60
+
61
+ selects: list[Select] = [
62
+ exp.select("*").from_(input_table.copy()) for _ in range(batch_size)
63
+ ]
64
+
65
+ query: Query = selects[0]
66
+ for next_select in selects[1:]:
67
+ query = query.union(next_select, distinct=False)
68
+
69
+ insert = exp.Insert(
70
+ expression=query,
71
+ into=output_table.copy(),
72
+ )
73
+
74
+ sql_statements.append(insert.sql(dialect=Dialects.EXASOL, pretty=True))
75
+ remaining -= batch_size
76
+
77
+ return sql_statements
78
+
79
+
80
+ def exponential_row_sql_data_generator(
81
+ schema_name: str,
82
+ output_table_name: str,
83
+ input_table_name: str,
84
+ exponent: int = 1,
85
+ ) -> list[str]:
86
+ """Generate SQL statements that grow ``output_table_name`` exponentially.
87
+
88
+ The first SQL statement copies the input table to the output table.
89
+ Each subsequent SQL statement inserts the output table into itself, doubling its
90
+ row count. Consequently, executing all returned SQL statements produces
91
+ ``2 ** exponent`` copies of the input table's rows.
92
+ """
93
+ if exponent < 1:
94
+ raise ValueError("exponent must be greater than or equal to 1")
95
+
96
+ input_table = exp.table_(table=input_table_name, db=schema_name)
97
+ output_table = exp.table_(table=output_table_name, db=schema_name)
98
+
99
+ source_tables = [input_table, *([output_table] * exponent)]
100
+ return [
101
+ exp.Insert(
102
+ this=output_table.copy(),
103
+ expression=exp.select("*").from_(source_table.copy()),
104
+ ).sql(dialect=Dialects.EXASOL, pretty=True)
105
+ for source_table in source_tables
106
+ ]
107
+
108
+
109
+ @pytest.fixture
110
+ def query_func() -> QueryFunc:
111
+ """
112
+ The fixture shall return a function which can be used to execute SQL queries.
113
+ The user has to override this fixture.
114
+ """
115
+ raise NotImplementedError("Override of fixture 'query_func' not implemented")
116
+
117
+
118
+ def get_enable_query_cache_sql() -> str:
119
+ return "alter session set query_cache='on'"
120
+
121
+
122
+ def get_disable_query_cache_sql() -> str:
123
+ return "alter session set query_cache='off'"
124
+
125
+
126
+ @contextmanager
127
+ def disable_query_cache_session(query_func: QueryFunc, disable_query_cache: bool):
128
+ if disable_query_cache:
129
+ query_func(get_disable_query_cache_sql())
130
+ yield
131
+ if disable_query_cache:
132
+ query_func(get_enable_query_cache_sql())
133
+
134
+
135
+ @pytest.fixture
136
+ def exasol_benchmark(benchmark, query_func: QueryFunc):
137
+ def run_benchmark(
138
+ target: Callable,
139
+ args: tuple = (),
140
+ rounds=1,
141
+ warmup_rounds=0,
142
+ iterations=1,
143
+ disable_query_cache: bool = True,
144
+ ):
145
+ with disable_query_cache_session(query_func, disable_query_cache):
146
+ return benchmark.pedantic(
147
+ target=target,
148
+ args=args,
149
+ rounds=rounds,
150
+ warmup_rounds=warmup_rounds,
151
+ iterations=iterations,
152
+ )
153
+
154
+ return run_benchmark
@@ -0,0 +1,109 @@
1
+ [project]
2
+ name = "pytest-exasol-benchmark"
3
+ version = "0.1.0"
4
+ description = ""
5
+ authors = [
6
+ { name = "Exasol AG", email = "opensource@exasol.com" },
7
+ ]
8
+
9
+ requires-python = ">=3.10,<3.14"
10
+ readme = "README.rst"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "Programming Language :: Python :: 3.10",
16
+ "Programming Language :: Python :: 3.11",
17
+ "Programming Language :: Python :: 3.12",
18
+ "Programming Language :: Python :: 3.13",
19
+ ]
20
+
21
+ dependencies = [
22
+ "pytest (>=9.1.1,<10.0.0)",
23
+ "pytest-benchmark (>=5.2.3,<6.0.0)",
24
+ "sqlglot (>=30.13.0,<31.0.0)"
25
+ ]
26
+
27
+ [project.entry-points.pytest11]
28
+ exasol_benchmark = "exasol.pytest_benchmark"
29
+
30
+ [dependency-groups]
31
+ dev = [
32
+ "exasol-toolbox>=10.3.0,<11",
33
+ "pyexasol (>=2.3.0,<3.0.0)",
34
+ "pytest-exasol-backend (>=1.5.1,<2.0.0)",
35
+ "pytest-exasol-extension (>=1.0.1,<2.0.0)"
36
+ ]
37
+
38
+ [tool.poetry]
39
+ requires-poetry = ">=2.3.0"
40
+ packages = [
41
+ { include = "exasol" },
42
+ ]
43
+ include = [
44
+ "README.rst",
45
+ "doc/changes/changelog.md",
46
+ "LICENSE",
47
+ "exasol/toolbox/templates/**/*"
48
+ ]
49
+
50
+ [tool.poetry.requires-plugins]
51
+ poetry-plugin-export = ">=1.8"
52
+
53
+ [project.urls]
54
+ repository = "https://github.com/exasol/pytest-exasol-benchmark"
55
+ homepage = "https://github.com/exasol/pytest-exasol-benchmark"
56
+
57
+ [build-system]
58
+ requires = ["poetry-core>=2.0.0"]
59
+ build-backend = "poetry.core.masonry.api"
60
+
61
+ # Tooling - marking what to show in the documentation
62
+ [tool.coverage.run]
63
+ relative_files = true
64
+ source = [
65
+ "exasol",
66
+ ]
67
+
68
+ [tool.coverage.report]
69
+ # Switch to 15 after code has been added
70
+ fail_under = 0
71
+
72
+ [tool.black]
73
+ line-length = 88
74
+ verbose = false
75
+ include = "\\.pyi?$"
76
+
77
+ [tool.isort]
78
+ profile = "black"
79
+ force_grid_wrap = 2
80
+
81
+ [tool.pylint.master]
82
+ fail-under = 5.0
83
+ ignore = []
84
+
85
+ [tool.pylint.format]
86
+ max-line-length = 88
87
+ max-module-lines = 800
88
+
89
+ [tool.ruff.lint]
90
+ extend-ignore = [
91
+ "E", # Syntax errors
92
+ "F", # Pyflakes rules (excluding F401)
93
+ "UP", # pyupgrade rules
94
+ "D", # Docstring rules
95
+ ]
96
+ extend-select = ["F401"]
97
+ unfixable = []
98
+
99
+ [[tool.mypy.overrides]]
100
+ module = [
101
+ "test.*",
102
+ ]
103
+ ignore_errors = true
104
+
105
+ [tool.sonar]
106
+ projectKey = "com.exasol:pytest-benchmark"
107
+ host.url = "https://sonarcloud.io"
108
+ organization = "exasol"
109
+ sonar.tests = "test"