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.
- pytest_exasol_benchmark-0.1.0/LICENSE +21 -0
- pytest_exasol_benchmark-0.1.0/PKG-INFO +62 -0
- pytest_exasol_benchmark-0.1.0/README.rst +40 -0
- pytest_exasol_benchmark-0.1.0/doc/changes/changelog.md +12 -0
- pytest_exasol_benchmark-0.1.0/exasol/pytest_benchmark/__init__.py +154 -0
- pytest_exasol_benchmark-0.1.0/exasol/pytest_benchmark/py.typed +0 -0
- pytest_exasol_benchmark-0.1.0/pyproject.toml +109 -0
|
@@ -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,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
|
|
File without changes
|
|
@@ -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"
|