From 23360a8029c0e80a61f1ce87d9072aa150175462 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 20:24:28 +0000 Subject: [PATCH 01/32] build(deps): bump sphinx-scylladb-theme from 1.9.1 to 1.9.2 in /docs Bumps [sphinx-scylladb-theme](https://github.com/scylladb/sphinx-scylladb-theme) from 1.9.1 to 1.9.2. - [Release notes](https://github.com/scylladb/sphinx-scylladb-theme/releases) - [Commits](https://github.com/scylladb/sphinx-scylladb-theme/compare/1.9.1...1.9.2) --- updated-dependencies: - dependency-name: sphinx-scylladb-theme dependency-version: 1.9.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/poetry.lock b/docs/poetry.lock index 6d439e504cc..08fdfc7ab4d 100644 --- a/docs/poetry.lock +++ b/docs/poetry.lock @@ -911,14 +911,14 @@ sphinx-markdown-tables = "0.0.17" [[package]] name = "sphinx-scylladb-theme" -version = "1.9.1" +version = "1.9.2" description = "A Sphinx Theme for ScyllaDB documentation projects" optional = false python-versions = ">=3.11" groups = ["main"] files = [ - {file = "sphinx_scylladb_theme-1.9.1-py3-none-any.whl", hash = "sha256:6156d60befc3da03bd11991fec9bc590e27ce7cc4ab05aa334edd5611424b106"}, - {file = "sphinx_scylladb_theme-1.9.1.tar.gz", hash = "sha256:2ba6367f005d2c68eee1916cc16385989b8e53bbddcc81193003bdeb3bd3415e"}, + {file = "sphinx_scylladb_theme-1.9.2-py3-none-any.whl", hash = "sha256:1d75463151693c3b31ef48b2401aa4db18953fc515b4061c6f127182242e0280"}, + {file = "sphinx_scylladb_theme-1.9.2.tar.gz", hash = "sha256:f4319deeefcc446779375c2d9cbdd922eaf63da092a50def74247dd2156f1274"}, ] [package.dependencies] From 433dc48459f38a1095915fbd1ffb9fbce5f18d11 Mon Sep 17 00:00:00 2001 From: David Garcia Date: Thu, 12 Mar 2026 15:17:29 +0000 Subject: [PATCH 02/32] docs: update theme 1.9 --- .github/dependabot.yaml | 2 +- .github/workflows/docs-pages.yml | 5 + .github/workflows/docs-pr.yml | 3 + docs/Makefile | 20 +- docs/poetry.lock | 1394 ------------------------------ docs/pyproject.toml | 40 +- docs/source/conf.py | 2 +- docs/uv.lock | 973 +++++++++++++++++++++ 8 files changed, 1012 insertions(+), 1427 deletions(-) delete mode 100644 docs/poetry.lock create mode 100644 docs/uv.lock diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index 28784749c45..ac3943ef576 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -1,6 +1,6 @@ version: 2 updates: - - package-ecosystem: "pip" + - package-ecosystem: "uv" directory: "/docs" schedule: interval: "daily" diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml index 524b07e0a7a..54ac052adc0 100644 --- a/.github/workflows/docs-pages.yml +++ b/.github/workflows/docs-pages.yml @@ -20,6 +20,8 @@ jobs: permissions: contents: write runs-on: ubuntu-latest + permissions: + contents: write concurrency: group: single cancel-in-progress: true @@ -42,6 +44,9 @@ jobs: java-version: '8' distribution: 'temurin' + - name: Install uv + uses: astral-sh/setup-uv@v6 + - name: Set up env run: make -C docs setupenv diff --git a/.github/workflows/docs-pr.yml b/.github/workflows/docs-pr.yml index 8e761c058bd..fc3e0667ef8 100644 --- a/.github/workflows/docs-pr.yml +++ b/.github/workflows/docs-pr.yml @@ -37,6 +37,9 @@ jobs: java-version: '8' distribution: 'temurin' + - name: Install uv + uses: astral-sh/setup-uv@v6 + - name: Set up env run: make -C docs setupenv diff --git a/docs/Makefile b/docs/Makefile index 3fd93c0aa0d..24fb9c6c703 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,7 +1,7 @@ # You can set these variables from the command line. -POETRY = poetry +UV = uv SPHINXOPTS = -j auto -SPHINXBUILD = $(POETRY) run sphinx-build +SPHINXBUILD = $(UV) run sphinx-build PAPER = BUILDDIR = _build SOURCEDIR = _source @@ -19,11 +19,11 @@ all: dirhtml # Setup commands .PHONY: setupenv setupenv: - pip install -q poetry + pip install -q uv .PHONY: setup setup: - $(POETRY) install + $(UV) sync @if [ ! -d "$(SOURCEDIR)" ]; then mkdir -p "$(SOURCEDIR)"; fi cp -RL source/* $(SOURCEDIR) cd $(SOURCEDIR) && find . -name README.md -execdir mv '{}' index.md ';' @@ -31,7 +31,7 @@ setup: .PHONY: update update: - $(POETRY) update + $(UV) sync --upgrade # Clean commands .PHONY: pristine @@ -42,7 +42,7 @@ pristine: clean clean: rm -rf $(BUILDDIR)/* rm -rf $(SOURCEDIR)/* - rm -f poetry.lock + rm -f uv.lock # Generate output commands .PHONY: dirhtml @@ -60,7 +60,7 @@ javadoc: setup .PHONY: multiversion multiversion: setup - $(POETRY) run ./_utils/multiversion.sh + $(UV) run ./_utils/multiversion.sh @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." @@ -85,15 +85,15 @@ dummy: setup # Preview commands .PHONY: preview preview: setup - $(POETRY) run sphinx-autobuild -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml --port 5500 --re-ignore 'api/*' + $(UV) run sphinx-autobuild -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml --port 5500 --re-ignore 'api/*' .PHONY: multiversionpreview multiversionpreview: multiversion - $(POETRY) run python -m http.server 5500 --directory $(BUILDDIR)/dirhtml + $(UV) run python -m http.server 5500 --directory $(BUILDDIR)/dirhtml .PHONY: redirects redirects: setup - $(POETRY) run redirects-cli fromfile --yaml-file _utils/redirects.yaml --output-dir $(BUILDDIR)/dirhtml + $(UV) run redirects-cli fromfile --yaml-file _utils/redirects.yaml --output-dir $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." diff --git a/docs/poetry.lock b/docs/poetry.lock deleted file mode 100644 index 6d439e504cc..00000000000 --- a/docs/poetry.lock +++ /dev/null @@ -1,1394 +0,0 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. - -[[package]] -name = "alabaster" -version = "0.7.16" -description = "A light, configurable Sphinx theme" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, - {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, -] - -[[package]] -name = "anyio" -version = "4.6.2.post1" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d"}, - {file = "anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c"}, -] - -[package.dependencies] -idna = ">=2.8" -sniffio = ">=1.1" - -[package.extras] -doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21.0b1) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\""] -trio = ["trio (>=0.26.1)"] - -[[package]] -name = "babel" -version = "2.16.0" -description = "Internationalization utilities" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, - {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, -] - -[package.extras] -dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] - -[[package]] -name = "beautifulsoup4" -version = "4.12.3" -description = "Screen-scraping library" -optional = false -python-versions = ">=3.6.0" -groups = ["main"] -files = [ - {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, - {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, -] - -[package.dependencies] -soupsieve = ">1.2" - -[package.extras] -cchardet = ["cchardet"] -chardet = ["chardet"] -charset-normalizer = ["charset-normalizer"] -html5lib = ["html5lib"] -lxml = ["lxml"] - -[[package]] -name = "certifi" -version = "2024.8.30" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, - {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.0" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -groups = ["main"] -files = [ - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, - {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, - {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, -] - -[[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main"] -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "commonmark" -version = "0.9.1" -description = "Python parser for the CommonMark Markdown spec" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, - {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, -] - -[package.extras] -test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] - -[[package]] -name = "docutils" -version = "0.21.2" -description = "Docutils -- Python Documentation Utilities" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, - {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, -] - -[[package]] -name = "h11" -version = "0.14.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] - -[[package]] -name = "idna" -version = "3.10" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, -] - -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] - -[[package]] -name = "imagesize" -version = "1.4.1" -description = "Getting image size from png/jpeg/jpeg2000/gif file" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["main"] -files = [ - {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, - {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, -] - -[[package]] -name = "jinja2" -version = "3.1.4" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "markdown" -version = "3.7" -description = "Python implementation of John Gruber's Markdown." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"}, - {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"}, -] - -[package.extras] -docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] -testing = ["coverage", "pyyaml"] - -[[package]] -name = "markdown-it-py" -version = "3.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "markupsafe" -version = "3.0.2" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, - {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, -] - -[[package]] -name = "mdit-py-plugins" -version = "0.4.2" -description = "Collection of plugins for markdown-it-py" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636"}, - {file = "mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5"}, -] - -[package.dependencies] -markdown-it-py = ">=1.0.0,<4.0.0" - -[package.extras] -code-style = ["pre-commit"] -rtd = ["myst-parser", "sphinx-book-theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - -[[package]] -name = "myst-parser" -version = "4.0.0" -description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser," -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "myst_parser-4.0.0-py3-none-any.whl", hash = "sha256:b9317997552424448c6096c2558872fdb6f81d3ecb3a40ce84a7518798f3f28d"}, - {file = "myst_parser-4.0.0.tar.gz", hash = "sha256:851c9dfb44e36e56d15d05e72f02b80da21a9e0d07cba96baf5e2d476bb91531"}, -] - -[package.dependencies] -docutils = ">=0.19,<0.22" -jinja2 = "*" -markdown-it-py = ">=3.0,<4.0" -mdit-py-plugins = ">=0.4.1,<1.0" -pyyaml = "*" -sphinx = ">=7,<9" - -[package.extras] -code-style = ["pre-commit (>=3.0,<4.0)"] -linkify = ["linkify-it-py (>=2.0,<3.0)"] -rtd = ["ipython", "sphinx (>=7)", "sphinx-autodoc2 (>=0.5.0,<0.6.0)", "sphinx-book-theme (>=1.1,<2.0)", "sphinx-copybutton", "sphinx-design", "sphinx-pyscript", "sphinx-tippy (>=0.4.3)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.9.0,<0.10.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] -testing = ["beautifulsoup4", "coverage[toml]", "defusedxml", "pytest (>=8,<9)", "pytest-cov", "pytest-param-files (>=0.6.0,<0.7.0)", "pytest-regressions", "sphinx-pytest"] -testing-docutils = ["pygments", "pytest (>=8,<9)", "pytest-param-files (>=0.6.0,<0.7.0)"] - -[[package]] -name = "packaging" -version = "24.2" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, - {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, -] - -[[package]] -name = "pygments" -version = "2.19.2" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, - {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pyyaml" -version = "6.0.2" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, -] - -[[package]] -name = "recommonmark" -version = "0.7.1" -description = "A docutils-compatibility bridge to CommonMark, enabling you to write CommonMark inside of Docutils & Sphinx projects." -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "recommonmark-0.7.1-py2.py3-none-any.whl", hash = "sha256:1b1db69af0231efce3fa21b94ff627ea33dee7079a01dd0a7f8482c3da148b3f"}, - {file = "recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67"}, -] - -[package.dependencies] -commonmark = ">=0.8.1" -docutils = ">=0.11" -sphinx = ">=1.3.1" - -[[package]] -name = "redirects-cli" -version = "0.1.3" -description = "Generates static redirections from a YAML file." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "redirects_cli-0.1.3-py3-none-any.whl", hash = "sha256:8a7a548d5f45b98db7d110fd8affbbb44b966cf250e35b5f4c9bd6541622272d"}, - {file = "redirects_cli-0.1.3.tar.gz", hash = "sha256:0cc6f35ae372d087d56bc03cfc639d6e2eac0771454c3c173ac6f3dc233969bc"}, -] - -[package.dependencies] -colorama = ">=0.4" -typer = ">=0.3" - -[package.extras] -test = ["pre-commit", "pytest"] - -[[package]] -name = "requests" -version = "2.32.3" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "rich" -version = "13.9.4" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -optional = false -python-versions = ">=3.8.0" -groups = ["main"] -files = [ - {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, - {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - -[[package]] -name = "roman-numerals-py" -version = "3.1.0" -description = "Manipulate well-formed Roman numerals" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c"}, - {file = "roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d"}, -] - -[package.extras] -lint = ["mypy (==1.15.0)", "pyright (==1.1.394)", "ruff (==0.9.7)"] -test = ["pytest (>=8)"] - -[[package]] -name = "setuptools" -version = "75.4.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "setuptools-75.4.0-py3-none-any.whl", hash = "sha256:b3c5d862f98500b06ffdf7cc4499b48c46c317d8d56cb30b5c8bce4d88f5c216"}, - {file = "setuptools-75.4.0.tar.gz", hash = "sha256:1dc484f5cf56fd3fe7216d7b8df820802e7246cfb534a1db2aa64f14fcb9cdcb"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.7.0) ; sys_platform != \"cygwin\""] -core = ["importlib-metadata (>=6) ; python_version < \"3.10\"", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib-metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (>=1.12,<1.14)", "pytest-mypy"] - -[[package]] -name = "shellingham" -version = "1.5.4" -description = "Tool to Detect Surrounding Shell" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, - {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, - {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, -] - -[[package]] -name = "snowballstemmer" -version = "2.2.0" -description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, - {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, -] - -[[package]] -name = "soupsieve" -version = "2.6" -description = "A modern CSS selector implementation for Beautiful Soup." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, - {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, -] - -[[package]] -name = "sphinx" -version = "8.2.3" -description = "Python documentation generator" -optional = false -python-versions = ">=3.11" -groups = ["main"] -files = [ - {file = "sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3"}, - {file = "sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348"}, -] - -[package.dependencies] -alabaster = ">=0.7.14" -babel = ">=2.13" -colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} -docutils = ">=0.20,<0.22" -imagesize = ">=1.3" -Jinja2 = ">=3.1" -packaging = ">=23.0" -Pygments = ">=2.17" -requests = ">=2.30.0" -roman-numerals-py = ">=1.0.0" -snowballstemmer = ">=2.2" -sphinxcontrib-applehelp = ">=1.0.7" -sphinxcontrib-devhelp = ">=1.0.6" -sphinxcontrib-htmlhelp = ">=2.0.6" -sphinxcontrib-jsmath = ">=1.0.1" -sphinxcontrib-qthelp = ">=1.0.6" -sphinxcontrib-serializinghtml = ">=1.1.9" - -[package.extras] -docs = ["sphinxcontrib-websupport"] -lint = ["betterproto (==2.0.0b6)", "mypy (==1.15.0)", "pypi-attestations (==0.0.21)", "pyright (==1.1.395)", "pytest (>=8.0)", "ruff (==0.9.9)", "sphinx-lint (>=0.9)", "types-Pillow (==10.2.0.20240822)", "types-Pygments (==2.19.0.20250219)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20241128)", "types-requests (==2.32.0.20241016)", "types-urllib3 (==1.26.25.14)"] -test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "pytest-xdist[psutil] (>=3.4)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] - -[[package]] -name = "sphinx-autobuild" -version = "2025.8.25" -description = "Rebuild Sphinx documentation on changes, with hot reloading in the browser." -optional = false -python-versions = ">=3.11" -groups = ["main"] -files = [ - {file = "sphinx_autobuild-2025.8.25-py3-none-any.whl", hash = "sha256:b750ac7d5a18603e4665294323fd20f6dcc0a984117026d1986704fa68f0379a"}, - {file = "sphinx_autobuild-2025.8.25.tar.gz", hash = "sha256:9cf5aab32853c8c31af572e4fecdc09c997e2b8be5a07daf2a389e270e85b213"}, -] - -[package.dependencies] -colorama = ">=0.4.6" -Sphinx = "*" -starlette = ">=0.35" -uvicorn = ">=0.25" -watchfiles = ">=0.20" -websockets = ">=11" - -[package.extras] -test = ["httpx", "pytest (>=6)"] - -[[package]] -name = "sphinx-collapse" -version = "0.1.4" -description = "Collapse extension for Sphinx." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "sphinx_collapse-0.1.4-py3-none-any.whl", hash = "sha256:76e9fa531bafb4984d6ef5f3dbe311982837f5965b7a35eda013bbd9dd41445e"}, - {file = "sphinx_collapse-0.1.4.tar.gz", hash = "sha256:ba860e50839c026cd1abcc164e1e7cb18bcc11c8214150e34a6550461be3229f"}, -] - -[package.dependencies] -sphinx = ">=3" - -[package.extras] -doc = ["alabaster"] -test = ["pre-commit", "pytest"] - -[[package]] -name = "sphinx-copybutton" -version = "0.5.2" -description = "Add a copy button to each of your code cells." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd"}, - {file = "sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e"}, -] - -[package.dependencies] -sphinx = ">=1.8" - -[package.extras] -code-style = ["pre-commit (==2.12.1)"] -rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"] - -[[package]] -name = "sphinx-last-updated-by-git" -version = "0.3.8" -description = "Get the \"last updated\" time for each Sphinx page from Git" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "sphinx_last_updated_by_git-0.3.8-py3-none-any.whl", hash = "sha256:6382c8285ac1f222483a58569b78c0371af5e55f7fbf9c01e5e8a72d6fdfa499"}, - {file = "sphinx_last_updated_by_git-0.3.8.tar.gz", hash = "sha256:c145011f4609d841805b69a9300099fc02fed8f5bb9e5bcef77d97aea97b7761"}, -] - -[package.dependencies] -sphinx = ">=1.8" - -[[package]] -name = "sphinx-markdown-tables" -version = "0.0.17" -description = "A Sphinx extension for rendering tables written in markdown" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "sphinx-markdown-tables-0.0.17.tar.gz", hash = "sha256:6bc6d3d400eaccfeebd288446bc08dd83083367c58b85d40fe6c12d77ef592f1"}, - {file = "sphinx_markdown_tables-0.0.17-py3-none-any.whl", hash = "sha256:2bd0c30779653e4dd120300cbd9ca412c480738cc2241f6dea477a883f299e04"}, -] - -[package.dependencies] -markdown = ">=3.4" - -[[package]] -name = "sphinx-multiversion-scylla" -version = "0.3.7" -description = "Add support for multiple versions to sphinx" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "sphinx_multiversion_scylla-0.3.7-py3-none-any.whl", hash = "sha256:6205d261a77c90b7ea3105311d1d56014736a5148966133c34344512bb8c4e4f"}, - {file = "sphinx_multiversion_scylla-0.3.7.tar.gz", hash = "sha256:fc1ddd58e82cfd8810c1be6db8717a244043c04c1c632e9bd1436415d1db0d3b"}, -] - -[package.dependencies] -sphinx = ">=2.1" - -[[package]] -name = "sphinx-notfound-page" -version = "1.0.4" -description = "Sphinx extension to build a 404 page with absolute URLs" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "sphinx_notfound_page-1.0.4-py3-none-any.whl", hash = "sha256:f7c26ae0df3cf3d6f38f56b068762e6203d0ebb7e1c804de1059598d7dd8b9d8"}, - {file = "sphinx_notfound_page-1.0.4.tar.gz", hash = "sha256:2a52f49cd367b5c4e64072de1591cc367714098500abf4ecb9a3ecb4fec25aae"}, -] - -[package.dependencies] -sphinx = ">=5" - -[package.extras] -doc = ["sphinx-autoapi", "sphinx-rtd-theme", "sphinx-tabs", "sphinxemoji"] -test = ["tox"] - -[[package]] -name = "sphinx-scylladb-markdown" -version = "0.1.3" -description = "Sphinx extension for ScyllaDB documentation with enhanced Markdown support through MystParser and recommonmark." -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "sphinx_scylladb_markdown-0.1.3-py3-none-any.whl", hash = "sha256:f20160b4aadf4c8cf95637f0a544121954b792914ab6ec05b67cae75e20a5566"}, -] - -[package.dependencies] -myst-parser = ">=2.0.0" -recommonmark = "0.7.1" -sphinx = ">=2.1" -sphinx-markdown-tables = "0.0.17" - -[[package]] -name = "sphinx-scylladb-theme" -version = "1.9.1" -description = "A Sphinx Theme for ScyllaDB documentation projects" -optional = false -python-versions = ">=3.11" -groups = ["main"] -files = [ - {file = "sphinx_scylladb_theme-1.9.1-py3-none-any.whl", hash = "sha256:6156d60befc3da03bd11991fec9bc590e27ce7cc4ab05aa334edd5611424b106"}, - {file = "sphinx_scylladb_theme-1.9.1.tar.gz", hash = "sha256:2ba6367f005d2c68eee1916cc16385989b8e53bbddcc81193003bdeb3bd3415e"}, -] - -[package.dependencies] -beautifulsoup4 = ">=4.12.3,<5" -pyyaml = ">=6.0.1,<7" -setuptools = ">=70.1.1,<83.0.0" -sphinx-collapse = ">=0.1.4,<0.2" -sphinx-copybutton = ">=0.5.2,<0.6" -sphinx-notfound-page = ">=1.0.4,<2" -sphinx-substitution-extensions = ">=2022.2.16,<2027.0.0" -sphinx-tabs = ">=3.4.5,<4" -sphinxcontrib-mermaid = ">=1,<3" - -[[package]] -name = "sphinx-sitemap" -version = "2.9.0" -description = "Sitemap generator for Sphinx" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "sphinx_sitemap-2.9.0-py3-none-any.whl", hash = "sha256:f1f1d3a9ad012ba17a7ef0b560d303bff2d0db26647567d6e810bcc754466664"}, - {file = "sphinx_sitemap-2.9.0.tar.gz", hash = "sha256:70f97bcdf444e3d68e118355cf82a1f54c4d3c03d651cd17fe87398b26e25e21"}, -] - -[package.dependencies] -sphinx-last-updated-by-git = "*" - -[package.extras] -dev = ["build", "flake8", "pre-commit", "pytest", "sphinx", "sphinx-last-updated-by-git", "tox"] - -[[package]] -name = "sphinx-substitution-extensions" -version = "2022.2.16" -description = "Extensions for Sphinx which allow for substitutions." -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "Sphinx Substitution Extensions-2022.2.16.tar.gz", hash = "sha256:ff7d05bd00e8b2d7eb8a403b9f317d70411d4e9b6812bf91534a50df22190c75"}, - {file = "Sphinx_Substitution_Extensions-2022.2.16-py3-none-any.whl", hash = "sha256:5a8ca34dac3984486344e95c36e3ed4766d402a71bdee7390d600f153db9795b"}, -] - -[package.dependencies] -docutils = ">=0.15" -sphinx = ">=4.0.0" - -[package.extras] -dev = ["autoflake (==1.4)", "black (==22.1.0)", "check-manifest (==0.47)", "doc8 (==0.10.1)", "flake8 (==4.0.1)", "flake8-commas (==2.1.0)", "flake8-quotes (==3.3.1)", "isort (==5.10.1)", "mypy (==0.931)", "pip-check-reqs (==2.3.2)", "pydocstyle (==6.1.1)", "pyenchant (==3.2.2)", "pylint (==2.12.2)", "pyroma (==3.2)", "pytest (==7.0.1)", "pytest-cov (==3.0.0)", "types-docutils (==0.17.5)", "vulture (==2.3)"] -prompt = ["sphinx-prompt (>=0.1)"] - -[[package]] -name = "sphinx-tabs" -version = "3.4.7" -description = "Tabbed views for Sphinx" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "sphinx-tabs-3.4.7.tar.gz", hash = "sha256:991ad4a424ff54119799ba1491701aa8130dd43509474aef45a81c42d889784d"}, - {file = "sphinx_tabs-3.4.7-py3-none-any.whl", hash = "sha256:c12d7a36fd413b369e9e9967a0a4015781b71a9c393575419834f19204bd1915"}, -] - -[package.dependencies] -docutils = "*" -pygments = "*" -sphinx = ">=1.8" - -[package.extras] -code-style = ["pre-commit (==2.13.0)"] -testing = ["bs4", "coverage", "pygments", "pytest (>=7.1,<8)", "pytest-cov", "pytest-regressions", "rinohtype"] - -[[package]] -name = "sphinxcontrib-applehelp" -version = "2.0.0" -description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, - {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, -] - -[package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-devhelp" -version = "2.0.0" -description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, - {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, -] - -[package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-htmlhelp" -version = "2.1.0" -description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, - {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, -] - -[package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] -test = ["html5lib", "pytest"] - -[[package]] -name = "sphinxcontrib-jsmath" -version = "1.0.1" -description = "A sphinx extension which renders display math in HTML via JavaScript" -optional = false -python-versions = ">=3.5" -groups = ["main"] -files = [ - {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, - {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, -] - -[package.extras] -test = ["flake8", "mypy", "pytest"] - -[[package]] -name = "sphinxcontrib-mermaid" -version = "1.0.0" -description = "Mermaid diagrams in yours Sphinx powered docs" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "sphinxcontrib_mermaid-1.0.0-py3-none-any.whl", hash = "sha256:60b72710ea02087f212028feb09711225fbc2e343a10d34822fe787510e1caa3"}, - {file = "sphinxcontrib_mermaid-1.0.0.tar.gz", hash = "sha256:2e8ab67d3e1e2816663f9347d026a8dee4a858acdd4ad32dd1c808893db88146"}, -] - -[package.dependencies] -pyyaml = "*" -sphinx = "*" - -[package.extras] -test = ["defusedxml", "myst-parser", "pytest", "ruff", "sphinx"] - -[[package]] -name = "sphinxcontrib-qthelp" -version = "2.0.0" -description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, - {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, -] - -[package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] -test = ["defusedxml (>=0.7.1)", "pytest"] - -[[package]] -name = "sphinxcontrib-serializinghtml" -version = "2.0.0" -description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, - {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, -] - -[package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] -test = ["pytest"] - -[[package]] -name = "starlette" -version = "0.41.2" -description = "The little ASGI library that shines." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "starlette-0.41.2-py3-none-any.whl", hash = "sha256:fbc189474b4731cf30fcef52f18a8d070e3f3b46c6a04c97579e85e6ffca942d"}, - {file = "starlette-0.41.2.tar.gz", hash = "sha256:9834fd799d1a87fd346deb76158668cfa0b0d56f85caefe8268e2d97c3468b62"}, -] - -[package.dependencies] -anyio = ">=3.4.0,<5" - -[package.extras] -full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] - -[[package]] -name = "typer" -version = "0.13.0" -description = "Typer, build great CLIs. Easy to code. Based on Python type hints." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "typer-0.13.0-py3-none-any.whl", hash = "sha256:d85fe0b777b2517cc99c8055ed735452f2659cd45e451507c76f48ce5c1d00e2"}, - {file = "typer-0.13.0.tar.gz", hash = "sha256:f1c7198347939361eec90139ffa0fd8b3df3a2259d5852a0f7400e476d95985c"}, -] - -[package.dependencies] -click = ">=8.0.0" -rich = ">=10.11.0" -shellingham = ">=1.3.0" -typing-extensions = ">=3.7.4.3" - -[[package]] -name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, -] - -[[package]] -name = "urllib3" -version = "2.2.3" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, - {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "uvicorn" -version = "0.32.0" -description = "The lightning-fast ASGI server." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "uvicorn-0.32.0-py3-none-any.whl", hash = "sha256:60b8f3a5ac027dcd31448f411ced12b5ef452c646f76f02f8cc3f25d8d26fd82"}, - {file = "uvicorn-0.32.0.tar.gz", hash = "sha256:f78b36b143c16f54ccdb8190d0a26b5f1901fe5a3c777e1ab29f26391af8551e"}, -] - -[package.dependencies] -click = ">=7.0" -h11 = ">=0.8" - -[package.extras] -standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"] - -[[package]] -name = "watchfiles" -version = "0.24.0" -description = "Simple, modern and high performance file watching and code reload in python." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "watchfiles-0.24.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:083dc77dbdeef09fa44bb0f4d1df571d2e12d8a8f985dccde71ac3ac9ac067a0"}, - {file = "watchfiles-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e94e98c7cb94cfa6e071d401ea3342767f28eb5a06a58fafdc0d2a4974f4f35c"}, - {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82ae557a8c037c42a6ef26c494d0631cacca040934b101d001100ed93d43f361"}, - {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:acbfa31e315a8f14fe33e3542cbcafc55703b8f5dcbb7c1eecd30f141df50db3"}, - {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b74fdffce9dfcf2dc296dec8743e5b0332d15df19ae464f0e249aa871fc1c571"}, - {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:449f43f49c8ddca87c6b3980c9284cab6bd1f5c9d9a2b00012adaaccd5e7decd"}, - {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4abf4ad269856618f82dee296ac66b0cd1d71450fc3c98532d93798e73399b7a"}, - {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f895d785eb6164678ff4bb5cc60c5996b3ee6df3edb28dcdeba86a13ea0465e"}, - {file = "watchfiles-0.24.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7ae3e208b31be8ce7f4c2c0034f33406dd24fbce3467f77223d10cd86778471c"}, - {file = "watchfiles-0.24.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2efec17819b0046dde35d13fb8ac7a3ad877af41ae4640f4109d9154ed30a188"}, - {file = "watchfiles-0.24.0-cp310-none-win32.whl", hash = "sha256:6bdcfa3cd6fdbdd1a068a52820f46a815401cbc2cb187dd006cb076675e7b735"}, - {file = "watchfiles-0.24.0-cp310-none-win_amd64.whl", hash = "sha256:54ca90a9ae6597ae6dc00e7ed0a040ef723f84ec517d3e7ce13e63e4bc82fa04"}, - {file = "watchfiles-0.24.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:bdcd5538e27f188dd3c804b4a8d5f52a7fc7f87e7fd6b374b8e36a4ca03db428"}, - {file = "watchfiles-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2dadf8a8014fde6addfd3c379e6ed1a981c8f0a48292d662e27cabfe4239c83c"}, - {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6509ed3f467b79d95fc62a98229f79b1a60d1b93f101e1c61d10c95a46a84f43"}, - {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8360f7314a070c30e4c976b183d1d8d1585a4a50c5cb603f431cebcbb4f66327"}, - {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:316449aefacf40147a9efaf3bd7c9bdd35aaba9ac5d708bd1eb5763c9a02bef5"}, - {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73bde715f940bea845a95247ea3e5eb17769ba1010efdc938ffcb967c634fa61"}, - {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3770e260b18e7f4e576edca4c0a639f704088602e0bc921c5c2e721e3acb8d15"}, - {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa0fd7248cf533c259e59dc593a60973a73e881162b1a2f73360547132742823"}, - {file = "watchfiles-0.24.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d7a2e3b7f5703ffbd500dabdefcbc9eafeff4b9444bbdd5d83d79eedf8428fab"}, - {file = "watchfiles-0.24.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d831ee0a50946d24a53821819b2327d5751b0c938b12c0653ea5be7dea9c82ec"}, - {file = "watchfiles-0.24.0-cp311-none-win32.whl", hash = "sha256:49d617df841a63b4445790a254013aea2120357ccacbed00253f9c2b5dc24e2d"}, - {file = "watchfiles-0.24.0-cp311-none-win_amd64.whl", hash = "sha256:d3dcb774e3568477275cc76554b5a565024b8ba3a0322f77c246bc7111c5bb9c"}, - {file = "watchfiles-0.24.0-cp311-none-win_arm64.whl", hash = "sha256:9301c689051a4857d5b10777da23fafb8e8e921bcf3abe6448a058d27fb67633"}, - {file = "watchfiles-0.24.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7211b463695d1e995ca3feb38b69227e46dbd03947172585ecb0588f19b0d87a"}, - {file = "watchfiles-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b8693502d1967b00f2fb82fc1e744df128ba22f530e15b763c8d82baee15370"}, - {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdab9555053399318b953a1fe1f586e945bc8d635ce9d05e617fd9fe3a4687d6"}, - {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34e19e56d68b0dad5cff62273107cf5d9fbaf9d75c46277aa5d803b3ef8a9e9b"}, - {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:41face41f036fee09eba33a5b53a73e9a43d5cb2c53dad8e61fa6c9f91b5a51e"}, - {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5148c2f1ea043db13ce9b0c28456e18ecc8f14f41325aa624314095b6aa2e9ea"}, - {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e4bd963a935aaf40b625c2499f3f4f6bbd0c3776f6d3bc7c853d04824ff1c9f"}, - {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c79d7719d027b7a42817c5d96461a99b6a49979c143839fc37aa5748c322f234"}, - {file = "watchfiles-0.24.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:32aa53a9a63b7f01ed32e316e354e81e9da0e6267435c7243bf8ae0f10b428ef"}, - {file = "watchfiles-0.24.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ce72dba6a20e39a0c628258b5c308779b8697f7676c254a845715e2a1039b968"}, - {file = "watchfiles-0.24.0-cp312-none-win32.whl", hash = "sha256:d9018153cf57fc302a2a34cb7564870b859ed9a732d16b41a9b5cb2ebed2d444"}, - {file = "watchfiles-0.24.0-cp312-none-win_amd64.whl", hash = "sha256:551ec3ee2a3ac9cbcf48a4ec76e42c2ef938a7e905a35b42a1267fa4b1645896"}, - {file = "watchfiles-0.24.0-cp312-none-win_arm64.whl", hash = "sha256:b52a65e4ea43c6d149c5f8ddb0bef8d4a1e779b77591a458a893eb416624a418"}, - {file = "watchfiles-0.24.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3d2e3ab79a1771c530233cadfd277fcc762656d50836c77abb2e5e72b88e3a48"}, - {file = "watchfiles-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:327763da824817b38ad125dcd97595f942d720d32d879f6c4ddf843e3da3fe90"}, - {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd82010f8ab451dabe36054a1622870166a67cf3fce894f68895db6f74bbdc94"}, - {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d64ba08db72e5dfd5c33be1e1e687d5e4fcce09219e8aee893a4862034081d4e"}, - {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1cf1f6dd7825053f3d98f6d33f6464ebdd9ee95acd74ba2c34e183086900a827"}, - {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43e3e37c15a8b6fe00c1bce2473cfa8eb3484bbeecf3aefbf259227e487a03df"}, - {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88bcd4d0fe1d8ff43675360a72def210ebad3f3f72cabfeac08d825d2639b4ab"}, - {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:999928c6434372fde16c8f27143d3e97201160b48a614071261701615a2a156f"}, - {file = "watchfiles-0.24.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:30bbd525c3262fd9f4b1865cb8d88e21161366561cd7c9e1194819e0a33ea86b"}, - {file = "watchfiles-0.24.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:edf71b01dec9f766fb285b73930f95f730bb0943500ba0566ae234b5c1618c18"}, - {file = "watchfiles-0.24.0-cp313-none-win32.whl", hash = "sha256:f4c96283fca3ee09fb044f02156d9570d156698bc3734252175a38f0e8975f07"}, - {file = "watchfiles-0.24.0-cp313-none-win_amd64.whl", hash = "sha256:a974231b4fdd1bb7f62064a0565a6b107d27d21d9acb50c484d2cdba515b9366"}, - {file = "watchfiles-0.24.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:ee82c98bed9d97cd2f53bdb035e619309a098ea53ce525833e26b93f673bc318"}, - {file = "watchfiles-0.24.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fd92bbaa2ecdb7864b7600dcdb6f2f1db6e0346ed425fbd01085be04c63f0b05"}, - {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f83df90191d67af5a831da3a33dd7628b02a95450e168785586ed51e6d28943c"}, - {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fca9433a45f18b7c779d2bae7beeec4f740d28b788b117a48368d95a3233ed83"}, - {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b995bfa6bf01a9e09b884077a6d37070464b529d8682d7691c2d3b540d357a0c"}, - {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed9aba6e01ff6f2e8285e5aa4154e2970068fe0fc0998c4380d0e6278222269b"}, - {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5171ef898299c657685306d8e1478a45e9303ddcd8ac5fed5bd52ad4ae0b69b"}, - {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4933a508d2f78099162da473841c652ad0de892719043d3f07cc83b33dfd9d91"}, - {file = "watchfiles-0.24.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95cf3b95ea665ab03f5a54765fa41abf0529dbaf372c3b83d91ad2cfa695779b"}, - {file = "watchfiles-0.24.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:01def80eb62bd5db99a798d5e1f5f940ca0a05986dcfae21d833af7a46f7ee22"}, - {file = "watchfiles-0.24.0-cp38-none-win32.whl", hash = "sha256:4d28cea3c976499475f5b7a2fec6b3a36208656963c1a856d328aeae056fc5c1"}, - {file = "watchfiles-0.24.0-cp38-none-win_amd64.whl", hash = "sha256:21ab23fdc1208086d99ad3f69c231ba265628014d4aed31d4e8746bd59e88cd1"}, - {file = "watchfiles-0.24.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b665caeeda58625c3946ad7308fbd88a086ee51ccb706307e5b1fa91556ac886"}, - {file = "watchfiles-0.24.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5c51749f3e4e269231510da426ce4a44beb98db2dce9097225c338f815b05d4f"}, - {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82b2509f08761f29a0fdad35f7e1638b8ab1adfa2666d41b794090361fb8b855"}, - {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a60e2bf9dc6afe7f743e7c9b149d1fdd6dbf35153c78fe3a14ae1a9aee3d98b"}, - {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f7d9b87c4c55e3ea8881dfcbf6d61ea6775fffed1fedffaa60bd047d3c08c430"}, - {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:78470906a6be5199524641f538bd2c56bb809cd4bf29a566a75051610bc982c3"}, - {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07cdef0c84c03375f4e24642ef8d8178e533596b229d32d2bbd69e5128ede02a"}, - {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d337193bbf3e45171c8025e291530fb7548a93c45253897cd764a6a71c937ed9"}, - {file = "watchfiles-0.24.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ec39698c45b11d9694a1b635a70946a5bad066b593af863460a8e600f0dff1ca"}, - {file = "watchfiles-0.24.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e28d91ef48eab0afb939fa446d8ebe77e2f7593f5f463fd2bb2b14132f95b6e"}, - {file = "watchfiles-0.24.0-cp39-none-win32.whl", hash = "sha256:7138eff8baa883aeaa074359daabb8b6c1e73ffe69d5accdc907d62e50b1c0da"}, - {file = "watchfiles-0.24.0-cp39-none-win_amd64.whl", hash = "sha256:b3ef2c69c655db63deb96b3c3e587084612f9b1fa983df5e0c3379d41307467f"}, - {file = "watchfiles-0.24.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:632676574429bee8c26be8af52af20e0c718cc7f5f67f3fb658c71928ccd4f7f"}, - {file = "watchfiles-0.24.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a2a9891723a735d3e2540651184be6fd5b96880c08ffe1a98bae5017e65b544b"}, - {file = "watchfiles-0.24.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7fa2bc0efef3e209a8199fd111b8969fe9db9c711acc46636686331eda7dd4"}, - {file = "watchfiles-0.24.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01550ccf1d0aed6ea375ef259706af76ad009ef5b0203a3a4cce0f6024f9b68a"}, - {file = "watchfiles-0.24.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:96619302d4374de5e2345b2b622dc481257a99431277662c30f606f3e22f42be"}, - {file = "watchfiles-0.24.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:85d5f0c7771dcc7a26c7a27145059b6bb0ce06e4e751ed76cdf123d7039b60b5"}, - {file = "watchfiles-0.24.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951088d12d339690a92cef2ec5d3cfd957692834c72ffd570ea76a6790222777"}, - {file = "watchfiles-0.24.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49fb58bcaa343fedc6a9e91f90195b20ccb3135447dc9e4e2570c3a39565853e"}, - {file = "watchfiles-0.24.0.tar.gz", hash = "sha256:afb72325b74fa7a428c009c1b8be4b4d7c2afedafb2982827ef2156646df2fe1"}, -] - -[package.dependencies] -anyio = ">=3.0.0" - -[[package]] -name = "websockets" -version = "14.0" -description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "websockets-14.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:064a72c0602c2d2c2586143561e0f179ef9b98e0825dc4a3d5cdf55a81898ed6"}, - {file = "websockets-14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9dc5a2726fd16c266d35838db086fa4e621bb049e3bbe498ab9d54ad5068f726"}, - {file = "websockets-14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1e541e4c8983b118a584c306070878e7f9670b7781e04184b6e05f9fc92e8a0e"}, - {file = "websockets-14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23b13edb4df2d4e5d6dc747d83e6b244e267a6615ede90f18ef13dfb2b6feb87"}, - {file = "websockets-14.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:288365a33049dae3065cdb2c2dd4b48df4b64839c565761c4f3f0c360460a561"}, - {file = "websockets-14.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79e2494047826a56f2951b2ada9dc139d2c3aff63122e86953cafe64ac0fde75"}, - {file = "websockets-14.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5a5b76b47b62de16d26439d362b18d71394ca4376eb2c8838352be64b27ba8af"}, - {file = "websockets-14.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:7ed4111f305770e35070e49fbb9fbf757a9b6c9a31bb86d352eb4031d4aa976f"}, - {file = "websockets-14.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9af48a2f4cc5e2e34cf69969079865100e418c27caa26c1e3369efcc20c81e17"}, - {file = "websockets-14.0-cp310-cp310-win32.whl", hash = "sha256:a97c10043bf74d7667be69383312007d54a507fac8fa101be492cc91e279d94d"}, - {file = "websockets-14.0-cp310-cp310-win_amd64.whl", hash = "sha256:5f86250ee98f6098479936b7d596418b6e4c919dfa156508e9d6ac5f8bfbe764"}, - {file = "websockets-14.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3c12e6c1331ee8833fcb565c033f7eb4cb5642af37cef81211c222b617b170df"}, - {file = "websockets-14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:445a53bce8344e62df4ed9a22fdd1f06cad8e404ead64b2a1f19bd826c8dad1b"}, - {file = "websockets-14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3e4be641fed120790241ae15fde27374a62cadaadcc0bd2b4ce35790bd284fb6"}, - {file = "websockets-14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b886b6d14cd089396155e6beb2935268bf995057bf24c3e5fd609af55c584a03"}, - {file = "websockets-14.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b8a85d62709a86a9a55d4720502e88968483ee7f365bd852b75935dec04e0d"}, - {file = "websockets-14.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08d62f438a591c016c5d4c79eaf9a8f7a85b6c3ea88793d676c00c930a41e775"}, - {file = "websockets-14.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:189e9f074f2a77f7cf54634797b29be28116ee564ece421c7653030a2cef48f0"}, - {file = "websockets-14.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b406f2387dbaf301996b7b2cf41519c1fbba7d5c9626406dd56f72075a60a00"}, - {file = "websockets-14.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a3741f4394ba3d55a64949ee11ffdba19e2a2bdaa1319a96a7ab93bf8bd2b9b2"}, - {file = "websockets-14.0-cp311-cp311-win32.whl", hash = "sha256:b639ea88a46f4629645b398c9e7be0366c92e4910203a6314f78469f5e631dc5"}, - {file = "websockets-14.0-cp311-cp311-win_amd64.whl", hash = "sha256:715b238c1772ed28b98af8830df41c5d68941729e22384fe1433db495b1d5438"}, - {file = "websockets-14.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f988f141a9be7a74d2e98d446b2f5411038bad14cdab80f9d1644b2329a71b48"}, - {file = "websockets-14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7fd212e7022c70b4f8246dee4449dde30ff50c7e8e1d61ac87b7879579badd03"}, - {file = "websockets-14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4c06f014fd8fa3827e5fd03ec012945e2139901f261fcc401e0622476cad9c5c"}, - {file = "websockets-14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad8f03dc976e710db785abf9deb76eb259312fb54d77b568c73f0162cef96e"}, - {file = "websockets-14.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6cff048a155024a580fee9f9a66b0ad9fc82683f6470c26eb76dd9280e6f459e"}, - {file = "websockets-14.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56ec8098dcc47817c8aee8037165f0fe30fec8efe543c66e0924781a4bfcbdfd"}, - {file = "websockets-14.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee5fb667aec4ae723d40ada9854128df427b35b526c600cd352ca0240aad4dd7"}, - {file = "websockets-14.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2752c98237057f27594a8393d498edd9db37e06abcfb99176d9cb6fb989dc883"}, - {file = "websockets-14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e9ff528498d9e5c543bee388023ca91870678ac50724d675853ba85b4f0a459e"}, - {file = "websockets-14.0-cp312-cp312-win32.whl", hash = "sha256:8982909857b09220ee31d9a45699fce26f8e5b94a10efa7fe07004d4f4200a33"}, - {file = "websockets-14.0-cp312-cp312-win_amd64.whl", hash = "sha256:61b60c2a07b6d25f7ce8cc0101d55fb0f1af388bec1eddfe0181085c2206e7b0"}, - {file = "websockets-14.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7cf000319db10a0cb5c7ce91bfd2a8699086b5cc0b5c5b83b92eec22a0448b2f"}, - {file = "websockets-14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0bae3caf386d418e83b62e8c1c4cec1b13348fac43e530b9894d6c7c02d921b5"}, - {file = "websockets-14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8eb46ac94d5c131336dc997a568f5579501958b14a507e6aa4840f6d856da980"}, - {file = "websockets-14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12c345585b1da70cd27a298b0b9a81aa18da7a690672f771b427db59c632d8aa"}, - {file = "websockets-14.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81758da7c76b4e2ddabc4a98a51f3c3aca8585a6d3a8662b5061613303bd5f68"}, - {file = "websockets-14.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4eae86193fd667667f35367d292b912685cb22c3f9f1dd6deaa3fdd713ab5976"}, - {file = "websockets-14.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7078dd0eac3a1dccf2c6f474004dbe8a4e936dbd19d37bbfb6efa70c923ae04e"}, - {file = "websockets-14.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2a418d596536a470f6f8e94cbb1fde66fe65e03d68c403eee0f2198b129e139a"}, - {file = "websockets-14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7d66eeab61956e231f35659e6d5b66dc04a3d51e65f2b8f71862dc6a8ba710d1"}, - {file = "websockets-14.0-cp313-cp313-win32.whl", hash = "sha256:b24f7286a5c4e350284623cf708662f0881fe7bc1146c1a1fe7e6a9be01a8d6b"}, - {file = "websockets-14.0-cp313-cp313-win_amd64.whl", hash = "sha256:fb260539dd2b64e93c9f2c59caa70d36d2020fb8e26fa17f62459ad50ebf6c24"}, - {file = "websockets-14.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0913596e0072202be8729dab05266398b72ee57c4232f48d52fe2a0370d0b53f"}, - {file = "websockets-14.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f2e7710f3c468519f9d5b01a291c407f809f8f831e5a204b238e02447046d78"}, - {file = "websockets-14.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ae0e14729038208711d2e2f769280621c22cd253e3dac00f809fa38c6ccb79d"}, - {file = "websockets-14.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4875d1c3ab3d1d9a9d8485dc1f4c2aaa63947824af03301911ea58d1e881e096"}, - {file = "websockets-14.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:678990bc5a1e4fa36e18d340d439079a21e6b8d249848b7066cad1a6cbd34b82"}, - {file = "websockets-14.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdaf3b31f8343dcc6c20d068c10eb29325dd70f5dc321ebb5fbeaa280436e70e"}, - {file = "websockets-14.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:633bbda2d30bc695900f6a07de4e5d92a4e8e8d0d8a536bb3c2051bee4dc3856"}, - {file = "websockets-14.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1c4ca7cc5a02f909789dad259dffe61be4f38ffb26dc5e26ab2dca2c7d7c87de"}, - {file = "websockets-14.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5ade11f4939b885303d28b53d512e96e1a8ea8fbebedd6fef3e2e1afe633cc2a"}, - {file = "websockets-14.0-cp39-cp39-win32.whl", hash = "sha256:281b5ab9514eb241e347a46367a2374cb60cf8f420c4283948aa188f05e7810c"}, - {file = "websockets-14.0-cp39-cp39-win_amd64.whl", hash = "sha256:72fe11675685412917363481b79c56e68175e62352f84ca4788ac264f9ea6ed0"}, - {file = "websockets-14.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3f1a697262e28682222f18fae70eb0800dfa50c6eb96b0561c6beb83d6cf78ca"}, - {file = "websockets-14.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e0e543e0e81c55e68552bd3c081282721c710a6379a2a78e1ec793853479b25"}, - {file = "websockets-14.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2786c74cbcb0263fd541e4a075aa8c932bdcaa91e5bbb8649c65304799acdd64"}, - {file = "websockets-14.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:176b39547950ff3520728bd1eadd0fa02c68492a1fabca636bab7883dd390905"}, - {file = "websockets-14.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86626d560ceb9d846d128b9c7bd2d0f247dbb62fb49c386762d109583140bf48"}, - {file = "websockets-14.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ca447967131023e98fcb4867f05cf8584adb424b9108180b2414745a6ff41c31"}, - {file = "websockets-14.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c4eb304743ab285f8f057344d115259fbe31e42151b9aae7610db83d2a7379b1"}, - {file = "websockets-14.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:cc7dbe53276429b2ca511a04a3979ce27aa2088fdd28c119c6913dccdfd0e909"}, - {file = "websockets-14.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6dd785f7a521189b1233d3c86c0b66fb73d4769a1d253ce5b31081c5946f05f"}, - {file = "websockets-14.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:77697c303b874daf1c76d4e167cd5d6871c26964bc189e4bdb40427067d53a86"}, - {file = "websockets-14.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20979614e4d7266f15018c154255d35dfb9fc828fdf6b4924166b6728fed359f"}, - {file = "websockets-14.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3fb3d9e3940ea15b30404200e768e6111c3ee2956c60ceb001cae057961ab058"}, - {file = "websockets-14.0-py3-none-any.whl", hash = "sha256:1a3bca8cfb66614e23a65aa5d6b87190876ec6f3247094939f9db877db55319c"}, - {file = "websockets-14.0.tar.gz", hash = "sha256:be90aa6dab180fed523c0c10a6729ad16c9ba79067402d01a4d8aa7ce48d4084"}, -] - -[[package]] -name = "wheel" -version = "0.46.3" -description = "Command line tool for manipulating wheel files" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "wheel-0.46.3-py3-none-any.whl", hash = "sha256:4b399d56c9d9338230118d705d9737a2a468ccca63d5e813e2a4fc7815d8bc4d"}, - {file = "wheel-0.46.3.tar.gz", hash = "sha256:e3e79874b07d776c40bd6033f8ddf76a7dad46a7b8aa1b2787a83083519a1803"}, -] - -[package.dependencies] -packaging = ">=24.0" - -[package.extras] -test = ["pytest (>=6.0.0)", "setuptools (>=77)"] - -[metadata] -lock-version = "2.1" -python-versions = "^3.13" -content-hash = "14969e9e6b34dd7cc02268760e60a934f1fd34ff20825fbfb35218f898906a10" diff --git a/docs/pyproject.toml b/docs/pyproject.toml index 34a72712f39..212f8eccd55 100644 --- a/docs/pyproject.toml +++ b/docs/pyproject.toml @@ -1,22 +1,20 @@ -[tool.poetry] -name = "scylladb-java-driver-docs" -version = "3.7.1" -description = "ScyllaDB Java Driver" -authors = ["Java Driver Contributors"] -package-mode = false +[project] +name = "java-driver" +description = "ScyllaDB Java Driver Documentation" +version = "0.1.0" +requires-python = ">=3.12" +dependencies = [ + "pygments>=2.18.0", + "sphinx-scylladb-theme>=1.9.1", + "myst-parser>=5.0.0", + "sphinx-autobuild>=2025.4.8", + "sphinx>=9.0", + "sphinx-multiversion-scylla>=0.3.7", + "sphinx-sitemap>=2.9.0", + "redirects_cli>=0.1.3", + "wheel>=0.45.0", + "sphinx-scylladb-markdown>=0.1.2", +] -[tool.poetry.dependencies] -python = "^3.13" -pygments = "^2.19.2" -redirects_cli = "^0.1.3" -sphinx-scylladb-theme = "^1.9.1" -sphinx-sitemap = "^2.8.0" -sphinx-autobuild = "^2025.0.0" -Sphinx = "^8.0.0" -sphinx-multiversion-scylla = "^0.3.7" -wheel = "^0.46.0" -sphinx-scylladb-markdown = "^0.1.2" - -[build-system] -requires = ["poetry>=1.8"] -build-backend = "poetry.masonry.api" +[tool.uv] +package = false diff --git a/docs/source/conf.py b/docs/source/conf.py index eadd842e25f..c4d02b0f97b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -71,7 +71,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '_utils'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '_utils', '.venv'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' diff --git a/docs/uv.lock b/docs/uv.lock new file mode 100644 index 00000000000..bf1d2830a44 --- /dev/null +++ b/docs/uv.lock @@ -0,0 +1,973 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, +] + +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + +[[package]] +name = "anyio" +version = "4.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, +] + +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, +] + +[[package]] +name = "beartype" +version = "0.22.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/94/1009e248bbfbab11397abca7193bea6626806be9a327d399810d523a07cb/beartype-0.22.9.tar.gz", hash = "sha256:8f82b54aa723a2848a56008d18875f91c1db02c32ef6a62319a002e3e25a975f", size = 1608866, upload-time = "2025-12-13T06:50:30.72Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl", hash = "sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2", size = 1333658, upload-time = "2025-12-13T06:50:28.266Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, +] + +[[package]] +name = "certifi" +version = "2026.2.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/35/02daf95b9cd686320bb622eb148792655c9412dbb9b67abb5694e5910a24/charset_normalizer-3.4.5.tar.gz", hash = "sha256:95adae7b6c42a6c5b5b559b1a99149f090a57128155daeea91732c8d970d8644", size = 134804, upload-time = "2026-03-06T06:03:19.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/b6/9ee9c1a608916ca5feae81a344dffbaa53b26b90be58cc2159e3332d44ec/charset_normalizer-3.4.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed97c282ee4f994ef814042423a529df9497e3c666dca19be1d4cd1129dc7ade", size = 280976, upload-time = "2026-03-06T06:01:15.276Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d8/a54f7c0b96f1df3563e9190f04daf981e365a9b397eedfdfb5dbef7e5c6c/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0294916d6ccf2d069727d65973c3a1ca477d68708db25fd758dd28b0827cff54", size = 189356, upload-time = "2026-03-06T06:01:16.511Z" }, + { url = "https://files.pythonhosted.org/packages/42/69/2bf7f76ce1446759a5787cb87d38f6a61eb47dbbdf035cfebf6347292a65/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dc57a0baa3eeedd99fafaef7511b5a6ef4581494e8168ee086031744e2679467", size = 206369, upload-time = "2026-03-06T06:01:17.853Z" }, + { url = "https://files.pythonhosted.org/packages/10/9c/949d1a46dab56b959d9a87272482195f1840b515a3380e39986989a893ae/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ed1a9a204f317ef879b32f9af507d47e49cd5e7f8e8d5d96358c98373314fc60", size = 203285, upload-time = "2026-03-06T06:01:19.473Z" }, + { url = "https://files.pythonhosted.org/packages/67/5c/ae30362a88b4da237d71ea214a8c7eb915db3eec941adda511729ac25fa2/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ad83b8f9379176c841f8865884f3514d905bcd2a9a3b210eaa446e7d2223e4d", size = 196274, upload-time = "2026-03-06T06:01:20.728Z" }, + { url = "https://files.pythonhosted.org/packages/b2/07/c9f2cb0e46cb6d64fdcc4f95953747b843bb2181bda678dc4e699b8f0f9a/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:a118e2e0b5ae6b0120d5efa5f866e58f2bb826067a646431da4d6a2bdae7950e", size = 184715, upload-time = "2026-03-06T06:01:22.194Z" }, + { url = "https://files.pythonhosted.org/packages/36/64/6b0ca95c44fddf692cd06d642b28f63009d0ce325fad6e9b2b4d0ef86a52/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:754f96058e61a5e22e91483f823e07df16416ce76afa4ebf306f8e1d1296d43f", size = 193426, upload-time = "2026-03-06T06:01:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/50/bc/a730690d726403743795ca3f5bb2baf67838c5fea78236098f324b965e40/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c300cefd9b0970381a46394902cd18eaf2aa00163f999590ace991989dcd0fc", size = 191780, upload-time = "2026-03-06T06:01:25.053Z" }, + { url = "https://files.pythonhosted.org/packages/97/4f/6c0bc9af68222b22951552d73df4532b5be6447cee32d58e7e8c74ecbb7b/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c108f8619e504140569ee7de3f97d234f0fbae338a7f9f360455071ef9855a95", size = 185805, upload-time = "2026-03-06T06:01:26.294Z" }, + { url = "https://files.pythonhosted.org/packages/dd/b9/a523fb9b0ee90814b503452b2600e4cbc118cd68714d57041564886e7325/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d1028de43596a315e2720a9849ee79007ab742c06ad8b45a50db8cdb7ed4a82a", size = 208342, upload-time = "2026-03-06T06:01:27.55Z" }, + { url = "https://files.pythonhosted.org/packages/4d/61/c59e761dee4464050713e50e27b58266cc8e209e518c0b378c1580c959ba/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:19092dde50335accf365cce21998a1c6dd8eafd42c7b226eb54b2747cdce2fac", size = 193661, upload-time = "2026-03-06T06:01:29.051Z" }, + { url = "https://files.pythonhosted.org/packages/1c/43/729fa30aad69783f755c5ad8649da17ee095311ca42024742701e202dc59/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4354e401eb6dab9aed3c7b4030514328a6c748d05e1c3e19175008ca7de84fb1", size = 204819, upload-time = "2026-03-06T06:01:30.298Z" }, + { url = "https://files.pythonhosted.org/packages/87/33/d9b442ce5a91b96fc0840455a9e49a611bbadae6122778d0a6a79683dd31/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a68766a3c58fde7f9aaa22b3786276f62ab2f594efb02d0a1421b6282e852e98", size = 198080, upload-time = "2026-03-06T06:01:31.478Z" }, + { url = "https://files.pythonhosted.org/packages/56/5a/b8b5a23134978ee9885cee2d6995f4c27cc41f9baded0a9685eabc5338f0/charset_normalizer-3.4.5-cp312-cp312-win32.whl", hash = "sha256:1827734a5b308b65ac54e86a618de66f935a4f63a8a462ff1e19a6788d6c2262", size = 132630, upload-time = "2026-03-06T06:01:33.056Z" }, + { url = "https://files.pythonhosted.org/packages/70/53/e44a4c07e8904500aec95865dc3f6464dc3586a039ef0df606eb3ac38e35/charset_normalizer-3.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:728c6a963dfab66ef865f49286e45239384249672cd598576765acc2a640a636", size = 142856, upload-time = "2026-03-06T06:01:34.489Z" }, + { url = "https://files.pythonhosted.org/packages/ea/aa/c5628f7cad591b1cf45790b7a61483c3e36cf41349c98af7813c483fd6e8/charset_normalizer-3.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:75dfd1afe0b1647449e852f4fb428195a7ed0588947218f7ba929f6538487f02", size = 132982, upload-time = "2026-03-06T06:01:35.641Z" }, + { url = "https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac59c15e3f1465f722607800c68713f9fbc2f672b9eb649fe831da4019ae9b23", size = 280788, upload-time = "2026-03-06T06:01:37.126Z" }, + { url = "https://files.pythonhosted.org/packages/0e/09/6003e7ffeb90cc0560da893e3208396a44c210c5ee42efff539639def59b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:165c7b21d19365464e8f70e5ce5e12524c58b48c78c1f5a57524603c1ab003f8", size = 188890, upload-time = "2026-03-06T06:01:38.73Z" }, + { url = "https://files.pythonhosted.org/packages/42/1e/02706edf19e390680daa694d17e2b8eab4b5f7ac285e2a51168b4b22ee6b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:28269983f25a4da0425743d0d257a2d6921ea7d9b83599d4039486ec5b9f911d", size = 206136, upload-time = "2026-03-06T06:01:40.016Z" }, + { url = "https://files.pythonhosted.org/packages/c7/87/942c3def1b37baf3cf786bad01249190f3ca3d5e63a84f831e704977de1f/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d27ce22ec453564770d29d03a9506d449efbb9fa13c00842262b2f6801c48cce", size = 202551, upload-time = "2026-03-06T06:01:41.522Z" }, + { url = "https://files.pythonhosted.org/packages/94/0a/af49691938dfe175d71b8a929bd7e4ace2809c0c5134e28bc535660d5262/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0625665e4ebdddb553ab185de5db7054393af8879fb0c87bd5690d14379d6819", size = 195572, upload-time = "2026-03-06T06:01:43.208Z" }, + { url = "https://files.pythonhosted.org/packages/20/ea/dfb1792a8050a8e694cfbde1570ff97ff74e48afd874152d38163d1df9ae/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:c23eb3263356d94858655b3e63f85ac5d50970c6e8febcdde7830209139cc37d", size = 184438, upload-time = "2026-03-06T06:01:44.755Z" }, + { url = "https://files.pythonhosted.org/packages/72/12/c281e2067466e3ddd0595bfaea58a6946765ace5c72dfa3edc2f5f118026/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e6302ca4ae283deb0af68d2fbf467474b8b6aedcd3dab4db187e07f94c109763", size = 193035, upload-time = "2026-03-06T06:01:46.051Z" }, + { url = "https://files.pythonhosted.org/packages/ba/4f/3792c056e7708e10464bad0438a44708886fb8f92e3c3d29ec5e2d964d42/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e51ae7d81c825761d941962450f50d041db028b7278e7b08930b4541b3e45cb9", size = 191340, upload-time = "2026-03-06T06:01:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/e7/86/80ddba897127b5c7a9bccc481b0cd36c8fefa485d113262f0fe4332f0bf4/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:597d10dec876923e5c59e48dbd366e852eacb2b806029491d307daea6b917d7c", size = 185464, upload-time = "2026-03-06T06:01:48.764Z" }, + { url = "https://files.pythonhosted.org/packages/4d/00/b5eff85ba198faacab83e0e4b6f0648155f072278e3b392a82478f8b988b/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5cffde4032a197bd3b42fd0b9509ec60fb70918d6970e4cc773f20fc9180ca67", size = 208014, upload-time = "2026-03-06T06:01:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/c8/11/d36f70be01597fd30850dde8a1269ebc8efadd23ba5785808454f2389bde/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2da4eedcb6338e2321e831a0165759c0c620e37f8cd044a263ff67493be8ffb3", size = 193297, upload-time = "2026-03-06T06:01:51.933Z" }, + { url = "https://files.pythonhosted.org/packages/1a/1d/259eb0a53d4910536c7c2abb9cb25f4153548efb42800c6a9456764649c0/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:65a126fb4b070d05340a84fc709dd9e7c75d9b063b610ece8a60197a291d0adf", size = 204321, upload-time = "2026-03-06T06:01:53.887Z" }, + { url = "https://files.pythonhosted.org/packages/84/31/faa6c5b9d3688715e1ed1bb9d124c384fe2fc1633a409e503ffe1c6398c1/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7a80a9242963416bd81f99349d5f3fce1843c303bd404f204918b6d75a75fd6", size = 197509, upload-time = "2026-03-06T06:01:56.439Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a5/c7d9dd1503ffc08950b3260f5d39ec2366dd08254f0900ecbcf3a6197c7c/charset_normalizer-3.4.5-cp313-cp313-win32.whl", hash = "sha256:f1d725b754e967e648046f00c4facc42d414840f5ccc670c5670f59f83693e4f", size = 132284, upload-time = "2026-03-06T06:01:57.812Z" }, + { url = "https://files.pythonhosted.org/packages/b9/0f/57072b253af40c8aa6636e6de7d75985624c1eb392815b2f934199340a89/charset_normalizer-3.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:e37bd100d2c5d3ba35db9c7c5ba5a9228cbcffe5c4778dc824b164e5257813d7", size = 142630, upload-time = "2026-03-06T06:01:59.062Z" }, + { url = "https://files.pythonhosted.org/packages/31/41/1c4b7cc9f13bd9d369ce3bc993e13d374ce25fa38a2663644283ecf422c1/charset_normalizer-3.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:93b3b2cc5cf1b8743660ce77a4f45f3f6d1172068207c1defc779a36eea6bb36", size = 133254, upload-time = "2026-03-06T06:02:00.281Z" }, + { url = "https://files.pythonhosted.org/packages/43/be/0f0fd9bb4a7fa4fb5067fb7d9ac693d4e928d306f80a0d02bde43a7c4aee/charset_normalizer-3.4.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8197abe5ca1ffb7d91e78360f915eef5addff270f8a71c1fc5be24a56f3e4873", size = 280232, upload-time = "2026-03-06T06:02:01.508Z" }, + { url = "https://files.pythonhosted.org/packages/28/02/983b5445e4bef49cd8c9da73a8e029f0825f39b74a06d201bfaa2e55142a/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2aecdb364b8a1802afdc7f9327d55dad5366bc97d8502d0f5854e50712dbc5f", size = 189688, upload-time = "2026-03-06T06:02:02.857Z" }, + { url = "https://files.pythonhosted.org/packages/d0/88/152745c5166437687028027dc080e2daed6fe11cfa95a22f4602591c42db/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a66aa5022bf81ab4b1bebfb009db4fd68e0c6d4307a1ce5ef6a26e5878dfc9e4", size = 206833, upload-time = "2026-03-06T06:02:05.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0f/ebc15c8b02af2f19be9678d6eed115feeeccc45ce1f4b098d986c13e8769/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d77f97e515688bd615c1d1f795d540f32542d514242067adcb8ef532504cb9ee", size = 202879, upload-time = "2026-03-06T06:02:06.446Z" }, + { url = "https://files.pythonhosted.org/packages/38/9c/71336bff6934418dc8d1e8a1644176ac9088068bc571da612767619c97b3/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01a1ed54b953303ca7e310fafe0fe347aab348bd81834a0bcd602eb538f89d66", size = 195764, upload-time = "2026-03-06T06:02:08.763Z" }, + { url = "https://files.pythonhosted.org/packages/b7/95/ce92fde4f98615661871bc282a856cf9b8a15f686ba0af012984660d480b/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:b2d37d78297b39a9eb9eb92c0f6df98c706467282055419df141389b23f93362", size = 183728, upload-time = "2026-03-06T06:02:10.137Z" }, + { url = "https://files.pythonhosted.org/packages/1c/e7/f5b4588d94e747ce45ae680f0f242bc2d98dbd4eccfab73e6160b6893893/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e71bbb595973622b817c042bd943c3f3667e9c9983ce3d205f973f486fec98a7", size = 192937, upload-time = "2026-03-06T06:02:11.663Z" }, + { url = "https://files.pythonhosted.org/packages/f9/29/9d94ed6b929bf9f48bf6ede6e7474576499f07c4c5e878fb186083622716/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cd966c2559f501c6fd69294d082c2934c8dd4719deb32c22961a5ac6db0df1d", size = 192040, upload-time = "2026-03-06T06:02:13.489Z" }, + { url = "https://files.pythonhosted.org/packages/15/d2/1a093a1cf827957f9445f2fe7298bcc16f8fc5e05c1ed2ad1af0b239035e/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d5e52d127045d6ae01a1e821acfad2f3a1866c54d0e837828538fabe8d9d1bd6", size = 184107, upload-time = "2026-03-06T06:02:14.83Z" }, + { url = "https://files.pythonhosted.org/packages/0f/7d/82068ce16bd36135df7b97f6333c5d808b94e01d4599a682e2337ed5fd14/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:30a2b1a48478c3428d047ed9690d57c23038dac838a87ad624c85c0a78ebeb39", size = 208310, upload-time = "2026-03-06T06:02:16.165Z" }, + { url = "https://files.pythonhosted.org/packages/84/4e/4dfb52307bb6af4a5c9e73e482d171b81d36f522b21ccd28a49656baa680/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d8ed79b8f6372ca4254955005830fd61c1ccdd8c0fac6603e2c145c61dd95db6", size = 192918, upload-time = "2026-03-06T06:02:18.144Z" }, + { url = "https://files.pythonhosted.org/packages/08/a4/159ff7da662cf7201502ca89980b8f06acf3e887b278956646a8aeb178ab/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c5af897b45fa606b12464ccbe0014bbf8c09191e0a66aab6aa9d5cf6e77e0c94", size = 204615, upload-time = "2026-03-06T06:02:19.821Z" }, + { url = "https://files.pythonhosted.org/packages/d6/62/0dd6172203cb6b429ffffc9935001fde42e5250d57f07b0c28c6046deb6b/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1088345bcc93c58d8d8f3d783eca4a6e7a7752bbff26c3eee7e73c597c191c2e", size = 197784, upload-time = "2026-03-06T06:02:21.86Z" }, + { url = "https://files.pythonhosted.org/packages/c7/5e/1aab5cb737039b9c59e63627dc8bbc0d02562a14f831cc450e5f91d84ce1/charset_normalizer-3.4.5-cp314-cp314-win32.whl", hash = "sha256:ee57b926940ba00bca7ba7041e665cc956e55ef482f851b9b65acb20d867e7a2", size = 133009, upload-time = "2026-03-06T06:02:23.289Z" }, + { url = "https://files.pythonhosted.org/packages/40/65/e7c6c77d7aaa4c0d7974f2e403e17f0ed2cb0fc135f77d686b916bf1eead/charset_normalizer-3.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:4481e6da1830c8a1cc0b746b47f603b653dadb690bcd851d039ffaefe70533aa", size = 143511, upload-time = "2026-03-06T06:02:26.195Z" }, + { url = "https://files.pythonhosted.org/packages/ba/91/52b0841c71f152f563b8e072896c14e3d83b195c188b338d3cc2e582d1d4/charset_normalizer-3.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:97ab7787092eb9b50fb47fa04f24c75b768a606af1bcba1957f07f128a7219e4", size = 133775, upload-time = "2026-03-06T06:02:27.473Z" }, + { url = "https://files.pythonhosted.org/packages/c5/60/3a621758945513adfd4db86827a5bafcc615f913dbd0b4c2ed64a65731be/charset_normalizer-3.4.5-py3-none-any.whl", hash = "sha256:9db5e3fcdcee89a78c04dffb3fe33c79f77bd741a624946db2591c81b2fc85b0", size = 55455, upload-time = "2026-03-06T06:03:17.827Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "commonmark" +version = "0.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/60/48/a60f593447e8f0894ebb7f6e6c1f25dafc5e89c5879fdc9360ae93ff83f0/commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60", size = 95764, upload-time = "2019-10-04T15:37:39.817Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/92/dfd892312d822f36c55366118b95d914e5f16de11044a27cf10a7d71bbbf/commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9", size = 51068, upload-time = "2019-10-04T15:37:37.674Z" }, +] + +[[package]] +name = "docutils" +version = "0.22.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "imagesize" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markdown" +version = "3.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205, upload-time = "2025-08-11T07:25:47.597Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "myst-parser" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "jinja2" }, + { name = "markdown-it-py" }, + { name = "mdit-py-plugins" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/fa/7b45eef11b7971f0beb29d27b7bfe0d747d063aa29e170d9edd004733c8a/myst_parser-5.0.0.tar.gz", hash = "sha256:f6f231452c56e8baa662cc352c548158f6a16fcbd6e3800fc594978002b94f3a", size = 98535, upload-time = "2026-01-15T09:08:18.036Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/ac/686789b9145413f1a61878c407210e41bfdb097976864e0913078b24098c/myst_parser-5.0.0-py3-none-any.whl", hash = "sha256:ab31e516024918296e169139072b81592336f2fef55b8986aa31c9f04b5f7211", size = 84533, upload-time = "2026-01-15T09:08:16.788Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "recommonmark" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "commonmark" }, + { name = "docutils" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/00/3dd2bdc4184b0ce754b5b446325abf45c2e0a347e022292ddc44670f628c/recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67", size = 34444, upload-time = "2020-12-17T19:24:56.523Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/77/ed589c75db5d02a77a1d5d2d9abc63f29676467d396c64277f98b50b79c2/recommonmark-0.7.1-py2.py3-none-any.whl", hash = "sha256:1b1db69af0231efce3fa21b94ff627ea33dee7079a01dd0a7f8482c3da148b3f", size = 10214, upload-time = "2020-12-17T19:24:55.137Z" }, +] + +[[package]] +name = "redirects-cli" +version = "0.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, + { name = "typer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/3e/942a3d5322f05aa75c903de1bdc101800cc0627e4c6c371768ef9070fa28/redirects_cli-0.1.3.tar.gz", hash = "sha256:0cc6f35ae372d087d56bc03cfc639d6e2eac0771454c3c173ac6f3dc233969bc", size = 4404, upload-time = "2022-11-29T19:11:20.776Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/a4/829d6901e0c2c492d0d46190aadf3f4b9c6db6594b4e14a814f844014b28/redirects_cli-0.1.3-py3-none-any.whl", hash = "sha256:8a7a548d5f45b98db7d110fd8affbbb44b966cf250e35b5f4c9bd6541622272d", size = 4655, upload-time = "2022-11-29T19:11:18.898Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "rich" +version = "14.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" }, +] + +[[package]] +name = "roman-numerals" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, +] + +[[package]] +name = "setuptools" +version = "82.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, +] + +[[package]] +name = "sphinx" +version = "9.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" }, +] + +[[package]] +name = "sphinx-autobuild" +version = "2025.8.25" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, + { name = "sphinx" }, + { name = "starlette" }, + { name = "uvicorn" }, + { name = "watchfiles" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/3c/a59a3a453d4133777f7ed2e83c80b7dc817d43c74b74298ca0af869662ad/sphinx_autobuild-2025.8.25.tar.gz", hash = "sha256:9cf5aab32853c8c31af572e4fecdc09c997e2b8be5a07daf2a389e270e85b213", size = 15200, upload-time = "2025-08-25T18:44:55.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/20/56411b52f917696995f5ad27d2ea7e9492c84a043c5b49a3a3173573cd93/sphinx_autobuild-2025.8.25-py3-none-any.whl", hash = "sha256:b750ac7d5a18603e4665294323fd20f6dcc0a984117026d1986704fa68f0379a", size = 12535, upload-time = "2025-08-25T18:44:54.164Z" }, +] + +[[package]] +name = "sphinx-collapse" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/14/a1/cb5bb03a5081bd1229b3296c2af347b4147017fdb62777d2aad855cd349f/sphinx_collapse-0.1.4.tar.gz", hash = "sha256:ba860e50839c026cd1abcc164e1e7cb18bcc11c8214150e34a6550461be3229f", size = 19412, upload-time = "2026-02-27T17:47:24.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/18/277f4663c97073606917becab629938237f1e03952f4e339f8b7d1f3096b/sphinx_collapse-0.1.4-py3-none-any.whl", hash = "sha256:76e9fa531bafb4984d6ef5f3dbe311982837f5965b7a35eda013bbd9dd41445e", size = 4811, upload-time = "2026-02-27T17:47:22.622Z" }, +] + +[[package]] +name = "sphinx-copybutton" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/2b/a964715e7f5295f77509e59309959f4125122d648f86b4fe7d70ca1d882c/sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd", size = 23039, upload-time = "2023-04-14T08:10:22.998Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/48/1ea60e74949eecb12cdd6ac43987f9fd331156388dcc2319b45e2ebb81bf/sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e", size = 13343, upload-time = "2023-04-14T08:10:20.844Z" }, +] + +[[package]] +name = "sphinx-docs" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "myst-parser" }, + { name = "pygments" }, + { name = "redirects-cli" }, + { name = "sphinx" }, + { name = "sphinx-autobuild" }, + { name = "sphinx-multiversion-scylla" }, + { name = "sphinx-scylladb-markdown" }, + { name = "sphinx-scylladb-theme" }, + { name = "sphinx-sitemap" }, + { name = "wheel" }, +] + +[package.metadata] +requires-dist = [ + { name = "myst-parser", specifier = ">=5.0.0" }, + { name = "pygments", specifier = ">=2.18.0" }, + { name = "redirects-cli", specifier = ">=0.1.3" }, + { name = "sphinx", specifier = ">=9.0" }, + { name = "sphinx-autobuild", specifier = ">=2025.4.8" }, + { name = "sphinx-multiversion-scylla", specifier = ">=0.3.7" }, + { name = "sphinx-scylladb-markdown", specifier = ">=0.1.2" }, + { name = "sphinx-scylladb-theme", specifier = ">=1.9.1" }, + { name = "sphinx-sitemap", specifier = ">=2.9.0" }, + { name = "wheel", specifier = ">=0.45.0" }, +] + +[[package]] +name = "sphinx-last-updated-by-git" +version = "0.3.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/03/fd/de1685b6dab173dff31da24e0d3b29f02873fc24a1cdbb7678721ddc8581/sphinx_last_updated_by_git-0.3.8.tar.gz", hash = "sha256:c145011f4609d841805b69a9300099fc02fed8f5bb9e5bcef77d97aea97b7761", size = 10785, upload-time = "2024-08-11T07:15:54.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/fb/e496f16fa11fbe2dbdd0b5e306ede153dfed050aae4766fc89d500720dc7/sphinx_last_updated_by_git-0.3.8-py3-none-any.whl", hash = "sha256:6382c8285ac1f222483a58569b78c0371af5e55f7fbf9c01e5e8a72d6fdfa499", size = 8580, upload-time = "2024-08-11T07:15:53.244Z" }, +] + +[[package]] +name = "sphinx-markdown-tables" +version = "0.0.17" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/36/af0e0a4aa69298b4d664d5176243412ae96bfc207d7e3314e12b3a6f2ccc/sphinx-markdown-tables-0.0.17.tar.gz", hash = "sha256:6bc6d3d400eaccfeebd288446bc08dd83083367c58b85d40fe6c12d77ef592f1", size = 15189, upload-time = "2022-07-29T00:35:46.641Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/8d/8785a36892992582ef8d87c69ab90e26124ab1059c501d93ebecd99d2323/sphinx_markdown_tables-0.0.17-py3-none-any.whl", hash = "sha256:2bd0c30779653e4dd120300cbd9ca412c480738cc2241f6dea477a883f299e04", size = 28018, upload-time = "2022-07-29T00:35:44.956Z" }, +] + +[[package]] +name = "sphinx-multiversion-scylla" +version = "0.3.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/b1/83fb37f6c9038469b3bd01453875bb2127b3c03f9f41247394ad2063645c/sphinx_multiversion_scylla-0.3.7.tar.gz", hash = "sha256:fc1ddd58e82cfd8810c1be6db8717a244043c04c1c632e9bd1436415d1db0d3b", size = 12665, upload-time = "2026-02-27T18:43:17.849Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/94/f5b6219ca1136dc0305aaf3fb6c96aa2dfe65224d6dc147e00a6485a1a22/sphinx_multiversion_scylla-0.3.7-py3-none-any.whl", hash = "sha256:6205d261a77c90b7ea3105311d1d56014736a5148966133c34344512bb8c4e4f", size = 12558, upload-time = "2026-02-27T18:43:16.988Z" }, +] + +[[package]] +name = "sphinx-notfound-page" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/b2/67603444a8ee97b4a8ea71b0a9d6bab1727ed65e362c87e02f818ee57b8a/sphinx_notfound_page-1.1.0.tar.gz", hash = "sha256:913e1754370bb3db201d9300d458a8b8b5fb22e9246a816643a819a9ea2b8067", size = 7392, upload-time = "2025-01-28T18:45:02.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/d4/019fe439c840a7966012bbb95ccbdd81c5c10271749706793b43beb05145/sphinx_notfound_page-1.1.0-py3-none-any.whl", hash = "sha256:835dc76ff7914577a1f58d80a2c8418fb6138c0932c8da8adce4d9096fbcd389", size = 8167, upload-time = "2025-01-28T18:45:00.465Z" }, +] + +[[package]] +name = "sphinx-scylladb-markdown" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "myst-parser" }, + { name = "recommonmark" }, + { name = "sphinx" }, + { name = "sphinx-markdown-tables" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/6d/8cc78e960668b0c70d99777b20c5bff1163ab26dc1fce76621f80e20e314/sphinx_scylladb_markdown-0.1.4.tar.gz", hash = "sha256:9db3ae0dcf7c3519262da65e48c7f9e4db0ad1ce9c5f874864ea218f4cbc4c68", size = 6751, upload-time = "2025-12-12T18:03:57.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/96/99d44f90de85254b7772c31ac7701bc56ebbb9a6b9f258543cd9c9486a07/sphinx_scylladb_markdown-0.1.4-py3-none-any.whl", hash = "sha256:598753e01cf159d4698eb1a707958828446e21749038d3d42c5b9c7e86eda6e4", size = 7198, upload-time = "2025-12-12T18:03:56.688Z" }, +] + +[[package]] +name = "sphinx-scylladb-theme" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "pyyaml" }, + { name = "setuptools" }, + { name = "sphinx-collapse" }, + { name = "sphinx-copybutton" }, + { name = "sphinx-notfound-page" }, + { name = "sphinx-substitution-extensions" }, + { name = "sphinx-tabs" }, + { name = "sphinxcontrib-mermaid" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/4e/e49e351d4c429b8fe3090657d39e956d53dff61187d783caac1cba81bd72/sphinx_scylladb_theme-1.9.1.tar.gz", hash = "sha256:2ba6367f005d2c68eee1916cc16385989b8e53bbddcc81193003bdeb3bd3415e", size = 1676201, upload-time = "2026-03-09T18:10:43.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/30/2b2bae1b022d1fabef405a4857f160464548e08d924f24d0b26d0ca6a848/sphinx_scylladb_theme-1.9.1-py3-none-any.whl", hash = "sha256:6156d60befc3da03bd11991fec9bc590e27ce7cc4ab05aa334edd5611424b106", size = 1662204, upload-time = "2026-03-09T18:10:45.638Z" }, +] + +[[package]] +name = "sphinx-sitemap" +version = "2.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx-last-updated-by-git" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/17/56fe0f65e3567f829b2b4153a622be1d4b222b781e0d90d7db5a7738f30f/sphinx_sitemap-2.9.0.tar.gz", hash = "sha256:70f97bcdf444e3d68e118355cf82a1f54c4d3c03d651cd17fe87398b26e25e21", size = 6978, upload-time = "2025-10-06T00:24:00.036Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/94/3c57e8b1985e755c48972e2ecd59526d4bf0b52a1fe805bc52a8e98cb92d/sphinx_sitemap-2.9.0-py3-none-any.whl", hash = "sha256:f1f1d3a9ad012ba17a7ef0b560d303bff2d0db26647567d6e810bcc754466664", size = 6218, upload-time = "2025-10-06T00:23:58.778Z" }, +] + +[[package]] +name = "sphinx-substitution-extensions" +version = "2026.1.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beartype" }, + { name = "docutils" }, + { name = "myst-parser" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/3e/a82aa5fed0d06161a89dc2f6971b160f837cad44f196c467fc6b2132acaa/sphinx_substitution_extensions-2026.1.12.tar.gz", hash = "sha256:25e0c6c40fbf9e1df593883da946879044a3bf8d85652c8c58f354a53575d736", size = 31676, upload-time = "2026-01-12T06:19:35.324Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/5e/9caa7167d2ef2b60326765150d64513be1b61b1864ad58a92683578b2776/sphinx_substitution_extensions-2026.1.12-py2.py3-none-any.whl", hash = "sha256:9152beb4f0f5cab52057681b376a473fa6b997defc85d4ac154dd12b13a3e987", size = 8766, upload-time = "2026-01-12T06:19:33.541Z" }, +] + +[[package]] +name = "sphinx-tabs" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "pygments" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/30/ca5b0de830f369968d8e3483dd45a8908fd10169c05cd9837f0bd075982e/sphinx_tabs-3.5.0.tar.gz", hash = "sha256:91dba1187e4c35fd37380a56ac228bbd54c6c649b2351829f3bf033718277537", size = 17006, upload-time = "2026-03-03T23:00:30.404Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/45/6adc5efeb19fd5fed4027e520b5c668ce58236a2b271ade5533c4c116276/sphinx_tabs-3.5.0-py3-none-any.whl", hash = "sha256:154be49de4d5c8249ea08c5d9bf88ca8f9c31e00a178305a93cbc33e000339e5", size = 9871, upload-time = "2026-03-03T23:00:28.89Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-mermaid" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2b/ae/999891de292919b66ea34f2c22fc22c9be90ab3536fbc0fca95716277351/sphinxcontrib_mermaid-2.0.1.tar.gz", hash = "sha256:a21a385a059a6cafd192aa3a586b14bf5c42721e229db67b459dc825d7f0a497", size = 19839, upload-time = "2026-03-05T14:10:41.901Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/46/25d64bcd7821c8d6f1080e1c43d5fcdfc442a18f759a230b5ccdc891093e/sphinxcontrib_mermaid-2.0.1-py3-none-any.whl", hash = "sha256:9dca7fbe827bad5e7e2b97c4047682cfd26e3e07398cfdc96c7a8842ae7f06e7", size = 14064, upload-time = "2026-03-05T14:10:40.533Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + +[[package]] +name = "starlette" +version = "0.52.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" }, +] + +[[package]] +name = "typer" +version = "0.24.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/24/cb09efec5cc954f7f9b930bf8279447d24618bb6758d4f6adf2574c41780/typer-0.24.1.tar.gz", hash = "sha256:e39b4732d65fbdcde189ae76cf7cd48aeae72919dea1fdfc16593be016256b45", size = 118613, upload-time = "2026-02-21T16:54:40.609Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-py3-none-any.whl", hash = "sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e", size = 56085, upload-time = "2026-02-21T16:54:41.616Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.41.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/32/ce/eeb58ae4ac36fe09e3842eb02e0eb676bf2c53ae062b98f1b2531673efdd/uvicorn-0.41.0.tar.gz", hash = "sha256:09d11cf7008da33113824ee5a1c6422d89fbc2ff476540d69a34c87fab8b571a", size = 82633, upload-time = "2026-02-16T23:07:24.1Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/e4/d04a086285c20886c0daad0e026f250869201013d18f81d9ff5eada73a88/uvicorn-0.41.0-py3-none-any.whl", hash = "sha256:29e35b1d2c36a04b9e180d4007ede3bcb32a85fbdfd6c6aeb3f26839de088187", size = 68783, upload-time = "2026-02-16T23:07:22.357Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745, upload-time = "2025-10-14T15:04:46.731Z" }, + { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769, upload-time = "2025-10-14T15:04:48.003Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374, upload-time = "2025-10-14T15:04:49.179Z" }, + { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", size = 459485, upload-time = "2025-10-14T15:04:50.155Z" }, + { url = "https://files.pythonhosted.org/packages/19/0c/286b6301ded2eccd4ffd0041a1b726afda999926cf720aab63adb68a1e36/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce", size = 488813, upload-time = "2025-10-14T15:04:51.059Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", size = 594816, upload-time = "2025-10-14T15:04:52.031Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", size = 475186, upload-time = "2025-10-14T15:04:53.064Z" }, + { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", size = 456812, upload-time = "2025-10-14T15:04:55.174Z" }, + { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", size = 630196, upload-time = "2025-10-14T15:04:56.22Z" }, + { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", size = 622657, upload-time = "2025-10-14T15:04:57.521Z" }, + { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", size = 272042, upload-time = "2025-10-14T15:04:59.046Z" }, + { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", size = 288410, upload-time = "2025-10-14T15:05:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", size = 278209, upload-time = "2025-10-14T15:05:01.168Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321, upload-time = "2025-10-14T15:05:02.063Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" }, + { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" }, + { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" }, + { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976, upload-time = "2025-10-14T15:05:05.905Z" }, + { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" }, + { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" }, + { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" }, + { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056, upload-time = "2025-10-14T15:05:12.156Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162, upload-time = "2025-10-14T15:05:13.208Z" }, + { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909, upload-time = "2025-10-14T15:05:14.49Z" }, + { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389, upload-time = "2025-10-14T15:05:15.777Z" }, + { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" }, + { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877, upload-time = "2025-10-14T15:05:20.094Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315, upload-time = "2025-10-14T15:05:26.501Z" }, + { url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869, upload-time = "2025-10-14T15:05:27.649Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919, upload-time = "2025-10-14T15:05:28.701Z" }, + { url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845, upload-time = "2025-10-14T15:05:30.064Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027, upload-time = "2025-10-14T15:05:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615, upload-time = "2025-10-14T15:05:32.074Z" }, + { url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836, upload-time = "2025-10-14T15:05:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099, upload-time = "2025-10-14T15:05:34.189Z" }, + { url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626, upload-time = "2025-10-14T15:05:35.216Z" }, + { url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519, upload-time = "2025-10-14T15:05:36.259Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078, upload-time = "2025-10-14T15:05:37.63Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664, upload-time = "2025-10-14T15:05:38.95Z" }, + { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154, upload-time = "2025-10-14T15:05:39.954Z" }, + { url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820, upload-time = "2025-10-14T15:05:40.932Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510, upload-time = "2025-10-14T15:05:41.945Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408, upload-time = "2025-10-14T15:05:43.385Z" }, + { url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968, upload-time = "2025-10-14T15:05:44.404Z" }, + { url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096, upload-time = "2025-10-14T15:05:45.398Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040, upload-time = "2025-10-14T15:05:46.502Z" }, + { url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847, upload-time = "2025-10-14T15:05:47.484Z" }, + { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" }, + { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" }, + { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" }, +] + +[[package]] +name = "websockets" +version = "16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" }, + { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" }, + { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" }, + { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" }, + { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" }, + { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" }, + { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" }, + { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" }, + { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" }, + { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" }, + { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" }, + { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" }, + { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, + { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, + { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, + { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, + { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, + { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, + { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, + { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, + { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, +] + +[[package]] +name = "wheel" +version = "0.46.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/24/a2eb353a6edac9a0303977c4cb048134959dd2a51b48a269dfc9dde00c8a/wheel-0.46.3.tar.gz", hash = "sha256:e3e79874b07d776c40bd6033f8ddf76a7dad46a7b8aa1b2787a83083519a1803", size = 60605, upload-time = "2026-01-22T12:39:49.136Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/22/b76d483683216dde3d67cba61fb2444be8d5be289bf628c13fc0fd90e5f9/wheel-0.46.3-py3-none-any.whl", hash = "sha256:4b399d56c9d9338230118d705d9737a2a468ccca63d5e813e2a4fc7815d8bc4d", size = 30557, upload-time = "2026-01-22T12:39:48.099Z" }, +] From 52b0f4ab5631e6228aea197a83cc7f727bcd19b9 Mon Sep 17 00:00:00 2001 From: David Garcia Date: Mon, 16 Mar 2026 18:00:50 +0000 Subject: [PATCH 03/32] chore: address comments --- .github/workflows/docs-pages.yml | 2 - docs/pyproject.toml | 2 +- docs/uv.lock | 240 +++++++++++++++++++++++-------- 3 files changed, 180 insertions(+), 64 deletions(-) diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml index 54ac052adc0..25d9eb6c66a 100644 --- a/.github/workflows/docs-pages.yml +++ b/.github/workflows/docs-pages.yml @@ -20,8 +20,6 @@ jobs: permissions: contents: write runs-on: ubuntu-latest - permissions: - contents: write concurrency: group: single cancel-in-progress: true diff --git a/docs/pyproject.toml b/docs/pyproject.toml index 212f8eccd55..126238b33cb 100644 --- a/docs/pyproject.toml +++ b/docs/pyproject.toml @@ -2,7 +2,7 @@ name = "java-driver" description = "ScyllaDB Java Driver Documentation" version = "0.1.0" -requires-python = ">=3.12" +requires-python = ">=3.11" dependencies = [ "pygments>=2.18.0", "sphinx-scylladb-theme>=1.9.1", diff --git a/docs/uv.lock b/docs/uv.lock index bf1d2830a44..4409d749544 100644 --- a/docs/uv.lock +++ b/docs/uv.lock @@ -1,6 +1,10 @@ version = 1 revision = 3 -requires-python = ">=3.12" +requires-python = ">=3.11" +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version < '3.12'", +] [[package]] name = "alabaster" @@ -79,6 +83,22 @@ version = "3.4.5" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/1d/35/02daf95b9cd686320bb622eb148792655c9412dbb9b67abb5694e5910a24/charset_normalizer-3.4.5.tar.gz", hash = "sha256:95adae7b6c42a6c5b5b559b1a99149f090a57128155daeea91732c8d970d8644", size = 134804, upload-time = "2026-03-06T06:03:19.46Z" } wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/9e/bcec3b22c64ecec47d39bf5167c2613efd41898c019dccd4183f6aa5d6a7/charset_normalizer-3.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:610f72c0ee565dfb8ae1241b666119582fdbfe7c0975c175be719f940e110694", size = 279531, upload-time = "2026-03-06T06:00:52.252Z" }, + { url = "https://files.pythonhosted.org/packages/58/12/81fd25f7e7078ab5d1eedbb0fac44be4904ae3370a3bf4533c8f2d159acd/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60d68e820af339df4ae8358c7a2e7596badeb61e544438e489035f9fbf3246a5", size = 188006, upload-time = "2026-03-06T06:00:53.8Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6e/f2d30e8c27c1b0736a6520311982cf5286cfc7f6cac77d7bc1325e3a23f2/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b473fc8dca1c3ad8559985794815f06ca3fc71942c969129070f2c3cdf7281", size = 205085, upload-time = "2026-03-06T06:00:55.311Z" }, + { url = "https://files.pythonhosted.org/packages/d0/90/d12cefcb53b5931e2cf792a33718d7126efb116a320eaa0742c7059a95e4/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d4eb8ac7469b2a5d64b5b8c04f84d8bf3ad340f4514b98523805cbf46e3b3923", size = 200545, upload-time = "2026-03-06T06:00:56.532Z" }, + { url = "https://files.pythonhosted.org/packages/03/f4/44d3b830a20e89ff82a3134912d9a1cf6084d64f3b95dcad40f74449a654/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bcb3227c3d9aaf73eaaab1db7ccd80a8995c509ee9941e2aae060ca6e4e5d81", size = 193863, upload-time = "2026-03-06T06:00:57.823Z" }, + { url = "https://files.pythonhosted.org/packages/25/4b/f212119c18a6320a9d4a730d1b4057875cdeabf21b3614f76549042ef8a8/charset_normalizer-3.4.5-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:75ee9c1cce2911581a70a3c0919d8bccf5b1cbc9b0e5171400ec736b4b569497", size = 181827, upload-time = "2026-03-06T06:00:59.323Z" }, + { url = "https://files.pythonhosted.org/packages/74/00/b26158e48b425a202a92965f8069e8a63d9af1481dfa206825d7f74d2a3c/charset_normalizer-3.4.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d1401945cb77787dbd3af2446ff2d75912327c4c3a1526ab7955ecf8600687c", size = 191085, upload-time = "2026-03-06T06:01:00.546Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c2/1c1737bf6fd40335fe53d28fe49afd99ee4143cc57a845e99635ce0b9b6d/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a45e504f5e1be0bd385935a8e1507c442349ca36f511a47057a71c9d1d6ea9e", size = 190688, upload-time = "2026-03-06T06:01:02.479Z" }, + { url = "https://files.pythonhosted.org/packages/5a/3d/abb5c22dc2ef493cd56522f811246a63c5427c08f3e3e50ab663de27fcf4/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e09f671a54ce70b79a1fc1dc6da3072b7ef7251fadb894ed92d9aa8218465a5f", size = 183077, upload-time = "2026-03-06T06:01:04.231Z" }, + { url = "https://files.pythonhosted.org/packages/44/33/5298ad4d419a58e25b3508e87f2758d1442ff00c2471f8e0403dab8edad5/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d01de5e768328646e6a3fa9e562706f8f6641708c115c62588aef2b941a4f88e", size = 206706, upload-time = "2026-03-06T06:01:05.773Z" }, + { url = "https://files.pythonhosted.org/packages/7b/17/51e7895ac0f87c3b91d276a449ef09f5532a7529818f59646d7a55089432/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:131716d6786ad5e3dc542f5cc6f397ba3339dc0fb87f87ac30e550e8987756af", size = 191665, upload-time = "2026-03-06T06:01:07.473Z" }, + { url = "https://files.pythonhosted.org/packages/90/8f/cce9adf1883e98906dbae380d769b4852bb0fa0004bc7d7a2243418d3ea8/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a374cc0b88aa710e8865dc1bd6edb3743c59f27830f0293ab101e4cf3ce9f85", size = 201950, upload-time = "2026-03-06T06:01:08.973Z" }, + { url = "https://files.pythonhosted.org/packages/08/ca/bce99cd5c397a52919e2769d126723f27a4c037130374c051c00470bcd38/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d31f0d1671e1534e395f9eb84a68e0fb670e1edb1fe819a9d7f564ae3bc4e53f", size = 195830, upload-time = "2026-03-06T06:01:10.155Z" }, + { url = "https://files.pythonhosted.org/packages/87/4f/2e3d023a06911f1281f97b8f036edc9872167036ca6f55cc874a0be6c12c/charset_normalizer-3.4.5-cp311-cp311-win32.whl", hash = "sha256:cace89841c0599d736d3d74a27bc5821288bb47c5441923277afc6059d7fbcb4", size = 132029, upload-time = "2026-03-06T06:01:11.706Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1f/a853b73d386521fd44b7f67ded6b17b7b2367067d9106a5c4b44f9a34274/charset_normalizer-3.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:f8102ae93c0bc863b1d41ea0f4499c20a83229f52ed870850892df555187154a", size = 142404, upload-time = "2026-03-06T06:01:12.865Z" }, + { url = "https://files.pythonhosted.org/packages/b4/10/dba36f76b71c38e9d391abe0fd8a5b818790e053c431adecfc98c35cd2a9/charset_normalizer-3.4.5-cp311-cp311-win_arm64.whl", hash = "sha256:ed98364e1c262cf5f9363c3eca8c2df37024f52a8fa1180a3610014f26eac51c", size = 132796, upload-time = "2026-03-06T06:01:14.106Z" }, { url = "https://files.pythonhosted.org/packages/9c/b6/9ee9c1a608916ca5feae81a344dffbaa53b26b90be58cc2159e3332d44ec/charset_normalizer-3.4.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed97c282ee4f994ef814042423a529df9497e3c666dca19be1d4cd1129dc7ade", size = 280976, upload-time = "2026-03-06T06:01:15.276Z" }, { url = "https://files.pythonhosted.org/packages/f8/d8/a54f7c0b96f1df3563e9190f04daf981e365a9b397eedfdfb5dbef7e5c6c/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0294916d6ccf2d069727d65973c3a1ca477d68708db25fd758dd28b0827cff54", size = 189356, upload-time = "2026-03-06T06:01:16.511Z" }, { url = "https://files.pythonhosted.org/packages/42/69/2bf7f76ce1446759a5787cb87d38f6a61eb47dbbdf035cfebf6347292a65/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dc57a0baa3eeedd99fafaef7511b5a6ef4581494e8168ee086031744e2679467", size = 206369, upload-time = "2026-03-06T06:01:17.853Z" }, @@ -196,6 +216,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" }, ] +[[package]] +name = "java-driver" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "myst-parser" }, + { name = "pygments" }, + { name = "redirects-cli" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "sphinx-autobuild" }, + { name = "sphinx-multiversion-scylla" }, + { name = "sphinx-scylladb-markdown" }, + { name = "sphinx-scylladb-theme" }, + { name = "sphinx-sitemap" }, + { name = "wheel" }, +] + +[package.metadata] +requires-dist = [ + { name = "myst-parser", specifier = ">=5.0.0" }, + { name = "pygments", specifier = ">=2.18.0" }, + { name = "redirects-cli", specifier = ">=0.1.3" }, + { name = "sphinx", specifier = ">=9.0" }, + { name = "sphinx-autobuild", specifier = ">=2025.4.8" }, + { name = "sphinx-multiversion-scylla", specifier = ">=0.3.7" }, + { name = "sphinx-scylladb-markdown", specifier = ">=0.1.2" }, + { name = "sphinx-scylladb-theme", specifier = ">=1.9.1" }, + { name = "sphinx-sitemap", specifier = ">=2.9.0" }, + { name = "wheel", specifier = ">=0.45.0" }, +] + [[package]] name = "jinja2" version = "3.1.6" @@ -235,6 +287,17 @@ version = "3.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } wheels = [ + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, @@ -323,7 +386,8 @@ dependencies = [ { name = "markdown-it-py" }, { name = "mdit-py-plugins" }, { name = "pyyaml" }, - { name = "sphinx" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/33/fa/7b45eef11b7971f0beb29d27b7bfe0d747d063aa29e170d9edd004733c8a/myst_parser-5.0.0.tar.gz", hash = "sha256:f6f231452c56e8baa662cc352c548158f6a16fcbd6e3800fc594978002b94f3a", size = 98535, upload-time = "2026-01-15T09:08:18.036Z" } wheels = [ @@ -354,6 +418,15 @@ version = "6.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, @@ -401,7 +474,8 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "commonmark" }, { name = "docutils" }, - { name = "sphinx" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1c/00/3dd2bdc4184b0ce754b5b446325abf45c2e0a347e022292ddc44670f628c/recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67", size = 34444, upload-time = "2020-12-17T19:24:56.523Z" } wheels = [ @@ -494,28 +568,62 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, ] +[[package]] +name = "sphinx" +version = "9.0.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version < '3.12'" }, + { name = "babel", marker = "python_full_version < '3.12'" }, + { name = "colorama", marker = "python_full_version < '3.12' and sys_platform == 'win32'" }, + { name = "docutils", marker = "python_full_version < '3.12'" }, + { name = "imagesize", marker = "python_full_version < '3.12'" }, + { name = "jinja2", marker = "python_full_version < '3.12'" }, + { name = "packaging", marker = "python_full_version < '3.12'" }, + { name = "pygments", marker = "python_full_version < '3.12'" }, + { name = "requests", marker = "python_full_version < '3.12'" }, + { name = "roman-numerals", marker = "python_full_version < '3.12'" }, + { name = "snowballstemmer", marker = "python_full_version < '3.12'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.12'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.12'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.12'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.12'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.12'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/50/a8c6ccc36d5eacdfd7913ddccd15a9cee03ecafc5ee2bc40e1f168d85022/sphinx-9.0.4.tar.gz", hash = "sha256:594ef59d042972abbc581d8baa577404abe4e6c3b04ef61bd7fc2acbd51f3fa3", size = 8710502, upload-time = "2025-12-04T07:45:27.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/3f/4bbd76424c393caead2e1eb89777f575dee5c8653e2d4b6afd7a564f5974/sphinx-9.0.4-py3-none-any.whl", hash = "sha256:5bebc595a5e943ea248b99c13814c1c5e10b3ece718976824ffa7959ff95fffb", size = 3917713, upload-time = "2025-12-04T07:45:24.944Z" }, +] + [[package]] name = "sphinx" version = "9.1.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", +] dependencies = [ - { name = "alabaster" }, - { name = "babel" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "docutils" }, - { name = "imagesize" }, - { name = "jinja2" }, - { name = "packaging" }, - { name = "pygments" }, - { name = "requests" }, - { name = "roman-numerals" }, - { name = "snowballstemmer" }, - { name = "sphinxcontrib-applehelp" }, - { name = "sphinxcontrib-devhelp" }, - { name = "sphinxcontrib-htmlhelp" }, - { name = "sphinxcontrib-jsmath" }, - { name = "sphinxcontrib-qthelp" }, - { name = "sphinxcontrib-serializinghtml" }, + { name = "alabaster", marker = "python_full_version >= '3.12'" }, + { name = "babel", marker = "python_full_version >= '3.12'" }, + { name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, + { name = "docutils", marker = "python_full_version >= '3.12'" }, + { name = "imagesize", marker = "python_full_version >= '3.12'" }, + { name = "jinja2", marker = "python_full_version >= '3.12'" }, + { name = "packaging", marker = "python_full_version >= '3.12'" }, + { name = "pygments", marker = "python_full_version >= '3.12'" }, + { name = "requests", marker = "python_full_version >= '3.12'" }, + { name = "roman-numerals", marker = "python_full_version >= '3.12'" }, + { name = "snowballstemmer", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } wheels = [ @@ -528,7 +636,8 @@ version = "2025.8.25" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama" }, - { name = "sphinx" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "starlette" }, { name = "uvicorn" }, { name = "watchfiles" }, @@ -544,7 +653,8 @@ name = "sphinx-collapse" version = "0.1.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "sphinx" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/14/a1/cb5bb03a5081bd1229b3296c2af347b4147017fdb62777d2aad855cd349f/sphinx_collapse-0.1.4.tar.gz", hash = "sha256:ba860e50839c026cd1abcc164e1e7cb18bcc11c8214150e34a6550461be3229f", size = 19412, upload-time = "2026-02-27T17:47:24.191Z" } wheels = [ @@ -556,50 +666,21 @@ name = "sphinx-copybutton" version = "0.5.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "sphinx" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fc/2b/a964715e7f5295f77509e59309959f4125122d648f86b4fe7d70ca1d882c/sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd", size = 23039, upload-time = "2023-04-14T08:10:22.998Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9e/48/1ea60e74949eecb12cdd6ac43987f9fd331156388dcc2319b45e2ebb81bf/sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e", size = 13343, upload-time = "2023-04-14T08:10:20.844Z" }, ] -[[package]] -name = "sphinx-docs" -version = "0.1.0" -source = { virtual = "." } -dependencies = [ - { name = "myst-parser" }, - { name = "pygments" }, - { name = "redirects-cli" }, - { name = "sphinx" }, - { name = "sphinx-autobuild" }, - { name = "sphinx-multiversion-scylla" }, - { name = "sphinx-scylladb-markdown" }, - { name = "sphinx-scylladb-theme" }, - { name = "sphinx-sitemap" }, - { name = "wheel" }, -] - -[package.metadata] -requires-dist = [ - { name = "myst-parser", specifier = ">=5.0.0" }, - { name = "pygments", specifier = ">=2.18.0" }, - { name = "redirects-cli", specifier = ">=0.1.3" }, - { name = "sphinx", specifier = ">=9.0" }, - { name = "sphinx-autobuild", specifier = ">=2025.4.8" }, - { name = "sphinx-multiversion-scylla", specifier = ">=0.3.7" }, - { name = "sphinx-scylladb-markdown", specifier = ">=0.1.2" }, - { name = "sphinx-scylladb-theme", specifier = ">=1.9.1" }, - { name = "sphinx-sitemap", specifier = ">=2.9.0" }, - { name = "wheel", specifier = ">=0.45.0" }, -] - [[package]] name = "sphinx-last-updated-by-git" version = "0.3.8" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "sphinx" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/03/fd/de1685b6dab173dff31da24e0d3b29f02873fc24a1cdbb7678721ddc8581/sphinx_last_updated_by_git-0.3.8.tar.gz", hash = "sha256:c145011f4609d841805b69a9300099fc02fed8f5bb9e5bcef77d97aea97b7761", size = 10785, upload-time = "2024-08-11T07:15:54.601Z" } wheels = [ @@ -623,7 +704,8 @@ name = "sphinx-multiversion-scylla" version = "0.3.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "sphinx" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/55/b1/83fb37f6c9038469b3bd01453875bb2127b3c03f9f41247394ad2063645c/sphinx_multiversion_scylla-0.3.7.tar.gz", hash = "sha256:fc1ddd58e82cfd8810c1be6db8717a244043c04c1c632e9bd1436415d1db0d3b", size = 12665, upload-time = "2026-02-27T18:43:17.849Z" } wheels = [ @@ -635,7 +717,8 @@ name = "sphinx-notfound-page" version = "1.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "sphinx" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6a/b2/67603444a8ee97b4a8ea71b0a9d6bab1727ed65e362c87e02f818ee57b8a/sphinx_notfound_page-1.1.0.tar.gz", hash = "sha256:913e1754370bb3db201d9300d458a8b8b5fb22e9246a816643a819a9ea2b8067", size = 7392, upload-time = "2025-01-28T18:45:02.871Z" } wheels = [ @@ -649,7 +732,8 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "myst-parser" }, { name = "recommonmark" }, - { name = "sphinx" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "sphinx-markdown-tables" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f1/6d/8cc78e960668b0c70d99777b20c5bff1163ab26dc1fce76621f80e20e314/sphinx_scylladb_markdown-0.1.4.tar.gz", hash = "sha256:9db3ae0dcf7c3519262da65e48c7f9e4db0ad1ce9c5f874864ea218f4cbc4c68", size = 6751, upload-time = "2025-12-12T18:03:57.879Z" } @@ -697,7 +781,8 @@ dependencies = [ { name = "beartype" }, { name = "docutils" }, { name = "myst-parser" }, - { name = "sphinx" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ca/3e/a82aa5fed0d06161a89dc2f6971b160f837cad44f196c467fc6b2132acaa/sphinx_substitution_extensions-2026.1.12.tar.gz", hash = "sha256:25e0c6c40fbf9e1df593883da946879044a3bf8d85652c8c58f354a53575d736", size = 31676, upload-time = "2026-01-12T06:19:35.324Z" } wheels = [ @@ -711,7 +796,8 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "docutils" }, { name = "pygments" }, - { name = "sphinx" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ce/30/ca5b0de830f369968d8e3483dd45a8908fd10169c05cd9837f0bd075982e/sphinx_tabs-3.5.0.tar.gz", hash = "sha256:91dba1187e4c35fd37380a56ac228bbd54c6c649b2351829f3bf033718277537", size = 17006, upload-time = "2026-03-03T23:00:30.404Z" } wheels = [ @@ -761,7 +847,8 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jinja2" }, { name = "pyyaml" }, - { name = "sphinx" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2b/ae/999891de292919b66ea34f2c22fc22c9be90ab3536fbc0fca95716277351/sphinxcontrib_mermaid-2.0.1.tar.gz", hash = "sha256:a21a385a059a6cafd192aa3a586b14bf5c42721e229db67b459dc825d7f0a497", size = 19839, upload-time = "2026-03-05T14:10:41.901Z" } wheels = [ @@ -854,6 +941,19 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/f8/2c5f479fb531ce2f0564eda479faecf253d886b1ab3630a39b7bf7362d46/watchfiles-1.1.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f57b396167a2565a4e8b5e56a5a1c537571733992b226f4f1197d79e94cf0ae5", size = 406529, upload-time = "2025-10-14T15:04:32.899Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cd/f515660b1f32f65df671ddf6f85bfaca621aee177712874dc30a97397977/watchfiles-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:421e29339983e1bebc281fab40d812742268ad057db4aee8c4d2bce0af43b741", size = 394384, upload-time = "2025-10-14T15:04:33.761Z" }, + { url = "https://files.pythonhosted.org/packages/7b/c3/28b7dc99733eab43fca2d10f55c86e03bd6ab11ca31b802abac26b23d161/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e43d39a741e972bab5d8100b5cdacf69db64e34eb19b6e9af162bccf63c5cc6", size = 448789, upload-time = "2025-10-14T15:04:34.679Z" }, + { url = "https://files.pythonhosted.org/packages/4a/24/33e71113b320030011c8e4316ccca04194bf0cbbaeee207f00cbc7d6b9f5/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f537afb3276d12814082a2e9b242bdcf416c2e8fd9f799a737990a1dbe906e5b", size = 460521, upload-time = "2025-10-14T15:04:35.963Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c3/3c9a55f255aa57b91579ae9e98c88704955fa9dac3e5614fb378291155df/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2cd9e04277e756a2e2d2543d65d1e2166d6fd4c9b183f8808634fda23f17b14", size = 488722, upload-time = "2025-10-14T15:04:37.091Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/506447b73eb46c120169dc1717fe2eff07c234bb3232a7200b5f5bd816e9/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f3f58818dc0b07f7d9aa7fe9eb1037aecb9700e63e1f6acfed13e9fef648f5d", size = 596088, upload-time = "2025-10-14T15:04:38.39Z" }, + { url = "https://files.pythonhosted.org/packages/82/ab/5f39e752a9838ec4d52e9b87c1e80f1ee3ccdbe92e183c15b6577ab9de16/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb9f66367023ae783551042d31b1d7fd422e8289eedd91f26754a66f44d5cff", size = 472923, upload-time = "2025-10-14T15:04:39.666Z" }, + { url = "https://files.pythonhosted.org/packages/af/b9/a419292f05e302dea372fa7e6fda5178a92998411f8581b9830d28fb9edb/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aebfd0861a83e6c3d1110b78ad54704486555246e542be3e2bb94195eabb2606", size = 456080, upload-time = "2025-10-14T15:04:40.643Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c3/d5932fd62bde1a30c36e10c409dc5d54506726f08cb3e1d8d0ba5e2bc8db/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5fac835b4ab3c6487b5dbad78c4b3724e26bcc468e886f8ba8cc4306f68f6701", size = 629432, upload-time = "2025-10-14T15:04:41.789Z" }, + { url = "https://files.pythonhosted.org/packages/f7/77/16bddd9779fafb795f1a94319dc965209c5641db5bf1edbbccace6d1b3c0/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:399600947b170270e80134ac854e21b3ccdefa11a9529a3decc1327088180f10", size = 623046, upload-time = "2025-10-14T15:04:42.718Z" }, + { url = "https://files.pythonhosted.org/packages/46/ef/f2ecb9a0f342b4bfad13a2787155c6ee7ce792140eac63a34676a2feeef2/watchfiles-1.1.1-cp311-cp311-win32.whl", hash = "sha256:de6da501c883f58ad50db3a32ad397b09ad29865b5f26f64c24d3e3281685849", size = 271473, upload-time = "2025-10-14T15:04:43.624Z" }, + { url = "https://files.pythonhosted.org/packages/94/bc/f42d71125f19731ea435c3948cad148d31a64fccde3867e5ba4edee901f9/watchfiles-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:35c53bd62a0b885bf653ebf6b700d1bf05debb78ad9292cf2a942b23513dc4c4", size = 287598, upload-time = "2025-10-14T15:04:44.516Z" }, + { url = "https://files.pythonhosted.org/packages/57/c9/a30f897351f95bbbfb6abcadafbaca711ce1162f4db95fc908c98a9165f3/watchfiles-1.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:57ca5281a8b5e27593cb7d82c2ac927ad88a96ed406aa446f6344e4328208e9e", size = 277210, upload-time = "2025-10-14T15:04:45.883Z" }, { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745, upload-time = "2025-10-14T15:04:46.731Z" }, { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769, upload-time = "2025-10-14T15:04:48.003Z" }, { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374, upload-time = "2025-10-14T15:04:49.179Z" }, @@ -913,6 +1013,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" }, { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" }, { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8e/e500f8b0b77be4ff753ac94dc06b33d8f0d839377fee1b78e8c8d8f031bf/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:db476ab59b6765134de1d4fe96a1a9c96ddf091683599be0f26147ea1b2e4b88", size = 408250, upload-time = "2025-10-14T15:06:10.264Z" }, + { url = "https://files.pythonhosted.org/packages/bd/95/615e72cd27b85b61eec764a5ca51bd94d40b5adea5ff47567d9ebc4d275a/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89eef07eee5e9d1fda06e38822ad167a044153457e6fd997f8a858ab7564a336", size = 396117, upload-time = "2025-10-14T15:06:11.28Z" }, + { url = "https://files.pythonhosted.org/packages/c9/81/e7fe958ce8a7fb5c73cc9fb07f5aeaf755e6aa72498c57d760af760c91f8/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce19e06cbda693e9e7686358af9cd6f5d61312ab8b00488bc36f5aabbaf77e24", size = 450493, upload-time = "2025-10-14T15:06:12.321Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d4/ed38dd3b1767193de971e694aa544356e63353c33a85d948166b5ff58b9e/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e6f39af2eab0118338902798b5aa6664f46ff66bc0280de76fca67a7f262a49", size = 457546, upload-time = "2025-10-14T15:06:13.372Z" }, ] [[package]] @@ -921,6 +1025,15 @@ version = "16.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/db/de907251b4ff46ae804ad0409809504153b3f30984daf82a1d84a9875830/websockets-16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31a52addea25187bde0797a97d6fc3d2f92b6f72a9370792d65a6e84615ac8a8", size = 177340, upload-time = "2026-01-10T09:22:34.539Z" }, + { url = "https://files.pythonhosted.org/packages/f3/fa/abe89019d8d8815c8781e90d697dec52523fb8ebe308bf11664e8de1877e/websockets-16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:417b28978cdccab24f46400586d128366313e8a96312e4b9362a4af504f3bbad", size = 175022, upload-time = "2026-01-10T09:22:36.332Z" }, + { url = "https://files.pythonhosted.org/packages/58/5d/88ea17ed1ded2079358b40d31d48abe90a73c9e5819dbcde1606e991e2ad/websockets-16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af80d74d4edfa3cb9ed973a0a5ba2b2a549371f8a741e0800cb07becdd20f23d", size = 175319, upload-time = "2026-01-10T09:22:37.602Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ae/0ee92b33087a33632f37a635e11e1d99d429d3d323329675a6022312aac2/websockets-16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:08d7af67b64d29823fed316505a89b86705f2b7981c07848fb5e3ea3020c1abe", size = 184631, upload-time = "2026-01-10T09:22:38.789Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c5/27178df583b6c5b31b29f526ba2da5e2f864ecc79c99dae630a85d68c304/websockets-16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be95cfb0a4dae143eaed2bcba8ac23f4892d8971311f1b06f3c6b78952ee70b", size = 185870, upload-time = "2026-01-10T09:22:39.893Z" }, + { url = "https://files.pythonhosted.org/packages/87/05/536652aa84ddc1c018dbb7e2c4cbcd0db884580bf8e95aece7593fde526f/websockets-16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d6297ce39ce5c2e6feb13c1a996a2ded3b6832155fcfc920265c76f24c7cceb5", size = 185361, upload-time = "2026-01-10T09:22:41.016Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e2/d5332c90da12b1e01f06fb1b85c50cfc489783076547415bf9f0a659ec19/websockets-16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c1b30e4f497b0b354057f3467f56244c603a79c0d1dafce1d16c283c25f6e64", size = 184615, upload-time = "2026-01-10T09:22:42.442Z" }, + { url = "https://files.pythonhosted.org/packages/77/fb/d3f9576691cae9253b51555f841bc6600bf0a983a461c79500ace5a5b364/websockets-16.0-cp311-cp311-win32.whl", hash = "sha256:5f451484aeb5cafee1ccf789b1b66f535409d038c56966d6101740c1614b86c6", size = 178246, upload-time = "2026-01-10T09:22:43.654Z" }, + { url = "https://files.pythonhosted.org/packages/54/67/eaff76b3dbaf18dcddabc3b8c1dba50b483761cccff67793897945b37408/websockets-16.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7f0659570eefb578dacde98e24fb60af35350193e4f56e11190787bee77dac", size = 178684, upload-time = "2026-01-10T09:22:44.941Z" }, { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" }, { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" }, { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" }, @@ -957,6 +1070,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, + { url = "https://files.pythonhosted.org/packages/72/07/c98a68571dcf256e74f1f816b8cc5eae6eb2d3d5cfa44d37f801619d9166/websockets-16.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:349f83cd6c9a415428ee1005cadb5c2c56f4389bc06a9af16103c3bc3dcc8b7d", size = 174947, upload-time = "2026-01-10T09:23:36.166Z" }, + { url = "https://files.pythonhosted.org/packages/7e/52/93e166a81e0305b33fe416338be92ae863563fe7bce446b0f687b9df5aea/websockets-16.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4a1aba3340a8dca8db6eb5a7986157f52eb9e436b74813764241981ca4888f03", size = 175260, upload-time = "2026-01-10T09:23:37.409Z" }, + { url = "https://files.pythonhosted.org/packages/56/0c/2dbf513bafd24889d33de2ff0368190a0e69f37bcfa19009ef819fe4d507/websockets-16.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f4a32d1bd841d4bcbffdcb3d2ce50c09c3909fbead375ab28d0181af89fd04da", size = 176071, upload-time = "2026-01-10T09:23:39.158Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8f/aea9c71cc92bf9b6cc0f7f70df8f0b420636b6c96ef4feee1e16f80f75dd/websockets-16.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0298d07ee155e2e9fda5be8a9042200dd2e3bb0b8a38482156576f863a9d457c", size = 176968, upload-time = "2026-01-10T09:23:41.031Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3f/f70e03f40ffc9a30d817eef7da1be72ee4956ba8d7255c399a01b135902a/websockets-16.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767", size = 178735, upload-time = "2026-01-10T09:23:42.259Z" }, { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, ] From 05da6c72238b7660a74725ee58dc61faa6488d7e Mon Sep 17 00:00:00 2001 From: Mikita Hradovich Date: Fri, 17 Apr 2026 23:16:07 +0200 Subject: [PATCH 04/32] Pin GitHub Actions to specific commit SHAs Replace all third-party action version tags (e.g. @v5) with immutable commit SHAs to reduce supply chain attack surface. Version tags are mutable and can be redirected to malicious code; SHAs are permanent. The pinned versions are: actions/checkout v5.0.1 93cb6efe18208431cddfb8368fd83d5badbf9bfd actions/setup-java v5.2.0 be666c2fcd27ec809703dec50e508c2fdc7f6654 actions/setup-python v6.2.0 a309ff8b426b58ec0e2a45f0f869d46889d02405 actions/cache (restore/save)v4.3.0 0057852bfaa89a56745cba8c7296529d2fc39830 actions/upload-artifact v4.6.2 ea165f8d65b6e75b540449e92b4886f43607fa02 astral-sh/setup-uv v6.8.0 d0cc045d04ccac9d8b7881df0226f9e82c39688e mikepenz/action-junit-report v5.6.2 3585e9575db828022551b4231f165eb59a0e74e3 scylladb/github-automation main 7b9848eb304fd3af1e757fe3c3c1ed497515f0fc Also adds a section to CONTRIBUTING.md explaining how to safely update pinned actions in the future (find SHA for tag, verify no impostor commit, update workflow files and repo settings allowlist). Closes: DRIVER-582 --- .github/workflows/call_jira_sync.yml | 2 +- .github/workflows/docs-pages.yml | 8 ++-- .github/workflows/docs-pr.yml | 8 ++-- .github/workflows/release.yml | 6 +-- .github/workflows/tests@v1.yml | 64 ++++++++++++++-------------- CONTRIBUTING.md | 28 ++++++++++++ 6 files changed, 72 insertions(+), 44 deletions(-) diff --git a/.github/workflows/call_jira_sync.yml b/.github/workflows/call_jira_sync.yml index 14f517df405..24753858e0a 100644 --- a/.github/workflows/call_jira_sync.yml +++ b/.github/workflows/call_jira_sync.yml @@ -11,7 +11,7 @@ permissions: jobs: jira-sync: - uses: scylladb/github-automation/.github/workflows/main_pr_events_jira_sync.yml@main + uses: scylladb/github-automation/.github/workflows/main_pr_events_jira_sync.yml@7b9848eb304fd3af1e757fe3c3c1ed497515f0fc # main with: caller_action: ${{ github.event.action }} secrets: diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml index 25d9eb6c66a..973da8bfaa2 100644 --- a/.github/workflows/docs-pages.yml +++ b/.github/workflows/docs-pages.yml @@ -26,24 +26,24 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.repository.default_branch }} persist-credentials: false fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.13' - name: Set up JDK 8.0 - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: java-version: '8' distribution: 'temurin' - name: Install uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 - name: Set up env run: make -C docs setupenv diff --git a/.github/workflows/docs-pr.yml b/.github/workflows/docs-pr.yml index fc3e0667ef8..58143e854f6 100644 --- a/.github/workflows/docs-pr.yml +++ b/.github/workflows/docs-pr.yml @@ -21,24 +21,24 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.13' - name: Set up JDK 8.0 - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: java-version: '8' distribution: 'temurin' - name: Install uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 - name: Set up env run: make -C docs setupenv diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8ff112f7f30..3a55f680e7e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Checkout Code One Commit Before ${{ inputs.target-tag }} if: inputs.target-tag != 'scylla-4.x' @@ -40,7 +40,7 @@ jobs: run: make checkout-one-commit-before - name: Set up Java - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: java-version: '8' distribution: 'temurin' @@ -80,7 +80,7 @@ jobs: run: make release-dry-run - name: Upload release logs - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: maven-stdout path: /tmp/java-driver-release-logs/*.log diff --git a/.github/workflows/tests@v1.yml b/.github/workflows/tests@v1.yml index fec1c39b1da..e146df623be 100644 --- a/.github/workflows/tests@v1.yml +++ b/.github/workflows/tests@v1.yml @@ -40,10 +40,10 @@ jobs: steps: - name: Checkout source - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Set up JDK ${{ matrix.java-version }} - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: java-version: ${{ matrix.java-version }} distribution: 'temurin' @@ -53,7 +53,7 @@ jobs: run: echo "value=${{ hashFiles('**/pom.xml') }}" >> "$GITHUB_OUTPUT" - name: Restore maven repository cache - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 id: java-cache with: path: ~/.m2/repository @@ -67,7 +67,7 @@ jobs: run: make download-all-dependencies - name: Save maven repository cache - uses: actions/cache/save@v4 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 if: steps.java-cache.outputs.cache-hit != 'true' with: path: ~/.m2/repository @@ -85,16 +85,16 @@ jobs: steps: - name: Checkout source - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Set up JDK ${{ matrix.java-version }} - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: java-version: ${{ matrix.java-version }} distribution: 'temurin' - name: Restore maven repository cache - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.m2/repository key: ${{ runner.os }}-${{ matrix.java-version }}-maven-${{ hashFiles('**/pom.xml') }} @@ -114,16 +114,16 @@ jobs: steps: - name: Checkout source - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Set up JDK 8 - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: java-version: ${{ matrix.java-version }} distribution: 'temurin' - name: Restore maven repository cache - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.m2/repository key: ${{ runner.os }}-${{ matrix.java-version }}-maven-${{ hashFiles('**/pom.xml') }} @@ -139,14 +139,14 @@ jobs: cp --parents ./**/target/*-reports/*.xml unit/ - name: Upload test results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 if: always() with: name: test-results path: "*/**/target/*-reports/*.xml" - name: Parse test results - uses: mikepenz/action-junit-report@v5 + uses: mikepenz/action-junit-report@3585e9575db828022551b4231f165eb59a0e74e3 # v5.6.2 if: always() with: check_name: Unit tests report @@ -164,10 +164,10 @@ jobs: steps: - name: Checkout source - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Setup Python 3 - uses: actions/setup-python@v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.13' @@ -186,22 +186,22 @@ jobs: steps: - name: Checkout source - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Set up JDK ${{ matrix.java-version }} - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: java-version: ${{ matrix.java-version }} distribution: 'temurin' - name: Restore maven repository cache - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.m2/repository key: ${{ runner.os }}-${{ matrix.java-version }}-maven-${{ hashFiles('**/pom.xml') }} - name: Setup Python 3 - uses: actions/setup-python@v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.13' @@ -215,7 +215,7 @@ jobs: run: make resolve-cassandra-version - name: Pull CCM image from the cache - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 id: ccm-cache with: path: ~/.ccm/repository @@ -229,7 +229,7 @@ jobs: - name: Save CCM image into the cache if: steps.ccm-cache.outputs.cache-hit != 'true' - uses: actions/cache/save@v4 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.ccm/repository key: ccm-cassandra-${{ runner.os }}-${{ steps.cassandra-version.outputs.value }} @@ -259,20 +259,20 @@ jobs: - name: Upload test results if: steps.run-integration-tests.outcome == 'failure' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: test-results-${{ matrix.java-version }}-${{ matrix.cassandra-version }}-${{ matrix.test-group }} path: "*/**/target/*-reports/*.xml" - name: Upload CCM logs - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 if: failure() with: name: ccm-log-cassandra-${{ matrix.java-version }}-${{ matrix.cassandra-version }}-${{ matrix.test-group }} path: /tmp/ccm*/ccm*/node*/logs/* - name: Parse test results - uses: mikepenz/action-junit-report@v5 + uses: mikepenz/action-junit-report@3585e9575db828022551b4231f165eb59a0e74e3 # v5.6.2 if: always() with: check_name: Integration tests report for Cassandra ${{ steps.cassandra-version.outputs.value }} (${{ matrix.test-group }}) @@ -298,22 +298,22 @@ jobs: steps: - name: Checkout source - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Set up JDK ${{ matrix.java-version }} - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: java-version: ${{ matrix.java-version }} distribution: 'temurin' - name: Restore maven repository cache - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.m2/repository key: ${{ runner.os }}-${{ matrix.java-version }}-maven-${{ hashFiles('**/pom.xml') }} - name: Setup Python 3 - uses: actions/setup-python@v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.13' @@ -327,7 +327,7 @@ jobs: run: make resolve-scylla-version - name: Pull CCM image from the cache - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 id: ccm-cache with: path: ~/.ccm/scylla-repository @@ -340,7 +340,7 @@ jobs: run: make download-scylla - name: Save CCM image into the cache - uses: actions/cache/save@v4 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 if: steps.ccm-cache.outputs.cache-hit != 'true' with: path: ~/.ccm/scylla-repository @@ -370,21 +370,21 @@ jobs: cp --parents ./**/target/*-reports/*.xml scylla-${{ matrix.scylla-version }}-${{ matrix.test-group }}/ - name: Upload test results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 if: steps.run-integration-tests.outcome == 'failure' with: name: test-results-${{ matrix.java-version }}-${{ matrix.scylla-version }}-${{ matrix.test-group }} path: "*/**/target/*-reports/*.xml" - name: Upload CCM logs - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 if: failure() with: name: ccm-log-scylla-${{ matrix.java-version }}-${{ matrix.scylla-version }}-${{ matrix.test-group }} path: /tmp/ccm*/ccm*/node*/logs/* - name: Parse test results - uses: mikepenz/action-junit-report@v5 + uses: mikepenz/action-junit-report@3585e9575db828022551b4231f165eb59a0e74e3 # v5.6.2 if: always() with: check_name: Integration tests report for Scylla ${{ steps.scylla-version.outputs.value }} (${{ matrix.test-group }}) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 460f0948b12..06f8321df59 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -565,3 +565,31 @@ relevant if there are intermediary rebases.) If you need new stuff from the base branch, it's fine to rebase and force-push, as long as you don't rewrite the history. Just give a heads up to the reviewers beforehand. Don't push a merge commit to a pull request. + +## Updating GitHub Actions workflows + +GitHub Actions workflows in this repository pin all third-party actions to specific commit SHAs +instead of mutable version tags (e.g. `@v5`). This is a supply chain security measure: tags can be +moved to point to different commits, but a SHA is immutable. + +The format used is: + +```yaml +uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 +``` + +There is no need to update workflow action versions on every release. Only do so when the current +version has a known vulnerability or when a new feature is needed. + +### How to update a pinned action + +1. Go to the action's GitHub repository (e.g. `github.com/actions/checkout`). +2. Navigate to the desired release tag (e.g. `v5.0.2`) via the Tags page. +3. Copy the full 40-character commit SHA from that tag's commit page. +4. Verify the commit is not an [impostor commit](https://www.chainguard.dev/unchained/what-the-fork-imposter-commits-in-github-actions-and-ci-cd): + open the commit on GitHub and ensure there is **no** banner saying + "This commit does not belong to any branch on this repository". +5. Replace the SHA and version comment in all workflow files. +6. Update the repository allowlist under + `Settings -> Actions -> General -> Allow or block specified actions and reusable workflows` + to include the new SHA. From 10c2b95e0b2537784c490352481110ea300b5b65 Mon Sep 17 00:00:00 2001 From: Mikita Hradovich Date: Wed, 22 Apr 2026 14:26:15 +0200 Subject: [PATCH 05/32] fix: replace SimpleStrategy with NetworkTopologyStrategy in integration tests ScyllaDB PR scylladb/scylladb#25342 changed ks_prop_defs::as_ks_metadata() so that tablets are now enabled by default for all replication strategies. SimpleStrategy does not support tablets and throws: InvalidConfigurationInQueryException: SimpleStrategy doesn't support tablet replication Replace all SimpleStrategy keyspace creation in integration tests with NetworkTopologyStrategy (datacenter1), which supports tablets. Files changed: - SessionUtils.createKeyspace(): central keyspace creator used by SessionRule (fixes the majority of test failures via cascade) - SchemaChangesIT: 3 CREATE + 1 ALTER CQL strings + updated assertion (NTS replication map has class + datacenter1 instead of class + replication_factor) - TokenITBase: 2 CREATE KEYSPACE (RF=1 and RF=2) - SimpleStatementCcmIT: 1 CREATE KEYSPACE - LWTLoadBalancingIT: 1 CREATE KEYSPACE (RF=3) - DefaultKeyspaceIT: 1 CREATE KEYSPACE (mapper test) - QueryKeyspaceAndTableIT: 1 CREATE KEYSPACE (mapper test) - MailboxServiceImpl: 1 CREATE KEYSPACE (osgi test) --- .../api/core/auth/DseProxyAuthenticationIT.java | 2 +- .../metadata/schema/KeyspaceGraphMetadataIT.java | 8 ++++---- .../oss/driver/core/cql/SimpleStatementCcmIT.java | 2 +- .../core/loadbalancing/LWTLoadBalancingIT.java | 3 ++- .../oss/driver/core/metadata/SchemaChangesIT.java | 13 ++++++------- .../oss/driver/core/metadata/TokenITBase.java | 4 ++-- .../oss/driver/mapper/DefaultKeyspaceIT.java | 2 +- .../oss/driver/mapper/QueryKeyspaceAndTableIT.java | 2 +- .../internal/osgi/service/MailboxServiceImpl.java | 2 +- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/integration-tests/src/test/java/com/datastax/dse/driver/api/core/auth/DseProxyAuthenticationIT.java b/integration-tests/src/test/java/com/datastax/dse/driver/api/core/auth/DseProxyAuthenticationIT.java index 73a4e74f964..4d43c75ed28 100644 --- a/integration-tests/src/test/java/com/datastax/dse/driver/api/core/auth/DseProxyAuthenticationIT.java +++ b/integration-tests/src/test/java/com/datastax/dse/driver/api/core/auth/DseProxyAuthenticationIT.java @@ -71,7 +71,7 @@ public void setupRoles() { session.execute( "CREATE ROLE IF NOT EXISTS steve WITH PASSWORD = 'fakePasswordForSteve' AND LOGIN = TRUE"); session.execute( - "CREATE KEYSPACE IF NOT EXISTS aliceks WITH REPLICATION = {'class':'SimpleStrategy', 'replication_factor':'1'}"); + "CREATE KEYSPACE IF NOT EXISTS aliceks WITH REPLICATION = {'class':'NetworkTopologyStrategy', 'dc1':'1'}"); session.execute( "CREATE TABLE IF NOT EXISTS aliceks.alicetable (key text PRIMARY KEY, value text)"); session.execute( diff --git a/integration-tests/src/test/java/com/datastax/dse/driver/api/core/metadata/schema/KeyspaceGraphMetadataIT.java b/integration-tests/src/test/java/com/datastax/dse/driver/api/core/metadata/schema/KeyspaceGraphMetadataIT.java index dc96b265140..49aba3adb40 100644 --- a/integration-tests/src/test/java/com/datastax/dse/driver/api/core/metadata/schema/KeyspaceGraphMetadataIT.java +++ b/integration-tests/src/test/java/com/datastax/dse/driver/api/core/metadata/schema/KeyspaceGraphMetadataIT.java @@ -51,7 +51,7 @@ public void should_expose_graph_engine_if_set() { CqlSession session = SESSION_RULE.session(); session.execute( "CREATE KEYSPACE keyspace_metadata_it_graph_engine " - + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1} " + + "WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1': 1} " + "AND graph_engine = 'Core'"); Metadata metadata = session.getMetadata(); assertThat(metadata.getKeyspace("keyspace_metadata_it_graph_engine")) @@ -66,7 +66,7 @@ public void should_expose_graph_engine_if_keyspace_altered() { CqlSession session = SESSION_RULE.session(); session.execute( "CREATE KEYSPACE keyspace_metadata_it_graph_engine_alter " - + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}"); + + "WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1': 1}"); assertThat(session.getMetadata().getKeyspace("keyspace_metadata_it_graph_engine_alter")) .hasValueSatisfying( keyspaceMetadata -> @@ -89,7 +89,7 @@ public void should_not_allow_classic_graph_engine_to_be_specified_on_keyspace() () -> session.execute( "CREATE KEYSPACE keyspace_metadata_it_graph_engine_classic " - + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1} " + + "WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1': 1} " + "AND graph_engine = 'Classic'")) .hasMessageContaining("Invalid/unknown graph engine name 'Classic'"); } @@ -99,7 +99,7 @@ public void should_expose_core_graph_engine_if_set() { CqlSession session = SESSION_RULE.session(); session.execute( "CREATE KEYSPACE keyspace_metadata_it_graph_engine_core " - + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1} " + + "WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1': 1} " + "AND graph_engine = 'Core'"); Metadata metadata = session.getMetadata(); assertThat(metadata.getKeyspace("keyspace_metadata_it_graph_engine_core")) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/core/cql/SimpleStatementCcmIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/core/cql/SimpleStatementCcmIT.java index 1d02c42b522..8c1f8212b06 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/core/cql/SimpleStatementCcmIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/core/cql/SimpleStatementCcmIT.java @@ -375,7 +375,7 @@ public void should_not_fail_on_empty_pages() { SESSION_RULE .session() .execute( - "CREATE KEYSPACE IF NOT EXISTS ks WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor': 1}"); + "CREATE KEYSPACE IF NOT EXISTS ks WITH REPLICATION = {'class': 'NetworkTopologyStrategy', 'dc1': 1}"); SESSION_RULE .session() diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/core/loadbalancing/LWTLoadBalancingIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/core/loadbalancing/LWTLoadBalancingIT.java index 0586b3236bb..062ca4f2f7e 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/core/loadbalancing/LWTLoadBalancingIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/core/loadbalancing/LWTLoadBalancingIT.java @@ -72,7 +72,8 @@ public static void setup() { session.execute( "CREATE KEYSPACE " + keyspace.asCql(false) - + " WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 3}"); + + " WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1': 3}" + + " AND tablets = {'enabled': false}"); session.execute("USE " + keyspace.asCql(false)); session.execute("CREATE TABLE foo (pk int, ck int, v int, PRIMARY KEY (pk, ck))"); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/core/metadata/SchemaChangesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/core/metadata/SchemaChangesIT.java index a03320f2616..79cc53e5695 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/core/metadata/SchemaChangesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/core/metadata/SchemaChangesIT.java @@ -99,16 +99,15 @@ public void should_handle_keyspace_creation() { null, String.format( "CREATE KEYSPACE %s " - + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}", + + "WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1': 1}", newKeyspaceId), metadata -> metadata.getKeyspace(newKeyspaceId), keyspace -> { assertThat(keyspace.getName()).isEqualTo(newKeyspaceId); assertThat(keyspace.isDurableWrites()).isTrue(); assertThat(keyspace.getReplication()) - .hasSize(2) - .containsEntry("class", "org.apache.cassandra.locator.SimpleStrategy") - .containsEntry("replication_factor", "1"); + .containsEntry("class", "org.apache.cassandra.locator.NetworkTopologyStrategy") + .containsEntry("dc1", "1"); }, (listener, keyspace) -> verify(listener).onKeyspaceCreated(keyspace), newKeyspaceId); @@ -121,7 +120,7 @@ public void should_handle_keyspace_drop() { ImmutableList.of( String.format( "CREATE KEYSPACE %s " - + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}", + + "WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1': 1}", newKeyspaceId.asCql(true))), String.format("DROP KEYSPACE %s", newKeyspaceId.asCql(true)), metadata -> metadata.getKeyspace(newKeyspaceId), @@ -136,11 +135,11 @@ public void should_handle_keyspace_update() { ImmutableList.of( String.format( "CREATE KEYSPACE %s " - + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}", + + "WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1': 1}", newKeyspaceId.asCql(true))), String.format( "ALTER KEYSPACE %s " - + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1} " + + "WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1': 1} " + "AND durable_writes = 'false'", newKeyspaceId.asCql(true)), metadata -> metadata.getKeyspace(newKeyspaceId), diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/core/metadata/TokenITBase.java b/integration-tests/src/test/java/com/datastax/oss/driver/core/metadata/TokenITBase.java index c97a2279efd..21f678fc268 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/core/metadata/TokenITBase.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/core/metadata/TokenITBase.java @@ -56,10 +56,10 @@ protected static void createSchema(CqlSession session) { for (String statement : ImmutableList.of( String.format( - "CREATE KEYSPACE %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}", + "CREATE KEYSPACE %s WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1': 1}", KS1.asCql(false)), String.format( - "CREATE KEYSPACE %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 2}", + "CREATE KEYSPACE %s WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1': 2}", KS2.asCql(false)), // Shouldn't really do that, but it makes the rest of the tests a bit prettier. diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/mapper/DefaultKeyspaceIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/mapper/DefaultKeyspaceIT.java index 30a808e87a9..512da133f7c 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/mapper/DefaultKeyspaceIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/mapper/DefaultKeyspaceIT.java @@ -75,7 +75,7 @@ public static void setup() { session.execute( SimpleStatement.builder( String.format( - "CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}", + "CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1': 1}", DEFAULT_KEYSPACE)) .setExecutionProfile(SESSION_RULE.slowProfile()) .build()); diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/mapper/QueryKeyspaceAndTableIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/mapper/QueryKeyspaceAndTableIT.java index 9391c0363f8..5a8e11ecd8d 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/mapper/QueryKeyspaceAndTableIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/mapper/QueryKeyspaceAndTableIT.java @@ -67,7 +67,7 @@ public static void createSchema() { ImmutableList.of( "CREATE TABLE foo(k int PRIMARY KEY)", String.format( - "CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}", + "CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1': 1}", OTHER_KEYSPACE.asCql(false)), String.format("CREATE TABLE %s.foo(k int PRIMARY KEY)", OTHER_KEYSPACE.asCql(false)))) { session.execute( diff --git a/osgi-tests/src/main/java/com/datastax/oss/driver/internal/osgi/service/MailboxServiceImpl.java b/osgi-tests/src/main/java/com/datastax/oss/driver/internal/osgi/service/MailboxServiceImpl.java index 1da97d7d611..91a135c8448 100644 --- a/osgi-tests/src/main/java/com/datastax/oss/driver/internal/osgi/service/MailboxServiceImpl.java +++ b/osgi-tests/src/main/java/com/datastax/oss/driver/internal/osgi/service/MailboxServiceImpl.java @@ -69,7 +69,7 @@ public synchronized void init() { protected void createSchema() { session.execute("DROP KEYSPACE IF EXISTS test_osgi"); session.execute( - "CREATE KEYSPACE IF NOT EXISTS test_osgi with replication = {'class': 'SimpleStrategy', 'replication_factor' : 1}"); + "CREATE KEYSPACE IF NOT EXISTS test_osgi with replication = {'class': 'NetworkTopologyStrategy', 'dc1' : 1}"); session.execute( "CREATE TABLE " + keyspace From bd9b26aec411adac25ef8a07459475c82d87258f Mon Sep 17 00:00:00 2001 From: Mikita Hradovich Date: Thu, 7 May 2026 14:55:55 +0200 Subject: [PATCH 06/32] fix: use unique partition keys in QueryReturnTypesIT to avoid LWT contention --- .../oss/driver/mapper/QueryReturnTypesIT.java | 72 ++++++++++--------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/mapper/QueryReturnTypesIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/mapper/QueryReturnTypesIT.java index c6e90912206..18dbc060bac 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/mapper/QueryReturnTypesIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/mapper/QueryReturnTypesIT.java @@ -50,6 +50,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; import org.junit.Before; import org.junit.BeforeClass; @@ -71,6 +72,10 @@ public class QueryReturnTypesIT { private static TestDao dao; + private static final AtomicInteger UNIQUE_ID = new AtomicInteger(); + + private int testId; + @BeforeClass public static void createSchema() { CqlSession session = SESSION_RULE.session(); @@ -87,38 +92,39 @@ public static void createSchema() { @Before public void insertData() { + testId = UNIQUE_ID.getAndIncrement(); for (int i = 0; i < 10; i++) { - dao.insert(new TestEntity(1, i, i)); + dao.insert(new TestEntity(testId, i, i)); } } @Test public void should_execute_query_and_map_to_void() { - dao.delete(1, 1); - assertThat(dao.findByIdAndRank(1, 1)).isNull(); + dao.delete(testId, 1); + assertThat(dao.findByIdAndRank(testId, 1)).isNull(); } @Test public void should_execute_async_query_and_map_to_void() { - CompletableFutures.getUninterruptibly(dao.deleteAsync(1, 1).toCompletableFuture()); - assertThat(dao.findByIdAndRank(1, 1)).isNull(); + CompletableFutures.getUninterruptibly(dao.deleteAsync(testId, 1).toCompletableFuture()); + assertThat(dao.findByIdAndRank(testId, 1)).isNull(); } @Test public void should_execute_conditional_query_and_map_to_boolean() { - assertThat(dao.deleteIfExists(1, 1)).isTrue(); - assertThat(dao.deleteIfExists(1, 1)).isFalse(); + assertThat(dao.deleteIfExists(testId, 1)).isTrue(); + assertThat(dao.deleteIfExists(testId, 1)).isFalse(); } @Test public void should_execute_async_conditional_query_and_map_to_boolean() { - assertThat(CompletableFutures.getUninterruptibly(dao.deleteIfExistsAsync(1, 1))).isTrue(); - assertThat(CompletableFutures.getUninterruptibly(dao.deleteIfExistsAsync(1, 1))).isFalse(); + assertThat(CompletableFutures.getUninterruptibly(dao.deleteIfExistsAsync(testId, 1))).isTrue(); + assertThat(CompletableFutures.getUninterruptibly(dao.deleteIfExistsAsync(testId, 1))).isFalse(); } @Test public void should_execute_count_query_and_map_to_long() { - assertThat(dao.countById(1)).isEqualTo(10); + assertThat(dao.countById(testId)).isEqualTo(10); } @Test @@ -133,111 +139,111 @@ public void should_fail_to_map_to_long_if_query_returns_other_type() { @Test public void should_execute_async_count_query_and_map_to_long() { - assertThat(CompletableFutures.getUninterruptibly(dao.countByIdAsync(1))).isEqualTo(10); + assertThat(CompletableFutures.getUninterruptibly(dao.countByIdAsync(testId))).isEqualTo(10); } @Test public void should_execute_query_and_map_to_row() { - Row row = dao.findRowByIdAndRank(1, 1); + Row row = dao.findRowByIdAndRank(testId, 1); assertThat(row).isNotNull(); assertThat(row.getColumnDefinitions().size()).isEqualTo(3); - assertThat(row.getInt("id")).isEqualTo(1); + assertThat(row.getInt("id")).isEqualTo(testId); assertThat(row.getInt("rank")).isEqualTo(1); assertThat(row.getInt("value")).isEqualTo(1); } @Test public void should_execute_async_query_and_map_to_row() { - Row row = CompletableFutures.getUninterruptibly(dao.findRowByIdAndRankAsync(1, 1)); + Row row = CompletableFutures.getUninterruptibly(dao.findRowByIdAndRankAsync(testId, 1)); assertThat(row).isNotNull(); assertThat(row.getColumnDefinitions().size()).isEqualTo(3); - assertThat(row.getInt("id")).isEqualTo(1); + assertThat(row.getInt("id")).isEqualTo(testId); assertThat(row.getInt("rank")).isEqualTo(1); assertThat(row.getInt("value")).isEqualTo(1); } @Test public void should_execute_query_and_map_to_result_set() { - ResultSet resultSet = dao.findRowsById(1); + ResultSet resultSet = dao.findRowsById(testId); assertThat(resultSet.all()).hasSize(10); } @Test public void should_execute_async_query_and_map_to_result_set() { - AsyncResultSet resultSet = CompletableFutures.getUninterruptibly(dao.findRowsByIdAsync(1)); + AsyncResultSet resultSet = CompletableFutures.getUninterruptibly(dao.findRowsByIdAsync(testId)); assertThat(ImmutableList.copyOf(resultSet.currentPage())).hasSize(10); assertThat(resultSet.hasMorePages()).isFalse(); } @Test public void should_execute_query_and_map_to_entity() { - TestEntity entity = dao.findByIdAndRank(1, 1); - assertThat(entity.getId()).isEqualTo(1); + TestEntity entity = dao.findByIdAndRank(testId, 1); + assertThat(entity.getId()).isEqualTo(testId); assertThat(entity.getRank()).isEqualTo(1); assertThat(entity.getValue()).isEqualTo(1); - entity = dao.findByIdAndRank(2, 1); + entity = dao.findByIdAndRank(-(testId + 1), 1); assertThat(entity).isNull(); } @Test public void should_execute_async_query_and_map_to_entity() { - TestEntity entity = CompletableFutures.getUninterruptibly(dao.findByIdAndRankAsync(1, 1)); - assertThat(entity.getId()).isEqualTo(1); + TestEntity entity = CompletableFutures.getUninterruptibly(dao.findByIdAndRankAsync(testId, 1)); + assertThat(entity.getId()).isEqualTo(testId); assertThat(entity.getRank()).isEqualTo(1); assertThat(entity.getValue()).isEqualTo(1); - entity = dao.findByIdAndRank(2, 1); + entity = dao.findByIdAndRank(-(testId + 1), 1); assertThat(entity).isNull(); } @Test public void should_execute_query_and_map_to_optional_entity() { - Optional maybeEntity = dao.findOptionalByIdAndRank(1, 1); + Optional maybeEntity = dao.findOptionalByIdAndRank(testId, 1); assertThat(maybeEntity) .hasValueSatisfying( entity -> { - assertThat(entity.getId()).isEqualTo(1); + assertThat(entity.getId()).isEqualTo(testId); assertThat(entity.getRank()).isEqualTo(1); assertThat(entity.getValue()).isEqualTo(1); }); - maybeEntity = dao.findOptionalByIdAndRank(2, 1); + maybeEntity = dao.findOptionalByIdAndRank(-(testId + 1), 1); assertThat(maybeEntity).isEmpty(); } @Test public void should_execute_async_query_and_map_to_optional_entity() { Optional maybeEntity = - CompletableFutures.getUninterruptibly(dao.findOptionalByIdAndRankAsync(1, 1)); + CompletableFutures.getUninterruptibly(dao.findOptionalByIdAndRankAsync(testId, 1)); assertThat(maybeEntity) .hasValueSatisfying( entity -> { - assertThat(entity.getId()).isEqualTo(1); + assertThat(entity.getId()).isEqualTo(testId); assertThat(entity.getRank()).isEqualTo(1); assertThat(entity.getValue()).isEqualTo(1); }); - maybeEntity = dao.findOptionalByIdAndRank(2, 1); + maybeEntity = dao.findOptionalByIdAndRank(-(testId + 1), 1); assertThat(maybeEntity).isEmpty(); } @Test public void should_execute_query_and_map_to_iterable() { - PagingIterable iterable = dao.findById(1); + PagingIterable iterable = dao.findById(testId); assertThat(iterable.all()).hasSize(10); } @Test public void should_execute_query_and_map_to_stream() { - Stream stream = dao.findByIdAsStream(1); + Stream stream = dao.findByIdAsStream(testId); assertThat(stream).hasSize(10); } @Test public void should_execute_async_query_and_map_to_iterable() { MappedAsyncPagingIterable iterable = - CompletableFutures.getUninterruptibly(dao.findByIdAsync(1)); + CompletableFutures.getUninterruptibly(dao.findByIdAsync(testId)); assertThat(ImmutableList.copyOf(iterable.currentPage())).hasSize(10); assertThat(iterable.hasMorePages()).isFalse(); } @@ -245,7 +251,7 @@ public void should_execute_async_query_and_map_to_iterable() { @Test public void should_execute_query_and_map_to_stream_async() throws ExecutionException, InterruptedException { - CompletableFuture> stream = dao.findByIdAsStreamAsync(1); + CompletableFuture> stream = dao.findByIdAsStreamAsync(testId); assertThat(stream.get()).hasSize(10); } From 441eb422b409b5c9ea3e95a1959bef06bb3114a9 Mon Sep 17 00:00:00 2001 From: Mikita Hradovich Date: Mon, 11 May 2026 15:58:25 +0200 Subject: [PATCH 07/32] fix: enrich DriverTimeoutException with per-node pool/channel diagnostics (DRIVER-540) Add NodeDiagnostics public inner class to DriverTimeoutException with fields for in-flight counts and pool capacity, and generate a diagnostic suffix in the exception message at timeout time. Refactor all four request-handler timeout paths (CqlRequestHandler, CqlPrepareHandler, GraphRequestHandler, ContinuousRequestHandlerBase) to build List instead of a raw message string. Update IT assertions that matched exact message strings to use hasMessageStartingWith() to accommodate the new suffix. --- .../ContinuousRequestHandlerBase.java | 39 +++- .../core/graph/GraphRequestHandler.java | 37 +++- .../api/core/DriverTimeoutException.java | 209 +++++++++++++++++- .../internal/core/cql/CqlPrepareHandler.java | 26 ++- .../internal/core/cql/CqlRequestHandler.java | 37 +++- .../driver/api/core/graph/GraphPagingIT.java | 6 +- .../api/core/graph/GraphTimeoutsIT.java | 2 +- .../core/cql/BoundStatementSimulacronIT.java | 4 +- .../core/cql/SimpleStatementSimulacronIT.java | 2 +- 9 files changed, 334 insertions(+), 28 deletions(-) diff --git a/core/src/main/java/com/datastax/dse/driver/internal/core/cql/continuous/ContinuousRequestHandlerBase.java b/core/src/main/java/com/datastax/dse/driver/internal/core/cql/continuous/ContinuousRequestHandlerBase.java index 0453022cb6a..8d10172c27b 100644 --- a/core/src/main/java/com/datastax/dse/driver/internal/core/cql/continuous/ContinuousRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/dse/driver/internal/core/cql/continuous/ContinuousRequestHandlerBase.java @@ -17,6 +17,8 @@ */ package com.datastax.dse.driver.internal.core.cql.continuous; +import static com.datastax.oss.driver.api.core.DriverTimeoutException.UNAVAILABLE; + import com.datastax.dse.driver.api.core.DseProtocolVersion; import com.datastax.dse.driver.api.core.cql.continuous.ContinuousAsyncResultSet; import com.datastax.dse.driver.internal.core.DseProtocolFeature; @@ -26,6 +28,7 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.DriverTimeoutException; +import com.datastax.oss.driver.api.core.DriverTimeoutException.NodeDiagnostics; import com.datastax.oss.driver.api.core.NodeUnavailableException; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.RequestThrottlingException; @@ -62,6 +65,7 @@ import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.datastax.oss.driver.internal.core.metrics.NodeMetricUpdater; import com.datastax.oss.driver.internal.core.metrics.SessionMetricUpdater; +import com.datastax.oss.driver.internal.core.pool.ChannelPool; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.session.RepreparePayload; import com.datastax.oss.driver.internal.core.util.Loggers; @@ -390,13 +394,32 @@ private Timeout scheduleGlobalTimeout() { } LOG.trace("[{}] Scheduling global timeout for pages in {}", logPrefix, globalTimeout); return timer.newTimeout( - timeout -> - abortGlobalRequestOrChosenCallback( - new DriverTimeoutException("Query timed out after " + globalTimeout)), + timeout -> { + NodeDiagnostics diagnostics = buildNodeDiagnostics(); + abortGlobalRequestOrChosenCallback( + new DriverTimeoutException("Query timed out after " + globalTimeout, diagnostics)); + }, globalTimeout.toNanos(), TimeUnit.NANOSECONDS); } + @Nullable + private NodeDiagnostics buildNodeDiagnostics() { + List callbacks = inFlightCallbacks; + if (callbacks.isEmpty()) { + return null; + } + NodeResponseCallback cb = callbacks.get(0); + int channelInFlight = cb.channel.getInFlight(); + ChannelPool pool = session.getPools().get(cb.node); + return NodeDiagnostics.of( + cb.node.getEndPoint(), + channelInFlight, + pool != null ? pool.getInFlight() : UNAVAILABLE, + pool != null ? pool.getAvailableIds() : UNAVAILABLE, + pool != null ? pool.getOrphanedIds() : UNAVAILABLE); + } + /** * Cancels the continuous paging request. * @@ -718,9 +741,17 @@ private void onPageTimeout(int expectedPage) { lock.lock(); try { if (state == expectedPage) { + int channelInFlight = channel.getInFlight(); + ChannelPool pool = session.getPools().get(node); abort( new DriverTimeoutException( - String.format("Timed out waiting for page %d", expectedPage)), + "Timed out waiting for page " + expectedPage, + NodeDiagnostics.of( + node.getEndPoint(), + channelInFlight, + pool != null ? pool.getInFlight() : UNAVAILABLE, + pool != null ? pool.getAvailableIds() : UNAVAILABLE, + pool != null ? pool.getOrphanedIds() : UNAVAILABLE)), false); } else { // Ignore timeout if the request has moved on in the interim. diff --git a/core/src/main/java/com/datastax/dse/driver/internal/core/graph/GraphRequestHandler.java b/core/src/main/java/com/datastax/dse/driver/internal/core/graph/GraphRequestHandler.java index 5c9ceb00df2..1e7dcd0f0f9 100644 --- a/core/src/main/java/com/datastax/dse/driver/internal/core/graph/GraphRequestHandler.java +++ b/core/src/main/java/com/datastax/dse/driver/internal/core/graph/GraphRequestHandler.java @@ -17,6 +17,8 @@ */ package com.datastax.dse.driver.internal.core.graph; +import static com.datastax.oss.driver.api.core.DriverTimeoutException.UNAVAILABLE; + import com.datastax.dse.driver.api.core.graph.AsyncGraphResultSet; import com.datastax.dse.driver.api.core.graph.GraphNode; import com.datastax.dse.driver.api.core.graph.GraphStatement; @@ -26,6 +28,7 @@ import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.DriverException; import com.datastax.oss.driver.api.core.DriverTimeoutException; +import com.datastax.oss.driver.api.core.DriverTimeoutException.NodeDiagnostics; import com.datastax.oss.driver.api.core.NodeUnavailableException; import com.datastax.oss.driver.api.core.RequestThrottlingException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -56,6 +59,7 @@ import com.datastax.oss.driver.internal.core.metadata.DefaultNode; import com.datastax.oss.driver.internal.core.metrics.NodeMetricUpdater; import com.datastax.oss.driver.internal.core.metrics.SessionMetricUpdater; +import com.datastax.oss.driver.internal.core.pool.ChannelPool; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.tracker.NoopRequestTracker; import com.datastax.oss.driver.internal.core.tracker.RequestLogger; @@ -68,6 +72,7 @@ import com.datastax.oss.protocol.internal.response.result.Rows; import com.datastax.oss.protocol.internal.response.result.Void; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import io.netty.handler.codec.EncoderException; import io.netty.util.Timeout; import io.netty.util.Timer; @@ -209,12 +214,15 @@ private Timeout scheduleTimeout(Duration timeoutDuration) { if (timeoutDuration != null && timeoutDuration.toNanos() > 0) { try { return this.timer.newTimeout( - (Timeout timeout1) -> - setFinalError( - initialStatement, - new DriverTimeoutException("Query timed out after " + timeoutDuration), - null, - NO_SUCCESSFUL_EXECUTION), + (Timeout timeout1) -> { + NodeDiagnostics diagnostics = buildNodeDiagnostics(); + setFinalError( + initialStatement, + new DriverTimeoutException( + "Query timed out after " + timeoutDuration, diagnostics), + null, + NO_SUCCESSFUL_EXECUTION); + }, timeoutDuration.toNanos(), TimeUnit.NANOSECONDS); } catch (IllegalStateException e) { @@ -229,6 +237,23 @@ private Timeout scheduleTimeout(Duration timeoutDuration) { return null; } + @Nullable + private NodeDiagnostics buildNodeDiagnostics() { + List callbacks = inFlightCallbacks; + if (callbacks.isEmpty()) { + return null; + } + NodeResponseCallback cb = callbacks.get(0); + int channelInFlight = cb.channel.getInFlight(); + ChannelPool pool = session.getPools().get(cb.node); + return NodeDiagnostics.of( + cb.node.getEndPoint(), + channelInFlight, + pool != null ? pool.getInFlight() : UNAVAILABLE, + pool != null ? pool.getAvailableIds() : UNAVAILABLE, + pool != null ? pool.getOrphanedIds() : UNAVAILABLE); + } + /** * Sends the request to the next available node. * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/DriverTimeoutException.java b/core/src/main/java/com/datastax/oss/driver/api/core/DriverTimeoutException.java index 8b4cc5dc5bb..81bf6c7e2f5 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/DriverTimeoutException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/DriverTimeoutException.java @@ -18,21 +18,222 @@ package com.datastax.oss.driver.api.core; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.metadata.EndPoint; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; -/** Thrown when a driver request timed out. */ +/** + * Thrown when a driver request timed out. + * + *

When thrown from the request execution path the exception carries a per-node diagnostic + * snapshot captured at the moment the timer fires (see {@link #getNodeDiagnostics()}). This + * information is also embedded in the exception message for easy log-based diagnosis. + */ public class DriverTimeoutException extends DriverException { + + /** + * Sentinel value used in {@link NodeDiagnostics} fields when the corresponding data was not + * available at the time the exception was created (e.g. the pool had already been removed). + */ + public static final int UNAVAILABLE = -1; + + /** + * Per-node diagnostic snapshot captured at timeout time. + * + *

Fields: + * + *

    + *
  • {@link #getChannelInFlight()}: requests currently awaiting a response on the specific + * connection used for this request. + *
  • {@link #getPoolInFlight()}: total in-flight across all connections to this host ({@link + * #UNAVAILABLE} if the pool was already removed). + *
  • {@link #getPoolAvailableIds()}: remaining stream IDs available to send new requests; a + * low value indicates pool contention ({@link #UNAVAILABLE} if pool was already removed). + *
  • {@link #getPoolOrphanedIds()}: stream IDs from previously timed-out or cancelled requests + * that cannot be released yet; a high value indicates stale stream ID accumulation ({@link + * #UNAVAILABLE} if pool was already removed). + *
+ * + *

Diagnosing failure modes: + * + *

    + *
  • {@code poolAvailableIds} near zero → pool contention; requests queuing inside the driver + * before reaching the server. + *
  • {@code poolAvailableIds} normal + high {@code channelInFlight} → server is slow; requests + * were sent but not answered within the timeout. + *
  • High {@code poolOrphanedIds} → previous timeouts consumed stream IDs that the driver is + * still waiting to reclaim. + *
+ */ + public static final class NodeDiagnostics { + + @NonNull private final EndPoint endPoint; + private final int channelInFlight; + private final int poolInFlight; + private final int poolAvailableIds; + private final int poolOrphanedIds; + + /** + * Creates a full diagnostic snapshot (pool was available at timeout time). + * + * @param endPoint the endpoint of the node. + * @param channelInFlight in-flight count on the specific channel. + * @param poolInFlight total in-flight across the pool for this host. + * @param poolAvailableIds remaining stream IDs available in the pool. + * @param poolOrphanedIds orphaned stream IDs in the pool. + */ + public NodeDiagnostics( + @NonNull EndPoint endPoint, + int channelInFlight, + int poolInFlight, + int poolAvailableIds, + int poolOrphanedIds) { + this.endPoint = endPoint; + this.channelInFlight = channelInFlight; + this.poolInFlight = poolInFlight; + this.poolAvailableIds = poolAvailableIds; + this.poolOrphanedIds = poolOrphanedIds; + } + + /** + * Creates a partial diagnostic snapshot for when the pool was unavailable at timeout time. The + * pool-related fields ({@link #getPoolInFlight()}, {@link #getPoolAvailableIds()}, {@link + * #getPoolOrphanedIds()}) will be {@link DriverTimeoutException#UNAVAILABLE}. + * + * @param endPoint the endpoint of the node. + * @param channelInFlight in-flight count on the specific channel. + */ + public NodeDiagnostics(@NonNull EndPoint endPoint, int channelInFlight) { + this(endPoint, channelInFlight, UNAVAILABLE, UNAVAILABLE, UNAVAILABLE); + } + + /** + * Creates a diagnostic snapshot using pre-computed pool stats. Pass {@link + * DriverTimeoutException#UNAVAILABLE} for pool fields when the pool was not available at + * timeout time. + * + * @param endPoint the endpoint of the node. + * @param channelInFlight in-flight count on the specific channel. + * @param poolInFlight total in-flight across the pool, or {@link + * DriverTimeoutException#UNAVAILABLE}. + * @param poolAvailableIds remaining stream IDs in the pool, or {@link + * DriverTimeoutException#UNAVAILABLE}. + * @param poolOrphanedIds orphaned stream IDs in the pool, or {@link + * DriverTimeoutException#UNAVAILABLE}. + */ + @NonNull + public static NodeDiagnostics of( + @NonNull EndPoint endPoint, + int channelInFlight, + int poolInFlight, + int poolAvailableIds, + int poolOrphanedIds) { + return new NodeDiagnostics( + endPoint, channelInFlight, poolInFlight, poolAvailableIds, poolOrphanedIds); + } + + /** Returns the endpoint of the node that had in-flight requests at timeout time. */ + @NonNull + public EndPoint getEndPoint() { + return endPoint; + } + + /** + * Returns the number of in-flight requests on the specific connection at timeout time, or + * {@link DriverTimeoutException#UNAVAILABLE} if not available. + */ + public int getChannelInFlight() { + return channelInFlight; + } + + /** + * Returns the total number of in-flight requests across all connections to this host at timeout + * time, or {@link DriverTimeoutException#UNAVAILABLE} if the pool was no longer available. + */ + public int getPoolInFlight() { + return poolInFlight; + } + + /** + * Returns the number of remaining stream IDs available in the pool at timeout time, or {@link + * DriverTimeoutException#UNAVAILABLE} if the pool was no longer available. A low value + * indicates pool contention. + */ + public int getPoolAvailableIds() { + return poolAvailableIds; + } + + /** + * Returns the number of orphaned stream IDs in the pool at timeout time, or {@link + * DriverTimeoutException#UNAVAILABLE} if the pool was no longer available. A high value + * indicates stale stream ID accumulation from previous timeouts. + */ + public int getPoolOrphanedIds() { + return poolOrphanedIds; + } + + @Override + public String toString() { + if (poolInFlight == UNAVAILABLE) { + return String.format("%s [channel in-flight: %d, pool: n/a]", endPoint, channelInFlight); + } + return String.format( + "%s [channel in-flight: %d, pool in-flight: %d, pool available ids: %d, pool orphaned ids: %d]", + endPoint, channelInFlight, poolInFlight, poolAvailableIds, poolOrphanedIds); + } + } + + @Nullable private final NodeDiagnostics nodeDiagnostics; + + /** + * Creates an exception with a plain message and no node diagnostics. Used for cases where the + * diagnostic data is unavailable (e.g. no nodes were in-flight at timeout time). + * + * @param message the exception message. + */ public DriverTimeoutException(@NonNull String message) { - this(message, null); + this(message, (NodeDiagnostics) null); } - private DriverTimeoutException(String message, ExecutionInfo executionInfo) { + /** + * Creates an exception with per-node diagnostic context captured at timeout time. The message is + * generated automatically from {@code baseMessage} and the diagnostic data. + * + * @param baseMessage the base timeout message (e.g. {@code "Query timed out after PT0.5S"}). + * @param nodeDiagnostics per-node diagnostic snapshot; may be {@code null} if unavailable, in + * which case no node information is appended to the message. + */ + public DriverTimeoutException( + @NonNull String baseMessage, @Nullable NodeDiagnostics nodeDiagnostics) { + this(buildMessage(baseMessage, nodeDiagnostics), nodeDiagnostics, null); + } + + private DriverTimeoutException( + String message, @Nullable NodeDiagnostics nodeDiagnostics, ExecutionInfo executionInfo) { super(message, executionInfo, null, true); + this.nodeDiagnostics = nodeDiagnostics; + } + + /** + * Returns the per-node diagnostic snapshot captured at timeout time, or {@code null} if not + * available. + */ + @Nullable + public NodeDiagnostics getNodeDiagnostics() { + return nodeDiagnostics; } @NonNull @Override public DriverException copy() { - return new DriverTimeoutException(getMessage(), getExecutionInfo()); + return new DriverTimeoutException(getMessage(), nodeDiagnostics, getExecutionInfo()); + } + + private static String buildMessage( + @NonNull String baseMessage, @Nullable NodeDiagnostics nodeDiagnostics) { + if (nodeDiagnostics == null) { + return baseMessage; + } + return baseMessage + " — node in flight: " + nodeDiagnostics; } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java index 8386e5ee999..4c99726a8b4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java @@ -17,9 +17,12 @@ */ package com.datastax.oss.driver.internal.core.cql; +import static com.datastax.oss.driver.api.core.DriverTimeoutException.UNAVAILABLE; + import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.DriverTimeoutException; +import com.datastax.oss.driver.api.core.DriverTimeoutException.NodeDiagnostics; import com.datastax.oss.driver.api.core.NodeUnavailableException; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.RequestThrottlingException; @@ -45,6 +48,7 @@ import com.datastax.oss.driver.internal.core.channel.DriverChannel; import com.datastax.oss.driver.internal.core.channel.ResponseCallback; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.pool.ChannelPool; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; @@ -55,6 +59,7 @@ import com.datastax.oss.protocol.internal.response.Error; import com.datastax.oss.protocol.internal.response.result.Prepared; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import io.netty.util.Timeout; import io.netty.util.Timer; import io.netty.util.concurrent.Future; @@ -165,7 +170,10 @@ private Timeout scheduleTimeout(Duration timeoutDuration) { if (timeoutDuration.toNanos() > 0) { return this.timer.newTimeout( (Timeout timeout1) -> { - setFinalError(new DriverTimeoutException("Query timed out after " + timeoutDuration)); + NodeDiagnostics diagnostics = buildNodeDiagnostics(); + setFinalError( + new DriverTimeoutException( + "Query timed out after " + timeoutDuration, diagnostics)); if (initialCallback != null) { initialCallback.cancel(); } @@ -177,6 +185,22 @@ private Timeout scheduleTimeout(Duration timeoutDuration) { } } + @Nullable + private NodeDiagnostics buildNodeDiagnostics() { + InitialPrepareCallback cb = initialCallback; + if (cb == null) { + return null; + } + int channelInFlight = cb.channel.getInFlight(); + ChannelPool pool = session.getPools().get(cb.node); + return NodeDiagnostics.of( + cb.node.getEndPoint(), + channelInFlight, + pool != null ? pool.getInFlight() : UNAVAILABLE, + pool != null ? pool.getAvailableIds() : UNAVAILABLE, + pool != null ? pool.getOrphanedIds() : UNAVAILABLE); + } + private void cancelTimeout() { if (this.scheduledTimeout != null) { this.scheduledTimeout.cancel(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java index 4008dd528f0..e3391161c59 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java @@ -23,10 +23,13 @@ */ package com.datastax.oss.driver.internal.core.cql; +import static com.datastax.oss.driver.api.core.DriverTimeoutException.UNAVAILABLE; + import com.datastax.oss.driver.api.core.AllNodesFailedException; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.DriverException; import com.datastax.oss.driver.api.core.DriverTimeoutException; +import com.datastax.oss.driver.api.core.DriverTimeoutException.NodeDiagnostics; import com.datastax.oss.driver.api.core.NodeUnavailableException; import com.datastax.oss.driver.api.core.RequestThrottlingException; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; @@ -66,6 +69,7 @@ import com.datastax.oss.driver.internal.core.metadata.token.TokenLong64; import com.datastax.oss.driver.internal.core.metrics.NodeMetricUpdater; import com.datastax.oss.driver.internal.core.metrics.SessionMetricUpdater; +import com.datastax.oss.driver.internal.core.pool.ChannelPool; import com.datastax.oss.driver.internal.core.protocol.TabletInfo; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.session.RepreparePayload; @@ -86,6 +90,7 @@ import com.datastax.oss.protocol.internal.response.result.Void; import com.datastax.oss.protocol.internal.util.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import io.netty.handler.codec.EncoderException; import io.netty.util.Timeout; import io.netty.util.Timer; @@ -224,12 +229,15 @@ private Timeout scheduleTimeout(Duration timeoutDuration) { if (timeoutDuration.toNanos() > 0) { try { return this.timer.newTimeout( - (Timeout timeout1) -> - setFinalError( - initialStatement, - new DriverTimeoutException("Query timed out after " + timeoutDuration), - null, - -1), + (Timeout timeout1) -> { + NodeDiagnostics diagnostics = buildNodeDiagnostics(); + setFinalError( + initialStatement, + new DriverTimeoutException( + "Query timed out after " + timeoutDuration, diagnostics), + null, + -1); + }, timeoutDuration.toNanos(), TimeUnit.NANOSECONDS); } catch (IllegalStateException e) { @@ -244,6 +252,23 @@ private Timeout scheduleTimeout(Duration timeoutDuration) { return null; } + @Nullable + private NodeDiagnostics buildNodeDiagnostics() { + List callbacks = inFlightCallbacks; + if (callbacks.isEmpty()) { + return null; + } + NodeResponseCallback cb = callbacks.get(0); + int channelInFlight = cb.channel.getInFlight(); + ChannelPool pool = session.getPools().get(cb.node); + return NodeDiagnostics.of( + cb.node.getEndPoint(), + channelInFlight, + pool != null ? pool.getInFlight() : UNAVAILABLE, + pool != null ? pool.getAvailableIds() : UNAVAILABLE, + pool != null ? pool.getOrphanedIds() : UNAVAILABLE); + } + private Token getRoutingToken(Statement statement) { Token token = statement.getRoutingToken(); if (token != null) { diff --git a/integration-tests/src/test/java/com/datastax/dse/driver/api/core/graph/GraphPagingIT.java b/integration-tests/src/test/java/com/datastax/dse/driver/api/core/graph/GraphPagingIT.java index 01938c34e07..aa0ada7840c 100644 --- a/integration-tests/src/test/java/com/datastax/dse/driver/api/core/graph/GraphPagingIT.java +++ b/integration-tests/src/test/java/com/datastax/dse/driver/api/core/graph/GraphPagingIT.java @@ -408,7 +408,7 @@ public void should_trigger_global_timeout_sync_from_config() { .setExecutionProfile(profile)); fail("Expecting DriverTimeoutException"); } catch (DriverTimeoutException e) { - assertThat(e).hasMessage("Query timed out after " + timeout); + assertThat(e).hasMessageStartingWith("Query timed out after " + timeout); } } finally { CCM_RULE.getCcmBridge().resume(1); @@ -433,7 +433,7 @@ public void should_trigger_global_timeout_sync_from_statement() { .setTimeout(timeout)); fail("Expecting DriverTimeoutException"); } catch (DriverTimeoutException e) { - assertThat(e).hasMessage("Query timed out after " + timeout); + assertThat(e).hasMessageStartingWith("Query timed out after " + timeout); } } finally { CCM_RULE.getCcmBridge().resume(1); @@ -461,7 +461,7 @@ public void should_trigger_global_timeout_async() throws InterruptedException { result.toCompletableFuture().get(); fail("Expecting DriverTimeoutException"); } catch (ExecutionException e) { - assertThat(e.getCause()).hasMessage("Query timed out after " + timeout); + assertThat(e.getCause()).hasMessageStartingWith("Query timed out after " + timeout); } finally { CCM_RULE.getCcmBridge().resume(1); } diff --git a/integration-tests/src/test/java/com/datastax/dse/driver/api/core/graph/GraphTimeoutsIT.java b/integration-tests/src/test/java/com/datastax/dse/driver/api/core/graph/GraphTimeoutsIT.java index d2b58cc0f9c..edf398a4c3c 100644 --- a/integration-tests/src/test/java/com/datastax/dse/driver/api/core/graph/GraphTimeoutsIT.java +++ b/integration-tests/src/test/java/com/datastax/dse/driver/api/core/graph/GraphTimeoutsIT.java @@ -171,7 +171,7 @@ public void should_take_into_account_request_timeout_if_less_than_server_timeout // client timeout for this request. We cannot know for sure if it will be a client timeout // error, or a server timeout, and during tests, both happened and not deterministically. } catch (DriverTimeoutException e) { - assertThat(e).hasMessage("Query timed out after " + clientTimeout); + assertThat(e).hasMessageStartingWith("Query timed out after " + clientTimeout); } catch (InvalidQueryException e) { assertThat(e) .hasMessageContainingAll( diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/core/cql/BoundStatementSimulacronIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/core/cql/BoundStatementSimulacronIT.java index cb81874d47a..3fdfccf7570 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/core/cql/BoundStatementSimulacronIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/core/cql/BoundStatementSimulacronIT.java @@ -151,7 +151,7 @@ public void should_use_timeout_from_simple_statement() { assertThat(t) .isInstanceOf(DriverTimeoutException.class) - .hasMessage("Query timed out after PT1S"); + .hasMessageStartingWith("Query timed out after PT1S"); } } @@ -185,7 +185,7 @@ public void should_use_timeout() { assertThat(t) .isInstanceOf(DriverTimeoutException.class) - .hasMessage("Query timed out after PT0.15S"); + .hasMessageStartingWith("Query timed out after PT0.15S"); } } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/core/cql/SimpleStatementSimulacronIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/core/cql/SimpleStatementSimulacronIT.java index bb8b4f6b731..3ec20e0f8e7 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/core/cql/SimpleStatementSimulacronIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/core/cql/SimpleStatementSimulacronIT.java @@ -98,6 +98,6 @@ public void should_use_timeout() { assertThat(t) .isInstanceOf(DriverTimeoutException.class) - .hasMessage("Query timed out after PT1S"); + .hasMessageStartingWith("Query timed out after PT1S"); } } From 0ae5b7aa7931463502fd7e25558325e6a15f21ef Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Tue, 12 May 2026 22:39:27 -0400 Subject: [PATCH 08/32] Start consider SERIAL SELECT as LWT queries Driver did not consider SERIAL SELECT as LWT and therefore routed them as regular queries causing LWT congestion. Fix is to consider consistency when RequestRoutingMethod is calculated. --- .../driver/api/core/cql/SimpleStatement.java | 7 +- .../driver/api/core/cql/StatementBuilder.java | 11 +- .../driver/internal/core/cql/Conversions.java | 2 +- .../core/cql/DefaultBatchStatement.java | 10 +- .../core/cql/DefaultBoundStatement.java | 22 +- .../core/cql/DefaultPreparedStatement.java | 16 +- .../core/cql/DefaultSimpleStatement.java | 14 +- .../core/cql/RequestRoutingTypeAccessor.java | 27 ++ .../BasicLoadBalancingPolicy.java | 77 ++++- .../api/core/cql/StatementBuilderTest.java | 85 +++++ .../core/cql/DefaultBatchStatementTest.java | 12 + .../cql/DefaultPreparedStatementTest.java | 112 +++++++ ...asicLoadBalancingPolicyDcFailoverTest.java | 41 +++ ...LoadBalancingPolicyRequestRoutingTest.java | 300 ++++++++++++++++++ .../LWTLoadBalancingMultiDcIT.java | 66 ++++ .../metadata/DefaultMetadataTabletMapIT.java | 60 +++- 16 files changed, 829 insertions(+), 33 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/cql/RequestRoutingTypeAccessor.java create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatementTest.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java index 20f17fa716e..2489254ed13 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/SimpleStatement.java @@ -20,7 +20,6 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; -import com.datastax.oss.driver.api.core.RequestRoutingType; import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.session.Request; import com.datastax.oss.driver.internal.core.cql.DefaultSimpleStatement; @@ -86,7 +85,7 @@ static SimpleStatement newInstance(@NonNull String cqlQuery) { null, null, Statement.NO_NOW_IN_SECONDS, - RequestRoutingType.REGULAR); + null); } /** @@ -121,7 +120,7 @@ static SimpleStatement newInstance( null, null, Statement.NO_NOW_IN_SECONDS, - RequestRoutingType.REGULAR); + null); } /** @@ -153,7 +152,7 @@ static SimpleStatement newInstance( null, null, Statement.NO_NOW_IN_SECONDS, - RequestRoutingType.REGULAR); + null); } /** diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java index 9894dd9c813..6f2dcf174ec 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/cql/StatementBuilder.java @@ -23,6 +23,7 @@ import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.token.Token; +import com.datastax.oss.driver.internal.core.cql.RequestRoutingTypeAccessor; import com.datastax.oss.driver.internal.core.util.RoutingKey; import com.datastax.oss.protocol.internal.util.collection.NullAllowingImmutableMap; import edu.umd.cs.findbugs.annotations.NonNull; @@ -89,7 +90,15 @@ protected StatementBuilder(StatementT template) { this.timeout = template.getTimeout(); this.node = template.getNode(); this.nowInSeconds = template.getNowInSeconds(); - this.requestRoutingType = template.getRequestRoutingType(); + this.requestRoutingType = getConfiguredRequestRoutingType(template); + } + + @Nullable + private RequestRoutingType getConfiguredRequestRoutingType(StatementT template) { + if (template instanceof RequestRoutingTypeAccessor) { + return ((RequestRoutingTypeAccessor) template).getConfiguredRequestRoutingType(); + } + return template.getRequestRoutingType(); } /** @see Statement#setExecutionProfileName(String) */ diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java index 0a864293b0d..dd11ee596e9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/Conversions.java @@ -430,7 +430,7 @@ public static DefaultPreparedStatement toPreparedStatement( context.getProtocolVersion(), lwtInfo != null && lwtInfo.isLwt(response.variablesMetadata.flags) ? RequestRoutingType.LWT - : RequestRoutingType.REGULAR); + : null); } public static ColumnDefinitions toColumnDefinitions( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java index cde8d91e4c9..af95829756f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatement.java @@ -49,7 +49,7 @@ import org.slf4j.LoggerFactory; @Immutable -public class DefaultBatchStatement implements BatchStatement { +public class DefaultBatchStatement implements BatchStatement, RequestRoutingTypeAccessor { private static final Logger LOG = LoggerFactory.getLogger(DefaultBatchStatement.class); private final BatchType batchType; @@ -857,6 +857,8 @@ public BatchStatement setNowInSeconds(int newNowInSeconds) { public RequestRoutingType getRequestRoutingType() { if (Objects.nonNull(requestRoutingType)) { return requestRoutingType; + } else if (consistencyLevel != null && consistencyLevel.isSerial()) { + return RequestRoutingType.LWT; } else if (Objects.isNull( cachedStatementsRequestRoutingType)) { // Immutability of the statement list and statements // allows us to cache the result @@ -870,6 +872,12 @@ public RequestRoutingType getRequestRoutingType() { return cachedStatementsRequestRoutingType; } + @Nullable + @Override + public RequestRoutingType getConfiguredRequestRoutingType() { + return requestRoutingType; + } + @NonNull @Override public BatchStatement setRequestRoutingType(RequestRoutingType requestRoutingType) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java index 2c3ad902f39..509d7a90a76 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultBoundStatement.java @@ -44,11 +44,10 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.Objects; import net.jcip.annotations.Immutable; @Immutable -public class DefaultBoundStatement implements BoundStatement { +public class DefaultBoundStatement implements BoundStatement, RequestRoutingTypeAccessor { private final PreparedStatement preparedStatement; private final ColumnDefinitions variableDefinitions; @@ -805,9 +804,22 @@ public BoundStatement setNowInSeconds(int newNowInSeconds) { @Nullable @Override public RequestRoutingType getRequestRoutingType() { - return Objects.nonNull(requestRoutingType) - ? requestRoutingType - : preparedStatement.getRequestRoutingType(); + if (requestRoutingType != null) { + return requestRoutingType; + } + if (consistencyLevel != null && consistencyLevel.isSerial()) { + return RequestRoutingType.LWT; + } + if (preparedStatement instanceof RequestRoutingTypeAccessor) { + return ((RequestRoutingTypeAccessor) preparedStatement).getConfiguredRequestRoutingType(); + } + return preparedStatement.getRequestRoutingType(); + } + + @Nullable + @Override + public RequestRoutingType getConfiguredRequestRoutingType() { + return requestRoutingType; } @NonNull diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java index 754a89ac228..652e3f50af7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatement.java @@ -58,7 +58,7 @@ import org.slf4j.LoggerFactory; @ThreadSafe -public class DefaultPreparedStatement implements PreparedStatement { +public class DefaultPreparedStatement implements PreparedStatement, RequestRoutingTypeAccessor { private static final Logger LOGGER = LoggerFactory.getLogger(DefaultPreparedStatement.class); private static final Splitter SPACE_SPLITTER = Splitter.onPattern("\\s+"); private static final Splitter COMMA_SPLITTER = Splitter.onPattern(","); @@ -196,6 +196,20 @@ public boolean isLWT() { @Nullable @Override public RequestRoutingType getRequestRoutingType() { + if (requestRoutingType != null) { + return requestRoutingType; + } + + if (consistencyLevelForBoundStatements != null + && consistencyLevelForBoundStatements.isSerial()) { + return RequestRoutingType.LWT; + } + return null; + } + + @Nullable + @Override + public RequestRoutingType getConfiguredRequestRoutingType() { return requestRoutingType; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java index 0268689d86f..39e308987fd 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultSimpleStatement.java @@ -42,7 +42,7 @@ import net.jcip.annotations.Immutable; @Immutable -public class DefaultSimpleStatement implements SimpleStatement { +public class DefaultSimpleStatement implements SimpleStatement, RequestRoutingTypeAccessor { private final String query; private final List positionalValues; @@ -776,6 +776,18 @@ public SimpleStatement setNowInSeconds(int newNowInSeconds) { @Nullable @Override public RequestRoutingType getRequestRoutingType() { + if (requestRoutingType != null) { + return requestRoutingType; + } + if (consistencyLevel != null && consistencyLevel.isSerial()) { + return RequestRoutingType.LWT; + } + return null; + } + + @Nullable + @Override + public RequestRoutingType getConfiguredRequestRoutingType() { return requestRoutingType; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/RequestRoutingTypeAccessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/RequestRoutingTypeAccessor.java new file mode 100644 index 00000000000..07e17e50da6 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/RequestRoutingTypeAccessor.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import com.datastax.oss.driver.api.core.RequestRoutingType; +import edu.umd.cs.findbugs.annotations.Nullable; + +/** Internal hook to distinguish stored routing type from consistency-inferred routing type. */ +public interface RequestRoutingTypeAccessor { + @Nullable + RequestRoutingType getConfiguredRequestRoutingType(); +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/BasicLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/BasicLoadBalancingPolicy.java index 2cd581237b1..f0e1cb3cf45 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/BasicLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/BasicLoadBalancingPolicy.java @@ -189,13 +189,37 @@ public RequestRoutingMethod getRequestRoutingMethod(@Nullable Request request) { if (request == null) { return RequestRoutingMethod.REGULAR; } - if (request.getRequestRoutingType() == RequestRoutingType.LWT) { + RequestRoutingType requestRoutingType = request.getRequestRoutingType(); + if (requestRoutingType == RequestRoutingType.LWT + || (requestRoutingType == null && hasSerialConsistency(request))) { return lwtRequestRoutingMethod; } else { return RequestRoutingMethod.REGULAR; } } + private boolean hasSerialConsistency(@NonNull Request request) { + if (!(request instanceof Statement)) { + return false; + } + + return getEffectiveConsistency((Statement) request).isSerial(); + } + + @NonNull + private Optional getRequestProfile(@NonNull Request request) { + DriverExecutionProfile requestProfile = request.getExecutionProfile(); + if (requestProfile != null) { + return Optional.of(requestProfile); + } + + String profileName = request.getExecutionProfileName(); + if (profileName != null && !profileName.isEmpty()) { + return Optional.of(context.getConfig().getProfile(profileName)); + } + return Optional.of(profile); + } + /** * Returns the local datacenter name, if known; empty otherwise. * @@ -362,11 +386,20 @@ protected Queue newQueryPlanPreserveReplicas( for (Object obj : getLiveNodes().dc(null).toArray()) { allNodes.add((Node) obj); } + replicas = filterNodesIn(replicas, new LinkedHashSet<>(allNodes)); queryPlan.addAll(replicas); addRotatedNonReplicas(queryPlan, allNodes, replicas, request); } else { - // With local DC: prioritize local, then remote - Map> nodesByDc = getAllNodesByDc(); + boolean includeRemoteDcs = isDcFailoverAllowedForRequest(request); + Map> nodesByDc = + includeRemoteDcs + ? getAllNodesByDc() + : Collections.singletonMap(localDc, dcNodeList(localDc)); + Set liveNodesForPlan = + nodesByDc.values().stream() + .flatMap(List::stream) + .collect(Collectors.toCollection(LinkedHashSet::new)); + replicas = filterNodesIn(replicas, liveNodesForPlan); addReplicasByDc(queryPlan, replicas, localDc); addNonReplicasByDc(queryPlan, nodesByDc, replicas, localDc, request); } @@ -374,6 +407,10 @@ protected Queue newQueryPlanPreserveReplicas( return new SimpleQueryPlan(queryPlan.toArray()); } + private List filterNodesIn(List nodes, Set nodesToKeep) { + return nodes.stream().filter(nodesToKeep::contains).collect(Collectors.toList()); + } + /** Collect all live nodes grouped by DC, with preferred remote DCs ordered first. */ private Map> getAllNodesByDc() { Map> nodesByDc = new LinkedHashMap<>(); @@ -537,15 +574,8 @@ protected Queue maybeAddDcFailover(@Nullable Request request, @NonNull Que if (maxNodesPerRemoteDc <= 0 || localDc == null) { return local; } - if (!allowDcFailoverForLocalCl && request instanceof Statement) { - Statement statement = (Statement) request; - ConsistencyLevel consistency = statement.getConsistencyLevel(); - if (consistency == null) { - consistency = defaultConsistencyLevel; - } - if (consistency.isDcLocal()) { - return local; - } + if (!isDcFailoverAllowedForRequest(request)) { + return local; } if (preferredRemoteDcs.isEmpty()) { return new CompositeQueryPlan(local, buildRemoteQueryPlanAll()); @@ -553,6 +583,29 @@ protected Queue maybeAddDcFailover(@Nullable Request request, @NonNull Que return new CompositeQueryPlan(local, buildRemoteQueryPlanPreferred()); } + private boolean isDcFailoverAllowedForRequest(@Nullable Request request) { + if (!allowDcFailoverForLocalCl && request instanceof Statement) { + return !getEffectiveConsistency((Statement) request).isDcLocal(); + } + return true; + } + + @NonNull + private ConsistencyLevel getEffectiveConsistency(@NonNull Statement statement) { + ConsistencyLevel consistency = statement.getConsistencyLevel(); + if (consistency != null) { + return consistency; + } + + return getRequestProfile(statement) + .map( + requestProfile -> + context + .getConsistencyLevelRegistry() + .nameToLevel(requestProfile.getString(DefaultDriverOption.REQUEST_CONSISTENCY))) + .orElse(defaultConsistencyLevel); + } + private QueryPlan buildRemoteQueryPlanAll() { return new LazyQueryPlan() { diff --git a/core/src/test/java/com/datastax/oss/driver/api/core/cql/StatementBuilderTest.java b/core/src/test/java/com/datastax/oss/driver/api/core/cql/StatementBuilderTest.java index a10208645fd..286f3f8db1a 100644 --- a/core/src/test/java/com/datastax/oss/driver/api/core/cql/StatementBuilderTest.java +++ b/core/src/test/java/com/datastax/oss/driver/api/core/cql/StatementBuilderTest.java @@ -21,9 +21,15 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; +import com.datastax.oss.driver.api.core.RequestRoutingType; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import com.datastax.oss.driver.internal.core.cql.DefaultBoundStatement; import com.datastax.oss.driver.shaded.guava.common.base.Charsets; import edu.umd.cs.findbugs.annotations.NonNull; import java.nio.ByteBuffer; +import java.util.Collections; import org.junit.Test; public class StatementBuilderTest { @@ -103,4 +109,83 @@ public void should_match_set_routing_key_vararg() { builderStmt = builder.setRoutingKey(buff2, buff1).build(); assertThat(expectedStmt.getRoutingKey()).isNotEqualTo(builderStmt.getRoutingKey()); } + + @Test + public void should_not_copy_inferred_simple_routing_type_as_explicit() { + SimpleStatement serialStatement = + SimpleStatement.builder("select * from test.foo") + .setConsistencyLevel(DefaultConsistencyLevel.LOCAL_SERIAL) + .build(); + + assertThat(serialStatement.getRequestRoutingType()).isEqualTo(RequestRoutingType.LWT); + + SimpleStatement regularStatement = + SimpleStatement.builder(serialStatement) + .setConsistencyLevel(DefaultConsistencyLevel.ONE) + .build(); + + assertThat(regularStatement.getRequestRoutingType()).isNull(); + } + + @Test + public void should_not_copy_inferred_bound_routing_type_as_explicit() { + BoundStatement serialStatement = newRegularBoundStatement(DefaultConsistencyLevel.LOCAL_SERIAL); + + assertThat(serialStatement.getRequestRoutingType()).isEqualTo(RequestRoutingType.LWT); + + BoundStatement regularStatement = + new BoundStatementBuilder(serialStatement) + .setConsistencyLevel(DefaultConsistencyLevel.ONE) + .build(); + + assertThat(regularStatement.getRequestRoutingType()).isEqualTo(RequestRoutingType.REGULAR); + } + + @Test + public void should_not_copy_inferred_batch_routing_type_as_explicit() { + BatchStatement serialStatement = + BatchStatement.builder(BatchType.LOGGED) + .setConsistencyLevel(DefaultConsistencyLevel.LOCAL_SERIAL) + .build(); + + assertThat(serialStatement.getRequestRoutingType()).isEqualTo(RequestRoutingType.LWT); + + BatchStatement regularStatement = + BatchStatement.builder(serialStatement) + .setConsistencyLevel(DefaultConsistencyLevel.ONE) + .build(); + + assertThat(regularStatement.getRequestRoutingType()).isEqualTo(RequestRoutingType.REGULAR); + } + + private BoundStatement newRegularBoundStatement(DefaultConsistencyLevel consistencyLevel) { + PreparedStatement preparedStatement = mock(PreparedStatement.class); + ColumnDefinitions variableDefinitions = mock(ColumnDefinitions.class); + when(preparedStatement.isLWT()).thenReturn(false); + when(preparedStatement.getRequestRoutingType()).thenReturn(RequestRoutingType.REGULAR); + when(preparedStatement.getVariableDefinitions()).thenReturn(variableDefinitions); + return new DefaultBoundStatement( + preparedStatement, + variableDefinitions, + new ByteBuffer[0], + null, + null, + null, + null, + null, + Collections.emptyMap(), + null, + false, + Statement.NO_DEFAULT_TIMESTAMP, + null, + Integer.MIN_VALUE, + consistencyLevel, + null, + null, + CodecRegistry.DEFAULT, + DefaultProtocolVersion.DEFAULT, + null, + Statement.NO_NOW_IN_SECONDS, + null); + } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatementTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatementTest.java index 3f38ddaf3cb..c62426eeffc 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatementTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultBatchStatementTest.java @@ -103,6 +103,18 @@ public void should_not_issue_log_warn_if_statement_have_no_consistency_level_set verify(logger.appender, times(0)).doAppend(logger.loggingEventCaptor.capture()); } + @Test + public void should_not_infer_lwt_status_from_serial_consistency_level_option() { + BatchStatement batch = + BatchStatement.builder(BatchType.LOGGED) + .addStatement(SimpleStatement.newInstance("UPDATE foo SET v = ? WHERE pk = ?", 1, 1)) + .setSerialConsistencyLevel(DefaultConsistencyLevel.LOCAL_SERIAL) + .build(); + + assertThat(batch.getRequestRoutingType()).isEqualTo(RequestRoutingType.REGULAR); + assertThat(batch.isLWT()).isFalse(); + } + @Test public void should_infer_lwt_status() { // SELECT is not allowed in practice but is sufficient for unit testing diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatementTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatementTest.java new file mode 100644 index 00000000000..7ec6232fea7 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/DefaultPreparedStatementTest.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; +import com.datastax.oss.driver.api.core.RequestRoutingType; +import com.datastax.oss.driver.api.core.cql.BoundStatement; +import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; +import com.datastax.oss.protocol.internal.util.Bytes; +import java.util.Collections; +import org.junit.Test; + +public class DefaultPreparedStatementTest { + + @Test + public void should_not_keep_inferred_routing_type_after_bound_consistency_override() { + DefaultPreparedStatement preparedStatement = + newPreparedStatement(DefaultConsistencyLevel.LOCAL_SERIAL, null, null); + + assertThat(preparedStatement.getRequestRoutingType()).isEqualTo(RequestRoutingType.LWT); + + BoundStatement boundStatement = + preparedStatement.bind().setConsistencyLevel(DefaultConsistencyLevel.ONE); + + assertThat(boundStatement.getConsistencyLevel()).isEqualTo(DefaultConsistencyLevel.ONE); + assertThat(boundStatement.getRequestRoutingType()).isNull(); + } + + @Test + public void should_not_infer_routing_type_from_prepared_serial_consistency_level_option() { + DefaultPreparedStatement preparedStatement = + newPreparedStatement(null, DefaultConsistencyLevel.LOCAL_SERIAL, null); + + assertThat(preparedStatement.getRequestRoutingType()).isNull(); + assertThat(preparedStatement.bind().getRequestRoutingType()).isNull(); + } + + @Test + public void should_not_infer_routing_type_from_bound_serial_consistency_level_override() { + DefaultPreparedStatement preparedStatement = newPreparedStatement(null, null, null); + + BoundStatement boundStatement = + preparedStatement.bind().setSerialConsistencyLevel(DefaultConsistencyLevel.LOCAL_SERIAL); + + assertThat(boundStatement.getRequestRoutingType()).isNull(); + } + + @Test + public void should_keep_detected_lwt_routing_type_after_bound_consistency_override() { + DefaultPreparedStatement preparedStatement = + newPreparedStatement(DefaultConsistencyLevel.LOCAL_SERIAL, null, RequestRoutingType.LWT); + + BoundStatement boundStatement = + preparedStatement.bind().setConsistencyLevel(DefaultConsistencyLevel.ONE); + + assertThat(boundStatement.getRequestRoutingType()).isEqualTo(RequestRoutingType.LWT); + } + + private DefaultPreparedStatement newPreparedStatement( + ConsistencyLevel consistencyLevel, + ConsistencyLevel serialConsistencyLevel, + RequestRoutingType requestRoutingType) { + ColumnDefinitions variableDefinitions = + DefaultColumnDefinitions.valueOf(Collections.emptyList()); + return new DefaultPreparedStatement( + Bytes.fromHexString("0x"), + "SELECT * FROM test.foo WHERE pk = ?", + variableDefinitions, + Collections.emptyList(), + null, + null, + null, + null, + Collections.emptyMap(), + null, + null, + null, + null, + null, + Collections.emptyMap(), + null, + null, + null, + Integer.MIN_VALUE, + consistencyLevel, + serialConsistencyLevel, + false, + CodecRegistry.DEFAULT, + DefaultProtocolVersion.DEFAULT, + requestRoutingType); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/BasicLoadBalancingPolicyDcFailoverTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/BasicLoadBalancingPolicyDcFailoverTest.java index 4ba2c3829ce..6e8360942c9 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/BasicLoadBalancingPolicyDcFailoverTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/BasicLoadBalancingPolicyDcFailoverTest.java @@ -33,8 +33,10 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.internal.core.metadata.DefaultEndPoint; import com.datastax.oss.driver.internal.core.metadata.DefaultNode; @@ -57,6 +59,45 @@ public class BasicLoadBalancingPolicyDcFailoverTest extends BasicLoadBalancingPo @Mock protected DefaultNode node8; @Mock protected DefaultNode node9; + @Test + public void should_not_add_remote_nodes_for_preserve_routing_with_local_serial_consistency() { + when(defaultProfile.getString( + DefaultDriverOption.LOAD_BALANCING_DEFAULT_LWT_REQUEST_ROUTING_METHOD)) + .thenReturn("PRESERVE_REPLICA_ORDER"); + policy = createAndInitPolicy(); + SimpleStatement statement = + SimpleStatement.newInstance("SELECT * FROM ks.foo") + .setConsistencyLevel(DefaultConsistencyLevel.LOCAL_SERIAL) + .setRoutingKeyspace(KEYSPACE) + .setRoutingKey(ROUTING_KEY); + when(tokenMap.getReplicasList(KEYSPACE, null, ROUTING_KEY)) + .thenReturn(ImmutableList.of(node7, node1, node2)); + + assertThat(policy.newQueryPlan(statement, session)) + .containsOnlyElementsOf(policy.getLiveNodes().dc("dc1")); + } + + @Test + public void should_ignore_down_replicas_for_preserve_routing_with_local_serial_consistency() { + when(defaultProfile.getString( + DefaultDriverOption.LOAD_BALANCING_DEFAULT_LWT_REQUEST_ROUTING_METHOD)) + .thenReturn("PRESERVE_REPLICA_ORDER"); + policy = createAndInitPolicy(); + SimpleStatement statement = + SimpleStatement.newInstance("SELECT * FROM ks.foo") + .setConsistencyLevel(DefaultConsistencyLevel.LOCAL_SERIAL) + .setRoutingKeyspace(KEYSPACE) + .setRoutingKey(ROUTING_KEY); + when(tokenMap.getReplicasList(KEYSPACE, null, ROUTING_KEY)) + .thenReturn(ImmutableList.of(node7, node1, node2)); + + for (Node node : ImmutableList.copyOf(policy.getLiveNodes().dc("dc1"))) { + policy.onDown(node); + } + + assertThat(policy.newQueryPlan(statement, session)).isEmpty(); + } + @Test @Override public void should_prioritize_single_replica() { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyRequestRoutingTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyRequestRoutingTest.java index 4877659092f..43d14326ba4 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyRequestRoutingTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicyRequestRoutingTest.java @@ -25,17 +25,27 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; import com.datastax.oss.driver.api.core.RequestRoutingType; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; +import com.datastax.oss.driver.api.core.cql.BatchStatement; +import com.datastax.oss.driver.api.core.cql.BatchType; +import com.datastax.oss.driver.api.core.cql.BoundStatement; +import com.datastax.oss.driver.api.core.cql.ColumnDefinitions; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.metadata.Metadata; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.TokenMap; import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.internal.core.cql.DefaultBoundStatement; import com.datastax.oss.driver.internal.core.loadbalancing.BasicLoadBalancingPolicy.RequestRoutingMethod; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; @@ -43,6 +53,7 @@ import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet; import com.datastax.oss.protocol.internal.util.Bytes; import java.nio.ByteBuffer; +import java.util.Collections; import java.util.Optional; import java.util.Queue; import java.util.UUID; @@ -165,6 +176,259 @@ public void should_dispatch_to_regular_query_plan_when_request_is_regular() { assertThat(plan2).containsExactlyInAnyOrder(node1, node2, node3); } + @Test + public void + should_dispatch_to_preserve_query_plan_when_simple_local_serial_select_and_config_preserve() { + // Given + initPolicy("PRESERVE_REPLICA_ORDER"); + SimpleStatement statement = + SimpleStatement.builder( + "SELECT * FROM unique_key_value " + + "WHERE unique_key=? AND unique_value=? AND context=?") + .setConsistencyLevel(DefaultConsistencyLevel.LOCAL_SERIAL) + .setRoutingKeyspace(KEYSPACE) + .setRoutingKey(ROUTING_KEY) + .build(); + given(tokenMap.getReplicasList(KEYSPACE, null, ROUTING_KEY)) + .willReturn(ImmutableList.of(node1, node2)); + + // When + Queue plan = policy.newQueryPlan(statement, session); + + // Then + assertThat(statement.isLWT()).isFalse(); + assertThat(statement.getRequestRoutingType()).isEqualTo(RequestRoutingType.LWT); + assertThat(policy.getRequestRoutingMethod(statement)) + .isEqualTo(RequestRoutingMethod.PRESERVE_REPLICA_ORDER); + assertThat(plan).containsExactly(node1, node2, node3); + } + + @Test + public void + should_dispatch_to_preserve_query_plan_when_new_instance_local_serial_select_and_config_preserve() { + // Given + initPolicy("PRESERVE_REPLICA_ORDER"); + SimpleStatement statement = + SimpleStatement.newInstance( + "SELECT * FROM unique_key_value " + + "WHERE unique_key=? AND unique_value=? AND context=?") + .setConsistencyLevel(DefaultConsistencyLevel.LOCAL_SERIAL) + .setRoutingKeyspace(KEYSPACE) + .setRoutingKey(ROUTING_KEY); + given(tokenMap.getReplicasList(KEYSPACE, null, ROUTING_KEY)) + .willReturn(ImmutableList.of(node1, node2)); + + // When + Queue plan = policy.newQueryPlan(statement, session); + + // Then + assertThat(statement.isLWT()).isFalse(); + assertThat(statement.getRequestRoutingType()).isEqualTo(RequestRoutingType.LWT); + assertThat(policy.getRequestRoutingMethod(statement)) + .isEqualTo(RequestRoutingMethod.PRESERVE_REPLICA_ORDER); + assertThat(plan).containsExactly(node1, node2, node3); + } + + @Test + public void + should_dispatch_to_preserve_query_plan_when_bound_local_serial_select_and_config_preserve() { + // Given + initPolicy("PRESERVE_REPLICA_ORDER"); + BoundStatement statement = newRegularBoundStatementWithLocalSerialConsistency(); + given(tokenMap.getReplicasList(KEYSPACE, null, ROUTING_KEY)) + .willReturn(ImmutableList.of(node1, node2)); + + // When + Queue plan = policy.newQueryPlan(statement, session); + + // Then + assertThat(statement.isLWT()).isFalse(); + assertThat(statement.getRequestRoutingType()).isEqualTo(RequestRoutingType.LWT); + assertThat(policy.getRequestRoutingMethod(statement)) + .isEqualTo(RequestRoutingMethod.PRESERVE_REPLICA_ORDER); + assertThat(plan).containsExactly(node1, node2, node3); + } + + @Test + public void + should_dispatch_to_regular_query_plan_when_simple_has_only_serial_consistency_level_option() { + // Given + initPolicy("PRESERVE_REPLICA_ORDER"); + SimpleStatement statement = + SimpleStatement.builder( + "SELECT * FROM unique_key_value " + + "WHERE unique_key=? AND unique_value=? AND context=?") + .setSerialConsistencyLevel(DefaultConsistencyLevel.LOCAL_SERIAL) + .setRoutingKeyspace(KEYSPACE) + .setRoutingKey(ROUTING_KEY) + .build(); + given(tokenMap.getReplicasList(KEYSPACE, null, ROUTING_KEY)) + .willReturn(ImmutableList.of(node1, node2)); + + // When + Queue plan = policy.newQueryPlan(statement, session); + + // Then + assertThat(statement.getRequestRoutingType()).isNull(); + assertThat(policy.getRequestRoutingMethod(statement)).isEqualTo(RequestRoutingMethod.REGULAR); + assertThat(plan).containsExactlyInAnyOrder(node1, node2, node3); + } + + @Test + public void + should_dispatch_to_regular_query_plan_when_bound_has_only_serial_consistency_level_option() { + // Given + initPolicy("PRESERVE_REPLICA_ORDER"); + BoundStatement statement = newRegularBoundStatement(null, DefaultConsistencyLevel.LOCAL_SERIAL); + given(tokenMap.getReplicasList(KEYSPACE, null, ROUTING_KEY)) + .willReturn(ImmutableList.of(node1, node2)); + + // When + Queue plan = policy.newQueryPlan(statement, session); + + // Then + assertThat(statement.getRequestRoutingType()).isEqualTo(RequestRoutingType.REGULAR); + assertThat(policy.getRequestRoutingMethod(statement)).isEqualTo(RequestRoutingMethod.REGULAR); + assertThat(plan).containsExactlyInAnyOrder(node1, node2, node3); + } + + @Test + public void + should_dispatch_to_regular_query_plan_when_batch_has_only_serial_consistency_level_option() { + // Given + initPolicy("PRESERVE_REPLICA_ORDER"); + BatchStatement statement = + BatchStatement.builder(BatchType.LOGGED) + .addStatement(SimpleStatement.newInstance("UPDATE foo SET v = ? WHERE pk = ?", 1, 1)) + .setSerialConsistencyLevel(DefaultConsistencyLevel.LOCAL_SERIAL) + .setRoutingKeyspace(KEYSPACE) + .setRoutingKey(ROUTING_KEY) + .build(); + given(tokenMap.getReplicasList(KEYSPACE, null, ROUTING_KEY)) + .willReturn(ImmutableList.of(node1, node2)); + + // When + Queue plan = policy.newQueryPlan(statement, session); + + // Then + assertThat(statement.getRequestRoutingType()).isEqualTo(RequestRoutingType.REGULAR); + assertThat(policy.getRequestRoutingMethod(statement)).isEqualTo(RequestRoutingMethod.REGULAR); + assertThat(plan).containsExactlyInAnyOrder(node1, node2, node3); + } + + @Test + public void should_dispatch_to_regular_query_plan_when_local_serial_select_and_config_regular() { + // Given + initPolicy("REGULAR"); + SimpleStatement statement = + SimpleStatement.builder( + "SELECT * FROM unique_key_value " + + "WHERE unique_key=? AND unique_value=? AND context=?") + .setConsistencyLevel(DefaultConsistencyLevel.LOCAL_SERIAL) + .setRoutingKeyspace(KEYSPACE) + .setRoutingKey(ROUTING_KEY) + .build(); + given(tokenMap.getReplicasList(KEYSPACE, null, ROUTING_KEY)) + .willReturn(ImmutableList.of(node1, node2)); + + // When + Queue plan = policy.newQueryPlan(statement, session); + + // Then + assertThat(statement.isLWT()).isFalse(); + assertThat(statement.getRequestRoutingType()).isEqualTo(RequestRoutingType.LWT); + assertThat(policy.getRequestRoutingMethod(statement)).isEqualTo(RequestRoutingMethod.REGULAR); + assertThat(plan).containsExactlyInAnyOrder(node1, node2, node3); + } + + @Test + public void should_dispatch_to_preserve_query_plan_when_profile_has_local_serial_consistency() { + // Given + initPolicy("PRESERVE_REPLICA_ORDER"); + DriverExecutionProfile serialProfile = mock(DriverExecutionProfile.class); + when(serialProfile.getString(DefaultDriverOption.REQUEST_CONSISTENCY)) + .thenReturn("LOCAL_SERIAL"); + SimpleStatement statement = + SimpleStatement.builder( + "SELECT * FROM unique_key_value " + + "WHERE unique_key=? AND unique_value=? AND context=?") + .setExecutionProfile(serialProfile) + .setRoutingKeyspace(KEYSPACE) + .setRoutingKey(ROUTING_KEY) + .build(); + given(tokenMap.getReplicasList(KEYSPACE, null, ROUTING_KEY)) + .willReturn(ImmutableList.of(node1, node2)); + + // When + Queue plan = policy.newQueryPlan(statement, session); + + // Then + assertThat(statement.getConsistencyLevel()).isNull(); + assertThat(statement.getRequestRoutingType()).isNull(); + assertThat(policy.getRequestRoutingMethod(statement)) + .isEqualTo(RequestRoutingMethod.PRESERVE_REPLICA_ORDER); + assertThat(plan).containsExactly(node1, node2, node3); + } + + @Test + public void + should_dispatch_to_preserve_query_plan_when_default_profile_has_local_serial_consistency() { + // Given + initPolicy("PRESERVE_REPLICA_ORDER"); + when(defaultProfile.getString(DefaultDriverOption.REQUEST_CONSISTENCY)) + .thenReturn("LOCAL_SERIAL"); + SimpleStatement statement = + SimpleStatement.builder( + "SELECT * FROM unique_key_value " + + "WHERE unique_key=? AND unique_value=? AND context=?") + .setRoutingKeyspace(KEYSPACE) + .setRoutingKey(ROUTING_KEY) + .build(); + given(tokenMap.getReplicasList(KEYSPACE, null, ROUTING_KEY)) + .willReturn(ImmutableList.of(node1, node2)); + + // When + Queue plan = policy.newQueryPlan(statement, session); + + // Then + assertThat(statement.getConsistencyLevel()).isNull(); + assertThat(statement.getRequestRoutingType()).isNull(); + assertThat(policy.getRequestRoutingMethod(statement)) + .isEqualTo(RequestRoutingMethod.PRESERVE_REPLICA_ORDER); + assertThat(plan).containsExactly(node1, node2, node3); + } + + @Test + public void + should_dispatch_to_preserve_query_plan_when_profile_name_has_local_serial_consistency() { + // Given + initPolicy("PRESERVE_REPLICA_ORDER"); + DriverExecutionProfile serialProfile = mock(DriverExecutionProfile.class); + when(config.getProfile("serial")).thenReturn(serialProfile); + when(serialProfile.getString(DefaultDriverOption.REQUEST_CONSISTENCY)) + .thenReturn("LOCAL_SERIAL"); + SimpleStatement statement = + SimpleStatement.builder( + "SELECT * FROM unique_key_value " + + "WHERE unique_key=? AND unique_value=? AND context=?") + .setExecutionProfileName("serial") + .setRoutingKeyspace(KEYSPACE) + .setRoutingKey(ROUTING_KEY) + .build(); + given(tokenMap.getReplicasList(KEYSPACE, null, ROUTING_KEY)) + .willReturn(ImmutableList.of(node1, node2)); + + // When + Queue plan = policy.newQueryPlan(statement, session); + + // Then + assertThat(statement.getConsistencyLevel()).isNull(); + assertThat(statement.getRequestRoutingType()).isNull(); + assertThat(policy.getRequestRoutingMethod(statement)) + .isEqualTo(RequestRoutingMethod.PRESERVE_REPLICA_ORDER); + assertThat(plan).containsExactly(node1, node2, node3); + } + @Test public void should_dispatch_to_preserve_query_plan_when_lwt_and_config_preserve() { // Given @@ -186,6 +450,42 @@ public void should_dispatch_to_preserve_query_plan_when_lwt_and_config_preserve( assertThat(plan3).containsExactly(node2, node1, node3); } + private BoundStatement newRegularBoundStatementWithLocalSerialConsistency() { + return newRegularBoundStatement(DefaultConsistencyLevel.LOCAL_SERIAL, null); + } + + private BoundStatement newRegularBoundStatement( + DefaultConsistencyLevel consistencyLevel, DefaultConsistencyLevel serialConsistencyLevel) { + PreparedStatement preparedStatement = mock(PreparedStatement.class); + ColumnDefinitions variableDefinitions = mock(ColumnDefinitions.class); + when(preparedStatement.isLWT()).thenReturn(false); + when(preparedStatement.getRequestRoutingType()).thenReturn(RequestRoutingType.REGULAR); + when(preparedStatement.getVariableDefinitions()).thenReturn(variableDefinitions); + return new DefaultBoundStatement( + preparedStatement, + variableDefinitions, + new ByteBuffer[0], + null, + null, + KEYSPACE, + ROUTING_KEY, + null, + Collections.emptyMap(), + null, + false, + Statement.NO_DEFAULT_TIMESTAMP, + null, + Integer.MIN_VALUE, + consistencyLevel, + serialConsistencyLevel, + null, + null, + null, + null, + Statement.NO_NOW_IN_SECONDS, + null); + } + @Test public void should_dispatch_to_regular_query_plan_when_lwt_but_config_regular() { // Given diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/core/loadbalancing/LWTLoadBalancingMultiDcIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/core/loadbalancing/LWTLoadBalancingMultiDcIT.java index 3e7d4de2b27..50ab832523c 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/core/loadbalancing/LWTLoadBalancingMultiDcIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/core/loadbalancing/LWTLoadBalancingMultiDcIT.java @@ -23,9 +23,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +import static org.junit.Assume.assumeTrue; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.RequestRoutingType; import com.datastax.oss.driver.api.core.Version; @@ -33,8 +35,10 @@ import com.datastax.oss.driver.api.core.cql.BatchStatement; import com.datastax.oss.driver.api.core.cql.BatchStatementBuilder; import com.datastax.oss.driver.api.core.cql.BatchType; +import com.datastax.oss.driver.api.core.cql.BoundStatement; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.TokenMap; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; @@ -57,6 +61,7 @@ public class LWTLoadBalancingMultiDcIT { private static final String LOCAL_DC = "dc1"; private static final String KEYSPACE = "test"; + private static final String LOCAL_SERIAL_PROFILE = "local-serial"; private static final CustomCcmRule CCM_RULE = CustomCcmRule.builder().withNodes(2, 1).build(); // 2 nodes in DC1, 1 node in DC2 @@ -67,7 +72,12 @@ public class LWTLoadBalancingMultiDcIT { .withConfigLoader( SessionUtils.configLoaderBuilder() .withString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, LOCAL_DC) + .withString( + DefaultDriverOption.LOAD_BALANCING_DEFAULT_LWT_REQUEST_ROUTING_METHOD, + "PRESERVE_REPLICA_ORDER") .withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofSeconds(30)) + .startProfile(LOCAL_SERIAL_PROFILE) + .withString(DefaultDriverOption.REQUEST_CONSISTENCY, "LOCAL_SERIAL") .build()) .build(); @@ -76,6 +86,7 @@ public class LWTLoadBalancingMultiDcIT { public static final int FIRST_TEST_PARTITION_KEY = 4242; public static final int SECOND_TEST_PARTITION_KEY = 4343; + public static final int THIRD_TEST_PARTITION_KEY = 4444; public static final int NUM_TEST_ITERATIONS = 30; @BeforeClass @@ -207,4 +218,59 @@ public void should_route_lwt_batch_to_local_dc_replicas() { assertThat(coordinators).isSubsetOf(localReplicas); assertThat(coordinatorDcs).containsOnly(LOCAL_DC); } + + @Test + public void should_route_prepared_local_serial_simple_select_as_lwt() { + assumeTrue(CcmBridge.isDistributionOf(BackendType.SCYLLA)); + + CqlSession session = SESSION_RULE.session(); + SimpleStatement simpleSelect = + SimpleStatement.builder("SELECT * FROM test.foo WHERE pk = ? AND ck = ?") + .setConsistencyLevel(DefaultConsistencyLevel.LOCAL_SERIAL) + .build(); + PreparedStatement select = session.prepare(simpleSelect); + BoundStatement statement = select.bind(THIRD_TEST_PARTITION_KEY, 0); + + assertThat(simpleSelect.isLWT()).isFalse(); + assertThat(simpleSelect.getRequestRoutingType()).isEqualTo(RequestRoutingType.LWT); + assertThat(simpleSelect.getConsistencyLevel()).isEqualTo(DefaultConsistencyLevel.LOCAL_SERIAL); + + assertThat(select.getRequestRoutingType()).isEqualTo(RequestRoutingType.LWT); + + assertThat(statement.getRequestRoutingType()).isEqualTo(RequestRoutingType.LWT); + assertThat(statement.getRoutingKeyspace()).isEqualTo(CqlIdentifier.fromCql(KEYSPACE)); + assertThat(statement.getRoutingKey()).isNotNull(); + assertThat(statement.getConsistencyLevel()).isEqualTo(DefaultConsistencyLevel.LOCAL_SERIAL); + + ResultSet result = session.execute(statement); + assertThat(result.getExecutionInfo().getCoordinator()).isNotNull(); + } + + @Test + public void should_route_prepared_profiled_local_serial_simple_select_with_lwt_policy() { + assumeTrue(CcmBridge.isDistributionOf(BackendType.SCYLLA)); + + CqlSession session = SESSION_RULE.session(); + SimpleStatement simpleSelect = + SimpleStatement.builder("SELECT * FROM test.foo WHERE pk = ? AND ck = ?") + .setExecutionProfileName(LOCAL_SERIAL_PROFILE) + .build(); + PreparedStatement select = session.prepare(simpleSelect); + BoundStatement statement = select.bind(THIRD_TEST_PARTITION_KEY, 0); + + assertThat(simpleSelect.isLWT()).isFalse(); + assertThat(simpleSelect.getRequestRoutingType()).isNull(); + assertThat(simpleSelect.getConsistencyLevel()).isNull(); + + assertThat(select.getRequestRoutingType()).isNull(); + + assertThat(statement.getRequestRoutingType()).isNull(); + assertThat(statement.getExecutionProfileName()).isEqualTo(LOCAL_SERIAL_PROFILE); + assertThat(statement.getRoutingKeyspace()).isEqualTo(CqlIdentifier.fromCql(KEYSPACE)); + assertThat(statement.getRoutingKey()).isNotNull(); + assertThat(statement.getConsistencyLevel()).isNull(); + + ResultSet result = session.execute(statement); + assertThat(result.getExecutionInfo().getCoordinator()).isNotNull(); + } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/core/metadata/DefaultMetadataTabletMapIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/core/metadata/DefaultMetadataTabletMapIT.java index 9a01a8c5164..2395e55f5c0 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/core/metadata/DefaultMetadataTabletMapIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/core/metadata/DefaultMetadataTabletMapIT.java @@ -4,6 +4,9 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; +import com.datastax.oss.driver.api.core.RequestRoutingType; +import com.datastax.oss.driver.api.core.Version; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.cql.ResultSet; @@ -14,6 +17,7 @@ import com.datastax.oss.driver.api.core.metadata.Tablet; import com.datastax.oss.driver.api.testinfra.ScyllaOnly; import com.datastax.oss.driver.api.testinfra.ScyllaRequirement; +import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; @@ -25,6 +29,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentSkipListSet; @@ -49,7 +54,10 @@ public class DefaultMetadataTabletMapIT { private static final Logger LOG = LoggerFactory.getLogger(DefaultMetadataTabletMapIT.class); private static final CustomCcmRule CCM_RULE = CustomCcmRule.builder() - .withNodes(2) + // Drop nodes back to 2 once https://github.com/scylladb/scylladb/issues/29874 is fixed + // After 2026.1 Scylla does not send tablet hint on a wrong-shard for LWT queries + // 3rd node makes one node completely incorrect, that is when Scylla sends tablet hint + .withNodes(3) .withCassandraConfiguration( "experimental_features", "['consistent-topology-changes','tablets']") .build(); @@ -67,6 +75,8 @@ public class DefaultMetadataTabletMapIT { private static final int INITIAL_TABLETS = 32; private static final int QUERIES = 1600; private static final int REPLICATION_FACTOR = 2; + private static final Version SCYLLA_LWT_TABLETS_SUPPORT_VERSION = + Objects.requireNonNull(Version.parse("2026.1")); private static final String KEYSPACE_NAME = "tabletsTest"; private static final String TABLE_NAME = "tabletsTable"; private static final String CREATE_KEYSPACE_QUERY = @@ -120,6 +130,14 @@ public class DefaultMetadataTabletMapIT { private static final SimpleStatement STMT_SELECT_CK_CONCRETE = buildStatement("SELECT pk, ck FROM %s.%s WHERE pk = ? AND ck = 1"); + private static final SimpleStatement STMT_SELECT_LOCAL_SERIAL = + buildStatement("SELECT pk,ck FROM %s.%s WHERE pk = ? AND ck = ?") + .setConsistencyLevel(DefaultConsistencyLevel.LOCAL_SERIAL); + + private static final SimpleStatement STMT_SELECT_SERIAL = + buildStatement("SELECT pk,ck FROM %s.%s WHERE pk = ? AND ck = ?") + .setConsistencyLevel(DefaultConsistencyLevel.SERIAL); + private static final SimpleStatement STMT_UPDATE = buildStatement("UPDATE %s.%s SET val = 1 WHERE pk = ? AND ck = ?"); @@ -195,6 +213,9 @@ public void every_statement_should_deliver_tablet_info() { statements.put("SELECT_CONCRETE_PREPARED", s -> s.prepare(STMT_SELECT_CONCRETE).bind()); statements.put("SELECT_PK_CONCRETE_PREPARED", s -> s.prepare(STMT_SELECT_PK_CONCRETE).bind(2)); statements.put("SELECT_CK_CONCRETE_PREPARED", s -> s.prepare(STMT_SELECT_CK_CONCRETE).bind(2)); + statements.put( + "SELECT_LOCAL_SERIAL_PREPARED", s -> s.prepare(STMT_SELECT_LOCAL_SERIAL).bind(2, 2)); + statements.put("SELECT_SERIAL_PREPARED", s -> s.prepare(STMT_SELECT_SERIAL).bind(2, 2)); statements.put("INSERT_CONCRETE", s -> STMT_INSERT_CONCRETE); statements.put("INSERT_PREPARED", s -> s.prepare(STMT_INSERT).bind(2, 2)); statements.put("INSERT_NO_KS_PREPARED", s -> s.prepare(STMT_INSERT_NO_KS).bind(2, 2)); @@ -227,8 +248,9 @@ public void every_statement_should_deliver_tablet_info() { // Scylla does not return tablet info for queries with PK built into query continue; } - if (stmtEntry.getKey().contains("LWT")) { - // LWT is not yet supported by scylla on tables with tablets + if ((stmtEntry.getKey().contains("LWT") || stmtEntry.getKey().contains("SERIAL")) + && !isLWTTabletsSupported()) { + // LWT is supported on tables with tablets starting with Scylla 2026.1. continue; } if (sessionEntry.getKey().equals("REGULAR") && stmtEntry.getKey().contains("NO_KS")) { @@ -256,6 +278,17 @@ public void every_statement_should_deliver_tablet_info() { ex.addSuppressed(e); throw ex; } + + if (stmtEntry.getKey().contains("SERIAL")) { + if (stmt.getRequestRoutingType() != RequestRoutingType.LWT) { + testErrors.add( + String.format( + "Statement %s on session %s is routed as regular query", + stmtEntry.getKey(), sessionEntry.getKey())); + continue; + } + } + try { if (!executeOnAllHostsAndReturnIfResultHasTabletsInfo(session, stmt)) { testErrors.add( @@ -296,7 +329,7 @@ public void every_statement_should_deliver_tablet_info() { } @Test - public void should_receive_each_tablet_exactly_once() { + public void should_receive_all_tablets_and_stop_receiving_tablet_info() { int counter = 0; try (CqlSession session = CqlSession.builder().addContactEndPoints(CCM_RULE.getContactPoints()).build()) { @@ -306,7 +339,7 @@ public void should_receive_each_tablet_exactly_once() { counter++; } } - Assert.assertEquals(INITIAL_TABLETS, counter); + assertReceivedAtLeastOnePayloadPerTablet(counter); assertSessionTabletMapIsFilled(session); } @@ -322,8 +355,8 @@ public void should_receive_each_tablet_exactly_once() { LOG.debug("Ran first set of queries"); - // With enough queries we should hit a wrong node for each tablet exactly once. - Assert.assertEquals(INITIAL_TABLETS, counter); + // With enough queries we should hit a wrong node for each tablet at least once. + assertReceivedAtLeastOnePayloadPerTablet(counter); assertSessionTabletMapIsFilled(session); // All tablet information should be available by now (unless for some reason cluster did sth @@ -342,6 +375,13 @@ public void should_receive_each_tablet_exactly_once() { } } + private static void assertReceivedAtLeastOnePayloadPerTablet(int payloadsCount) { + Assert.assertTrue( + String.format( + "Expected at least %d tablet payloads, got %d", INITIAL_TABLETS, payloadsCount), + payloadsCount >= INITIAL_TABLETS); + } + private static boolean waitSessionLearnedTabletInfo(CqlSession session) { try { await() @@ -354,6 +394,12 @@ private static boolean waitSessionLearnedTabletInfo(CqlSession session) { } } + private static boolean isLWTTabletsSupported() { + return CcmBridge.getScyllaVersion() + .map(version -> version.compareTo(SCYLLA_LWT_TABLETS_SUPPORT_VERSION) >= 0) + .orElse(false); + } + private static boolean checkIfRoutedProperly(CqlSession session, Statement stmt) { // DefaultLoadBalancingPolicy suppose to prioritize nodes from replica list randomly shuffling // them From 940259a8e4dde5cf8b16cd4ffc7926e32625565f Mon Sep 17 00:00:00 2001 From: Mikita Hradovich Date: Wed, 13 May 2026 14:30:25 +0200 Subject: [PATCH 09/32] fix: extend NodeDiagnostics with node state, distance, datacenter and pool size (DRIVER-540) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add four additional fields to DriverTimeoutException.NodeDiagnostics captured at timeout time: - nodeState: UP/DOWN/FORCED_DOWN/UNKNOWN — immediately explains timeouts to downed nodes - nodeDistance: LOCAL/REMOTE/IGNORED — contextualizes latency expectations - datacenter: node DC — helps diagnose cross-DC routing issues - poolSize: active connection count — reveals degraded pools (fewer connections than expected) All four are available from Node and ChannelPool already in scope at each buildNodeDiagnostics() call site. No new infrastructure required. Updated toString() example: /10.0.0.1:9042 [state: UP, distance: LOCAL, dc: dc1, channel in-flight: 5, pool size: 3, pool in-flight: 12, pool available ids: 988, pool orphaned ids: 2] --- .../ContinuousRequestHandlerBase.java | 8 ++ .../core/graph/GraphRequestHandler.java | 4 + .../api/core/DriverTimeoutException.java | 132 ++++++++++++++++-- .../internal/core/cql/CqlPrepareHandler.java | 4 + .../internal/core/cql/CqlRequestHandler.java | 4 + 5 files changed, 143 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/com/datastax/dse/driver/internal/core/cql/continuous/ContinuousRequestHandlerBase.java b/core/src/main/java/com/datastax/dse/driver/internal/core/cql/continuous/ContinuousRequestHandlerBase.java index 8d10172c27b..0107ebd1176 100644 --- a/core/src/main/java/com/datastax/dse/driver/internal/core/cql/continuous/ContinuousRequestHandlerBase.java +++ b/core/src/main/java/com/datastax/dse/driver/internal/core/cql/continuous/ContinuousRequestHandlerBase.java @@ -414,7 +414,11 @@ private NodeDiagnostics buildNodeDiagnostics() { ChannelPool pool = session.getPools().get(cb.node); return NodeDiagnostics.of( cb.node.getEndPoint(), + cb.node.getState(), + cb.node.getDistance(), + cb.node.getDatacenter(), channelInFlight, + pool != null ? pool.size() : UNAVAILABLE, pool != null ? pool.getInFlight() : UNAVAILABLE, pool != null ? pool.getAvailableIds() : UNAVAILABLE, pool != null ? pool.getOrphanedIds() : UNAVAILABLE); @@ -748,7 +752,11 @@ private void onPageTimeout(int expectedPage) { "Timed out waiting for page " + expectedPage, NodeDiagnostics.of( node.getEndPoint(), + node.getState(), + node.getDistance(), + node.getDatacenter(), channelInFlight, + pool != null ? pool.size() : UNAVAILABLE, pool != null ? pool.getInFlight() : UNAVAILABLE, pool != null ? pool.getAvailableIds() : UNAVAILABLE, pool != null ? pool.getOrphanedIds() : UNAVAILABLE)), diff --git a/core/src/main/java/com/datastax/dse/driver/internal/core/graph/GraphRequestHandler.java b/core/src/main/java/com/datastax/dse/driver/internal/core/graph/GraphRequestHandler.java index 1e7dcd0f0f9..015467a66fa 100644 --- a/core/src/main/java/com/datastax/dse/driver/internal/core/graph/GraphRequestHandler.java +++ b/core/src/main/java/com/datastax/dse/driver/internal/core/graph/GraphRequestHandler.java @@ -248,7 +248,11 @@ private NodeDiagnostics buildNodeDiagnostics() { ChannelPool pool = session.getPools().get(cb.node); return NodeDiagnostics.of( cb.node.getEndPoint(), + cb.node.getState(), + cb.node.getDistance(), + cb.node.getDatacenter(), channelInFlight, + pool != null ? pool.size() : UNAVAILABLE, pool != null ? pool.getInFlight() : UNAVAILABLE, pool != null ? pool.getAvailableIds() : UNAVAILABLE, pool != null ? pool.getOrphanedIds() : UNAVAILABLE); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/DriverTimeoutException.java b/core/src/main/java/com/datastax/oss/driver/api/core/DriverTimeoutException.java index 81bf6c7e2f5..b98976ad0b5 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/DriverTimeoutException.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/DriverTimeoutException.java @@ -18,7 +18,9 @@ package com.datastax.oss.driver.api.core; import com.datastax.oss.driver.api.core.cql.ExecutionInfo; +import com.datastax.oss.driver.api.core.loadbalancing.NodeDistance; import com.datastax.oss.driver.api.core.metadata.EndPoint; +import com.datastax.oss.driver.api.core.metadata.NodeState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -43,8 +45,14 @@ public class DriverTimeoutException extends DriverException { *

Fields: * *

    + *
  • {@link #getNodeState()}: the state of the node (UP, DOWN, etc.) at timeout time. + *
  • {@link #getNodeDistance()}: the distance assigned to the node by the load-balancing + * policy (LOCAL, REMOTE, or IGNORED). + *
  • {@link #getDatacenter()}: the datacenter the node belongs to. *
  • {@link #getChannelInFlight()}: requests currently awaiting a response on the specific * connection used for this request. + *
  • {@link #getPoolSize()}: number of active connections in the pool ({@link #UNAVAILABLE} if + * the pool was already removed). *
  • {@link #getPoolInFlight()}: total in-flight across all connections to this host ({@link * #UNAVAILABLE} if the pool was already removed). *
  • {@link #getPoolAvailableIds()}: remaining stream IDs available to send new requests; a @@ -63,12 +71,18 @@ public class DriverTimeoutException extends DriverException { * were sent but not answered within the timeout. *
  • High {@code poolOrphanedIds} → previous timeouts consumed stream IDs that the driver is * still waiting to reclaim. + *
  • {@code poolSize} below expected → pool is degraded; some connections have been lost. + *
  • {@code nodeState} DOWN or FORCED_DOWN → node is known to be unavailable. *
*/ public static final class NodeDiagnostics { @NonNull private final EndPoint endPoint; + @Nullable private final NodeState nodeState; + @Nullable private final NodeDistance nodeDistance; + @Nullable private final String datacenter; private final int channelInFlight; + private final int poolSize; private final int poolInFlight; private final int poolAvailableIds; private final int poolOrphanedIds; @@ -77,19 +91,31 @@ public static final class NodeDiagnostics { * Creates a full diagnostic snapshot (pool was available at timeout time). * * @param endPoint the endpoint of the node. + * @param nodeState the state of the node at timeout time. + * @param nodeDistance the distance assigned to the node by the load-balancing policy. + * @param datacenter the datacenter the node belongs to. * @param channelInFlight in-flight count on the specific channel. + * @param poolSize number of active connections in the pool. * @param poolInFlight total in-flight across the pool for this host. * @param poolAvailableIds remaining stream IDs available in the pool. * @param poolOrphanedIds orphaned stream IDs in the pool. */ public NodeDiagnostics( @NonNull EndPoint endPoint, + @Nullable NodeState nodeState, + @Nullable NodeDistance nodeDistance, + @Nullable String datacenter, int channelInFlight, + int poolSize, int poolInFlight, int poolAvailableIds, int poolOrphanedIds) { this.endPoint = endPoint; + this.nodeState = nodeState; + this.nodeDistance = nodeDistance; + this.datacenter = datacenter; this.channelInFlight = channelInFlight; + this.poolSize = poolSize; this.poolInFlight = poolInFlight; this.poolAvailableIds = poolAvailableIds; this.poolOrphanedIds = poolOrphanedIds; @@ -97,14 +123,32 @@ public NodeDiagnostics( /** * Creates a partial diagnostic snapshot for when the pool was unavailable at timeout time. The - * pool-related fields ({@link #getPoolInFlight()}, {@link #getPoolAvailableIds()}, {@link - * #getPoolOrphanedIds()}) will be {@link DriverTimeoutException#UNAVAILABLE}. + * pool-related fields ({@link #getPoolSize()}, {@link #getPoolInFlight()}, {@link + * #getPoolAvailableIds()}, {@link #getPoolOrphanedIds()}) will be {@link + * DriverTimeoutException#UNAVAILABLE}. * * @param endPoint the endpoint of the node. + * @param nodeState the state of the node at timeout time. + * @param nodeDistance the distance assigned to the node by the load-balancing policy. + * @param datacenter the datacenter the node belongs to. * @param channelInFlight in-flight count on the specific channel. */ - public NodeDiagnostics(@NonNull EndPoint endPoint, int channelInFlight) { - this(endPoint, channelInFlight, UNAVAILABLE, UNAVAILABLE, UNAVAILABLE); + public NodeDiagnostics( + @NonNull EndPoint endPoint, + @Nullable NodeState nodeState, + @Nullable NodeDistance nodeDistance, + @Nullable String datacenter, + int channelInFlight) { + this( + endPoint, + nodeState, + nodeDistance, + datacenter, + channelInFlight, + UNAVAILABLE, + UNAVAILABLE, + UNAVAILABLE, + UNAVAILABLE); } /** @@ -113,7 +157,12 @@ public NodeDiagnostics(@NonNull EndPoint endPoint, int channelInFlight) { * timeout time. * * @param endPoint the endpoint of the node. + * @param nodeState the state of the node at timeout time. + * @param nodeDistance the distance assigned to the node by the load-balancing policy. + * @param datacenter the datacenter the node belongs to. * @param channelInFlight in-flight count on the specific channel. + * @param poolSize number of active connections in the pool, or {@link + * DriverTimeoutException#UNAVAILABLE}. * @param poolInFlight total in-flight across the pool, or {@link * DriverTimeoutException#UNAVAILABLE}. * @param poolAvailableIds remaining stream IDs in the pool, or {@link @@ -124,12 +173,24 @@ public NodeDiagnostics(@NonNull EndPoint endPoint, int channelInFlight) { @NonNull public static NodeDiagnostics of( @NonNull EndPoint endPoint, + @Nullable NodeState nodeState, + @Nullable NodeDistance nodeDistance, + @Nullable String datacenter, int channelInFlight, + int poolSize, int poolInFlight, int poolAvailableIds, int poolOrphanedIds) { return new NodeDiagnostics( - endPoint, channelInFlight, poolInFlight, poolAvailableIds, poolOrphanedIds); + endPoint, + nodeState, + nodeDistance, + datacenter, + channelInFlight, + poolSize, + poolInFlight, + poolAvailableIds, + poolOrphanedIds); } /** Returns the endpoint of the node that had in-flight requests at timeout time. */ @@ -138,6 +199,30 @@ public EndPoint getEndPoint() { return endPoint; } + /** + * Returns the state of the node at timeout time (e.g. UP, DOWN, FORCED_DOWN), or {@code null} + * if not available. + */ + @Nullable + public NodeState getNodeState() { + return nodeState; + } + + /** + * Returns the distance assigned to this node by the load-balancing policy at timeout time (e.g. + * LOCAL, REMOTE, IGNORED), or {@code null} if not available. + */ + @Nullable + public NodeDistance getNodeDistance() { + return nodeDistance; + } + + /** Returns the datacenter this node belongs to, or {@code null} if not available. */ + @Nullable + public String getDatacenter() { + return datacenter; + } + /** * Returns the number of in-flight requests on the specific connection at timeout time, or * {@link DriverTimeoutException#UNAVAILABLE} if not available. @@ -146,6 +231,14 @@ public int getChannelInFlight() { return channelInFlight; } + /** + * Returns the number of active connections in the pool at timeout time, or {@link + * DriverTimeoutException#UNAVAILABLE} if the pool was no longer available. + */ + public int getPoolSize() { + return poolSize; + } + /** * Returns the total number of in-flight requests across all connections to this host at timeout * time, or {@link DriverTimeoutException#UNAVAILABLE} if the pool was no longer available. @@ -174,12 +267,33 @@ public int getPoolOrphanedIds() { @Override public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(endPoint); + sb.append(" ["); + if (nodeState != null) { + sb.append("state: ").append(nodeState).append(", "); + } + if (nodeDistance != null) { + sb.append("distance: ").append(nodeDistance).append(", "); + } + if (datacenter != null) { + sb.append("dc: ").append(datacenter).append(", "); + } + sb.append("channel in-flight: ").append(channelInFlight).append(", "); if (poolInFlight == UNAVAILABLE) { - return String.format("%s [channel in-flight: %d, pool: n/a]", endPoint, channelInFlight); + sb.append("pool: n/a"); + } else { + sb.append("pool size: ") + .append(poolSize) + .append(", pool in-flight: ") + .append(poolInFlight) + .append(", pool available ids: ") + .append(poolAvailableIds) + .append(", pool orphaned ids: ") + .append(poolOrphanedIds); } - return String.format( - "%s [channel in-flight: %d, pool in-flight: %d, pool available ids: %d, pool orphaned ids: %d]", - endPoint, channelInFlight, poolInFlight, poolAvailableIds, poolOrphanedIds); + sb.append("]"); + return sb.toString(); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java index 4c99726a8b4..69e98ca5197 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareHandler.java @@ -195,7 +195,11 @@ private NodeDiagnostics buildNodeDiagnostics() { ChannelPool pool = session.getPools().get(cb.node); return NodeDiagnostics.of( cb.node.getEndPoint(), + cb.node.getState(), + cb.node.getDistance(), + cb.node.getDatacenter(), channelInFlight, + pool != null ? pool.size() : UNAVAILABLE, pool != null ? pool.getInFlight() : UNAVAILABLE, pool != null ? pool.getAvailableIds() : UNAVAILABLE, pool != null ? pool.getOrphanedIds() : UNAVAILABLE); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java index e3391161c59..25f27e5a39d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java @@ -263,7 +263,11 @@ private NodeDiagnostics buildNodeDiagnostics() { ChannelPool pool = session.getPools().get(cb.node); return NodeDiagnostics.of( cb.node.getEndPoint(), + cb.node.getState(), + cb.node.getDistance(), + cb.node.getDatacenter(), channelInFlight, + pool != null ? pool.size() : UNAVAILABLE, pool != null ? pool.getInFlight() : UNAVAILABLE, pool != null ? pool.getAvailableIds() : UNAVAILABLE, pool != null ? pool.getOrphanedIds() : UNAVAILABLE); From 4e3fdba89f0351e4d43b6ab4922452c68c08c8b0 Mon Sep 17 00:00:00 2001 From: ScyllaDB Promoter Date: Thu, 14 May 2026 11:10:21 +0000 Subject: [PATCH 10/32] [maven-release-plugin] prepare release 4.19.0.9 --- bom/pom.xml | 20 ++++++++++---------- core-shaded/pom.xml | 2 +- core/pom.xml | 2 +- distribution-source/pom.xml | 2 +- distribution-tests/pom.xml | 2 +- distribution/pom.xml | 2 +- examples/pom.xml | 2 +- guava-shaded/pom.xml | 2 +- integration-tests/pom.xml | 2 +- mapper-processor/pom.xml | 2 +- mapper-runtime/pom.xml | 2 +- metrics/micrometer/pom.xml | 2 +- metrics/microprofile/pom.xml | 2 +- osgi-tests/pom.xml | 2 +- pom.xml | 6 +++--- query-builder/pom.xml | 2 +- test-infra/pom.xml | 2 +- 17 files changed, 28 insertions(+), 28 deletions(-) diff --git a/bom/pom.xml b/bom/pom.xml index e634fbbdb5a..db5fe6327dc 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9-SNAPSHOT + 4.19.0.9 java-driver-bom pom @@ -38,47 +38,47 @@ com.scylladb java-driver-core - 4.19.0.9-SNAPSHOT + 4.19.0.9 com.scylladb java-driver-core-shaded - 4.19.0.9-SNAPSHOT + 4.19.0.9 com.scylladb java-driver-mapper-processor - 4.19.0.9-SNAPSHOT + 4.19.0.9 com.scylladb java-driver-mapper-runtime - 4.19.0.9-SNAPSHOT + 4.19.0.9 com.scylladb java-driver-query-builder - 4.19.0.9-SNAPSHOT + 4.19.0.9 com.scylladb java-driver-guava-shaded - 4.19.0.9-SNAPSHOT + 4.19.0.9 com.scylladb java-driver-test-infra - 4.19.0.9-SNAPSHOT + 4.19.0.9 com.scylladb java-driver-metrics-micrometer - 4.19.0.9-SNAPSHOT + 4.19.0.9 com.scylladb java-driver-metrics-microprofile - 4.19.0.9-SNAPSHOT + 4.19.0.9 com.scylladb diff --git a/core-shaded/pom.xml b/core-shaded/pom.xml index 771298cceac..75905c7a672 100644 --- a/core-shaded/pom.xml +++ b/core-shaded/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9-SNAPSHOT + 4.19.0.9 java-driver-core-shaded Java driver for Scylla and Apache Cassandra(R) - core with shaded deps diff --git a/core/pom.xml b/core/pom.xml index ec7b9a2e96a..90ea515b7c4 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9-SNAPSHOT + 4.19.0.9 java-driver-core bundle diff --git a/distribution-source/pom.xml b/distribution-source/pom.xml index 3c0a8ddd2f6..552af583972 100644 --- a/distribution-source/pom.xml +++ b/distribution-source/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9-SNAPSHOT + 4.19.0.9 java-driver-distribution-source pom diff --git a/distribution-tests/pom.xml b/distribution-tests/pom.xml index ef02fee6750..1d508a290b5 100644 --- a/distribution-tests/pom.xml +++ b/distribution-tests/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9-SNAPSHOT + 4.19.0.9 java-driver-distribution-tests Scylla Java Driver - distribution tests diff --git a/distribution/pom.xml b/distribution/pom.xml index 450bcfaa5da..765bab43d5d 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9-SNAPSHOT + 4.19.0.9 java-driver-distribution diff --git a/examples/pom.xml b/examples/pom.xml index 97d5841dc3b..0d54466e259 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ java-driver-parent com.scylladb - 4.19.0.9-SNAPSHOT + 4.19.0.9 java-driver-examples Java driver for Scylla and Apache Cassandra(R) - examples. diff --git a/guava-shaded/pom.xml b/guava-shaded/pom.xml index 294ca379019..fe4569bda1a 100644 --- a/guava-shaded/pom.xml +++ b/guava-shaded/pom.xml @@ -23,7 +23,7 @@ com.scylladb java-driver-parent - 4.19.0.9-SNAPSHOT + 4.19.0.9 java-driver-guava-shaded Scylla Java Driver - guava shaded dep diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index cf08e744b23..7eb99a4c1ed 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9-SNAPSHOT + 4.19.0.9 java-driver-integration-tests jar diff --git a/mapper-processor/pom.xml b/mapper-processor/pom.xml index af732fa2c88..f94ce1bdea7 100644 --- a/mapper-processor/pom.xml +++ b/mapper-processor/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9-SNAPSHOT + 4.19.0.9 java-driver-mapper-processor Java driver for Scylla and Apache Cassandra(R) - object mapper processor diff --git a/mapper-runtime/pom.xml b/mapper-runtime/pom.xml index cea3d4d84db..3934a4653b6 100644 --- a/mapper-runtime/pom.xml +++ b/mapper-runtime/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9-SNAPSHOT + 4.19.0.9 java-driver-mapper-runtime bundle diff --git a/metrics/micrometer/pom.xml b/metrics/micrometer/pom.xml index cf5f895000e..00119c65aeb 100644 --- a/metrics/micrometer/pom.xml +++ b/metrics/micrometer/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9-SNAPSHOT + 4.19.0.9 ../../ java-driver-metrics-micrometer diff --git a/metrics/microprofile/pom.xml b/metrics/microprofile/pom.xml index 76cb064588c..ab153efbb1f 100644 --- a/metrics/microprofile/pom.xml +++ b/metrics/microprofile/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9-SNAPSHOT + 4.19.0.9 ../../ java-driver-metrics-microprofile diff --git a/osgi-tests/pom.xml b/osgi-tests/pom.xml index ceb86ccd041..b276d05e05f 100644 --- a/osgi-tests/pom.xml +++ b/osgi-tests/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9-SNAPSHOT + 4.19.0.9 java-driver-osgi-tests jar diff --git a/pom.xml b/pom.xml index 2bff8332440..3319f64d5b7 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ 4.0.0 com.scylladb java-driver-parent - 4.19.0.9-SNAPSHOT + 4.19.0.9 pom Java Driver for Scylla and Apache Cassandra A driver for Scylla and Apache Cassandra(R) 2.1+ that works exclusively with the Cassandra Query Language version 3 (CQL3) and Cassandra's native protocol versions 3 and above. @@ -100,7 +100,7 @@ ${skipTests} false false - + @@ -1141,7 +1141,7 @@ height="0" width="0" style="display:none;visibility:hidden"> scm:git:https://github.com/scylladb/java-driver scm:git:https://github.com/scylladb/java-driver https://github.com/scylladb/java-driver - HEAD + 4.19.0.9 diff --git a/query-builder/pom.xml b/query-builder/pom.xml index f5ff1dc582a..76739930f5d 100644 --- a/query-builder/pom.xml +++ b/query-builder/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9-SNAPSHOT + 4.19.0.9 java-driver-query-builder bundle diff --git a/test-infra/pom.xml b/test-infra/pom.xml index 28262fa91d3..9616082d72f 100644 --- a/test-infra/pom.xml +++ b/test-infra/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9-SNAPSHOT + 4.19.0.9 java-driver-test-infra bundle From 9149592b462f9244d4c4758fb209f0450d575589 Mon Sep 17 00:00:00 2001 From: ScyllaDB Promoter Date: Thu, 14 May 2026 11:10:22 +0000 Subject: [PATCH 11/32] [maven-release-plugin] prepare for next development iteration --- bom/pom.xml | 20 ++++++++++---------- core-shaded/pom.xml | 2 +- core/pom.xml | 2 +- distribution-source/pom.xml | 2 +- distribution-tests/pom.xml | 2 +- distribution/pom.xml | 2 +- examples/pom.xml | 2 +- guava-shaded/pom.xml | 2 +- integration-tests/pom.xml | 2 +- mapper-processor/pom.xml | 2 +- mapper-runtime/pom.xml | 2 +- metrics/micrometer/pom.xml | 2 +- metrics/microprofile/pom.xml | 2 +- osgi-tests/pom.xml | 2 +- pom.xml | 6 +++--- query-builder/pom.xml | 2 +- test-infra/pom.xml | 2 +- 17 files changed, 28 insertions(+), 28 deletions(-) diff --git a/bom/pom.xml b/bom/pom.xml index db5fe6327dc..1502064c25a 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9 + 4.19.0.10-SNAPSHOT java-driver-bom pom @@ -38,47 +38,47 @@ com.scylladb java-driver-core - 4.19.0.9 + 4.19.0.10-SNAPSHOT com.scylladb java-driver-core-shaded - 4.19.0.9 + 4.19.0.10-SNAPSHOT com.scylladb java-driver-mapper-processor - 4.19.0.9 + 4.19.0.10-SNAPSHOT com.scylladb java-driver-mapper-runtime - 4.19.0.9 + 4.19.0.10-SNAPSHOT com.scylladb java-driver-query-builder - 4.19.0.9 + 4.19.0.10-SNAPSHOT com.scylladb java-driver-guava-shaded - 4.19.0.9 + 4.19.0.10-SNAPSHOT com.scylladb java-driver-test-infra - 4.19.0.9 + 4.19.0.10-SNAPSHOT com.scylladb java-driver-metrics-micrometer - 4.19.0.9 + 4.19.0.10-SNAPSHOT com.scylladb java-driver-metrics-microprofile - 4.19.0.9 + 4.19.0.10-SNAPSHOT com.scylladb diff --git a/core-shaded/pom.xml b/core-shaded/pom.xml index 75905c7a672..1dda9f1db8f 100644 --- a/core-shaded/pom.xml +++ b/core-shaded/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9 + 4.19.0.10-SNAPSHOT java-driver-core-shaded Java driver for Scylla and Apache Cassandra(R) - core with shaded deps diff --git a/core/pom.xml b/core/pom.xml index 90ea515b7c4..0d1538502e3 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9 + 4.19.0.10-SNAPSHOT java-driver-core bundle diff --git a/distribution-source/pom.xml b/distribution-source/pom.xml index 552af583972..19ad9f68bf1 100644 --- a/distribution-source/pom.xml +++ b/distribution-source/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9 + 4.19.0.10-SNAPSHOT java-driver-distribution-source pom diff --git a/distribution-tests/pom.xml b/distribution-tests/pom.xml index 1d508a290b5..5ff2f590dc0 100644 --- a/distribution-tests/pom.xml +++ b/distribution-tests/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9 + 4.19.0.10-SNAPSHOT java-driver-distribution-tests Scylla Java Driver - distribution tests diff --git a/distribution/pom.xml b/distribution/pom.xml index 765bab43d5d..cb38a8c8475 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9 + 4.19.0.10-SNAPSHOT java-driver-distribution diff --git a/examples/pom.xml b/examples/pom.xml index 0d54466e259..19e70197168 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ java-driver-parent com.scylladb - 4.19.0.9 + 4.19.0.10-SNAPSHOT java-driver-examples Java driver for Scylla and Apache Cassandra(R) - examples. diff --git a/guava-shaded/pom.xml b/guava-shaded/pom.xml index fe4569bda1a..277024a2dd5 100644 --- a/guava-shaded/pom.xml +++ b/guava-shaded/pom.xml @@ -23,7 +23,7 @@ com.scylladb java-driver-parent - 4.19.0.9 + 4.19.0.10-SNAPSHOT java-driver-guava-shaded Scylla Java Driver - guava shaded dep diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 7eb99a4c1ed..ed5428dace1 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9 + 4.19.0.10-SNAPSHOT java-driver-integration-tests jar diff --git a/mapper-processor/pom.xml b/mapper-processor/pom.xml index f94ce1bdea7..6122dc5a0c9 100644 --- a/mapper-processor/pom.xml +++ b/mapper-processor/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9 + 4.19.0.10-SNAPSHOT java-driver-mapper-processor Java driver for Scylla and Apache Cassandra(R) - object mapper processor diff --git a/mapper-runtime/pom.xml b/mapper-runtime/pom.xml index 3934a4653b6..0273cda9e5e 100644 --- a/mapper-runtime/pom.xml +++ b/mapper-runtime/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9 + 4.19.0.10-SNAPSHOT java-driver-mapper-runtime bundle diff --git a/metrics/micrometer/pom.xml b/metrics/micrometer/pom.xml index 00119c65aeb..ab68d2f05f6 100644 --- a/metrics/micrometer/pom.xml +++ b/metrics/micrometer/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9 + 4.19.0.10-SNAPSHOT ../../ java-driver-metrics-micrometer diff --git a/metrics/microprofile/pom.xml b/metrics/microprofile/pom.xml index ab153efbb1f..2cb91adae1e 100644 --- a/metrics/microprofile/pom.xml +++ b/metrics/microprofile/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9 + 4.19.0.10-SNAPSHOT ../../ java-driver-metrics-microprofile diff --git a/osgi-tests/pom.xml b/osgi-tests/pom.xml index b276d05e05f..83c93eae434 100644 --- a/osgi-tests/pom.xml +++ b/osgi-tests/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9 + 4.19.0.10-SNAPSHOT java-driver-osgi-tests jar diff --git a/pom.xml b/pom.xml index 3319f64d5b7..f3f674bbac7 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ 4.0.0 com.scylladb java-driver-parent - 4.19.0.9 + 4.19.0.10-SNAPSHOT pom Java Driver for Scylla and Apache Cassandra A driver for Scylla and Apache Cassandra(R) 2.1+ that works exclusively with the Cassandra Query Language version 3 (CQL3) and Cassandra's native protocol versions 3 and above. @@ -100,7 +100,7 @@ ${skipTests} false false - + @@ -1141,7 +1141,7 @@ height="0" width="0" style="display:none;visibility:hidden"> scm:git:https://github.com/scylladb/java-driver scm:git:https://github.com/scylladb/java-driver https://github.com/scylladb/java-driver - 4.19.0.9 + HEAD diff --git a/query-builder/pom.xml b/query-builder/pom.xml index 76739930f5d..3a56f3a278b 100644 --- a/query-builder/pom.xml +++ b/query-builder/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9 + 4.19.0.10-SNAPSHOT java-driver-query-builder bundle diff --git a/test-infra/pom.xml b/test-infra/pom.xml index 9616082d72f..c263e79df7c 100644 --- a/test-infra/pom.xml +++ b/test-infra/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.9 + 4.19.0.10-SNAPSHOT java-driver-test-infra bundle From c7779834eb2c3bd43313cfa3ec4c31bdab28a3d5 Mon Sep 17 00:00:00 2001 From: Mikita Hradovich Date: Mon, 18 May 2026 23:04:58 +0200 Subject: [PATCH 12/32] Skip defensive copy for completed prepare cache futures (#892) When the cached CompletableFuture is already done, return it directly instead of creating a defensive copy via thenApply(x -> x). Completed futures are immutable (cancel/complete are no-ops), so the copy only served to release the caller's strong reference to the cached value, causing premature weak-value eviction under GC pressure. This led to repeated PREPARE requests being sent to all nodes on every execution, as the cache entry would be garbage-collected between calls. With prepare-on-all-nodes=true (default), each eviction multiplied the re-prepare cost by the cluster node count. The defensive copy is still used for in-flight futures to protect the shared cache entry from cancellation by concurrent waiters. Ref: CUSTOMER-372 --- .../core/cql/CqlPrepareAsyncProcessor.java | 12 ++- .../cql/CqlPrepareAsyncProcessorTest.java | 88 +++++++++++++++++++ 2 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessorTest.java diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java index a3d11cff054..d38443524f2 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java @@ -162,8 +162,16 @@ public CompletionStage process( }); } } - // Return a defensive copy. So if a client cancels its request, the cache won't be impacted - // nor a potential concurrent request. + if (result.isDone()) { + // Once a CompletableFuture is completed, cancel(), complete(), and + // completeExceptionally() are no-ops (return false), so returning the cached + // instance directly is safe from concurrent callers. This also keeps the cache + // entry alive via the caller's strong reference, preventing premature weak-value + // eviction under GC pressure. + return result; + } + // Defensive copy for in-flight preparations only: protects the shared cached future + // from cancellation by one of multiple concurrent waiters. return result.thenApply(x -> x); // copy() is available only since Java 9 } catch (ExecutionException e) { return CompletableFutures.failedFuture(e.getCause()); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessorTest.java new file mode 100644 index 00000000000..96cf3256df8 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessorTest.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.cql; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.datastax.oss.driver.api.core.cql.PrepareRequest; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.shaded.guava.common.cache.Cache; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +/** + * Unit tests for {@link CqlPrepareAsyncProcessor} focusing on the caching behavior of {@link + * CqlPrepareAsyncProcessor#process} with respect to defensive copies and weak-value retention. + */ +public class CqlPrepareAsyncProcessorTest { + + private CqlPrepareAsyncProcessor processor; + private Cache> cache; + + @Before + public void setup() { + processor = new CqlPrepareAsyncProcessor(Optional.empty()); + cache = processor.getCache(); + } + + /** + * When the cached future is already completed, process() should return the exact same instance + * (identity). This ensures callers hold a strong reference to the cached CF, preventing + * weak-value eviction under GC pressure. + */ + @Test + public void should_return_cached_future_directly_when_already_completed() throws Exception { + PrepareRequest request = new DefaultPrepareRequest("SELECT 1"); + PreparedStatement ps = Mockito.mock(PreparedStatement.class); + + // Pre-populate cache with a completed future + CompletableFuture completed = CompletableFuture.completedFuture(ps); + cache.put(request, completed); + + // process() should return the exact same object + CompletionStage returned = processor.process(request, null, null, "test"); + + assertThat(returned).isSameAs(completed); + } + + /** + * When the cached future is still in-flight (not yet done), process() should return a defensive + * copy to protect the cache from cancellation by the caller. + */ + @Test + public void should_return_defensive_copy_when_future_is_in_flight() throws Exception { + PrepareRequest request = new DefaultPrepareRequest("SELECT 1"); + + // Pre-populate cache with an incomplete future + CompletableFuture inFlight = new CompletableFuture<>(); + cache.put(request, inFlight); + + CompletionStage returned = processor.process(request, null, null, "test"); + + // Should NOT be the same instance + assertThat(returned).isNotSameAs(inFlight); + + // Cancelling the returned copy should NOT affect the cached future + returned.toCompletableFuture().cancel(false); + assertThat(inFlight.isCancelled()).isFalse(); + } +} From 7a812c2ed5c53a7fa27747d2fa9cfdc464428a6c Mon Sep 17 00:00:00 2001 From: Mikita Hradovich Date: Tue, 19 May 2026 14:19:44 +0200 Subject: [PATCH 13/32] fix: bump netty to 4.1.133.Final to remediate CVE-2026-42583 Lz4FrameDecoder in netty-codec prior to 4.1.133.Final allocates up to 32 MB per block before decompression runs. A peer can trigger this with a 21-byte crafted LZ4 header, causing memory exhaustion (DoS). Bump netty.version from 4.1.127.Final to 4.1.133.Final so all consumers of java-driver-core inherit the fix transitively, without needing local dependencyManagement overrides. Ref: scylladb/kafka-connect-scylladb#164 CVE: CVE-2026-42583 CVSS: 7.5 (HIGH) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f3f674bbac7..917d96c943d 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ 2.2.2 4.2.37 - 4.1.127.Final + 4.1.133.Final 1.2.1 +### 4.19.2 + +- [bug] CASSJAVA-116: Retry or Speculative Execution with RequestIdGenerator throws "Duplicate Key" + +### 4.19.1 + +- [improvement] CASSJAVA-97: Let users inject an ID for each request and write to the custom payload +- [improvement] CASSJAVA-92: Add Local DC to driver connection info and provide visibility with nodetool clientstats +- [bug] PR 2025: Eliminate lock in ConcurrencyLimitingRequestThrottler +- [improvement] CASSJAVA-89: Fix deprecated table configs in Cassandra 5 +- [improvement] PR 2028: Remove unnecessary locking in DefaultNettyOptions +- [improvement] CASSJAVA-102: Fix revapi spurious complaints about optional dependencies +- [improvement] PR 2013: Add SubnetAddressTranslator +- [improvement] CASSJAVA-68: Improve DefaultCodecRegistry.CacheKey#hashCode() to eliminate Object[] allocation +- [improvement] PR 1989: Bump Jackson version to la(te)st 2.13.x, 2.13.5 +- [improvement] CASSJAVA-76: Make guava an optional dependency of java-driver-guava-shaded +- [bug] PR 2035: Prevent long overflow in SNI address resolution +- [improvement] CASSJAVA-77: 4.x: Upgrade Netty to 4.1.119 +- [improvement] CASSJAVA-40: Driver testing against Java 21 +- [improvement] CASSJAVA-90: Update native-protocol +- [improvement] CASSJAVA-80: Support configuration to disable DNS reverse-lookups for SAN validation + ### 4.19.0 - [bug] JAVA-3055: Prevent PreparedStatement cache to be polluted if a request is cancelled. diff --git a/ci/create-user.sh b/ci/create-user.sh new file mode 100644 index 00000000000..fb193df9a00 --- /dev/null +++ b/ci/create-user.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +################################ +# +# Prep +# +################################ + +if [ "$1" == "-h" ]; then + echo "$0 [-h] " + echo " this script is used internally by other scripts in the same directory to create a user with the running host user's same uid and gid" + exit 1 +fi + +# arguments +username=$1 +uid=$2 +gid=$3 +BUILD_HOME=$4 + +################################ +# +# Main +# +################################ + +# disable git directory ownership checks +su ${username} -c "git config --global safe.directory '*'" + +if grep "^ID=" /etc/os-release | grep -q 'debian\|ubuntu' ; then + deluser docker + adduser --quiet --disabled-login --no-create-home --uid $uid --gecos ${username} ${username} + groupmod --non-unique -g $gid $username + gpasswd -a ${username} sudo >/dev/null +else + adduser --no-create-home --uid $uid ${username} +fi + +# sudo priviledges +echo "${username} ALL=(root) NOPASSWD:ALL" > /etc/sudoers.d/${username} +chmod 0440 /etc/sudoers.d/${username} + +# proper permissions +chown -R ${username}:${username} /home/docker +chmod og+wx ${BUILD_HOME} \ No newline at end of file diff --git a/ci/run-tests.sh b/ci/run-tests.sh new file mode 100755 index 00000000000..5268bdd7113 --- /dev/null +++ b/ci/run-tests.sh @@ -0,0 +1,12 @@ +#!/bin/bash -x + +. ~/.jabba/jabba.sh +. ~/env.txt +cd $(dirname "$(readlink -f "$0")")/.. +printenv | sort +mvn -B -V install -DskipTests -Dmaven.javadoc.skip=true +jabba use ${TEST_JAVA_VERSION} +# Find out the latest patch version of Cassandra +PATCH_SERVER_VERSION=$(curl -s https://downloads.apache.org/cassandra/ | grep -oP '(?<=href=\")[0-9]+\.[0-9]+\.[0-9]+(?=)' | sort -rV | uniq -w 3 | grep $SERVER_VERSION) +printenv | sort +mvn -B -V verify -T 1 -Ptest-jdk-${TEST_JAVA_MAJOR_VERSION} -DtestJavaHome=$(jabba which ${TEST_JAVA_VERSION}) -Dccm.version=${PATCH_SERVER_VERSION} -Dccm.dse=false -Dmaven.test.failure.ignore=true -Dmaven.javadoc.skip=true diff --git a/core-shaded/pom.xml b/core-shaded/pom.xml index 1dda9f1db8f..bf776b91e67 100644 --- a/core-shaded/pom.xml +++ b/core-shaded/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.10-SNAPSHOT + 4.19.2.0-SNAPSHOT java-driver-core-shaded Java driver for Scylla and Apache Cassandra(R) - core with shaded deps diff --git a/core/pom.xml b/core/pom.xml index 0d1538502e3..0eb477e0218 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.10-SNAPSHOT + 4.19.2.0-SNAPSHOT java-driver-core bundle diff --git a/core/revapi.json b/core/revapi.json index 28bbd8d634b..6e9d7234d8e 100644 --- a/core/revapi.json +++ b/core/revapi.json @@ -1,5 +1,3 @@ -// Configures Revapi (https://revapi.org/getting-started.html) to check API compatibility between -// successive driver versions. { "revapi": { "java": { @@ -7383,6 +7381,34 @@ "old": "method com.datastax.oss.driver.api.core.type.reflect.GenericType> com.datastax.oss.driver.api.core.type.reflect.GenericType::vectorOf(java.lang.Class)", "new": "method com.datastax.oss.driver.api.core.type.reflect.GenericType> com.datastax.oss.driver.api.core.type.reflect.GenericType::vectorOf(java.lang.Class)", "justification": "JAVA-3143: Extend driver vector support to arbitrary subtypes and fix handling of variable length types (OSS C* 5.0)" + }, + { + "code": "java.class.nonPublicPartOfAPI", + "old": "class com.datastax.oss.driver.internal.core.config.typesafe.TypesafeDriverExecutionProfile.Base", + "justification": "CASSJAVA-102: Fix spurious complaints about optional dependencies" + }, + { + "code": "java.class.nonPublicPartOfAPI", + "old": "class com.fasterxml.jackson.databind.type.TypeParser.MyTokenizer", + "justification": "CASSJAVA-102: Fix spurious complaints about optional dependencies" + }, + { + "code": "java.class.nonPublicPartOfAPI", + "old": "class org.apache.tinkerpop.shaded.jackson.databind.type.TypeParser.MyTokenizer", + "justification": "CASSJAVA-102: Fix spurious complaints about optional dependencies" + }, + { + "code": "java.class.externalClassExposedInAPI", + "justification": "CASSJAVA-102: Migrate revapi config into dedicated config files, ported from pom.xml" + }, + { + "code": "java.method.varargOverloadsOnlyDifferInVarargParameter", + "justification": "CASSJAVA-102: Migrate revapi config into dedicated config files, ported from pom.xml" + }, + { + "code": "java.method.addedToInterface", + "new": "method java.util.Optional com.datastax.oss.driver.api.core.context.DriverContext::getRequestIdGenerator()", + "justification": "CASSJAVA-97: Let users inject an ID for each request and write to the custom payload" } ] } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java index c5a482c5d50..56a6fe9c2be 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java @@ -1119,7 +1119,61 @@ public enum DefaultDriverOption implements DriverOption { * *

Value type: boolean */ - CLIENT_ROUTES_SHARD_AWARENESS_ENABLED("advanced.client-routes.shard-awarness-enabled"); + CLIENT_ROUTES_SHARD_AWARENESS_ENABLED("advanced.client-routes.shard-awarness-enabled"), + + /** + * Whether or not to do a DNS reverse-lookup of provided server addresses for SAN addresses. + * + *

Value-type: boolean + */ + SSL_ALLOW_DNS_REVERSE_LOOKUP_SAN("advanced.ssl-engine-factory.allow-dns-reverse-lookup-san"), + /** + * The class of session-wide component that generates request IDs. + * + *

Value-type: {@link String} + */ + REQUEST_ID_GENERATOR_CLASS("advanced.request-id.generator.class"), + /** + * An address to always translate all node addresses to that same proxy hostname no matter what IP + * address a node has, but still using its native transport port. + * + *

Value-Type: {@link String} + */ + ADDRESS_TRANSLATOR_ADVERTISED_HOSTNAME("advanced.address-translator.advertised-hostname"), + /** + * A map of Cassandra node subnets (CIDR notations) to target addresses, for example (note quoted + * keys): + * + *

+   * advanced.address-translator.subnet-addresses {
+   *   "100.64.0.0/15" = "cassandra.datacenter1.com:9042"
+   *   "100.66.0.0/15" = "cassandra.datacenter2.com:9042"
+   *   # IPv6 example:
+   *   # "::ffff:6440:0/111" = "cassandra.datacenter1.com:9042"
+   *   # "::ffff:6442:0/111" = "cassandra.datacenter2.com:9042"
+   * }
+   * 
+ * + * Note: subnets must be represented as prefix blocks, see {@code + * inet.ipaddr.Address#isPrefixBlock()}. + * + *

Value type: {@link java.util.Map Map}<{@link String},{@link String}> + */ + ADDRESS_TRANSLATOR_SUBNET_ADDRESSES("advanced.address-translator.subnet-addresses"), + /** + * A default address to fallback to if Cassandra node IP isn't contained in any of the configured + * subnets. + * + *

Value-Type: {@link String} + */ + ADDRESS_TRANSLATOR_DEFAULT_ADDRESS("advanced.address-translator.default-address"), + /** + * Whether to resolve the addresses on initialization (if true) or on each node (re-)connection + * (if false). Defaults to false. + * + *

Value-Type: boolean + */ + ADDRESS_TRANSLATOR_RESOLVE_ADDRESSES("advanced.address-translator.resolve-addresses"); private final String path; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java b/core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java index db5edb5b947..e7c606cade8 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java @@ -248,6 +248,10 @@ public String toString() { */ public static final TypedDriverOption SSL_HOSTNAME_VALIDATION = new TypedDriverOption<>(DefaultDriverOption.SSL_HOSTNAME_VALIDATION, GenericType.BOOLEAN); + + public static final TypedDriverOption SSL_ALLOW_DNS_REVERSE_LOOKUP_SAN = + new TypedDriverOption<>( + DefaultDriverOption.SSL_ALLOW_DNS_REVERSE_LOOKUP_SAN, GenericType.BOOLEAN); /** The location of the keystore file. */ public static final TypedDriverOption SSL_KEYSTORE_PATH = new TypedDriverOption<>(DefaultDriverOption.SSL_KEYSTORE_PATH, GenericType.STRING); @@ -296,6 +300,10 @@ public String toString() { new TypedDriverOption<>( DefaultDriverOption.REQUEST_TRACKER_CLASSES, GenericType.listOf(String.class)); + /** The class of a session-wide component that generates request IDs. */ + public static final TypedDriverOption REQUEST_ID_GENERATOR_CLASS = + new TypedDriverOption<>(DefaultDriverOption.REQUEST_ID_GENERATOR_CLASS, GenericType.STRING); + /** Whether to log successful requests. */ public static final TypedDriverOption REQUEST_LOGGER_SUCCESS_ENABLED = new TypedDriverOption<>( @@ -923,6 +931,20 @@ public String toString() { DefaultDriverOption.LOAD_BALANCING_DC_FAILOVER_ALLOW_FOR_LOCAL_CONSISTENCY_LEVELS, GenericType.BOOLEAN); + public static final TypedDriverOption ADDRESS_TRANSLATOR_ADVERTISED_HOSTNAME = + new TypedDriverOption<>( + DefaultDriverOption.ADDRESS_TRANSLATOR_ADVERTISED_HOSTNAME, GenericType.STRING); + public static final TypedDriverOption> ADDRESS_TRANSLATOR_SUBNET_ADDRESSES = + new TypedDriverOption<>( + DefaultDriverOption.ADDRESS_TRANSLATOR_SUBNET_ADDRESSES, + GenericType.mapOf(GenericType.STRING, GenericType.STRING)); + public static final TypedDriverOption ADDRESS_TRANSLATOR_DEFAULT_ADDRESS = + new TypedDriverOption<>( + DefaultDriverOption.ADDRESS_TRANSLATOR_DEFAULT_ADDRESS, GenericType.STRING); + public static final TypedDriverOption ADDRESS_TRANSLATOR_RESOLVE_ADDRESSES = + new TypedDriverOption<>( + DefaultDriverOption.ADDRESS_TRANSLATOR_RESOLVE_ADDRESSES, GenericType.BOOLEAN); + /** * Ordered preference list of remote dcs optionally supplied for automatic failover and included * in query plan. This feature is enabled only when max-nodes-per-remote-dc is greater than 0. diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java index 5b32389e362..6f0afd3df8a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java @@ -33,6 +33,7 @@ import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; import com.datastax.oss.driver.api.core.time.TimestampGenerator; +import com.datastax.oss.driver.api.core.tracker.RequestIdGenerator; import com.datastax.oss.driver.api.core.tracker.RequestTracker; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Map; @@ -139,6 +140,10 @@ default SpeculativeExecutionPolicy getSpeculativeExecutionPolicy(@NonNull String @NonNull RequestTracker getRequestTracker(); + /** @return The driver's request ID generator; never {@code null}. */ + @NonNull + Optional getRequestIdGenerator(); + /** @return The driver's request throttler; never {@code null}. */ @NonNull RequestThrottler getRequestThrottler(); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/ProgrammaticArguments.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/ProgrammaticArguments.java index 0373831da41..4db44655dc2 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/ProgrammaticArguments.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/ProgrammaticArguments.java @@ -24,6 +24,7 @@ import com.datastax.oss.driver.api.core.metadata.NodeStateListener; import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; +import com.datastax.oss.driver.api.core.tracker.RequestIdGenerator; import com.datastax.oss.driver.api.core.tracker.RequestTracker; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.codec.registry.MutableCodecRegistry; @@ -60,6 +61,7 @@ public static Builder builder() { private final NodeStateListener nodeStateListener; private final SchemaChangeListener schemaChangeListener; private final RequestTracker requestTracker; + private final RequestIdGenerator requestIdGenerator; private final Map localDatacenters; private final Map> nodeFilters; private final Map nodeDistanceEvaluators; @@ -79,6 +81,7 @@ private ProgrammaticArguments( @Nullable NodeStateListener nodeStateListener, @Nullable SchemaChangeListener schemaChangeListener, @Nullable RequestTracker requestTracker, + @Nullable RequestIdGenerator requestIdGenerator, @NonNull Map localDatacenters, @NonNull Map> nodeFilters, @NonNull Map nodeDistanceEvaluators, @@ -97,6 +100,7 @@ private ProgrammaticArguments( this.nodeStateListener = nodeStateListener; this.schemaChangeListener = schemaChangeListener; this.requestTracker = requestTracker; + this.requestIdGenerator = requestIdGenerator; this.localDatacenters = localDatacenters; this.nodeFilters = nodeFilters; this.nodeDistanceEvaluators = nodeDistanceEvaluators; @@ -132,6 +136,11 @@ public RequestTracker getRequestTracker() { return requestTracker; } + @Nullable + public RequestIdGenerator getRequestIdGenerator() { + return requestIdGenerator; + } + @NonNull public Map getLocalDatacenters() { return localDatacenters; @@ -205,6 +214,7 @@ public static class Builder { private NodeStateListener nodeStateListener; private SchemaChangeListener schemaChangeListener; private RequestTracker requestTracker; + private RequestIdGenerator requestIdGenerator; private ImmutableMap.Builder localDatacentersBuilder = ImmutableMap.builder(); private final ImmutableMap.Builder> nodeFiltersBuilder = ImmutableMap.builder(); @@ -304,6 +314,12 @@ public Builder addRequestTracker(@NonNull RequestTracker requestTracker) { return this; } + @NonNull + public Builder withRequestIdGenerator(@Nullable RequestIdGenerator requestIdGenerator) { + this.requestIdGenerator = requestIdGenerator; + return this; + } + @NonNull public Builder withLocalDatacenter( @NonNull String profileName, @NonNull String localDatacenter) { @@ -433,6 +449,7 @@ public ProgrammaticArguments build() { nodeStateListener, schemaChangeListener, requestTracker, + requestIdGenerator, localDatacentersBuilder.build(), nodeFiltersBuilder.build(), nodeDistanceEvaluatorsBuilder.build(), diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java index a2c0f933efc..8375f0ef30b 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java @@ -42,6 +42,7 @@ import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; import com.datastax.oss.driver.api.core.ssl.ProgrammaticSslEngineFactory; import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; +import com.datastax.oss.driver.api.core.tracker.RequestIdGenerator; import com.datastax.oss.driver.api.core.tracker.RequestTracker; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.codec.registry.MutableCodecRegistry; @@ -54,6 +55,7 @@ import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.DefaultEndPoint; import com.datastax.oss.driver.internal.core.session.DefaultSession; +import com.datastax.oss.driver.internal.core.tracker.W3CContextRequestIdGenerator; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; import edu.umd.cs.findbugs.annotations.NonNull; @@ -90,6 +92,8 @@ @NotThreadSafe public abstract class SessionBuilder { + public static final String ASTRA_PAYLOAD_KEY = "traceparent"; + private static final Logger LOG = LoggerFactory.getLogger(SessionBuilder.class); @SuppressWarnings("unchecked") @@ -324,6 +328,17 @@ public SelfT addRequestTracker(@NonNull RequestTracker requestTracker) { return self; } + /** + * Registers a request ID generator. The driver will use the generated ID in the logs and + * optionally add to the custom payload so that users can correlate logs about the same request + * from the Cassandra side. + */ + @NonNull + public SelfT withRequestIdGenerator(@NonNull RequestIdGenerator requestIdGenerator) { + this.programmaticArgumentsBuilder.withRequestIdGenerator(requestIdGenerator); + return self; + } + /** * Registers an authentication provider to use with the session. * @@ -905,6 +920,13 @@ protected final CompletionStage buildDefaultSessionAsync() { List configContactPoints = defaultConfig.getStringList(DefaultDriverOption.CONTACT_POINTS, Collections.emptyList()); if (cloudConfigInputStream != null) { + // override request id generator, unless user has already set it + if (programmaticArguments.getRequestIdGenerator() == null) { + programmaticArgumentsBuilder.withRequestIdGenerator( + new W3CContextRequestIdGenerator(ASTRA_PAYLOAD_KEY)); + LOG.debug( + "A secure connect bundle is provided, using W3CContextRequestIdGenerator as request ID generator."); + } if (!programmaticContactPoints.isEmpty() || !configContactPoints.isEmpty()) { LOG.info( "Both a secure connect bundle and contact points were provided. These are mutually exclusive. The contact points from the secure bundle will have priority."); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/throttling/RequestThrottler.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/throttling/RequestThrottler.java index 7e2b41ebbdb..73d347d533e 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/throttling/RequestThrottler.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/throttling/RequestThrottler.java @@ -23,10 +23,10 @@ /** * Limits the number of concurrent requests executed by the driver. * - *

Usage in non-blocking applications: beware that all built-in implementations of this interface - * use locks for internal coordination, and do not qualify as lock-free, with the obvious exception - * of {@code PassThroughRequestThrottler}. If your application enforces strict lock-freedom, then - * request throttling should not be enabled. + *

Usage in non-blocking applications: beware that some implementations of this interface use + * locks for internal coordination, and do not qualify as lock-free. If your application enforces + * strict lock-freedom, then you should use the {@code PassThroughRequestThrottler} or the {@code + * ConcurrencyLimitingRequestThrottler}. */ public interface RequestThrottler extends Closeable { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/ssl/ProgrammaticSslEngineFactory.java b/core/src/main/java/com/datastax/oss/driver/api/core/ssl/ProgrammaticSslEngineFactory.java index 6dfe4087b91..d65eaa864aa 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/ssl/ProgrammaticSslEngineFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/ssl/ProgrammaticSslEngineFactory.java @@ -45,6 +45,7 @@ public class ProgrammaticSslEngineFactory implements SslEngineFactory { protected final SSLContext sslContext; protected final String[] cipherSuites; protected final boolean requireHostnameValidation; + protected final boolean allowDnsReverseLookupSan; /** * Creates an instance with the given {@link SSLContext}, default cipher suites and no host name @@ -80,9 +81,28 @@ public ProgrammaticSslEngineFactory( @NonNull SSLContext sslContext, @Nullable String[] cipherSuites, boolean requireHostnameValidation) { + this(sslContext, cipherSuites, requireHostnameValidation, true); + } + + /** + * Creates an instance with the given {@link SSLContext}, cipher suites and host name validation. + * + * @param sslContext the {@link SSLContext} to use. + * @param cipherSuites the cipher suites to use, or null to use the default ones. + * @param requireHostnameValidation whether to enable host name validation. If enabled, host name + * validation will be done using HTTPS algorithm. + * @param allowDnsReverseLookupSan whether to allow raw server IPs to be DNS reverse-resolved to + * choose the appropriate Subject Alternative Name. + */ + public ProgrammaticSslEngineFactory( + @NonNull SSLContext sslContext, + @Nullable String[] cipherSuites, + boolean requireHostnameValidation, + boolean allowDnsReverseLookupSan) { this.sslContext = sslContext; this.cipherSuites = cipherSuites; this.requireHostnameValidation = requireHostnameValidation; + this.allowDnsReverseLookupSan = allowDnsReverseLookupSan; } @NonNull @@ -92,7 +112,12 @@ public SSLEngine newSslEngine(@NonNull EndPoint remoteEndpoint) { SocketAddress remoteAddress = remoteEndpoint.resolve(); if (remoteAddress instanceof InetSocketAddress) { InetSocketAddress socketAddress = (InetSocketAddress) remoteAddress; - engine = sslContext.createSSLEngine(socketAddress.getHostName(), socketAddress.getPort()); + engine = + sslContext.createSSLEngine( + allowDnsReverseLookupSan + ? socketAddress.getHostName() + : socketAddress.getHostString(), + socketAddress.getPort()); } else { engine = sslContext.createSSLEngine(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/tracker/RequestIdGenerator.java b/core/src/main/java/com/datastax/oss/driver/api/core/tracker/RequestIdGenerator.java new file mode 100644 index 00000000000..21db3793b01 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/api/core/tracker/RequestIdGenerator.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.api.core.tracker; + +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.session.Request; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Interface responsible for generating request IDs. + * + *

Note that all request IDs have a parent/child relationship. A "session request ID" can loosely + * be thought of as encompassing a sequence of a request + any attendant retries, speculative + * executions etc. It's scope is identical to that of a {@link + * com.datastax.oss.driver.internal.core.cql.CqlRequestHandler}. A "node request ID" represents a + * single request within this larger scope. Note that a request corresponding to a request ID may be + * retried; in that case the retry count will be appended to the corresponding identifier in the + * logs. + */ +public interface RequestIdGenerator { + + String DEFAULT_PAYLOAD_KEY = "request-id"; + + /** + * Generates a unique identifier for the session request. This will be the identifier for the + * entire `session.execute()` call. This identifier will be added to logs, and propagated to + * request trackers. + * + * @return a unique identifier for the session request + */ + String getSessionRequestId(); + + /** + * Generates a unique identifier for the node request. This will be the identifier for the CQL + * request against a particular node. There can be one or more node requests for a single session + * request, due to retries or speculative executions. This identifier will be added to logs, and + * propagated to request trackers. + * + * @param statement the statement to be executed + * @param parentId the session request identifier + * @return a unique identifier for the node request + */ + String getNodeRequestId(@NonNull Request statement, @NonNull String parentId); + + default String getCustomPayloadKey() { + return DEFAULT_PAYLOAD_KEY; + } + + default Statement getDecoratedStatement( + @NonNull Statement statement, @NonNull String requestId) { + + Map existing = new HashMap<>(statement.getCustomPayload()); + String key = getCustomPayloadKey(); + + // Add or overwrite + existing.put(key, ByteBuffer.wrap(requestId.getBytes(StandardCharsets.UTF_8))); + + // Allowing null key/values + // Wrap a map inside to be immutable without instanciating a new map + Map unmodifiableMap = Collections.unmodifiableMap(existing); + + return statement.setCustomPayload(unmodifiableMap); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/tracker/RequestTracker.java b/core/src/main/java/com/datastax/oss/driver/api/core/tracker/RequestTracker.java index d29ee48d352..065b41e496a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/tracker/RequestTracker.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/tracker/RequestTracker.java @@ -47,21 +47,22 @@ default void onSuccess( @NonNull Node node) {} /** - * Invoked each time a request succeeds. + * Invoked each time a session request succeeds. A session request is a `session.execute()` call * * @param latencyNanos the overall execution time (from the {@link Session#execute(Request, * GenericType) session.execute} call until the result is made available to the client). * @param executionProfile the execution profile of this request. * @param node the node that returned the successful response. - * @param requestLogPrefix the dedicated log prefix for this request + * @param sessionRequestLogPrefix the dedicated log prefix for this request */ default void onSuccess( @NonNull Request request, long latencyNanos, @NonNull DriverExecutionProfile executionProfile, @NonNull Node node, - @NonNull String requestLogPrefix) { - // If client doesn't override onSuccess with requestLogPrefix delegate call to the old method + @NonNull String sessionRequestLogPrefix) { + // If client doesn't override onSuccess with sessionRequestLogPrefix delegate call to the old + // method onSuccess(request, latencyNanos, executionProfile, node); } @@ -78,13 +79,13 @@ default void onError( @Nullable Node node) {} /** - * Invoked each time a request fails. + * Invoked each time a session request fails. A session request is a `session.execute()` call * * @param latencyNanos the overall execution time (from the {@link Session#execute(Request, * GenericType) session.execute} call until the error is propagated to the client). * @param executionProfile the execution profile of this request. * @param node the node that returned the error response, or {@code null} if the error occurred - * @param requestLogPrefix the dedicated log prefix for this request + * @param sessionRequestLogPrefix the dedicated log prefix for this request */ default void onError( @NonNull Request request, @@ -92,8 +93,9 @@ default void onError( long latencyNanos, @NonNull DriverExecutionProfile executionProfile, @Nullable Node node, - @NonNull String requestLogPrefix) { - // If client doesn't override onError with requestLogPrefix delegate call to the old method + @NonNull String sessionRequestLogPrefix) { + // If client doesn't override onError with sessionRequestLogPrefix delegate call to the old + // method onError(request, error, latencyNanos, executionProfile, node); } @@ -110,14 +112,15 @@ default void onNodeError( @NonNull Node node) {} /** - * Invoked each time a request fails at the node level. Similar to {@link #onError(Request, - * Throwable, long, DriverExecutionProfile, Node, String)} but at a per node level. + * Invoked each time a node request fails. A node request is a CQL request sent to a particular + * node. There can be one or more node requests for a single session request, due to retries or + * speculative executions. * * @param latencyNanos the overall execution time (from the {@link Session#execute(Request, * GenericType) session.execute} call until the error is propagated to the client). * @param executionProfile the execution profile of this request. * @param node the node that returned the error response. - * @param requestLogPrefix the dedicated log prefix for this request + * @param nodeRequestLogPrefix the dedicated log prefix for this request */ default void onNodeError( @NonNull Request request, @@ -125,8 +128,9 @@ default void onNodeError( long latencyNanos, @NonNull DriverExecutionProfile executionProfile, @NonNull Node node, - @NonNull String requestLogPrefix) { - // If client doesn't override onNodeError with requestLogPrefix delegate call to the old method + @NonNull String nodeRequestLogPrefix) { + // If client doesn't override onNodeError with nodeRequestLogPrefix delegate call to the old + // method onNodeError(request, error, latencyNanos, executionProfile, node); } @@ -142,22 +146,23 @@ default void onNodeSuccess( @NonNull Node node) {} /** - * Invoked each time a request succeeds at the node level. Similar to {@link #onSuccess(Request, - * long, DriverExecutionProfile, Node, String)} but at per node level. + * Invoked each time a node request succeeds. A node request is a CQL request sent to a particular + * node. There can be one or more node requests for a single session request, due to retries or + * speculative executions. * * @param latencyNanos the overall execution time (from the {@link Session#execute(Request, * GenericType) session.execute} call until the result is made available to the client). * @param executionProfile the execution profile of this request. * @param node the node that returned the successful response. - * @param requestLogPrefix the dedicated log prefix for this request + * @param nodeRequestLogPrefix the dedicated log prefix for this request */ default void onNodeSuccess( @NonNull Request request, long latencyNanos, @NonNull DriverExecutionProfile executionProfile, @NonNull Node node, - @NonNull String requestLogPrefix) { - // If client doesn't override onNodeSuccess with requestLogPrefix delegate call to the old + @NonNull String nodeRequestLogPrefix) { + // If client doesn't override onNodeSuccess with nodeRequestLogPrefix delegate call to the old // method onNodeSuccess(request, latencyNanos, executionProfile, node); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java index 1ed2a1cebf3..bb65661b72f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ContactPoints.java @@ -19,14 +19,11 @@ import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.internal.core.metadata.DefaultEndPoint; +import com.datastax.oss.driver.internal.core.util.AddressUtils; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet; import com.datastax.oss.driver.shaded.guava.common.collect.Sets; -import java.net.InetAddress; import java.net.InetSocketAddress; -import java.net.UnknownHostException; -import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Set; import org.slf4j.Logger; @@ -41,7 +38,22 @@ public static Set merge( Set result = Sets.newHashSet(programmaticContactPoints); for (String spec : configContactPoints) { - for (InetSocketAddress address : extract(spec, resolve)) { + + Set addresses = Collections.emptySet(); + try { + addresses = AddressUtils.extract(spec, resolve); + } catch (RuntimeException e) { + LOG.warn("Ignoring invalid contact point {} ({})", spec, e.getMessage(), e); + } + + if (addresses.size() > 1) { + LOG.info( + "Contact point {} resolves to multiple addresses, will use them all ({})", + spec, + addresses); + } + + for (InetSocketAddress address : addresses) { DefaultEndPoint endPoint = new DefaultEndPoint(address); boolean wasNew = result.add(endPoint); if (!wasNew) { @@ -51,43 +63,4 @@ public static Set merge( } return ImmutableSet.copyOf(result); } - - private static Set extract(String spec, boolean resolve) { - int separator = spec.lastIndexOf(':'); - if (separator < 0) { - LOG.warn("Ignoring invalid contact point {} (expecting host:port)", spec); - return Collections.emptySet(); - } - - String host = spec.substring(0, separator); - String portSpec = spec.substring(separator + 1); - int port; - try { - port = Integer.parseInt(portSpec); - } catch (NumberFormatException e) { - LOG.warn("Ignoring invalid contact point {} (expecting a number, got {})", spec, portSpec); - return Collections.emptySet(); - } - if (!resolve) { - return ImmutableSet.of(InetSocketAddress.createUnresolved(host, port)); - } else { - try { - InetAddress[] inetAddresses = InetAddress.getAllByName(host); - if (inetAddresses.length > 1) { - LOG.info( - "Contact point {} resolves to multiple addresses, will use them all ({})", - spec, - Arrays.deepToString(inetAddresses)); - } - Set result = new HashSet<>(); - for (InetAddress inetAddress : inetAddresses) { - result.add(new InetSocketAddress(inetAddress, port)); - } - return result; - } catch (UnknownHostException e) { - LOG.warn("Ignoring invalid contact point {} (unknown host {})", spec, host); - return Collections.emptySet(); - } - } - } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/FixedHostNameAddressTranslator.java b/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/FixedHostNameAddressTranslator.java index ea624c3e84d..0ee1e22a7ac 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/FixedHostNameAddressTranslator.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/FixedHostNameAddressTranslator.java @@ -17,8 +17,9 @@ */ package com.datastax.oss.driver.internal.core.addresstranslation; +import static com.datastax.oss.driver.api.core.config.DefaultDriverOption.ADDRESS_TRANSLATOR_ADVERTISED_HOSTNAME; + import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; -import com.datastax.oss.driver.api.core.config.DriverOption; import com.datastax.oss.driver.api.core.context.DriverContext; import edu.umd.cs.findbugs.annotations.NonNull; import java.net.InetSocketAddress; @@ -43,28 +44,13 @@ public class FixedHostNameAddressTranslator implements AddressTranslator { private static final Logger LOG = LoggerFactory.getLogger(FixedHostNameAddressTranslator.class); - public static final String ADDRESS_TRANSLATOR_ADVERTISED_HOSTNAME = - "advanced.address-translator.advertised-hostname"; - - public static DriverOption ADDRESS_TRANSLATOR_ADVERTISED_HOSTNAME_OPTION = - new DriverOption() { - @NonNull - @Override - public String getPath() { - return ADDRESS_TRANSLATOR_ADVERTISED_HOSTNAME; - } - }; - private final String advertisedHostname; private final String logPrefix; public FixedHostNameAddressTranslator(@NonNull DriverContext context) { logPrefix = context.getSessionName(); advertisedHostname = - context - .getConfig() - .getDefaultProfile() - .getString(ADDRESS_TRANSLATOR_ADVERTISED_HOSTNAME_OPTION); + context.getConfig().getDefaultProfile().getString(ADDRESS_TRANSLATOR_ADVERTISED_HOSTNAME); } @NonNull diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/Subnet.java b/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/Subnet.java new file mode 100644 index 00000000000..7c25e94e2f9 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/Subnet.java @@ -0,0 +1,176 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.addresstranslation; + +import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting; +import com.datastax.oss.driver.shaded.guava.common.base.Splitter; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.List; + +class Subnet { + private final byte[] subnet; + private final byte[] networkMask; + private final byte[] upper; + private final byte[] lower; + + private Subnet(byte[] subnet, byte[] networkMask) { + this.subnet = subnet; + this.networkMask = networkMask; + + byte[] upper = new byte[subnet.length]; + byte[] lower = new byte[subnet.length]; + for (int i = 0; i < subnet.length; i++) { + upper[i] = (byte) (subnet[i] | ~networkMask[i]); + lower[i] = (byte) (subnet[i] & networkMask[i]); + } + this.upper = upper; + this.lower = lower; + } + + static Subnet parse(String subnetCIDR) throws UnknownHostException { + List parts = Splitter.on("/").splitToList(subnetCIDR); + if (parts.size() != 2) { + throw new IllegalArgumentException("Invalid subnet: " + subnetCIDR); + } + + boolean isIPv6 = parts.get(0).contains(":"); + byte[] subnet = InetAddress.getByName(parts.get(0)).getAddress(); + if (isIPv4(subnet) && isIPv6) { + subnet = toIPv6(subnet); + } + int prefixLength = Integer.parseInt(parts.get(1)); + validatePrefixLength(subnet, prefixLength); + + byte[] networkMask = toNetworkMask(subnet, prefixLength); + validateSubnetIsPrefixBlock(subnet, networkMask, subnetCIDR); + return new Subnet(subnet, networkMask); + } + + private static byte[] toNetworkMask(byte[] subnet, int prefixLength) { + int fullBytes = prefixLength / 8; + int remainingBits = prefixLength % 8; + byte[] mask = new byte[subnet.length]; + Arrays.fill(mask, 0, fullBytes, (byte) 0xFF); + if (remainingBits > 0) { + mask[fullBytes] = (byte) (0xFF << (8 - remainingBits)); + } + return mask; + } + + private static void validatePrefixLength(byte[] subnet, int prefixLength) { + int max_prefix_length = subnet.length * 8; + if (prefixLength < 0 || max_prefix_length < prefixLength) { + throw new IllegalArgumentException( + String.format( + "Prefix length %s must be within [0; %s]", prefixLength, max_prefix_length)); + } + } + + private static void validateSubnetIsPrefixBlock( + byte[] subnet, byte[] networkMask, String subnetCIDR) { + byte[] prefixBlock = toPrefixBlock(subnet, networkMask); + if (!Arrays.equals(subnet, prefixBlock)) { + throw new IllegalArgumentException( + String.format("Subnet %s must be represented as a network prefix block", subnetCIDR)); + } + } + + private static byte[] toPrefixBlock(byte[] subnet, byte[] networkMask) { + byte[] prefixBlock = new byte[subnet.length]; + for (int i = 0; i < subnet.length; i++) { + prefixBlock[i] = (byte) (subnet[i] & networkMask[i]); + } + return prefixBlock; + } + + @VisibleForTesting + byte[] getSubnet() { + return Arrays.copyOf(subnet, subnet.length); + } + + @VisibleForTesting + byte[] getNetworkMask() { + return Arrays.copyOf(networkMask, networkMask.length); + } + + byte[] getUpper() { + return Arrays.copyOf(upper, upper.length); + } + + byte[] getLower() { + return Arrays.copyOf(lower, lower.length); + } + + boolean isIPv4() { + return isIPv4(subnet); + } + + boolean isIPv6() { + return isIPv6(subnet); + } + + boolean contains(byte[] ip) { + if (isIPv4() && !isIPv4(ip)) { + return false; + } + if (isIPv6() && isIPv4(ip)) { + ip = toIPv6(ip); + } + if (subnet.length != ip.length) { + throw new IllegalArgumentException( + "IP version is unknown: " + Arrays.toString(toZeroBasedByteArray(ip))); + } + for (int i = 0; i < subnet.length; i++) { + if (subnet[i] != (byte) (ip[i] & networkMask[i])) { + return false; + } + } + return true; + } + + private static boolean isIPv4(byte[] ip) { + return ip.length == 4; + } + + private static boolean isIPv6(byte[] ip) { + return ip.length == 16; + } + + private static byte[] toIPv6(byte[] ipv4) { + byte[] ipv6 = new byte[16]; + ipv6[10] = (byte) 0xFF; + ipv6[11] = (byte) 0xFF; + System.arraycopy(ipv4, 0, ipv6, 12, 4); + return ipv6; + } + + @Override + public String toString() { + return Arrays.toString(toZeroBasedByteArray(subnet)); + } + + private static int[] toZeroBasedByteArray(byte[] bytes) { + int[] res = new int[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + res[i] = bytes[i] & 0xFF; + } + return res; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddress.java b/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddress.java new file mode 100644 index 00000000000..105e776a507 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddress.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.addresstranslation; + +import java.net.InetSocketAddress; +import java.net.UnknownHostException; + +class SubnetAddress { + private final Subnet subnet; + private final InetSocketAddress address; + + SubnetAddress(String subnetCIDR, InetSocketAddress address) { + try { + this.subnet = Subnet.parse(subnetCIDR); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + this.address = address; + } + + InetSocketAddress getAddress() { + return this.address; + } + + boolean isOverlapping(SubnetAddress other) { + Subnet thisSubnet = this.subnet; + Subnet otherSubnet = other.subnet; + return thisSubnet.contains(otherSubnet.getLower()) + || thisSubnet.contains(otherSubnet.getUpper()) + || otherSubnet.contains(thisSubnet.getLower()) + || otherSubnet.contains(thisSubnet.getUpper()); + } + + boolean contains(InetSocketAddress address) { + return subnet.contains(address.getAddress().getAddress()); + } + + boolean isIPv4() { + return subnet.isIPv4(); + } + + boolean isIPv6() { + return subnet.isIPv6(); + } + + @Override + public String toString() { + return "SubnetAddress[subnet=" + subnet + ", address=" + address + "]"; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTranslator.java b/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTranslator.java new file mode 100644 index 00000000000..85f29e3fadd --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTranslator.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.addresstranslation; + +import static com.datastax.oss.driver.api.core.config.DefaultDriverOption.ADDRESS_TRANSLATOR_DEFAULT_ADDRESS; +import static com.datastax.oss.driver.api.core.config.DefaultDriverOption.ADDRESS_TRANSLATOR_RESOLVE_ADDRESSES; +import static com.datastax.oss.driver.api.core.config.DefaultDriverOption.ADDRESS_TRANSLATOR_SUBNET_ADDRESSES; + +import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.internal.core.util.AddressUtils; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This translator returns the proxy address of the private subnet containing the Cassandra node IP, + * or default address if no matching subnets, or passes through the original node address if no + * default configured. + * + *

The translator can be used for scenarios when all nodes are behind some kind of proxy, and + * that proxy is different for nodes located in different subnets (eg. when Cassandra is deployed in + * multiple datacenters/regions). One can use this, for example, for Cassandra on Kubernetes with + * different Cassandra datacenters deployed to different Kubernetes clusters. + */ +public class SubnetAddressTranslator implements AddressTranslator { + private static final Logger LOG = LoggerFactory.getLogger(SubnetAddressTranslator.class); + + private final List subnetAddresses; + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + private final Optional defaultAddress; + + private final String logPrefix; + + public SubnetAddressTranslator(@NonNull DriverContext context) { + logPrefix = context.getSessionName(); + boolean resolveAddresses = + context + .getConfig() + .getDefaultProfile() + .getBoolean(ADDRESS_TRANSLATOR_RESOLVE_ADDRESSES, false); + this.subnetAddresses = + context.getConfig().getDefaultProfile().getStringMap(ADDRESS_TRANSLATOR_SUBNET_ADDRESSES) + .entrySet().stream() + .map( + e -> { + // Quoted and/or containing forward slashes map keys in reference.conf are read to + // strings with additional quotes, eg. 100.64.0.0/15 -> '100.64.0."0/15"' or + // "100.64.0.0/15" -> '"100.64.0.0/15"' + String subnetCIDR = e.getKey().replaceAll("\"", ""); + String address = e.getValue(); + return new SubnetAddress(subnetCIDR, parseAddress(address, resolveAddresses)); + }) + .collect(Collectors.toList()); + this.defaultAddress = + Optional.ofNullable( + context + .getConfig() + .getDefaultProfile() + .getString(ADDRESS_TRANSLATOR_DEFAULT_ADDRESS, null)) + .map(address -> parseAddress(address, resolveAddresses)); + + validateSubnetsAreOfSameProtocol(this.subnetAddresses); + validateSubnetsAreNotOverlapping(this.subnetAddresses); + } + + private static void validateSubnetsAreOfSameProtocol(List subnets) { + for (int i = 0; i < subnets.size() - 1; i++) { + for (int j = i + 1; j < subnets.size(); j++) { + SubnetAddress subnet1 = subnets.get(i); + SubnetAddress subnet2 = subnets.get(j); + if (subnet1.isIPv4() != subnet2.isIPv4() && subnet1.isIPv6() != subnet2.isIPv6()) { + throw new IllegalArgumentException( + String.format( + "Configured subnets are of the different protocols: %s, %s", subnet1, subnet2)); + } + } + } + } + + private static void validateSubnetsAreNotOverlapping(List subnets) { + for (int i = 0; i < subnets.size() - 1; i++) { + for (int j = i + 1; j < subnets.size(); j++) { + SubnetAddress subnet1 = subnets.get(i); + SubnetAddress subnet2 = subnets.get(j); + if (subnet1.isOverlapping(subnet2)) { + throw new IllegalArgumentException( + String.format("Configured subnets are overlapping: %s, %s", subnet1, subnet2)); + } + } + } + } + + @NonNull + @Override + public InetSocketAddress translate(@NonNull InetSocketAddress address) { + InetSocketAddress translatedAddress = null; + for (SubnetAddress subnetAddress : subnetAddresses) { + if (subnetAddress.contains(address)) { + translatedAddress = subnetAddress.getAddress(); + } + } + if (translatedAddress == null && defaultAddress.isPresent()) { + translatedAddress = defaultAddress.get(); + } + if (translatedAddress == null) { + translatedAddress = address; + } + LOG.debug("[{}] Translated {} to {}", logPrefix, address, translatedAddress); + return translatedAddress; + } + + @Override + public void close() {} + + @Nullable + private InetSocketAddress parseAddress(String address, boolean resolve) { + try { + InetSocketAddress parsedAddress = AddressUtils.extract(address, resolve).iterator().next(); + LOG.debug("[{}] Parsed {} to {}", logPrefix, address, parsedAddress); + return parsedAddress; + } catch (RuntimeException e) { + throw new IllegalArgumentException( + String.format("Invalid address %s (%s)", address, e.getMessage()), e); + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index 7c14a811b61..52d9cdb264c 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -46,6 +46,7 @@ import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; import com.datastax.oss.driver.api.core.time.TimestampGenerator; +import com.datastax.oss.driver.api.core.tracker.RequestIdGenerator; import com.datastax.oss.driver.api.core.tracker.RequestTracker; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; @@ -234,6 +235,7 @@ public class DefaultDriverContext implements InternalDriverContext { private final LazyReference nodeStateListenerRef; private final LazyReference schemaChangeListenerRef; private final LazyReference requestTrackerRef; + private final LazyReference> requestIdGeneratorRef; private final LazyReference> authProviderRef; private final LazyReference> lifecycleListenersRef = new LazyReference<>("lifecycleListeners", this::buildLifecycleListeners, cycleDetector); @@ -296,6 +298,11 @@ public DefaultDriverContext( this.requestTrackerRef = new LazyReference<>( "requestTracker", () -> buildRequestTracker(requestTrackerFromBuilder), cycleDetector); + this.requestIdGeneratorRef = + new LazyReference<>( + "requestIdGenerator", + () -> buildRequestIdGenerator(programmaticArguments.getRequestIdGenerator()), + cycleDetector); this.sslEngineFactoryRef = new LazyReference<>( "sslEngineFactory", @@ -350,7 +357,7 @@ public DefaultDriverContext( } /** - * Builds a map of options to send in a Startup message. + * Returns the options to send in a Startup message. * * @see #getStartupOptions() */ @@ -895,6 +902,17 @@ protected RequestTracker buildRequestTracker(RequestTracker requestTrackerFromBu } } + protected Optional buildRequestIdGenerator( + RequestIdGenerator requestIdGenerator) { + return (requestIdGenerator != null) + ? Optional.of(requestIdGenerator) + : Reflection.buildFromConfig( + this, + DefaultDriverOption.REQUEST_ID_GENERATOR_CLASS, + RequestIdGenerator.class, + "com.datastax.oss.driver.internal.core.tracker"); + } + protected Optional buildAuthProvider(AuthProvider authProviderFromBuilder) { return (authProviderFromBuilder != null) ? Optional.of(authProviderFromBuilder) @@ -1159,6 +1177,12 @@ public RequestTracker getRequestTracker() { return requestTrackerRef.get(); } + @NonNull + @Override + public Optional getRequestIdGenerator() { + return requestIdGeneratorRef.get(); + } + @Nullable @Override public String getLocalDatacenter(@NonNull String profileName) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java index c5d3b3670f0..763a71f8b12 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultNettyOptions.java @@ -200,7 +200,7 @@ public Future onClose() { } @Override - public synchronized Timer getTimer() { + public Timer getTimer() { return timer; } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java index d38443524f2..57411097b82 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java @@ -162,16 +162,13 @@ public CompletionStage process( }); } } + // If the future is already completed, return it directly to maintain a strong reference + // in the cache and avoid premature GC with weakValues() (ScyllaDB PR #892). if (result.isDone()) { - // Once a CompletableFuture is completed, cancel(), complete(), and - // completeExceptionally() are no-ops (return false), so returning the cached - // instance directly is safe from concurrent callers. This also keeps the cache - // entry alive via the caller's strong reference, preventing premature weak-value - // eviction under GC pressure. return result; } - // Defensive copy for in-flight preparations only: protects the shared cached future - // from cancellation by one of multiple concurrent waiters. + // Return a defensive copy. So if a client cancels its request, the cache won't be impacted + // nor a potential concurrent request. return result.thenApply(x -> x); // copy() is available only since Java 9 } catch (ExecutionException e) { return CompletableFutures.failedFuture(e.getCause()); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java index 25f27e5a39d..29f595e5225 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandler.java @@ -58,6 +58,7 @@ import com.datastax.oss.driver.api.core.servererrors.WriteTimeoutException; import com.datastax.oss.driver.api.core.session.throttling.RequestThrottler; import com.datastax.oss.driver.api.core.session.throttling.Throttled; +import com.datastax.oss.driver.api.core.tracker.RequestIdGenerator; import com.datastax.oss.driver.api.core.tracker.RequestTracker; import com.datastax.oss.driver.internal.core.adminrequest.ThrottledAdminRequestHandler; import com.datastax.oss.driver.internal.core.adminrequest.UnexpectedResponseException; @@ -77,6 +78,7 @@ import com.datastax.oss.driver.internal.core.tracker.RequestLogger; import com.datastax.oss.driver.internal.core.util.Loggers; import com.datastax.oss.driver.internal.core.util.collection.SimpleQueryPlan; +import com.datastax.oss.driver.shaded.guava.common.base.Joiner; import com.datastax.oss.protocol.internal.Frame; import com.datastax.oss.protocol.internal.Message; import com.datastax.oss.protocol.internal.ProtocolConstants; @@ -101,6 +103,7 @@ import java.util.AbstractMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Queue; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; @@ -119,7 +122,7 @@ public class CqlRequestHandler implements Throttled { private static final long NANOTIME_NOT_MEASURED_YET = -1; private final long startTimeNanos; - private final String logPrefix; + private final String handlerLogPrefix; private final Statement initialStatement; private final DefaultSession session; private final CqlIdentifier keyspace; @@ -144,6 +147,7 @@ public class CqlRequestHandler implements Throttled { private final List inFlightCallbacks; private final RequestThrottler throttler; private final RequestTracker requestTracker; + private final Optional requestIdGenerator; private final SessionMetricUpdater sessionMetricUpdater; private final DriverExecutionProfile executionProfile; @@ -151,15 +155,25 @@ public class CqlRequestHandler implements Throttled { // We don't use a map because nodes can appear multiple times. private volatile List> errors; + private final Joiner logPrefixJoiner = Joiner.on('|'); + private final String sessionName; + private final String sessionRequestId; + protected CqlRequestHandler( Statement statement, DefaultSession session, InternalDriverContext context, - String sessionLogPrefix) { + String sessionName) { this.startTimeNanos = System.nanoTime(); - this.logPrefix = sessionLogPrefix + "|" + this.hashCode(); - LOG.trace("[{}] Creating new handler for request {}", logPrefix, statement); + this.requestIdGenerator = context.getRequestIdGenerator(); + this.sessionName = sessionName; + this.sessionRequestId = + this.requestIdGenerator + .map(RequestIdGenerator::getSessionRequestId) + .orElse(Integer.toString(this.hashCode())); + this.handlerLogPrefix = logPrefixJoiner.join(sessionName, sessionRequestId); + LOG.trace("[{}] Creating new handler for request {}", handlerLogPrefix, statement); this.initialStatement = statement; this.session = session; @@ -174,7 +188,7 @@ protected CqlRequestHandler( context.getRequestThrottler().signalCancel(this); } } catch (Throwable t2) { - Loggers.warnWithException(LOG, "[{}] Uncaught exception", logPrefix, t2); + Loggers.warnWithException(LOG, "[{}] Uncaught exception", handlerLogPrefix, t2); } return null; }); @@ -370,7 +384,7 @@ private void sendRequest( || (channel = session.getChannel( node, - logPrefix, + handlerLogPrefix, getRoutingToken(statement), getShardFromTabletMap(statement, node, getRoutingToken(statement)))) == null) { @@ -378,7 +392,7 @@ private void sendRequest( channel = session.getChannel( node, - logPrefix, + handlerLogPrefix, getRoutingToken(statement), getShardFromTabletMap(statement, node, getRoutingToken(statement))); if (channel != null) { @@ -395,6 +409,16 @@ private void sendRequest( setFinalError(statement, AllNodesFailedException.fromErrors(this.errors), null, -1); } } else { + Statement finalStatement = statement; + String nodeRequestId = + this.requestIdGenerator + .map((g) -> g.getNodeRequestId(finalStatement, sessionRequestId)) + .orElse(Integer.toString(this.hashCode())); + statement = + this.requestIdGenerator + .map((g) -> g.getDecoratedStatement(finalStatement, nodeRequestId)) + .orElse(finalStatement); + NodeResponseCallback nodeResponseCallback = new NodeResponseCallback( statement, @@ -404,7 +428,7 @@ private void sendRequest( currentExecutionIndex, retryCount, scheduleNextExecution, - logPrefix); + logPrefixJoiner.join(this.sessionName, nodeRequestId, currentExecutionIndex)); Message message = Conversions.toMessage(statement, executionProfile, context); channel .write(message, statement.isTracing(), statement.getCustomPayload(), nodeResponseCallback) @@ -463,9 +487,17 @@ private void setFinalResult( totalLatencyNanos = completionTimeNanos - startTimeNanos; long nodeLatencyNanos = completionTimeNanos - callback.nodeStartTimeNanos; requestTracker.onNodeSuccess( - callback.statement, nodeLatencyNanos, executionProfile, callback.node, logPrefix); + callback.statement, + nodeLatencyNanos, + executionProfile, + callback.node, + handlerLogPrefix); requestTracker.onSuccess( - callback.statement, totalLatencyNanos, executionProfile, callback.node, logPrefix); + callback.statement, + totalLatencyNanos, + executionProfile, + callback.node, + handlerLogPrefix); } if (sessionMetricUpdater.isEnabled( DefaultSessionMetric.CQL_REQUESTS, executionProfile.getName())) { @@ -589,7 +621,8 @@ private void setFinalError(Statement statement, Throwable error, Node node, i cancelScheduledTasks(); if (!(requestTracker instanceof NoopRequestTracker)) { long latencyNanos = System.nanoTime() - startTimeNanos; - requestTracker.onError(statement, error, latencyNanos, executionProfile, node, logPrefix); + requestTracker.onError( + statement, error, latencyNanos, executionProfile, node, handlerLogPrefix); } if (error instanceof DriverTimeoutException) { throttler.signalTimeout(this); @@ -639,7 +672,7 @@ private NodeResponseCallback( this.execution = execution; this.retryCount = retryCount; this.scheduleNextExecution = scheduleNextExecution; - this.logPrefix = logPrefix + "|" + execution; + this.logPrefix = logPrefix; } // this gets invoked once the write completes. @@ -717,7 +750,7 @@ private void scheduleSpeculativeExecution(int index, long delay) { if (!result.isDone()) { LOG.trace( "[{}] Starting speculative execution {}", - CqlRequestHandler.this.logPrefix, + CqlRequestHandler.this.handlerLogPrefix, index); activeExecutionsCount.incrementAndGet(); startedSpeculativeExecutionsCount.incrementAndGet(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/BasicLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/BasicLoadBalancingPolicy.java index f0e1cb3cf45..71160cb5612 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/BasicLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/BasicLoadBalancingPolicy.java @@ -173,6 +173,9 @@ public BasicLoadBalancingPolicy(@NonNull DriverContext context, @NonNull String private RequestRoutingMethod parseLwtRequestRoutingMethod() { String methodString = profile.getString(DefaultDriverOption.LOAD_BALANCING_DEFAULT_LWT_REQUEST_ROUTING_METHOD); + if (methodString == null) { + return RequestRoutingMethod.PRESERVE_REPLICA_ORDER; + } try { return RequestRoutingMethod.valueOf(methodString.toUpperCase()); } catch (IllegalArgumentException e) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java index 6b0935a887d..09a4dff653f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java @@ -267,7 +267,7 @@ public void onNodeSuccess( long latencyNanos, @NonNull DriverExecutionProfile executionProfile, @NonNull Node node, - @NonNull String logPrefix) { + @NonNull String nodeRequestLogPrefix) { updateResponseTimes(node); } @@ -278,7 +278,7 @@ public void onNodeError( long latencyNanos, @NonNull DriverExecutionProfile executionProfile, @NonNull Node node, - @NonNull String logPrefix) { + @NonNull String nodeRequestLogPrefix) { updateResponseTimes(node); } @@ -326,7 +326,8 @@ protected class NodeResponseRateSample { @VisibleForTesting protected final OptionalLong newest; private NodeResponseRateSample() { - this.oldest = nanoTime(); + long now = nanoTime(); + this.oldest = now; this.newest = OptionalLong.empty(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/helper/OptionalLocalDcHelper.java b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/helper/OptionalLocalDcHelper.java index 97aab92fff7..b93a16a6525 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/helper/OptionalLocalDcHelper.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/helper/OptionalLocalDcHelper.java @@ -26,6 +26,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import java.util.ArrayList; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -67,34 +68,73 @@ public OptionalLocalDcHelper( @Override @NonNull public Optional discoverLocalDc(@NonNull Map nodes) { - String localDc = context.getLocalDatacenter(profile.getName()); - if (localDc != null) { - LOG.debug("[{}] Local DC set programmatically: {}", logPrefix, localDc); + String localDcStr = context.getLocalDatacenter(profile.getName()); + Optional localDc; + if (localDcStr != null) { + LOG.debug("[{}] Local DC set programmatically: {}", logPrefix, localDcStr); + localDc = Optional.of(localDcStr); } else if (profile.isDefined(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER)) { - localDc = profile.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER); - LOG.debug("[{}] Local DC set from configuration: {}", logPrefix, localDc); + localDcStr = profile.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER); + LOG.debug("[{}] Local DC set from configuration: {}", logPrefix, localDcStr); + localDc = Optional.of(localDcStr); + } else { + localDc = Optional.empty(); + } + if (localDc.isPresent()) { + checkLocalDatacenterCompatibility( + localDc.get(), context.getMetadataManager().getContactPoints()); + // Also warn if the configured DC doesn't match any node in the cluster + if (!nodes.isEmpty()) { + boolean found = false; + for (Node node : nodes.values()) { + if (localDc.get().equals(node.getDatacenter())) { + found = true; + break; + } + } + if (!found) { + LOG.warn( + "[{}] Configured local DC '{}' does not match any node's datacenter" + + " (available DCs: {}); please verify your configuration", + logPrefix, + localDc.get(), + formatDcs(nodes.values())); + } + } } else { LOG.debug("[{}] Local DC not set, DC awareness will be disabled", logPrefix); - return Optional.empty(); } - if (!nodes.isEmpty()) { - boolean found = false; - for (Node node : nodes.values()) { - if (localDc.equals(node.getDatacenter())) { - found = true; - break; + return localDc; + } + + /** + * Checks if the contact points are compatible with the local datacenter specified either through + * configuration, or programmatically. + * + *

The default implementation logs a warning when a contact point reports a datacenter + * different from the local one, and only for the default profile. + * + * @param localDc The local datacenter, as specified in the config, or programmatically. + * @param contactPoints The contact points provided when creating the session. + */ + protected void checkLocalDatacenterCompatibility( + @NonNull String localDc, Set contactPoints) { + if (profile.getName().equals(DriverExecutionProfile.DEFAULT_NAME)) { + Set badContactPoints = new LinkedHashSet<>(); + for (Node node : contactPoints) { + if (!Objects.equals(localDc, node.getDatacenter())) { + badContactPoints.add(node); } } - if (!found) { + if (!badContactPoints.isEmpty()) { LOG.warn( - "[{}] Configured local DC '{}' does not match any node's datacenter" - + " (available DCs: {}); please verify your configuration", + "[{}] You specified {} as the local DC, but some contact points are from a different DC: {}; " + + "please provide the correct local DC, or check your contact points", logPrefix, localDc, - formatDcs(nodes.values())); + formatNodesAndDcs(badContactPoints)); } } - return Optional.of(localDc); } /** diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SniEndPoint.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SniEndPoint.java index ace4e82617d..d1ab8eec98d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SniEndPoint.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/SniEndPoint.java @@ -26,10 +26,10 @@ import java.util.Arrays; import java.util.Comparator; import java.util.Objects; -import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicInteger; public class SniEndPoint implements EndPoint { - private static final AtomicLong OFFSET = new AtomicLong(); + private static final AtomicInteger OFFSET = new AtomicInteger(); private final InetSocketAddress proxyAddress; private final String serverName; @@ -64,7 +64,10 @@ public InetSocketAddress resolve() { // The order of the returned address is unspecified. Sort by IP to make sure we get a true // round-robin Arrays.sort(aRecords, IP_COMPARATOR); - int index = (aRecords.length == 1) ? 0 : (int) OFFSET.getAndIncrement() % aRecords.length; + int index = + (aRecords.length == 1) + ? 0 + : OFFSET.getAndUpdate(x -> x == Integer.MAX_VALUE ? 0 : x + 1) % aRecords.length; return new InetSocketAddress(aRecords[index], proxyAddress.getPort()); } catch (UnknownHostException e) { throw new IllegalArgumentException( diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottler.java b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottler.java index ffe0ffe9650..8146c5b113a 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottler.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/session/throttling/ConcurrencyLimitingRequestThrottler.java @@ -26,10 +26,9 @@ import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.util.ArrayDeque; import java.util.Deque; -import java.util.concurrent.locks.ReentrantLock; -import net.jcip.annotations.GuardedBy; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.atomic.AtomicInteger; import net.jcip.annotations.ThreadSafe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,17 +60,12 @@ public class ConcurrencyLimitingRequestThrottler implements RequestThrottler { private final String logPrefix; private final int maxConcurrentRequests; private final int maxQueueSize; - - private final ReentrantLock lock = new ReentrantLock(); - - @GuardedBy("lock") - private int concurrentRequests; - - @GuardedBy("lock") - private final Deque queue = new ArrayDeque<>(); - - @GuardedBy("lock") - private boolean closed; + private final AtomicInteger concurrentRequests = new AtomicInteger(0); + // CLQ is not O(1) for size(), as it forces a full iteration of the queue. So, we track + // the size of the queue explicitly. + private final Deque queue = new ConcurrentLinkedDeque<>(); + private final AtomicInteger queueSize = new AtomicInteger(0); + private volatile boolean closed = false; public ConcurrencyLimitingRequestThrottler(DriverContext context) { this.logPrefix = context.getSessionName(); @@ -88,50 +82,62 @@ public ConcurrencyLimitingRequestThrottler(DriverContext context) { @Override public void register(@NonNull Throttled request) { - boolean notifyReadyRequired = false; + if (closed) { + LOG.trace("[{}] Rejecting request after shutdown", logPrefix); + fail(request, "The session is shutting down"); + return; + } - lock.lock(); - try { - if (closed) { - LOG.trace("[{}] Rejecting request after shutdown", logPrefix); - fail(request, "The session is shutting down"); - } else if (queue.isEmpty() && concurrentRequests < maxConcurrentRequests) { - // We have capacity for one more concurrent request + // Implementation note: Technically the "concurrent requests" or "queue size" + // could read transiently over the limit, but the queue itself will never grow + // beyond the limit since we always check for that condition and revert if + // over-limit. We do this instead of a CAS-loop to avoid the potential loop. + + // If no backlog exists AND we get capacity, we can execute immediately + if (queueSize.get() == 0) { + // Take a claim first, and then check if we are OK to proceed + int newConcurrent = concurrentRequests.incrementAndGet(); + if (newConcurrent <= maxConcurrentRequests) { LOG.trace("[{}] Starting newly registered request", logPrefix); - concurrentRequests += 1; - notifyReadyRequired = true; - } else if (queue.size() < maxQueueSize) { - LOG.trace("[{}] Enqueuing request", logPrefix); - queue.add(request); + request.onThrottleReady(false); + return; } else { - LOG.trace("[{}] Rejecting request because of full queue", logPrefix); - fail( - request, - String.format( - "The session has reached its maximum capacity " - + "(concurrent requests: %d, queue size: %d)", - maxConcurrentRequests, maxQueueSize)); + // We exceeded the limit, decrement the count and fall through to the queuing logic + concurrentRequests.decrementAndGet(); } - } finally { - lock.unlock(); } - // no need to hold the lock while allowing the task to progress - if (notifyReadyRequired) { - request.onThrottleReady(false); + // If we have a backlog, or we failed to claim capacity, try to enqueue + int newQueueSize = queueSize.incrementAndGet(); + if (newQueueSize <= maxQueueSize) { + LOG.trace("[{}] Enqueuing request", logPrefix); + queue.offer(request); + + // Double-check that we were still supposed to be enqueued; it is possible + // that the session was closed while we were enqueuing, it's also possible + // that it is right now removing the request, so we need to check both + if (closed) { + if (queue.remove(request)) { + queueSize.decrementAndGet(); + LOG.trace("[{}] Rejecting late request after shutdown", logPrefix); + fail(request, "The session is shutting down"); + } + } + } else { + LOG.trace("[{}] Rejecting request because of full queue", logPrefix); + queueSize.decrementAndGet(); + fail( + request, + String.format( + "The session has reached its maximum capacity " + + "(concurrent requests: %d, queue size: %d)", + maxConcurrentRequests, maxQueueSize)); } } @Override public void signalSuccess(@NonNull Throttled request) { - Throttled nextRequest = null; - lock.lock(); - try { - nextRequest = onRequestDoneAndDequeNext(); - } finally { - lock.unlock(); - } - + Throttled nextRequest = onRequestDoneAndDequeNext(); if (nextRequest != null) { nextRequest.onThrottleReady(true); } @@ -145,17 +151,13 @@ public void signalError(@NonNull Throttled request, @NonNull Throwable error) { @Override public void signalTimeout(@NonNull Throttled request) { Throttled nextRequest = null; - lock.lock(); - try { - if (!closed) { - if (queue.remove(request)) { // The request timed out before it was active - LOG.trace("[{}] Removing timed out request from the queue", logPrefix); - } else { - nextRequest = onRequestDoneAndDequeNext(); - } + if (!closed) { + if (queue.remove(request)) { // The request timed out before it was active + queueSize.decrementAndGet(); + LOG.trace("[{}] Removing timed out request from the queue", logPrefix); + } else { + nextRequest = onRequestDoneAndDequeNext(); } - } finally { - lock.unlock(); } if (nextRequest != null) { @@ -166,17 +168,13 @@ public void signalTimeout(@NonNull Throttled request) { @Override public void signalCancel(@NonNull Throttled request) { Throttled nextRequest = null; - lock.lock(); - try { - if (!closed) { - if (queue.remove(request)) { // The request has been cancelled before it was active - LOG.trace("[{}] Removing cancelled request from the queue", logPrefix); - } else { - nextRequest = onRequestDoneAndDequeNext(); - } + if (!closed) { + if (queue.remove(request)) { // The request has been cancelled before it was active + queueSize.decrementAndGet(); + LOG.trace("[{}] Removing cancelled request from the queue", logPrefix); + } else { + nextRequest = onRequestDoneAndDequeNext(); } - } finally { - lock.unlock(); } if (nextRequest != null) { @@ -184,17 +182,16 @@ public void signalCancel(@NonNull Throttled request) { } } - @SuppressWarnings("GuardedBy") // this method is only called with the lock held @Nullable private Throttled onRequestDoneAndDequeNext() { - assert lock.isHeldByCurrentThread(); if (!closed) { - if (queue.isEmpty()) { - concurrentRequests -= 1; + Throttled nextRequest = queue.poll(); + if (nextRequest == null) { + concurrentRequests.decrementAndGet(); } else { + queueSize.decrementAndGet(); LOG.trace("[{}] Starting dequeued request", logPrefix); - // don't touch concurrentRequests since we finished one but started another - return queue.poll(); + return nextRequest; } } @@ -204,45 +201,28 @@ private Throttled onRequestDoneAndDequeNext() { @Override public void close() { - lock.lock(); - try { - closed = true; - LOG.debug("[{}] Rejecting {} queued requests after shutdown", logPrefix, queue.size()); - for (Throttled request : queue) { - fail(request, "The session is shutting down"); - } - } finally { - lock.unlock(); + closed = true; + + LOG.debug("[{}] Rejecting {} queued requests after shutdown", logPrefix, queueSize.get()); + Throttled request; + while ((request = queue.poll()) != null) { + queueSize.decrementAndGet(); + fail(request, "The session is shutting down"); } } public int getQueueSize() { - lock.lock(); - try { - return queue.size(); - } finally { - lock.unlock(); - } + return queueSize.get(); } @VisibleForTesting int getConcurrentRequests() { - lock.lock(); - try { - return concurrentRequests; - } finally { - lock.unlock(); - } + return concurrentRequests.get(); } @VisibleForTesting Deque getQueue() { - lock.lock(); - try { - return queue; - } finally { - lock.unlock(); - } + return queue; } private static void fail(Throttled request, String message) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java index 475ec38d578..343d3f9e4e7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java @@ -22,6 +22,7 @@ import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.core.metadata.EndPoint; import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; +import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.InputStream; import java.net.InetSocketAddress; @@ -69,6 +70,7 @@ public class DefaultSslEngineFactory implements SslEngineFactory { private final SSLContext sslContext; private final String[] cipherSuites; private final boolean requireHostnameValidation; + private final boolean allowDnsReverseLookupSan; private ReloadingKeyManagerFactory kmf; /** Builds a new instance from the driver configuration. */ @@ -88,6 +90,28 @@ public DefaultSslEngineFactory(DriverContext driverContext) { } this.requireHostnameValidation = config.getBoolean(DefaultDriverOption.SSL_HOSTNAME_VALIDATION, true); + this.allowDnsReverseLookupSan = + config.getBoolean(DefaultDriverOption.SSL_ALLOW_DNS_REVERSE_LOOKUP_SAN, true); + } + + @VisibleForTesting + protected String hostname(InetSocketAddress addr) { + return allowDnsReverseLookupSan ? hostMaybeFromDnsReverseLookup(addr) : hostNoLookup(addr); + } + + @VisibleForTesting + protected String hostMaybeFromDnsReverseLookup(InetSocketAddress addr) { + // See java.net.InetSocketAddress.getHostName: + // "This method may trigger a name service reverse lookup if the address was created with a + // literal IP address." + return addr.getHostName(); + } + + @VisibleForTesting + protected String hostNoLookup(InetSocketAddress addr) { + // See java.net.InetSocketAddress.getHostString: + // "This has the benefit of not attempting a reverse lookup" + return addr.getHostString(); } @NonNull @@ -97,7 +121,7 @@ public SSLEngine newSslEngine(@NonNull EndPoint remoteEndpoint) { SocketAddress remoteAddress = remoteEndpoint.resolve(); if (remoteAddress instanceof InetSocketAddress) { InetSocketAddress socketAddress = (InetSocketAddress) remoteAddress; - engine = sslContext.createSSLEngine(socketAddress.getHostName(), socketAddress.getPort()); + engine = sslContext.createSSLEngine(hostname(socketAddress), socketAddress.getPort()); } else { engine = sslContext.createSSLEngine(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/SniSslEngineFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/SniSslEngineFactory.java index 98af19045dc..4d2cb69fbfc 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/SniSslEngineFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/SniSslEngineFactory.java @@ -38,9 +38,15 @@ public class SniSslEngineFactory implements SslEngineFactory { private final SSLContext sslContext; private final CopyOnWriteArrayList fakePorts = new CopyOnWriteArrayList<>(); + private final boolean allowDnsReverseLookupSan; public SniSslEngineFactory(SSLContext sslContext) { + this(sslContext, true); + } + + public SniSslEngineFactory(SSLContext sslContext, boolean allowDnsReverseLookupSan) { this.sslContext = sslContext; + this.allowDnsReverseLookupSan = allowDnsReverseLookupSan; } @NonNull @@ -71,8 +77,8 @@ public SSLEngine newSslEngine(@NonNull EndPoint remoteEndpoint) { // To avoid that, we create a unique "fake" port for every node. We still get session reuse for // a given node, but not across nodes. This is safe because the advisory port is only used for // session caching. - SSLEngine engine = - sslContext.createSSLEngine(address.getHostName(), getFakePort(sniServerName)); + String peerHost = allowDnsReverseLookupSan ? address.getHostName() : address.getHostString(); + SSLEngine engine = sslContext.createSSLEngine(peerHost, getFakePort(sniServerName)); engine.setUseClientMode(true); SSLParameters parameters = engine.getSSLParameters(); parameters.setServerNames(ImmutableList.of(new SNIHostName(sniServerName))); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/MultiplexingRequestTracker.java b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/MultiplexingRequestTracker.java index d4d20f3eb78..6fe2ba059bd 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/MultiplexingRequestTracker.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/MultiplexingRequestTracker.java @@ -82,10 +82,12 @@ public void onSuccess( long latencyNanos, @NonNull DriverExecutionProfile executionProfile, @NonNull Node node, - @NonNull String logPrefix) { + @NonNull String sessionRequestLogPrefix) { invokeTrackers( - tracker -> tracker.onSuccess(request, latencyNanos, executionProfile, node, logPrefix), - logPrefix, + tracker -> + tracker.onSuccess( + request, latencyNanos, executionProfile, node, sessionRequestLogPrefix), + sessionRequestLogPrefix, "onSuccess"); } @@ -96,10 +98,12 @@ public void onError( long latencyNanos, @NonNull DriverExecutionProfile executionProfile, @Nullable Node node, - @NonNull String logPrefix) { + @NonNull String sessionRequestLogPrefix) { invokeTrackers( - tracker -> tracker.onError(request, error, latencyNanos, executionProfile, node, logPrefix), - logPrefix, + tracker -> + tracker.onError( + request, error, latencyNanos, executionProfile, node, sessionRequestLogPrefix), + sessionRequestLogPrefix, "onError"); } @@ -109,10 +113,12 @@ public void onNodeSuccess( long latencyNanos, @NonNull DriverExecutionProfile executionProfile, @NonNull Node node, - @NonNull String logPrefix) { + @NonNull String nodeRequestLogPrefix) { invokeTrackers( - tracker -> tracker.onNodeSuccess(request, latencyNanos, executionProfile, node, logPrefix), - logPrefix, + tracker -> + tracker.onNodeSuccess( + request, latencyNanos, executionProfile, node, nodeRequestLogPrefix), + nodeRequestLogPrefix, "onNodeSuccess"); } @@ -123,11 +129,12 @@ public void onNodeError( long latencyNanos, @NonNull DriverExecutionProfile executionProfile, @NonNull Node node, - @NonNull String logPrefix) { + @NonNull String nodeRequestLogPrefix) { invokeTrackers( tracker -> - tracker.onNodeError(request, error, latencyNanos, executionProfile, node, logPrefix), - logPrefix, + tracker.onNodeError( + request, error, latencyNanos, executionProfile, node, nodeRequestLogPrefix), + nodeRequestLogPrefix, "onNodeError"); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/NoopRequestTracker.java b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/NoopRequestTracker.java index 09ac27e5e75..3821c6ace2d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/NoopRequestTracker.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/NoopRequestTracker.java @@ -42,7 +42,7 @@ public void onSuccess( long latencyNanos, @NonNull DriverExecutionProfile executionProfile, @NonNull Node node, - @NonNull String requestPrefix) { + @NonNull String sessionRequestLogPrefix) { // nothing to do } @@ -53,7 +53,7 @@ public void onError( long latencyNanos, @NonNull DriverExecutionProfile executionProfile, Node node, - @NonNull String requestPrefix) { + @NonNull String sessionRequestLogPrefix) { // nothing to do } @@ -64,7 +64,7 @@ public void onNodeError( long latencyNanos, @NonNull DriverExecutionProfile executionProfile, @NonNull Node node, - @NonNull String requestPrefix) { + @NonNull String nodeRequestLogPrefix) { // nothing to do } @@ -74,7 +74,7 @@ public void onNodeSuccess( long latencyNanos, @NonNull DriverExecutionProfile executionProfile, @NonNull Node node, - @NonNull String requestPrefix) { + @NonNull String nodeRequestLogPrefix) { // nothing to do } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java index 235ef051b40..f242ff89c54 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/RequestLogger.java @@ -86,7 +86,7 @@ public void onSuccess( long latencyNanos, @NonNull DriverExecutionProfile executionProfile, @NonNull Node node, - @NonNull String logPrefix) { + @NonNull String sessionRequestLogPrefix) { boolean successEnabled = executionProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_SUCCESS_ENABLED, false); @@ -129,7 +129,7 @@ public void onSuccess( showValues, maxValues, maxValueLength, - logPrefix); + sessionRequestLogPrefix); } @Override @@ -139,7 +139,7 @@ public void onError( long latencyNanos, @NonNull DriverExecutionProfile executionProfile, Node node, - @NonNull String logPrefix) { + @NonNull String sessionRequestLogPrefix) { if (!executionProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_ERROR_ENABLED, false)) { return; @@ -173,7 +173,7 @@ public void onError( maxValues, maxValueLength, showStackTraces, - logPrefix); + sessionRequestLogPrefix); } @Override @@ -183,7 +183,7 @@ public void onNodeError( long latencyNanos, @NonNull DriverExecutionProfile executionProfile, @NonNull Node node, - @NonNull String logPrefix) { + @NonNull String nodeRequestLogPrefix) { // Nothing to do } @@ -193,7 +193,7 @@ public void onNodeSuccess( long latencyNanos, @NonNull DriverExecutionProfile executionProfile, @NonNull Node node, - @NonNull String logPrefix) { + @NonNull String nodeRequestLogPrefix) { // Nothing to do } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/UuidRequestIdGenerator.java b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/UuidRequestIdGenerator.java new file mode 100644 index 00000000000..cc07d6717f4 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/UuidRequestIdGenerator.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.tracker; + +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.tracker.RequestIdGenerator; +import com.datastax.oss.driver.api.core.uuid.Uuids; +import edu.umd.cs.findbugs.annotations.NonNull; + +public class UuidRequestIdGenerator implements RequestIdGenerator { + public UuidRequestIdGenerator(DriverContext context) {} + + /** Generates a random v4 UUID. */ + @Override + public String getSessionRequestId() { + return Uuids.random().toString(); + } + + /** + * {session-request-id}-{random-uuid} All node requests for a session request will have the same + * session request id + */ + @Override + public String getNodeRequestId(@NonNull Request statement, @NonNull String parentId) { + return parentId + "-" + Uuids.random(); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/W3CContextRequestIdGenerator.java b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/W3CContextRequestIdGenerator.java new file mode 100644 index 00000000000..fe15b93bc8e --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/tracker/W3CContextRequestIdGenerator.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.tracker; + +import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.tracker.RequestIdGenerator; +import com.datastax.oss.driver.shaded.guava.common.io.BaseEncoding; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.security.SecureRandom; +import java.util.Random; + +public class W3CContextRequestIdGenerator implements RequestIdGenerator { + + private final Random random = new SecureRandom(); + private final BaseEncoding baseEncoding = BaseEncoding.base16().lowerCase(); + private final String payloadKey; + + public W3CContextRequestIdGenerator(DriverContext context) { + payloadKey = RequestIdGenerator.super.getCustomPayloadKey(); + } + + public W3CContextRequestIdGenerator(String payloadKey) { + this.payloadKey = payloadKey; + } + + /** Random 16 bytes, e.g. "4bf92f3577b34da6a3ce929d0e0e4736" */ + @Override + public String getSessionRequestId() { + byte[] bytes = new byte[16]; + random.nextBytes(bytes); + return baseEncoding.encode(bytes); + } + + /** + * Following the format of W3C "traceparent" spec, + * https://www.w3.org/TR/trace-context/#traceparent-header-field-values e.g. + * "00-4bf92f3577b34da6a3ce929d0e0e4736-a3ce929d0e0e4736-01" All node requests in the same session + * request share the same "trace-id" field value + */ + @Override + public String getNodeRequestId(@NonNull Request statement, @NonNull String parentId) { + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + return String.format("00-%s-%s-00", parentId, baseEncoding.encode(bytes)); + } + + @Override + public String getCustomPayloadKey() { + return this.payloadKey; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/DefaultCodecRegistry.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/DefaultCodecRegistry.java index cfd053ea56e..cc14740e180 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/DefaultCodecRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/DefaultCodecRegistry.java @@ -159,7 +159,10 @@ public boolean equals(Object other) { @Override public int hashCode() { - return Objects.hash(cqlType, javaType, isJavaCovariant); + // NOTE: inlined Objects.hash for performance reasons (avoid Object[] allocation + // seen in profiler allocation traces) + return ((31 + Objects.hashCode(cqlType)) * 31 + Objects.hashCode(javaType)) * 31 + + Boolean.hashCode(isJavaCovariant); } } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/AddressUtils.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/AddressUtils.java new file mode 100644 index 00000000000..8905edb9192 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/AddressUtils.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.util; + +import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.HashSet; +import java.util.Set; + +public class AddressUtils { + + public static Set extract(String address, boolean resolve) { + int separator = address.lastIndexOf(':'); + if (separator < 0) { + throw new IllegalArgumentException("expecting format host:port"); + } + + String host = address.substring(0, separator); + String portString = address.substring(separator + 1); + int port; + try { + port = Integer.parseInt(portString); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("expecting port to be a number, got " + portString, e); + } + if (!resolve) { + return ImmutableSet.of(InetSocketAddress.createUnresolved(host, port)); + } else { + InetAddress[] inetAddresses; + try { + inetAddresses = InetAddress.getAllByName(host); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + Set result = new HashSet<>(); + for (InetAddress inetAddress : inetAddresses) { + result.add(new InetSocketAddress(inetAddress, port)); + } + return result; + } + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java index 17b6c01178d..8862c1cd6c4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/ArrayUtils.java @@ -18,6 +18,7 @@ package com.datastax.oss.driver.internal.core.util; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Random; import java.util.concurrent.ThreadLocalRandom; public class ArrayUtils { @@ -77,7 +78,7 @@ public static void shuffleHead(@NonNull ElementT[] elements, int n) { * Fisher-Yates shuffle */ public static void shuffleHead( - @NonNull ElementT[] elements, int n, @NonNull ThreadLocalRandom random) { + @NonNull ElementT[] elements, int n, @NonNull Random random) { if (n > elements.length) { throw new ArrayIndexOutOfBoundsException( String.format( diff --git a/core/src/main/resources/reference.conf b/core/src/main/resources/reference.conf index 92c5b98118f..721afa1cd76 100644 --- a/core/src/main/resources/reference.conf +++ b/core/src/main/resources/reference.conf @@ -866,6 +866,12 @@ datastax-java-driver { # name matches the hostname of the server being connected to. If not set, defaults to true. // hostname-validation = true + # Whether or not to allow a DNS reverse-lookup of provided server addresses for SAN addresses, + # if cluster endpoints are specified as literal IPs. + # This is left as true for compatibility, but in most environments a DNS reverse-lookup should + # not be necessary to get an address that matches the server certificate SANs. + // allow-dns-reverse-lookup-san = true + # The locations and passwords used to access truststore and keystore contents. # These properties are optional. If either truststore-path or keystore-path are specified, # the driver builds an SSLContext from these files. If neither option is specified, the @@ -989,6 +995,13 @@ datastax-java-driver { } } + advanced.request-id { + generator { + # The component that generates a unique identifier for each CQL request, and possibly write the id to the custom payload . + // class = W3CContextRequestIdGenerator + } + } + # A session-wide component that controls the rate at which requests are executed. # # Implementations vary, but throttlers generally track a metric that represents the level of @@ -1097,8 +1110,9 @@ datastax-java-driver { # the package com.datastax.oss.driver.internal.core.addresstranslation. # # The driver provides the following implementations out of the box: - # - PassThroughAddressTranslator: returns all addresses unchanged + # - PassThroughAddressTranslator: returns all addresses unchanged. # - FixedHostNameAddressTranslator: translates all addresses to a specific hostname. + # - SubnetAddressTranslator: translates addresses to hostname based on the subnet match. # - Ec2MultiRegionAddressTranslator: suitable for an Amazon multi-region EC2 deployment where # clients are also deployed in EC2. It optimizes network costs by favoring private IPs over # public ones whenever possible. @@ -1106,8 +1120,23 @@ datastax-java-driver { # You can also specify a custom class that implements AddressTranslator and has a public # constructor with a DriverContext argument. class = PassThroughAddressTranslator + # # This property has to be set only in case you use FixedHostNameAddressTranslator. # advertised-hostname = mycustomhostname + # + # These properties are only applicable in case you use SubnetAddressTranslator. + # subnet-addresses { + # "100.64.0.0/15" = "cassandra.datacenter1.com:9042" + # "100.66.0.0/15" = "cassandra.datacenter2.com:9042" + # # IPv6 example: + # # "::ffff:6440:0/111" = "cassandra.datacenter1.com:9042" + # # "::ffff:6442:0/111" = "cassandra.datacenter2.com:9042" + # } + # Optional. When configured, addresses not matching the configured subnets are translated to this address. + # default-address = "cassandra.datacenter1.com:9042" + # Whether to resolve the addresses once on initialization (if true) or on each node (re-)connection (if false). + # If not configured, defaults to false. + # resolve-addresses = false } # Client routes configuration for cloud private-endpoint deployments diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/ContactPointsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/ContactPointsTest.java index 9e0d8737619..72b875b8602 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/ContactPointsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/ContactPointsTest.java @@ -121,7 +121,7 @@ public void should_ignore_malformed_host_and_port_and_warn() { ContactPoints.merge(Collections.emptySet(), ImmutableList.of("foobar"), true); assertThat(endPoints).isEmpty(); - assertLog(Level.WARN, "Ignoring invalid contact point foobar (expecting host:port)"); + assertLog(Level.WARN, "Ignoring invalid contact point foobar (expecting format host:port)"); } @Test @@ -132,7 +132,7 @@ public void should_ignore_malformed_port_and_warn() { assertThat(endPoints).isEmpty(); assertLog( Level.WARN, - "Ignoring invalid contact point 127.0.0.1:foobar (expecting a number, got foobar)"); + "Ignoring invalid contact point 127.0.0.1:foobar (expecting port to be a number, got foobar)"); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/addresstranslation/FixedHostNameAddressTranslatorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/addresstranslation/FixedHostNameAddressTranslatorTest.java index c5e864b4bae..92800998056 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/addresstranslation/FixedHostNameAddressTranslatorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/addresstranslation/FixedHostNameAddressTranslatorTest.java @@ -17,6 +17,7 @@ */ package com.datastax.oss.driver.internal.core.addresstranslation; +import static com.datastax.oss.driver.api.core.config.DefaultDriverOption.ADDRESS_TRANSLATOR_ADVERTISED_HOSTNAME; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -33,9 +34,7 @@ public class FixedHostNameAddressTranslatorTest { @Test public void should_translate_address() { DriverExecutionProfile defaultProfile = mock(DriverExecutionProfile.class); - when(defaultProfile.getString( - FixedHostNameAddressTranslator.ADDRESS_TRANSLATOR_ADVERTISED_HOSTNAME_OPTION)) - .thenReturn("myaddress"); + when(defaultProfile.getString(ADDRESS_TRANSLATOR_ADVERTISED_HOSTNAME)).thenReturn("myaddress"); DefaultDriverContext defaultDriverContext = MockedDriverContextFactory.defaultDriverContext(Optional.of(defaultProfile)); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTest.java new file mode 100644 index 00000000000..bd505f5dd44 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTest.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.addresstranslation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import java.net.InetSocketAddress; +import org.junit.Test; + +public class SubnetAddressTest { + @Test + public void should_return_return_true_on_overlapping_with_another_subnet_address() { + SubnetAddress subnetAddress1 = + new SubnetAddress("100.64.0.0/15", mock(InetSocketAddress.class)); + SubnetAddress subnetAddress2 = + new SubnetAddress("100.65.0.0/16", mock(InetSocketAddress.class)); + assertThat(subnetAddress1.isOverlapping(subnetAddress2)).isTrue(); + } + + @Test + public void should_return_return_false_on_not_overlapping_with_another_subnet_address() { + SubnetAddress subnetAddress1 = + new SubnetAddress("100.64.0.0/15", mock(InetSocketAddress.class)); + SubnetAddress subnetAddress2 = + new SubnetAddress("100.66.0.0/15", mock(InetSocketAddress.class)); + assertThat(subnetAddress1.isOverlapping(subnetAddress2)).isFalse(); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTranslatorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTranslatorTest.java new file mode 100644 index 00000000000..2aa6ae75bc2 --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTranslatorTest.java @@ -0,0 +1,153 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.addresstranslation; + +import static com.datastax.oss.driver.api.core.config.DefaultDriverOption.ADDRESS_TRANSLATOR_DEFAULT_ADDRESS; +import static com.datastax.oss.driver.api.core.config.DefaultDriverOption.ADDRESS_TRANSLATOR_SUBNET_ADDRESSES; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.datastax.oss.driver.api.core.config.DriverExecutionProfile; +import com.datastax.oss.driver.internal.core.context.DefaultDriverContext; +import com.datastax.oss.driver.internal.core.context.MockedDriverContextFactory; +import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; +import java.net.InetSocketAddress; +import java.util.Map; +import java.util.Optional; +import org.junit.Test; + +@SuppressWarnings("resource") +public class SubnetAddressTranslatorTest { + + @Test + public void should_translate_to_correct_subnet_address_ipv4() { + Map subnetAddresses = + ImmutableMap.of( + "\"100.64.0.0/15\"", "cassandra.datacenter1.com:19042", + "100.66.0.\"0/15\"", "cassandra.datacenter2.com:19042"); + DefaultDriverContext context = context(subnetAddresses); + SubnetAddressTranslator translator = new SubnetAddressTranslator(context); + InetSocketAddress address = new InetSocketAddress("100.64.0.1", 9042); + assertThat(translator.translate(address)) + .isEqualTo(InetSocketAddress.createUnresolved("cassandra.datacenter1.com", 19042)); + } + + @Test + public void should_translate_to_correct_subnet_address_ipv6() { + Map subnetAddresses = + ImmutableMap.of( + "\"::ffff:6440:0/111\"", "cassandra.datacenter1.com:19042", + "\"::ffff:6442:0/111\"", "cassandra.datacenter2.com:19042"); + DefaultDriverContext context = context(subnetAddresses); + SubnetAddressTranslator translator = new SubnetAddressTranslator(context); + InetSocketAddress address = new InetSocketAddress("::ffff:6440:1", 9042); + assertThat(translator.translate(address)) + .isEqualTo(InetSocketAddress.createUnresolved("cassandra.datacenter1.com", 19042)); + } + + @Test + public void should_translate_to_default_address() { + DefaultDriverContext context = context(ImmutableMap.of()); + when(context + .getConfig() + .getDefaultProfile() + .getString(ADDRESS_TRANSLATOR_DEFAULT_ADDRESS, null)) + .thenReturn("cassandra.com:19042"); + SubnetAddressTranslator translator = new SubnetAddressTranslator(context); + InetSocketAddress address = new InetSocketAddress("100.68.0.1", 9042); + assertThat(translator.translate(address)) + .isEqualTo(InetSocketAddress.createUnresolved("cassandra.com", 19042)); + } + + @Test + public void should_pass_through_not_matched_address() { + DefaultDriverContext context = context(ImmutableMap.of()); + SubnetAddressTranslator translator = new SubnetAddressTranslator(context); + InetSocketAddress address = new InetSocketAddress("100.68.0.1", 9042); + assertThat(translator.translate(address)).isEqualTo(address); + } + + @Test + public void should_fail_on_intersecting_subnets_ipv4() { + Map subnetAddresses = + ImmutableMap.of( + "\"100.64.0.0/15\"", "cassandra.datacenter1.com:19042", + "100.65.0.\"0/16\"", "cassandra.datacenter2.com:19042"); + DefaultDriverContext context = context(subnetAddresses); + assertThatIllegalArgumentException() + .isThrownBy(() -> new SubnetAddressTranslator(context)) + .withMessage( + "Configured subnets are overlapping: " + + String.format( + "SubnetAddress[subnet=[100, 64, 0, 0], address=%s], ", + InetSocketAddress.createUnresolved("cassandra.datacenter1.com", 19042)) + + String.format( + "SubnetAddress[subnet=[100, 65, 0, 0], address=%s]", + InetSocketAddress.createUnresolved("cassandra.datacenter2.com", 19042))); + } + + @Test + public void should_fail_on_intersecting_subnets_ipv6() { + Map subnetAddresses = + ImmutableMap.of( + "\"::ffff:6440:0/111\"", "cassandra.datacenter1.com:19042", + "\"::ffff:6441:0/112\"", "cassandra.datacenter2.com:19042"); + DefaultDriverContext context = context(subnetAddresses); + assertThatIllegalArgumentException() + .isThrownBy(() -> new SubnetAddressTranslator(context)) + .withMessage( + "Configured subnets are overlapping: " + + String.format( + "SubnetAddress[subnet=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 100, 64, 0, 0], address=%s], ", + InetSocketAddress.createUnresolved("cassandra.datacenter1.com", 19042)) + + String.format( + "SubnetAddress[subnet=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 100, 65, 0, 0], address=%s]", + InetSocketAddress.createUnresolved("cassandra.datacenter2.com", 19042))); + } + + @Test + public void should_fail_on_subnet_address_without_port() { + Map subnetAddresses = + ImmutableMap.of("\"100.64.0.0/15\"", "cassandra.datacenter1.com"); + DefaultDriverContext context = context(subnetAddresses); + assertThatIllegalArgumentException() + .isThrownBy(() -> new SubnetAddressTranslator(context)) + .withMessage("Invalid address cassandra.datacenter1.com (expecting format host:port)"); + } + + @Test + public void should_fail_on_default_address_without_port() { + DefaultDriverContext context = context(ImmutableMap.of()); + when(context + .getConfig() + .getDefaultProfile() + .getString(ADDRESS_TRANSLATOR_DEFAULT_ADDRESS, null)) + .thenReturn("cassandra.com"); + assertThatIllegalArgumentException() + .isThrownBy(() -> new SubnetAddressTranslator(context)) + .withMessage("Invalid address cassandra.com (expecting format host:port)"); + } + + private static DefaultDriverContext context(Map subnetAddresses) { + DriverExecutionProfile profile = mock(DriverExecutionProfile.class); + when(profile.getStringMap(ADDRESS_TRANSLATOR_SUBNET_ADDRESSES)).thenReturn(subnetAddresses); + return MockedDriverContextFactory.defaultDriverContext(Optional.of(profile)); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetTest.java new file mode 100644 index 00000000000..f8ba8929e9e --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetTest.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.addresstranslation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatNoException; + +import java.net.UnknownHostException; +import org.junit.Test; + +public class SubnetTest { + @Test + public void should_parse_to_correct_ipv4_subnet() throws UnknownHostException { + Subnet subnet = Subnet.parse("100.64.0.0/15"); + assertThat(subnet.getSubnet()).containsExactly(100, 64, 0, 0); + assertThat(subnet.getNetworkMask()).containsExactly(255, 254, 0, 0); + assertThat(subnet.getUpper()).containsExactly(100, 65, 255, 255); + assertThat(subnet.getLower()).containsExactly(100, 64, 0, 0); + } + + @Test + public void should_parse_to_correct_ipv6_subnet() throws UnknownHostException { + Subnet subnet = Subnet.parse("2001:db8:85a3::8a2e:370:0/111"); + assertThat(subnet.getSubnet()) + .containsExactly(32, 1, 13, 184, 133, 163, 0, 0, 0, 0, 138, 46, 3, 112, 0, 0); + assertThat(subnet.getNetworkMask()) + .containsExactly( + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 0, 0); + assertThat(subnet.getUpper()) + .containsExactly(32, 1, 13, 184, 133, 163, 0, 0, 0, 0, 138, 46, 3, 113, 255, 255); + assertThat(subnet.getLower()) + .containsExactly(32, 1, 13, 184, 133, 163, 0, 0, 0, 0, 138, 46, 3, 112, 0, 0); + } + + @Test + public void should_parse_to_correct_ipv6_subnet_ipv4_convertible() throws UnknownHostException { + Subnet subnet = Subnet.parse("::ffff:6440:0/111"); + assertThat(subnet.getSubnet()) + .containsExactly(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 100, 64, 0, 0); + assertThat(subnet.getNetworkMask()) + .containsExactly( + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 0, 0); + assertThat(subnet.getUpper()) + .containsExactly(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 100, 65, 255, 255); + assertThat(subnet.getLower()) + .containsExactly(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 100, 64, 0, 0); + } + + @Test + public void should_fail_on_invalid_cidr_format() { + assertThatIllegalArgumentException() + .isThrownBy(() -> Subnet.parse("invalid")) + .withMessage("Invalid subnet: invalid"); + } + + @Test + public void should_parse_bounding_prefix_lengths_correctly() { + assertThatNoException().isThrownBy(() -> Subnet.parse("0.0.0.0/0")); + assertThatNoException().isThrownBy(() -> Subnet.parse("100.64.0.0/32")); + } + + @Test + public void should_fail_on_invalid_prefix_length() { + assertThatIllegalArgumentException() + .isThrownBy(() -> Subnet.parse("100.64.0.0/-1")) + .withMessage("Prefix length -1 must be within [0; 32]"); + assertThatIllegalArgumentException() + .isThrownBy(() -> Subnet.parse("100.64.0.0/33")) + .withMessage("Prefix length 33 must be within [0; 32]"); + } + + @Test + public void should_fail_on_not_prefix_block_subnet_ipv4() { + assertThatIllegalArgumentException() + .isThrownBy(() -> Subnet.parse("100.65.0.0/15")) + .withMessage("Subnet 100.65.0.0/15 must be represented as a network prefix block"); + } + + @Test + public void should_fail_on_not_prefix_block_subnet_ipv6() { + assertThatIllegalArgumentException() + .isThrownBy(() -> Subnet.parse("::ffff:6441:0/111")) + .withMessage("Subnet ::ffff:6441:0/111 must be represented as a network prefix block"); + } + + @Test + public void should_return_true_on_containing_address() throws UnknownHostException { + Subnet subnet = Subnet.parse("100.64.0.0/15"); + assertThat(subnet.contains(new byte[] {100, 64, 0, 0})).isTrue(); + assertThat(subnet.contains(new byte[] {100, 65, (byte) 255, (byte) 255})).isTrue(); + assertThat(subnet.contains(new byte[] {100, 65, 100, 100})).isTrue(); + } + + @Test + public void should_return_false_on_not_containing_address() throws UnknownHostException { + Subnet subnet = Subnet.parse("100.64.0.0/15"); + assertThat(subnet.contains(new byte[] {100, 63, (byte) 255, (byte) 255})).isFalse(); + assertThat(subnet.contains(new byte[] {100, 66, 0, 0})).isFalse(); + // IPv6 cannot be contained by IPv4 subnet. + assertThat(subnet.contains(new byte[16])).isFalse(); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/MockOptions.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/MockOptions.java index 25c1e8b26fd..cee57abbfdf 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/MockOptions.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/MockOptions.java @@ -24,6 +24,7 @@ public enum MockOptions implements DriverOption { INT1("int1"), INT2("int2"), AUTH_PROVIDER("auth_provider"), + SUBNET_ADDRESSES("subnet_addresses"), ; private final String path; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigTest.java index 16ccb73da9f..4a78c3ccb03 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/typesafe/TypesafeDriverConfigTest.java @@ -101,7 +101,6 @@ public void should_fetch_string_map() { parse( "int1 = 42 \n auth_provider { auth_thing_one= one \n auth_thing_two = two \n auth_thing_three = three}"); DriverExecutionProfile base = config.getDefaultProfile(); - base.getStringMap(MockOptions.AUTH_PROVIDER); Map map = base.getStringMap(MockOptions.AUTH_PROVIDER); assertThat(map.entrySet().size()).isEqualTo(3); assertThat(map.get("auth_thing_one")).isEqualTo("one"); @@ -109,6 +108,19 @@ public void should_fetch_string_map() { assertThat(map.get("auth_thing_three")).isEqualTo("three"); } + @Test + public void should_fetch_string_map_with_forward_slash_in_keys() { + TypesafeDriverConfig config = + parse( + "subnet_addresses { 100.64.0.0/15 = \"cassandra.datacenter1.com:9042\" \n \"100.66.0.0/15\" = \"cassandra.datacenter2.com\" \n \"::ffff:6440:0/111\" = \"cassandra.datacenter3.com:19042\" }"); + DriverExecutionProfile base = config.getDefaultProfile(); + Map map = base.getStringMap(MockOptions.SUBNET_ADDRESSES); + assertThat(map.entrySet().size()).isEqualTo(3); + assertThat(map.get("100.64.0.\"0/15\"")).isEqualTo("cassandra.datacenter1.com:9042"); + assertThat(map.get("\"100.66.0.0/15\"")).isEqualTo("cassandra.datacenter2.com"); + assertThat(map.get("\"::ffff:6440:0/111\"")).isEqualTo("cassandra.datacenter3.com:19042"); + } + @Test public void should_create_derived_profile_with_string_map() { TypesafeDriverConfig config = parse("int1 = 42"); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/context/MockedDriverContextFactory.java b/core/src/test/java/com/datastax/oss/driver/internal/core/context/MockedDriverContextFactory.java index 06817326844..f59df87606f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/context/MockedDriverContextFactory.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/context/MockedDriverContextFactory.java @@ -54,6 +54,9 @@ public static DefaultDriverContext defaultDriverContext( .thenReturn(true); when(blankProfile.getString(DefaultDriverOption.METRICS_FACTORY_CLASS)) .thenReturn("DefaultMetricsFactory"); + when(blankProfile.getString( + DefaultDriverOption.LOAD_BALANCING_DEFAULT_LWT_REQUEST_ROUTING_METHOD)) + .thenReturn("PRESERVE_REPLICA_ORDER"); return blankProfile; }); @@ -62,7 +65,6 @@ public static DefaultDriverContext defaultDriverContext( final DriverConfigLoader configLoader = mock(DriverConfigLoader.class); when(configLoader.getInitialConfig()).thenReturn(driverConfig); when(driverConfig.getDefaultProfile()).thenReturn(profile); - ProgrammaticArguments args = ProgrammaticArguments.builder() .withNodeStateListener(mock(NodeStateListener.class)) diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/context/StartupOptionsBuilderTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/context/StartupOptionsBuilderTest.java index 33811b2793a..2f8f4174093 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/context/StartupOptionsBuilderTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/context/StartupOptionsBuilderTest.java @@ -41,6 +41,7 @@ private DefaultDriverContext buildMockedContext(String compression) { DriverExecutionProfile defaultProfile = mock(DriverExecutionProfile.class); when(defaultProfile.getString(DefaultDriverOption.PROTOCOL_COMPRESSION, "none")) .thenReturn(compression); + when(defaultProfile.getName()).thenReturn(DriverExecutionProfile.DEFAULT_NAME); return MockedDriverContextFactory.defaultDriverContext(Optional.of(defaultProfile)); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java index bea52891c18..ccac873c616 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlRequestHandlerRetryTest.java @@ -48,6 +48,8 @@ import com.datastax.oss.driver.api.core.servererrors.ServerError; import com.datastax.oss.driver.api.core.servererrors.UnavailableException; import com.datastax.oss.driver.api.core.servererrors.WriteTimeoutException; +import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.tracker.RequestIdGenerator; import com.datastax.oss.protocol.internal.ProtocolConstants; import com.datastax.oss.protocol.internal.response.Error; import com.datastax.oss.protocol.internal.response.error.ReadTimeout; @@ -55,9 +57,13 @@ import com.datastax.oss.protocol.internal.response.error.WriteTimeout; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.UseDataProvider; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.Iterator; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; public class CqlRequestHandlerRetryTest extends CqlRequestHandlerTestBase { @@ -384,6 +390,63 @@ public void should_rethrow_error_if_not_idempotent_and_error_unsafe_or_policy_re } } + @Test + @UseDataProvider("failureAndIdempotent") + public void should_not_fail_with_duplicate_key_when_retrying_with_request_id_generator( + FailureScenario failureScenario, boolean defaultIdempotence, Statement statement) { + + // Create a RequestIdGenerator that uses the same key as the statement's custom payload + RequestIdGenerator requestIdGenerator = + new RequestIdGenerator() { + private AtomicInteger counter = new AtomicInteger(0); + + @Override + public String getSessionRequestId() { + return "session-123"; + } + + @Override + public String getNodeRequestId(@NonNull Request request, @NonNull String parentId) { + return parentId + "-" + counter.getAndIncrement(); + } + }; + + RequestHandlerTestHarness.Builder harnessBuilder = + RequestHandlerTestHarness.builder() + .withDefaultIdempotence(defaultIdempotence) + .withRequestIdGenerator(requestIdGenerator); + failureScenario.mockRequestError(harnessBuilder, node1); + harnessBuilder.withResponse(node2, defaultFrameOf(singleRow())); + + try (RequestHandlerTestHarness harness = harnessBuilder.build()) { + failureScenario.mockRetryPolicyVerdict( + harness.getContext().getRetryPolicy(anyString()), RetryVerdict.RETRY_NEXT); + + CompletionStage resultSetFuture = + new CqlRequestHandler(statement, harness.getSession(), harness.getContext(), "test") + .handle(); + + // The test should succeed without throwing a duplicate key exception + assertThatStage(resultSetFuture) + .isSuccess( + resultSet -> { + Iterator rows = resultSet.currentPage().iterator(); + assertThat(rows.hasNext()).isTrue(); + assertThat(rows.next().getString("message")).isEqualTo("hello, world"); + + ExecutionInfo executionInfo = resultSet.getExecutionInfo(); + assertThat(executionInfo.getCoordinator()).isEqualTo(node2); + assertThat(executionInfo.getErrors()).hasSize(1); + assertThat(executionInfo.getErrors().get(0).getKey()).isEqualTo(node1); + + // Verify that the custom payload still contains the request ID key + // (either the original value or the generated one, depending on implementation) + assertThat(executionInfo.getRequest().getCustomPayload().get("request-id")) + .isEqualTo(ByteBuffer.wrap("session-123-1".getBytes(StandardCharsets.UTF_8))); + }); + } + } + /** * Sets up the mocks to simulate an error from a node, and make the retry policy return a given * decision for that error. diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java index b355e0fc9f0..dc238775bc1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/QueryTraceFetcherTest.java @@ -79,7 +79,7 @@ public class QueryTraceFetcherTest { @Mock private NettyOptions nettyOptions; @Mock private EventExecutorGroup adminEventExecutorGroup; @Mock private EventExecutor eventExecutor; - @Mock private InetAddress address; + private InetAddress address = InetAddress.getLoopbackAddress(); @Captor private ArgumentCaptor statementCaptor; diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java index 3ec30286755..8ddc66c9c4e 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/RequestHandlerTestHarness.java @@ -44,6 +44,7 @@ import com.datastax.oss.driver.api.core.session.Session; import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy; import com.datastax.oss.driver.api.core.time.TimestampGenerator; +import com.datastax.oss.driver.api.core.tracker.RequestIdGenerator; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import com.datastax.oss.driver.internal.core.DefaultConsistencyLevelRegistry; import com.datastax.oss.driver.internal.core.ProtocolFeature; @@ -69,6 +70,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Queue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; @@ -194,6 +196,9 @@ protected RequestHandlerTestHarness(Builder builder) { when(context.getRequestThrottler()).thenReturn(new PassThroughRequestThrottler(context)); when(context.getRequestTracker()).thenReturn(new NoopRequestTracker(context)); + + when(context.getRequestIdGenerator()) + .thenReturn(Optional.ofNullable(builder.requestIdGenerator)); } public DefaultSession getSession() { @@ -226,6 +231,7 @@ public static class Builder { private final List poolBehaviors = new ArrayList<>(); private boolean defaultIdempotence; private ProtocolVersion protocolVersion; + private RequestIdGenerator requestIdGenerator; /** * Sets the given node as the next one in the query plan; an empty pool will be simulated when @@ -281,6 +287,11 @@ public Builder withProtocolVersion(ProtocolVersion protocolVersion) { return this; } + public Builder withRequestIdGenerator(RequestIdGenerator requestIdGenerator) { + this.requestIdGenerator = requestIdGenerator; + return this; + } + /** * Sets the given node as the next one in the query plan; the test code is responsible of * calling the methods on the returned object to complete the write and the query. diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/tracker/RequestIdGeneratorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/tracker/RequestIdGeneratorTest.java new file mode 100644 index 00000000000..fb1883e125f --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/tracker/RequestIdGeneratorTest.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.tracker; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.tracker.RequestIdGenerator; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.Strict.class) +public class RequestIdGeneratorTest { + @Mock private InternalDriverContext context; + @Mock private Statement statement; + + @Test + public void uuid_generator_should_generate() { + // given + UuidRequestIdGenerator generator = new UuidRequestIdGenerator(context); + // when + String parentId = generator.getSessionRequestId(); + String requestId = generator.getNodeRequestId(statement, parentId); + // then + // e.g. "550e8400-e29b-41d4-a716-446655440000", which is 36 characters long + assertThat(parentId.length()).isEqualTo(36); + // e.g. "550e8400-e29b-41d4-a716-446655440000-550e8400-e29b-41d4-a716-446655440000", which is 73 + // characters long + assertThat(requestId.length()).isEqualTo(73); + } + + @Test + public void w3c_generator_should_generate() { + // given + W3CContextRequestIdGenerator generator = new W3CContextRequestIdGenerator(context); + // when + String parentId = generator.getSessionRequestId(); + String requestId = generator.getNodeRequestId(statement, parentId); + // then + // e.g. "4bf92f3577b34da6a3ce929d0e0e4736", which is 32 characters long + assertThat(parentId.length()).isEqualTo(32); + // According to W3C "traceparent" spec, + // https://www.w3.org/TR/trace-context/#traceparent-header-field-values + // e.g. "00-4bf92f3577b34da6a3ce929d0e0e4736-a3ce929d0e0e4736-01", which 55 characters long + assertThat(requestId.length()).isEqualTo(55); + } + + @Test + public void w3c_generator_default_payloadkey() { + W3CContextRequestIdGenerator w3cGenerator = new W3CContextRequestIdGenerator(context); + assertThat(w3cGenerator.getCustomPayloadKey()) + .isEqualTo(RequestIdGenerator.DEFAULT_PAYLOAD_KEY); + } + + @Test + public void w3c_generator_provided_payloadkey() { + String someString = RandomStringUtils.random(12); + W3CContextRequestIdGenerator w3cGenerator = new W3CContextRequestIdGenerator(someString); + assertThat(w3cGenerator.getCustomPayloadKey()).isEqualTo(someString); + } +} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/VectorCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/VectorCodecTest.java index fe8e9e7be0f..17c78514127 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/VectorCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/VectorCodecTest.java @@ -34,8 +34,8 @@ import com.tngtech.java.junit.dataprovider.UseDataProvider; import java.nio.ByteBuffer; import java.time.LocalTime; -import java.util.Arrays; import java.util.HashMap; +import org.apache.commons.lang3.ArrayUtils; import org.junit.Test; import org.junit.runner.RunWith; @@ -128,10 +128,7 @@ public void should_throw_on_encode_with_too_few_elements(TestDataContainer testD @Test @UseDataProvider("dataProvider") public void should_throw_on_encode_with_too_many_elements(TestDataContainer testData) { - Object[] first = testData.getValues(); - Object[] second = testData.getValues(); - Object[] doubled = Arrays.copyOf(first, first.length + second.length); - System.arraycopy(second, 0, doubled, first.length, second.length); + Object[] doubled = ArrayUtils.addAll(testData.getValues(), testData.getValues()); TypeCodec> codec = getCodec(testData.getDataType()); assertThatThrownBy(() -> codec.encode(CqlVector.newInstance(doubled), ProtocolVersion.DEFAULT)) .isInstanceOf(IllegalArgumentException.class); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/util/ArrayUtilsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/util/ArrayUtilsTest.java index c2a7fb70304..c2df6449fdb 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/util/ArrayUtilsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/util/ArrayUtilsTest.java @@ -22,7 +22,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import java.util.concurrent.ThreadLocalRandom; +import java.util.Random; import org.junit.Test; public class ArrayUtilsTest { @@ -86,7 +86,7 @@ public void should_not_bubble_down_when_target_index_lower() { @Test public void should_shuffle_head() { String[] array = {"a", "b", "c", "d", "e"}; - ThreadLocalRandom random = mock(ThreadLocalRandom.class); + Random random = mock(Random.class); when(random.nextInt(anyInt())) .thenAnswer( (invocation) -> { diff --git a/distribution-source/pom.xml b/distribution-source/pom.xml index 19ad9f68bf1..23256425f31 100644 --- a/distribution-source/pom.xml +++ b/distribution-source/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.10-SNAPSHOT + 4.19.2.0-SNAPSHOT java-driver-distribution-source pom diff --git a/distribution-tests/pom.xml b/distribution-tests/pom.xml index 5ff2f590dc0..4996f658ab7 100644 --- a/distribution-tests/pom.xml +++ b/distribution-tests/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.10-SNAPSHOT + 4.19.2.0-SNAPSHOT java-driver-distribution-tests Scylla Java Driver - distribution tests diff --git a/distribution/pom.xml b/distribution/pom.xml index cb38a8c8475..bf86d2e1fe8 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.10-SNAPSHOT + 4.19.2.0-SNAPSHOT java-driver-distribution diff --git a/examples/pom.xml b/examples/pom.xml index 19e70197168..0d9a5c64373 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -28,7 +28,7 @@ java-driver-parent com.scylladb - 4.19.0.10-SNAPSHOT + 4.19.2.0-SNAPSHOT java-driver-examples Java driver for Scylla and Apache Cassandra(R) - examples. diff --git a/guava-shaded/pom.xml b/guava-shaded/pom.xml index 277024a2dd5..27d5b69077d 100644 --- a/guava-shaded/pom.xml +++ b/guava-shaded/pom.xml @@ -23,7 +23,7 @@ com.scylladb java-driver-parent - 4.19.0.10-SNAPSHOT + 4.19.2.0-SNAPSHOT java-driver-guava-shaded Scylla Java Driver - guava shaded dep @@ -46,6 +46,7 @@ error_prone_annotations + true org.graalvm.nativeimage @@ -56,6 +57,32 @@ + + + org.codehaus.mojo + build-helper-maven-plugin + 1.12 + + + regex-property + + regex-property + + + maven.main.skip + ${java.version} + ^(?!1.8).+ + true + false + + + + maven-shade-plugin diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index ed5428dace1..05a3e0a315d 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.10-SNAPSHOT + 4.19.2.0-SNAPSHOT java-driver-integration-tests jar diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/core/cql/PreparedStatementCachingIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/core/cql/PreparedStatementCachingIT.java index 83163cfedfb..b5db3a8436e 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/core/cql/PreparedStatementCachingIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/core/cql/PreparedStatementCachingIT.java @@ -40,7 +40,6 @@ import com.datastax.oss.driver.internal.core.session.BuiltInRequestProcessors; import com.datastax.oss.driver.internal.core.session.RequestProcessor; import com.datastax.oss.driver.internal.core.session.RequestProcessorRegistry; -import com.datastax.oss.driver.shaded.guava.common.cache.CacheBuilder; import com.datastax.oss.driver.shaded.guava.common.cache.RemovalListener; import com.datastax.oss.driver.shaded.guava.common.util.concurrent.Uninterruptibles; import com.google.common.collect.ImmutableList; @@ -136,9 +135,7 @@ private static RemovalListener buildCacheRemoveCallback( public TestCqlPrepareAsyncProcessor(@NonNull Optional context) { // Default CqlPrepareAsyncProcessor uses weak values here as well. We avoid doing so // to prevent cache entries from unexpectedly disappearing mid-test. - super( - context, - builder -> CacheBuilder.newBuilder().removalListener(buildCacheRemoveCallback(context))); + super(context, builder -> builder.removalListener(buildCacheRemoveCallback(context))); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/core/ssl/DefaultSslEngineFactoryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/core/ssl/DefaultSslEngineFactoryIT.java index ed3acb93dfd..50c9561ff9a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/core/ssl/DefaultSslEngineFactoryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/core/ssl/DefaultSslEngineFactoryIT.java @@ -27,10 +27,13 @@ import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; +import com.datastax.oss.driver.api.core.context.DriverContext; import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge; import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; import com.datastax.oss.driver.api.testinfra.session.SessionUtils; +import com.datastax.oss.driver.assertions.Assertions; import com.datastax.oss.driver.internal.core.ssl.DefaultSslEngineFactory; +import java.net.InetSocketAddress; import org.junit.ClassRule; import org.junit.Test; @@ -94,4 +97,67 @@ public void should_not_connect_if_not_using_ssl() { session.execute("select * from system.local where key='local'"); } } + + public static class InstrumentedSslEngineFactory extends DefaultSslEngineFactory { + int countReverseLookups = 0; + int countNoLookups = 0; + + public InstrumentedSslEngineFactory(DriverContext driverContext) { + super(driverContext); + } + + @Override + protected String hostMaybeFromDnsReverseLookup(InetSocketAddress addr) { + countReverseLookups++; + return super.hostMaybeFromDnsReverseLookup(addr); + } + + @Override + protected String hostNoLookup(InetSocketAddress addr) { + countNoLookups++; + return super.hostNoLookup(addr); + } + }; + + @Test + public void should_respect_config_for_san_resolution() { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withClass( + DefaultDriverOption.SSL_ENGINE_FACTORY_CLASS, InstrumentedSslEngineFactory.class) + .withBoolean(DefaultDriverOption.SSL_HOSTNAME_VALIDATION, false) + .withString( + DefaultDriverOption.SSL_TRUSTSTORE_PATH, + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath()) + .withString( + DefaultDriverOption.SSL_TRUSTSTORE_PASSWORD, + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD) + .build(); + try (CqlSession session = SessionUtils.newSession(CCM_RULE, loader)) { + InstrumentedSslEngineFactory ssl = + (InstrumentedSslEngineFactory) session.getContext().getSslEngineFactory().get(); + Assertions.assertThat(ssl.countReverseLookups).isGreaterThan(0); + Assertions.assertThat(ssl.countNoLookups).isEqualTo(0); + } + + loader = + SessionUtils.configLoaderBuilder() + .withClass( + DefaultDriverOption.SSL_ENGINE_FACTORY_CLASS, InstrumentedSslEngineFactory.class) + .withBoolean(DefaultDriverOption.SSL_HOSTNAME_VALIDATION, false) + .withString( + DefaultDriverOption.SSL_TRUSTSTORE_PATH, + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_FILE.getAbsolutePath()) + .withString( + DefaultDriverOption.SSL_TRUSTSTORE_PASSWORD, + CcmBridge.DEFAULT_CLIENT_TRUSTSTORE_PASSWORD) + .withBoolean(DefaultDriverOption.SSL_ALLOW_DNS_REVERSE_LOOKUP_SAN, false) + .build(); + try (CqlSession session = SessionUtils.newSession(CCM_RULE, loader)) { + InstrumentedSslEngineFactory ssl = + (InstrumentedSslEngineFactory) session.getContext().getSslEngineFactory().get(); + Assertions.assertThat(ssl.countReverseLookups).isEqualTo(0); + Assertions.assertThat(ssl.countNoLookups).isGreaterThan(0); + } + } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/core/tracker/RequestIdGeneratorIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/core/tracker/RequestIdGeneratorIT.java new file mode 100644 index 00000000000..d2f7ed98205 --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/core/tracker/RequestIdGeneratorIT.java @@ -0,0 +1,152 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.core.tracker; + +import static com.datastax.oss.driver.Assertions.assertThatStage; +import static org.assertj.core.api.Assertions.assertThat; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; +import com.datastax.oss.driver.api.core.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.SimpleStatement; +import com.datastax.oss.driver.api.core.cql.Statement; +import com.datastax.oss.driver.api.core.session.Request; +import com.datastax.oss.driver.api.core.tracker.RequestIdGenerator; +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.requirement.BackendRequirement; +import com.datastax.oss.driver.api.testinfra.requirement.BackendType; +import com.datastax.oss.driver.api.testinfra.session.SessionUtils; +import com.datastax.oss.driver.categories.ParallelizableTests; +import com.datastax.oss.protocol.internal.util.collection.NullAllowingImmutableMap; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; + +// Scylla does not support custom payload (RequestIdGenerator writes request IDs into the custom +// payload); Scylla returns a truncated-frame protocol error when custom payload entries are +// present. +@BackendRequirement( + type = BackendType.CASSANDRA, + description = "Custom payload (request ID) not supported on Scylla") +@Category(ParallelizableTests.class) +public class RequestIdGeneratorIT { + private static CcmRule ccmRule = CcmRule.getInstance(); + + @ClassRule public static TestRule chain = RuleChain.outerRule(ccmRule); + + @Test + public void should_write_uuid_to_custom_payload_with_key() { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withString(DefaultDriverOption.REQUEST_ID_GENERATOR_CLASS, "UuidRequestIdGenerator") + .build(); + try (CqlSession session = SessionUtils.newSession(ccmRule, loader)) { + String query = "SELECT * FROM system.local"; + ResultSet rs = session.execute(query); + ByteBuffer id = rs.getExecutionInfo().getRequest().getCustomPayload().get("request-id"); + assertThat(id.remaining()).isEqualTo(73); + } + } + + @Test + public void should_write_default_request_id_to_custom_payload_with_key() { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withString( + DefaultDriverOption.REQUEST_ID_GENERATOR_CLASS, "W3CContextRequestIdGenerator") + .build(); + try (CqlSession session = SessionUtils.newSession(ccmRule, loader)) { + String query = "SELECT * FROM system.local"; + ResultSet rs = session.execute(query); + ByteBuffer id = rs.getExecutionInfo().getRequest().getCustomPayload().get("request-id"); + assertThat(id.remaining()).isEqualTo(55); + } + } + + @Test + public void should_use_customized_request_id_generator() { + RequestIdGenerator myRequestIdGenerator = + new RequestIdGenerator() { + @Override + public String getSessionRequestId() { + return "123"; + } + + @Override + public String getNodeRequestId(@NonNull Request statement, @NonNull String parentId) { + return "456"; + } + + @Override + public Statement getDecoratedStatement( + @NonNull Statement statement, @NonNull String requestId) { + Map customPayload = + NullAllowingImmutableMap.builder() + .putAll(statement.getCustomPayload()) + .put("trace_key", ByteBuffer.wrap(requestId.getBytes(StandardCharsets.UTF_8))) + .build(); + return statement.setCustomPayload(customPayload); + } + }; + try (CqlSession session = + (CqlSession) + SessionUtils.baseBuilder() + .addContactEndPoints(ccmRule.getContactPoints()) + .withRequestIdGenerator(myRequestIdGenerator) + .build()) { + String query = "SELECT * FROM system.local"; + ResultSet rs = session.execute(query); + ByteBuffer id = rs.getExecutionInfo().getRequest().getCustomPayload().get("trace_key"); + assertThat(id).isEqualTo(ByteBuffer.wrap("456".getBytes(StandardCharsets.UTF_8))); + } + } + + @Test + public void should_not_write_id_to_custom_payload_when_key_is_not_set() { + DriverConfigLoader loader = SessionUtils.configLoaderBuilder().build(); + try (CqlSession session = SessionUtils.newSession(ccmRule, loader)) { + String query = "SELECT * FROM system.local"; + ResultSet rs = session.execute(query); + assertThat(rs.getExecutionInfo().getRequest().getCustomPayload().get("request-id")).isNull(); + } + } + + @Test + public void should_succeed_with_null_value_in_custom_payload() { + DriverConfigLoader loader = + SessionUtils.configLoaderBuilder() + .withString( + DefaultDriverOption.REQUEST_ID_GENERATOR_CLASS, "W3CContextRequestIdGenerator") + .build(); + try (CqlSession session = SessionUtils.newSession(ccmRule, loader)) { + String query = "SELECT * FROM system.local"; + Map customPayload = + new NullAllowingImmutableMap.Builder(1).put("my_key", null).build(); + SimpleStatement statement = + SimpleStatement.newInstance(query).setCustomPayload(customPayload); + assertThatStage(session.executeAsync(statement)).isSuccess(); + } + } +} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/core/tracker/RequestNodeLoggerExample.java b/integration-tests/src/test/java/com/datastax/oss/driver/core/tracker/RequestNodeLoggerExample.java index eae98339637..8eb2fb80a73 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/core/tracker/RequestNodeLoggerExample.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/core/tracker/RequestNodeLoggerExample.java @@ -39,7 +39,7 @@ public void onNodeError( long latencyNanos, @NonNull DriverExecutionProfile executionProfile, @NonNull Node node, - @NonNull String logPrefix) { + @NonNull String nodeRequestLogPrefix) { if (!executionProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_ERROR_ENABLED)) { return; } @@ -66,7 +66,7 @@ public void onNodeError( maxValues, maxValueLength, showStackTraces, - logPrefix); + nodeRequestLogPrefix); } @Override @@ -75,7 +75,7 @@ public void onNodeSuccess( long latencyNanos, @NonNull DriverExecutionProfile executionProfile, @NonNull Node node, - @NonNull String logPrefix) { + @NonNull String nodeRequestLogPrefix) { boolean successEnabled = executionProfile.getBoolean(DefaultDriverOption.REQUEST_LOGGER_SUCCESS_ENABLED); boolean slowEnabled = @@ -114,6 +114,6 @@ public void onNodeSuccess( showValues, maxValues, maxValueLength, - logPrefix); + nodeRequestLogPrefix); } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/querybuilder/RelationOptionsIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/querybuilder/RelationOptionsIT.java new file mode 100644 index 00000000000..fc571ccf44d --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/querybuilder/RelationOptionsIT.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.querybuilder; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.querybuilder.SchemaBuilder; +import com.datastax.oss.driver.api.testinfra.ccm.CcmRule; +import com.datastax.oss.driver.api.testinfra.requirement.BackendRequirement; +import com.datastax.oss.driver.api.testinfra.requirement.BackendType; +import com.datastax.oss.driver.api.testinfra.session.SessionRule; +import com.datastax.oss.driver.categories.ParallelizableTests; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.RuleChain; +import org.junit.rules.TestName; +import org.junit.rules.TestRule; + +@Category(ParallelizableTests.class) +public class RelationOptionsIT { + + private CcmRule ccmRule = CcmRule.getInstance(); + + private SessionRule sessionRule = SessionRule.builder(ccmRule).build(); + + @Rule public TestRule chain = RuleChain.outerRule(ccmRule).around(sessionRule); + + @Rule public TestName name = new TestName(); + + @Test + @BackendRequirement( + type = BackendType.CASSANDRA, + minInclusive = "3.0", + description = "CRC check chance was moved to top level table in Cassandra 3.0") + public void should_create_table_with_crc_check_chance() { + sessionRule + .session() + .execute( + SchemaBuilder.createTable(name.getMethodName()) + .withPartitionKey("id", DataTypes.INT) + .withColumn("name", DataTypes.TEXT) + .withColumn("age", DataTypes.INT) + .withCRCCheckChance(0.8) + .build()); + KeyspaceMetadata keyspaceMetadata = + sessionRule + .session() + .getMetadata() + .getKeyspace(sessionRule.keyspace()) + .orElseThrow(AssertionError::new); + String describeOutput = keyspaceMetadata.describeWithChildren(true).trim(); + + assertThat(describeOutput).contains("crc_check_chance = 0.8"); + } + + @Test + @BackendRequirement( + type = BackendType.CASSANDRA, + minInclusive = "5.0", + description = "chunk_length_kb was renamed to chunk_length_in_kb in Cassandra 5.0") + public void should_create_table_with_chunk_length_in_kb() { + sessionRule + .session() + .execute( + SchemaBuilder.createTable(name.getMethodName()) + .withPartitionKey("id", DataTypes.INT) + .withColumn("name", DataTypes.TEXT) + .withColumn("age", DataTypes.INT) + .withLZ4Compression(4096) + .build()); + KeyspaceMetadata keyspaceMetadata = + sessionRule + .session() + .getMetadata() + .getKeyspace(sessionRule.keyspace()) + .orElseThrow(AssertionError::new); + String describeOutput = keyspaceMetadata.describeWithChildren(true).trim(); + + assertThat(describeOutput).contains("'class':'org.apache.cassandra.io.compress.LZ4Compressor'"); + assertThat(describeOutput).contains("'chunk_length_in_kb':'4096'"); + } + + @Test + @BackendRequirement( + type = BackendType.CASSANDRA, + minInclusive = "3.0", + maxExclusive = "5.0", + description = + "Deprecated compression options should still work with Cassandra >= 3.0 & < 5.0") + public void should_create_table_with_deprecated_options() { + sessionRule + .session() + .execute( + SchemaBuilder.createTable(name.getMethodName()) + .withPartitionKey("id", DataTypes.INT) + .withColumn("name", DataTypes.TEXT) + .withColumn("age", DataTypes.INT) + .withLZ4Compression(4096, 0.8) + .build()); + KeyspaceMetadata keyspaceMetadata = + sessionRule + .session() + .getMetadata() + .getKeyspace(sessionRule.keyspace()) + .orElseThrow(AssertionError::new); + String describeOutput = keyspaceMetadata.describeWithChildren(true).trim(); + + assertThat(describeOutput).contains("'class':'org.apache.cassandra.io.compress.LZ4Compressor'"); + assertThat(describeOutput).contains("'chunk_length_in_kb':'4096'"); + assertThat(describeOutput).contains("crc_check_chance = 0.8"); + } +} diff --git a/manual/core/README.md b/manual/core/README.md index bce32f27b03..f40754fdb36 100644 --- a/manual/core/README.md +++ b/manual/core/README.md @@ -379,6 +379,7 @@ for (ColumnDefinitions.Definition definition : row.getColumnDefinitions()) { pooling/* query_timestamps/* reactive/* + request_id/* reconnection/* request_tracker/* retries/* diff --git a/manual/core/address_resolution/README.md b/manual/core/address_resolution/README.md index 758fb36f33b..0d80312d82d 100644 --- a/manual/core/address_resolution/README.md +++ b/manual/core/address_resolution/README.md @@ -204,6 +204,54 @@ the JVM DNS cache TTL via the `networkaddress.cache.ttl` security property (e.g. (scylladb/scylladb#27323). Not yet available on ScyllaDB OSS. - Not supported on Apache Cassandra. - Mutually exclusive with custom `AddressTranslator` and with cloud secure connect bundles. +### Fixed proxy hostname + +If your client applications access Cassandra through some kind of proxy (eg. with AWS PrivateLink when all Cassandra +nodes are exposed via one hostname pointing to AWS Endpoint), you can configure driver with +`FixedHostNameAddressTranslator` to always translate all node addresses to that same proxy hostname, no matter what IP +address a node has but still using its native transport port. + +To use it, specify the following in the [configuration](../configuration): + +``` +datastax-java-driver.advanced.address-translator.class = FixedHostNameAddressTranslator +advertised-hostname = proxyhostname +``` + +### Fixed proxy hostname per subnet + +When running Cassandra in a private network and accessing it from outside of that private network via some kind of +proxy, we have an option to use `FixedHostNameAddressTranslator`. But for multi-datacenter Cassandra deployments, we +want to have more control over routing queries to a specific datacenter (eg. for optimizing latencies), which requires +setting up a separate proxy per datacenter. + +Normally, each Cassandra datacenter nodes are deployed to a different subnet to support internode communications in the +cluster and avoid IP address collisions. So when Cassandra broadcasts its nodes IP addresses, we can determine which +datacenter that node belongs to by checking its IP address against the given datacenter subnet. + +For such scenarios you can use `SubnetAddressTranslator` to translate node IPs to the datacenter proxy address +associated with it. + +To use it, specify the following in the [configuration](../configuration): +``` +datastax-java-driver.advanced.address-translator { + class = SubnetAddressTranslator + subnet-addresses { + "100.64.0.0/15" = "cassandra.datacenter1.com:9042" + "100.66.0.0/15" = "cassandra.datacenter2.com:9042" + # IPv6 example: + # "::ffff:6440:0/111" = "cassandra.datacenter1.com:9042" + # "::ffff:6442:0/111" = "cassandra.datacenter2.com:9042" + } + # Optional. When configured, addresses not matching the configured subnets are translated to this address. + default-address = "cassandra.datacenter1.com:9042" + # Whether to resolve the addresses once on initialization (if true) or on each node (re-)connection (if false). + # If not configured, defaults to false. + resolve-addresses = false +} +``` + +Such setup is common for running Cassandra on Kubernetes with [k8ssandra](https://docs.k8ssandra.io/). ### EC2 multi-region diff --git a/manual/core/non_blocking/README.md b/manual/core/non_blocking/README.md index 7abe9d856a3..f320ffd13d2 100644 --- a/manual/core/non_blocking/README.md +++ b/manual/core/non_blocking/README.md @@ -152,15 +152,13 @@ should not be used if strict lock-freedom is enforced. [`SafeInitNodeStateListener`]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/core/metadata/SafeInitNodeStateListener.html -The same is valid for both built-in [request throttlers]: +The `RateLimitingRequestThrottler` is currently blocking. The `ConcurrencyLimitingRequestThrottler` +is lock-free. -* `ConcurrencyLimitingRequestThrottler` -* `RateLimitingRequestThrottler` - -See the section about [throttling](../throttling) for details about these components. Again, they -use locks internally, and depending on how many requests are being executed in parallel, the thread -contention on these locks can be high: in short, if your application enforces strict lock-freedom, -then these components should not be used. +See the section about [throttling](../throttling) for details about these components. Depending on +how many requests are being executed in parallel, the thread contention on these locks can be high: +in short, if your application enforces strict lock-freedom, then you should not use the +`RateLimitingRequestThrottler`. [request throttlers]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/core/session/throttling/RequestThrottler.html diff --git a/manual/core/request_id/README.md b/manual/core/request_id/README.md new file mode 100644 index 00000000000..a766a4419af --- /dev/null +++ b/manual/core/request_id/README.md @@ -0,0 +1,48 @@ + + +## Request Id + +### Quick overview + +Users can inject an identifier for each individual CQL request, and such ID can be written in to the [custom payload](https://github.com/apache/cassandra/blob/trunk/doc/native_protocol_v5.spec) to +correlate a request across the driver and the Apache Cassandra server. + +A request ID generator needs to generate both: +- Session request ID: an identifier for an entire session.execute() call +- Node request ID: an identifier for the execution of a CQL statement against a particular node. There can be one or more node requests for a single session request, due to retries or speculative executions. + +Usage: +* Inject ID generator: set the desired `RequestIdGenerator` in `advanced.request-id.generator.class`. +* Add ID to custom payload: the default behavior of a `RequestIdGenerator` is to add the request ID into the custom payload with the key `request-id`. Override `RequestIdGenerator.getDecoratedStatement` to customize the behavior. + +### Request Id Generator Configuration + +Request ID generator can be declared in the [configuration](../configuration/) as follows: + +``` +datastax-java-driver.advanced.request-id.generator { + class = com.example.app.MyGenerator +} +``` + +To register your own request ID generator, specify the name of the class +that implements `RequestIdGenerator`. + +The generated ID will be added to the log message of `CqlRequestHandler`, and propagated to other classes, e.g. the request trackers. \ No newline at end of file diff --git a/mapper-processor/pom.xml b/mapper-processor/pom.xml index 6122dc5a0c9..45dd5148ba5 100644 --- a/mapper-processor/pom.xml +++ b/mapper-processor/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.10-SNAPSHOT + 4.19.2.0-SNAPSHOT java-driver-mapper-processor Java driver for Scylla and Apache Cassandra(R) - object mapper processor diff --git a/mapper-runtime/pom.xml b/mapper-runtime/pom.xml index 0273cda9e5e..226c952d7a7 100644 --- a/mapper-runtime/pom.xml +++ b/mapper-runtime/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.10-SNAPSHOT + 4.19.2.0-SNAPSHOT java-driver-mapper-runtime bundle diff --git a/mapper-runtime/revapi.json b/mapper-runtime/revapi.json index 18d26a7f7e9..3dc2ea21671 100644 --- a/mapper-runtime/revapi.json +++ b/mapper-runtime/revapi.json @@ -1,5 +1,3 @@ -// Configures Revapi (https://revapi.org/getting-started.html) to check API compatibility between -// successive driver versions. { "revapi": { "java": { @@ -11,7 +9,7 @@ "com\\.datastax\\.(oss|dse)\\.driver\\.internal(\\..+)?", "com\\.datastax\\.oss\\.driver\\.shaded(\\..+)?", "com\\.datastax\\.oss\\.simulacron(\\..+)?", - // Don't re-check sibling modules that this module depends on + "// Don't re-check sibling modules that this module depends on", "com\\.datastax\\.(oss|dse)\\.driver\\.api\\.core(\\..+)?", "com\\.datastax\\.(oss|dse)\\.driver\\.api\\.querybuilder(\\..+)?" ] @@ -22,7 +20,7 @@ { "regex": true, "code": "java.annotation.attributeValueChanged", - "old": "@interface com\.datastax\.oss\.driver\.api\.mapper\.annotations\..*", + "old": "@interface com\\.datastax\\.oss\\.driver\\.api\\.mapper\\.annotations\\..*", "annotationType": "java.lang.annotation.Retention", "attribute": "value", "oldValue": "java.lang.annotation.RetentionPolicy.CLASS", diff --git a/metrics/micrometer/pom.xml b/metrics/micrometer/pom.xml index ab68d2f05f6..06a129430a5 100644 --- a/metrics/micrometer/pom.xml +++ b/metrics/micrometer/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.10-SNAPSHOT + 4.19.2.0-SNAPSHOT ../../ java-driver-metrics-micrometer diff --git a/metrics/microprofile/pom.xml b/metrics/microprofile/pom.xml index 2cb91adae1e..a14d7b41460 100644 --- a/metrics/microprofile/pom.xml +++ b/metrics/microprofile/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.10-SNAPSHOT + 4.19.2.0-SNAPSHOT ../../ java-driver-metrics-microprofile diff --git a/osgi-tests/pom.xml b/osgi-tests/pom.xml index 83c93eae434..dc9eae882d7 100644 --- a/osgi-tests/pom.xml +++ b/osgi-tests/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.10-SNAPSHOT + 4.19.2.0-SNAPSHOT java-driver-osgi-tests jar diff --git a/osgi-tests/src/test/java/com/datastax/oss/driver/internal/osgi/support/BundleOptions.java b/osgi-tests/src/test/java/com/datastax/oss/driver/internal/osgi/support/BundleOptions.java index 35ef8632211..ffac828a0a8 100644 --- a/osgi-tests/src/test/java/com/datastax/oss/driver/internal/osgi/support/BundleOptions.java +++ b/osgi-tests/src/test/java/com/datastax/oss/driver/internal/osgi/support/BundleOptions.java @@ -35,7 +35,7 @@ public class BundleOptions { public static CompositeOption commonBundles() { return () -> options( - mavenBundle("com.scylladb", "java-driver-guava-shaded").versionAsInProject(), + mavenBundle("org.apache.cassandra", "java-driver-guava-shaded").versionAsInProject(), mavenBundle("io.dropwizard.metrics", "metrics-core").versionAsInProject(), mavenBundle("org.slf4j", "slf4j-api").versionAsInProject(), mavenBundle("org.hdrhistogram", "HdrHistogram").versionAsInProject(), diff --git a/pom.xml b/pom.xml index 917d96c943d..e7e570a5e36 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ 4.0.0 com.scylladb java-driver-parent - 4.19.0.10-SNAPSHOT + 4.19.2.0-SNAPSHOT pom Java Driver for Scylla and Apache Cassandra A driver for Scylla and Apache Cassandra(R) 2.1+ that works exclusively with the Cassandra Query Language version 3 (CQL3) and Cassandra's native protocol versions 3 and above. @@ -656,10 +656,14 @@ org.revapi revapi-maven-plugin - 0.10.5 + 0.15.1 false \d+\.\d+\.\d+ + + + ${project.groupId}:${project.artifactId}:RELEASE + @@ -668,12 +672,15 @@ + + revapi.json + org.revapi revapi-java - 0.22.1 + 0.28.4 @@ -687,6 +694,11 @@ flatten-maven-plugin 1.7.3 + + org.apache.maven.plugins + maven-enforcer-plugin + 3.5.0 + @@ -978,12 +990,6 @@ height="0" width="0" style="display:none;visibility:hidden"> check - - - - revapi.json - - @@ -1118,6 +1124,19 @@ height="0" width="0" style="display:none;visibility:hidden"> --add-opens java.base/jdk.internal.util.random=ALL-UNNAMED + + + test-jdk-21 + + [21,) + + + + -XX:+AllowRedefinitionToAddDeleteMethods + + --add-opens=java.base/jdk.internal.util.random=ALL-UNNAMED + + diff --git a/query-builder/pom.xml b/query-builder/pom.xml index 3a56f3a278b..b1deb7bc6f6 100644 --- a/query-builder/pom.xml +++ b/query-builder/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.10-SNAPSHOT + 4.19.2.0-SNAPSHOT java-driver-query-builder bundle diff --git a/query-builder/revapi.json b/query-builder/revapi.json index c4d8aa27212..ed97379332c 100644 --- a/query-builder/revapi.json +++ b/query-builder/revapi.json @@ -1,5 +1,3 @@ -// Configures Revapi (https://revapi.org/getting-started.html) to check API compatibility between -// successive driver versions. { "revapi": { "java": { @@ -11,7 +9,7 @@ "com\\.datastax\\.(oss|dse)\\.driver\\.internal(\\..+)?", "com\\.datastax\\.oss\\.driver\\.shaded(\\..+)?", "org\\.assertj(\\..+)?", - // Don't re-check sibling modules that this module depends on + "// Don't re-check sibling modules that this module depends on", "com\\.datastax\\.(oss|dse)\\.driver\\.api\\.core(\\..+)?" ] } @@ -2782,8 +2780,11 @@ "code": "java.method.addedToInterface", "new": "method com.datastax.oss.driver.api.querybuilder.select.Select com.datastax.oss.driver.api.querybuilder.select.Select::orderByAnnOf(com.datastax.oss.driver.api.core.CqlIdentifier, com.datastax.oss.driver.api.core.data.CqlVector)", "justification": "JAVA-3118: Add support for vector data type in Schema Builder, QueryBuilder" + }, + { + "code": "java.method.varargOverloadsOnlyDifferInVarargParameter", + "justification": "CASSJAVA-102: Suppress newly-supported varargs check" } ] } } - diff --git a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/RelationOptions.java b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/RelationOptions.java index 022562def81..49b342acb7f 100644 --- a/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/RelationOptions.java +++ b/query-builder/src/main/java/com/datastax/oss/driver/api/querybuilder/schema/RelationOptions.java @@ -58,6 +58,18 @@ default SelfT withCDC(boolean enabled) { return withOption("cdc", enabled); } + /** + * Defines the crc check chance. + * + *

Note that using this option with a version of Apache Cassandra less than 3.0 will raise a + * syntax error. + */ + @NonNull + @CheckReturnValue + default SelfT withCRCCheckChance(double crcCheckChance) { + return withOption("crc_check_chance", crcCheckChance); + } + /** * Defines the caching criteria. * @@ -97,22 +109,32 @@ default SelfT withCompaction(@NonNull CompactionStrategy compactionStrategy) } /** - * Configures compression using the LZ4 algorithm with the given chunk length and crc check - * chance. - * - * @see #withCompression(String, int, double) + * @deprecated This method only exists for backward compatibility. Will not work with Apache + * Cassandra 5.0 or later. Use {@link #withLZ4Compression(int)} instead. */ + @Deprecated @NonNull @CheckReturnValue default SelfT withLZ4Compression(int chunkLengthKB, double crcCheckChance) { return withCompression("LZ4Compressor", chunkLengthKB, crcCheckChance); } + /** + * Configures compression using the LZ4 algorithm with the given chunk length. + * + * @see #withCompression(String, int) + */ + @NonNull + @CheckReturnValue + default SelfT withLZ4Compression(int chunkLengthKB) { + return withCompression("LZ4Compressor", chunkLengthKB); + } + /** * Configures compression using the LZ4 algorithm using the default configuration (64kb - * chunk_length, and 1.0 crc_check_chance). + * chunk_length). * - * @see #withCompression(String, int, double) + * @see #withCompression(String, int) */ @NonNull @CheckReturnValue @@ -121,22 +143,57 @@ default SelfT withLZ4Compression() { } /** - * Configures compression using the Snappy algorithm with the given chunk length and crc check - * chance. + * Configures compression using the Zstd algorithm with the given chunk length. * - * @see #withCompression(String, int, double) + * @see #withCompression(String, int) */ @NonNull @CheckReturnValue + default SelfT withZstdCompression(int chunkLengthKB) { + return withCompression("ZstdCompressor", chunkLengthKB); + } + + /** + * Configures compression using the Zstd algorithm using the default configuration (64kb + * chunk_length). + * + * @see #withCompression(String, int) + */ + @NonNull + @CheckReturnValue + default SelfT withZstdCompression() { + return withCompression("ZstdCompressor"); + } + + /** + * @deprecated This method only exists for backward compatibility. Will not work with Apache + * Cassandra 5.0 or later due to removal of deprecated table properties (CASSANDRA-18742). Use + * {@link #withSnappyCompression(int)} instead. + */ + @Deprecated + @NonNull + @CheckReturnValue default SelfT withSnappyCompression(int chunkLengthKB, double crcCheckChance) { return withCompression("SnappyCompressor", chunkLengthKB, crcCheckChance); } + /** + * Configures compression using the Snappy algorithm with the given chunk length. + * + * @see #withCompression(String, int) + */ + @NonNull + @CheckReturnValue + default SelfT withSnappyCompression(int chunkLengthKB) { + return withCompression("SnappyCompressor", chunkLengthKB); + } + /** * Configures compression using the Snappy algorithm using the default configuration (64kb - * chunk_length, and 1.0 crc_check_chance). + * chunk_length). * - * @see #withCompression(String, int, double) + * @see #withCompression(String, int) */ @NonNull @CheckReturnValue @@ -145,22 +202,34 @@ default SelfT withSnappyCompression() { } /** - * Configures compression using the Deflate algorithm with the given chunk length and crc check - * chance. - * - * @see #withCompression(String, int, double) + * @deprecated This method only exists for backward compatibility. Will not work with Apache + * Cassandra 5.0 or later due to removal of deprecated table properties (CASSANDRA-18742). Use + * {@link #withDeflateCompression(int)} instead. */ + @Deprecated @NonNull @CheckReturnValue default SelfT withDeflateCompression(int chunkLengthKB, double crcCheckChance) { return withCompression("DeflateCompressor", chunkLengthKB, crcCheckChance); } + /** + * Configures compression using the Deflate algorithm with the given chunk length. + * + * @see #withCompression(String, int) + */ + @NonNull + @CheckReturnValue + default SelfT withDeflateCompression(int chunkLengthKB) { + return withCompression("DeflateCompressor", chunkLengthKB); + } + /** * Configures compression using the Deflate algorithm using the default configuration (64kb - * chunk_length, and 1.0 crc_check_chance). + * chunk_length). * - * @see #withCompression(String, int, double) + * @see #withCompression(String, int) */ @NonNull @CheckReturnValue @@ -170,13 +239,13 @@ default SelfT withDeflateCompression() { /** * Configures compression using the given algorithm using the default configuration (64kb - * chunk_length, and 1.0 crc_check_chance). + * chunk_length). * *

Unless specifying a custom compression algorithm implementation, it is recommended to use * {@link #withLZ4Compression()}, {@link #withSnappyCompression()}, or {@link * #withDeflateCompression()}. * - * @see #withCompression(String, int, double) + * @see #withCompression(String, int) */ @NonNull @CheckReturnValue @@ -185,7 +254,7 @@ default SelfT withCompression(@NonNull String compressionAlgorithmName) { } /** - * Configures compression using the given algorithm, chunk length and crc check chance. + * Configures compression using the given algorithm, chunk length. * *

Unless specifying a custom compression algorithm implementation, it is recommended to use * {@link #withLZ4Compression()}, {@link #withSnappyCompression()}, or {@link @@ -193,11 +262,24 @@ default SelfT withCompression(@NonNull String compressionAlgorithmName) { * * @param compressionAlgorithmName The class name of the compression algorithm. * @param chunkLengthKB The chunk length in KB of compression blocks. Defaults to 64. - * @param crcCheckChance The probability (0.0 to 1.0) that checksum will be checked on each read. - * Defaults to 1.0. */ @NonNull @CheckReturnValue + default SelfT withCompression(@NonNull String compressionAlgorithmName, int chunkLengthKB) { + return withOption( + "compression", + ImmutableMap.of("class", compressionAlgorithmName, "chunk_length_in_kb", chunkLengthKB)); + } + + /** + * @deprecated This method only exists for backward compatibility. Will not work with Apache + * Cassandra 5.0 or later due to removal of deprecated table properties (CASSANDRA-18742). Use + * {@link #withCompression(String, int)} instead. + */ + @NonNull + @CheckReturnValue + @Deprecated default SelfT withCompression( @NonNull String compressionAlgorithmName, int chunkLengthKB, double crcCheckChance) { return withOption( diff --git a/query-builder/src/test/java/com/datastax/dse/driver/api/querybuilder/schema/CreateDseTableTest.java b/query-builder/src/test/java/com/datastax/dse/driver/api/querybuilder/schema/CreateDseTableTest.java index 7fec9674628..d8ee1c4e380 100644 --- a/query-builder/src/test/java/com/datastax/dse/driver/api/querybuilder/schema/CreateDseTableTest.java +++ b/query-builder/src/test/java/com/datastax/dse/driver/api/querybuilder/schema/CreateDseTableTest.java @@ -195,6 +195,17 @@ public void should_generate_create_table_lz4_compression() { @Test public void should_generate_create_table_lz4_compression_options() { + assertThat( + createDseTable("bar") + .withPartitionKey("k", DataTypes.INT) + .withColumn("v", DataTypes.TEXT) + .withLZ4Compression(1024)) + .hasCql( + "CREATE TABLE bar (k int PRIMARY KEY,v text) WITH compression={'class':'LZ4Compressor','chunk_length_in_kb':1024}"); + } + + @Test + public void should_generate_create_table_lz4_compression_options_crc() { assertThat( createDseTable("bar") .withPartitionKey("k", DataTypes.INT) @@ -204,6 +215,28 @@ public void should_generate_create_table_lz4_compression_options() { "CREATE TABLE bar (k int PRIMARY KEY,v text) WITH compression={'class':'LZ4Compressor','chunk_length_kb':1024,'crc_check_chance':0.5}"); } + @Test + public void should_generate_create_table_zstd_compression() { + assertThat( + createDseTable("bar") + .withPartitionKey("k", DataTypes.INT) + .withColumn("v", DataTypes.TEXT) + .withZstdCompression()) + .hasCql( + "CREATE TABLE bar (k int PRIMARY KEY,v text) WITH compression={'class':'ZstdCompressor'}"); + } + + @Test + public void should_generate_create_table_zstd_compression_options() { + assertThat( + createDseTable("bar") + .withPartitionKey("k", DataTypes.INT) + .withColumn("v", DataTypes.TEXT) + .withZstdCompression(1024)) + .hasCql( + "CREATE TABLE bar (k int PRIMARY KEY,v text) WITH compression={'class':'ZstdCompressor','chunk_length_in_kb':1024}"); + } + @Test public void should_generate_create_table_snappy_compression() { assertThat( @@ -217,6 +250,17 @@ public void should_generate_create_table_snappy_compression() { @Test public void should_generate_create_table_snappy_compression_options() { + assertThat( + createDseTable("bar") + .withPartitionKey("k", DataTypes.INT) + .withColumn("v", DataTypes.TEXT) + .withSnappyCompression(2048)) + .hasCql( + "CREATE TABLE bar (k int PRIMARY KEY,v text) WITH compression={'class':'SnappyCompressor','chunk_length_in_kb':2048}"); + } + + @Test + public void should_generate_create_table_snappy_compression_options_crc() { assertThat( createDseTable("bar") .withPartitionKey("k", DataTypes.INT) @@ -239,6 +283,17 @@ public void should_generate_create_table_deflate_compression() { @Test public void should_generate_create_table_deflate_compression_options() { + assertThat( + createDseTable("bar") + .withPartitionKey("k", DataTypes.INT) + .withColumn("v", DataTypes.TEXT) + .withDeflateCompression(4096)) + .hasCql( + "CREATE TABLE bar (k int PRIMARY KEY,v text) WITH compression={'class':'DeflateCompressor','chunk_length_in_kb':4096}"); + } + + @Test + public void should_generate_create_table_deflate_compression_options_crc() { assertThat( createDseTable("bar") .withPartitionKey("k", DataTypes.INT) @@ -389,4 +444,14 @@ public void should_generate_create_table_with_named_edge() { + "FROM person(contributor) " + "TO soft((company_name,software_name),software_version)"); } + + @Test + public void should_generate_create_table_crc_check_chance() { + assertThat( + createDseTable("bar") + .withPartitionKey("k", DataTypes.INT) + .withColumn("v", DataTypes.TEXT) + .withCRCCheckChance(0.8)) + .hasCql("CREATE TABLE bar (k int PRIMARY KEY,v text) WITH crc_check_chance=0.8"); + } } diff --git a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTableTest.java b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTableTest.java index 15cd12c75eb..31efc278472 100644 --- a/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTableTest.java +++ b/query-builder/src/test/java/com/datastax/oss/driver/api/querybuilder/schema/CreateTableTest.java @@ -199,6 +199,17 @@ public void should_generate_create_table_lz4_compression() { @Test public void should_generate_create_table_lz4_compression_options() { + assertThat( + createTable("bar") + .withPartitionKey("k", DataTypes.INT) + .withColumn("v", DataTypes.TEXT) + .withLZ4Compression(1024)) + .hasCql( + "CREATE TABLE bar (k int PRIMARY KEY,v text) WITH compression={'class':'LZ4Compressor','chunk_length_in_kb':1024}"); + } + + @Test + public void should_generate_create_table_lz4_compression_options_crc() { assertThat( createTable("bar") .withPartitionKey("k", DataTypes.INT) @@ -208,6 +219,28 @@ public void should_generate_create_table_lz4_compression_options() { "CREATE TABLE bar (k int PRIMARY KEY,v text) WITH compression={'class':'LZ4Compressor','chunk_length_kb':1024,'crc_check_chance':0.5}"); } + @Test + public void should_generate_create_table_zstd_compression() { + assertThat( + createTable("bar") + .withPartitionKey("k", DataTypes.INT) + .withColumn("v", DataTypes.TEXT) + .withZstdCompression()) + .hasCql( + "CREATE TABLE bar (k int PRIMARY KEY,v text) WITH compression={'class':'ZstdCompressor'}"); + } + + @Test + public void should_generate_create_table_zstd_compression_options() { + assertThat( + createTable("bar") + .withPartitionKey("k", DataTypes.INT) + .withColumn("v", DataTypes.TEXT) + .withZstdCompression(1024)) + .hasCql( + "CREATE TABLE bar (k int PRIMARY KEY,v text) WITH compression={'class':'ZstdCompressor','chunk_length_in_kb':1024}"); + } + @Test public void should_generate_create_table_snappy_compression() { assertThat( @@ -221,6 +254,17 @@ public void should_generate_create_table_snappy_compression() { @Test public void should_generate_create_table_snappy_compression_options() { + assertThat( + createTable("bar") + .withPartitionKey("k", DataTypes.INT) + .withColumn("v", DataTypes.TEXT) + .withSnappyCompression(2048)) + .hasCql( + "CREATE TABLE bar (k int PRIMARY KEY,v text) WITH compression={'class':'SnappyCompressor','chunk_length_in_kb':2048}"); + } + + @Test + public void should_generate_create_table_snappy_compression_options_crc() { assertThat( createTable("bar") .withPartitionKey("k", DataTypes.INT) @@ -243,6 +287,17 @@ public void should_generate_create_table_deflate_compression() { @Test public void should_generate_create_table_deflate_compression_options() { + assertThat( + createTable("bar") + .withPartitionKey("k", DataTypes.INT) + .withColumn("v", DataTypes.TEXT) + .withDeflateCompression(4096)) + .hasCql( + "CREATE TABLE bar (k int PRIMARY KEY,v text) WITH compression={'class':'DeflateCompressor','chunk_length_in_kb':4096}"); + } + + @Test + public void should_generate_create_table_deflate_compression_options_crc() { assertThat( createTable("bar") .withPartitionKey("k", DataTypes.INT) diff --git a/test-infra/pom.xml b/test-infra/pom.xml index c263e79df7c..b6821c0ccf0 100644 --- a/test-infra/pom.xml +++ b/test-infra/pom.xml @@ -28,7 +28,7 @@ com.scylladb java-driver-parent - 4.19.0.10-SNAPSHOT + 4.19.2.0-SNAPSHOT java-driver-test-infra bundle diff --git a/test-infra/revapi.json b/test-infra/revapi.json index c75a98cb4af..293d9f4d142 100644 --- a/test-infra/revapi.json +++ b/test-infra/revapi.json @@ -1,5 +1,3 @@ -// Configures Revapi (https://revapi.org/getting-started.html) to check API compatibility between -// successive driver versions. { "revapi": { "java": { @@ -12,7 +10,7 @@ "com\\.datastax\\.oss\\.driver\\.shaded(\\..+)?", "com\\.datastax\\.oss\\.simulacron(\\..+)?", "org\\.assertj(\\..+)?", - // Don't re-check sibling modules that this module depends on + "// Don't re-check sibling modules that this module depends on", "com\\.datastax\\.(oss|dse)\\.driver\\.api\\.core(\\..+)?" ] } From c6878d8eab4067924341a845e56c6b73595a6f5a Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sun, 24 May 2026 18:53:02 -0400 Subject: [PATCH 15/32] chore: remove ASF Jenkinsfile --- Jenkinsfile-asf | 81 ------------------------------------------------- 1 file changed, 81 deletions(-) delete mode 100644 Jenkinsfile-asf diff --git a/Jenkinsfile-asf b/Jenkinsfile-asf deleted file mode 100644 index 4b5041903c1..00000000000 --- a/Jenkinsfile-asf +++ /dev/null @@ -1,81 +0,0 @@ -#!groovy - -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -pipeline { - agent { - label 'cassandra-small' - } - - triggers { - // schedules only run against release branches (i.e. 3.x, 4.x, 4.5.x, etc.) - cron(branchPatternCron().matcher(env.BRANCH_NAME).matches() ? '@weekly' : '') - } - - stages { - stage('Matrix') { - matrix { - axes { - axis { - name 'TEST_JAVA_VERSION' - values 'openjdk@1.8.0-292', 'openjdk@1.11.0-9', 'openjdk@1.17.0', 'openjdk@1.21.0' - } - axis { - name 'SERVER_VERSION' - values '3.11', - '4.0', - '4.1', - '5.0' - } - } - stages { - stage('Tests') { - agent { - label 'cassandra-medium' - } - steps { - script { - executeTests() - junit testResults: '**/target/surefire-reports/TEST-*.xml', allowEmptyResults: true - junit testResults: '**/target/failsafe-reports/TEST-*.xml', allowEmptyResults: true - } - } - } - } - } - } - } -} - -def executeTests() { - def testJavaMajorVersion = (TEST_JAVA_VERSION =~ /@(?:1\.)?(\d+)/)[0][1] - sh """ - container_id=\$(docker run -td -e TEST_JAVA_VERSION=${TEST_JAVA_VERSION} -e SERVER_VERSION=${SERVER_VERSION} -e TEST_JAVA_MAJOR_VERSION=${testJavaMajorVersion} -v \$(pwd):/home/docker/cassandra-java-driver apache.jfrog.io/cassan-docker/apache/cassandra-java-driver-testing-ubuntu2204 'sleep 2h') - docker exec --user root \$container_id bash -c \"sudo bash /home/docker/cassandra-java-driver/ci/create-user.sh docker \$(id -u) \$(id -g) /home/docker/cassandra-java-driver\" - docker exec --user docker \$container_id './cassandra-java-driver/ci/run-tests.sh' - ( nohup docker stop \$container_id >/dev/null 2>/dev/null & ) - """ -} - -// branch pattern for cron -// should match 3.x, 4.x, 4.5.x, etc -def branchPatternCron() { - ~'((\\d+(\\.[\\dx]+)+))' -} From 3893f93b3503e412dfa64b5e10b3be819404c07a Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Mon, 25 May 2026 15:40:45 -0400 Subject: [PATCH 16/32] ci: authenticate integration version lookups The Cassandra and Scylla integration workflows resolve matrix values such as latest through helper scripts that can query GitHub. Without GH_TOKEN those requests run anonymously, so CI can fail when the API requires authentication or hits anonymous rate limits before the integration jobs start. Pass github.token to the Cassandra and Scylla version-resolution steps and to the Cassandra integration test step. Also pass the original CASSANDRA_VERSION matrix value alongside CASSANDRA_VERSION_RESOLVED so the test helper can distinguish the requested version from the concrete resolved version. Co-authored-by: Mikita Hradovich --- .github/workflows/tests@v1.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/tests@v1.yml b/.github/workflows/tests@v1.yml index e146df623be..0f8d9dac934 100644 --- a/.github/workflows/tests@v1.yml +++ b/.github/workflows/tests@v1.yml @@ -212,6 +212,7 @@ jobs: id: cassandra-version env: CASSANDRA_VERSION: ${{ matrix.cassandra-version }} + GH_TOKEN: ${{ github.token }} run: make resolve-cassandra-version - name: Pull CCM image from the cache @@ -246,7 +247,9 @@ jobs: - name: Run integration tests on Cassandra (${{ steps.cassandra-version.outputs.value }}) - ${{ matrix.test-group }} id: run-integration-tests env: + CASSANDRA_VERSION: ${{ matrix.cassandra-version }} CASSANDRA_VERSION_RESOLVED: ${{ steps.cassandra-version.outputs.value }} + GH_TOKEN: ${{ github.token }} MAVEN_EXTRA_ARGS: ${{ steps.test-skip-args.outputs.value }} run: make test-integration-cassandra @@ -324,6 +327,7 @@ jobs: id: scylla-version env: SCYLLA_VERSION: ${{ matrix.scylla-version }} + GH_TOKEN: ${{ github.token }} run: make resolve-scylla-version - name: Pull CCM image from the cache From 19b1cbb1c2f0acc79c215c5c7bd444930257410e Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sun, 24 May 2026 20:08:25 -0400 Subject: [PATCH 17/32] build: align native-protocol BOM version Publish the same native-protocol patch version through the BOM that the parent build already manages, so BOM consumers resolve the tested protocol artifact. --- bom/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/pom.xml b/bom/pom.xml index 764fd9dfb39..dbef5097893 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -83,7 +83,7 @@ com.scylladb native-protocol - 1.5.2.1 + 1.5.2.2 From 916794ccb557592f13da1d6e373e18f3d38b03e0 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Mon, 25 May 2026 06:20:18 -0400 Subject: [PATCH 18/32] fix: invalidate prepared cache on UDT changes Match changed UDTs by keyspace and type name instead of full field equality. Result and variable metadata can contain a different field shape after ALTER TYPE, so full UDT equality is too strict and leaves affected prepared statements cached. Ignore type-create events that do not have an old type, add a unit regression for nested UDT matching, and make PreparedStatementCachingIT retain cached futures so it validates invalidation rather than racing weak-value collection. --- .../core/cql/CqlPrepareAsyncProcessor.java | 11 +++- .../cql/CqlPrepareAsyncProcessorTest.java | 21 ++++++++ .../core/cql/PreparedStatementCachingIT.java | 53 ++++++++++++++----- 3 files changed, 69 insertions(+), 16 deletions(-) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java index 57411097b82..570d98d0aa0 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessor.java @@ -83,12 +83,12 @@ protected CqlPrepareAsyncProcessor( }); } - private static boolean typeMatches(UserDefinedType oldType, DataType typeToCheck) { + static boolean typeMatches(UserDefinedType oldType, DataType typeToCheck) { switch (typeToCheck.getProtocolCode()) { case ProtocolConstants.DataType.UDT: UserDefinedType udtType = (UserDefinedType) typeToCheck; - return udtType.equals(oldType) + return sameTypeName(udtType, oldType) ? true : Iterables.any(udtType.getFieldTypes(), (testType) -> typeMatches(oldType, testType)); case ProtocolConstants.DataType.LIST: @@ -110,7 +110,14 @@ private static boolean typeMatches(UserDefinedType oldType, DataType typeToCheck } } + private static boolean sameTypeName(UserDefinedType left, UserDefinedType right) { + return left.getKeyspace().equals(right.getKeyspace()) && left.getName().equals(right.getName()); + } + private void onTypeChanged(TypeChangeEvent event) { + if (event.oldType == null) { + return; + } for (Map.Entry> entry : this.cache.asMap().entrySet()) { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessorTest.java index 96cf3256df8..8bde253dfac 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/cql/CqlPrepareAsyncProcessorTest.java @@ -21,6 +21,9 @@ import com.datastax.oss.driver.api.core.cql.PrepareRequest; import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.internal.core.type.UserDefinedTypeBuilder; import com.datastax.oss.driver.shaded.guava.common.cache.Cache; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -85,4 +88,22 @@ public void should_return_defensive_copy_when_future_is_in_flight() throws Excep returned.toCompletableFuture().cancel(false); assertThat(inFlight.isCancelled()).isFalse(); } + + @Test + public void should_match_udt_by_name_when_field_definitions_differ() { + UserDefinedType oldType = + new UserDefinedTypeBuilder("ks", "test_type_2") + .withField("c", DataTypes.INT) + .withField("d", DataTypes.TEXT) + .build(); + UserDefinedType resultType = + new UserDefinedTypeBuilder("ks", "test_type_2") + .withField("c", DataTypes.INT) + .withField("d", DataTypes.TEXT) + .withField("i", DataTypes.BLOB) + .build(); + + assertThat(CqlPrepareAsyncProcessor.typeMatches(oldType, DataTypes.listOf(resultType))) + .isTrue(); + } } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/core/cql/PreparedStatementCachingIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/core/cql/PreparedStatementCachingIT.java index b5db3a8436e..57310156ab9 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/core/cql/PreparedStatementCachingIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/core/cql/PreparedStatementCachingIT.java @@ -18,12 +18,14 @@ package com.datastax.oss.driver.core.cql; import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; import com.codahale.metrics.Gauge; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.DefaultDriverOption; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.context.DriverContext; +import com.datastax.oss.driver.api.core.cql.PrepareRequest; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import com.datastax.oss.driver.api.core.metrics.DefaultSessionMetric; import com.datastax.oss.driver.api.core.session.ProgrammaticArguments; @@ -34,10 +36,12 @@ import com.datastax.oss.driver.api.testinfra.session.SessionUtils; import com.datastax.oss.driver.categories.IsolatedTests; import com.datastax.oss.driver.internal.core.context.DefaultDriverContext; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.cql.CqlPrepareAsyncProcessor; import com.datastax.oss.driver.internal.core.cql.CqlPrepareSyncProcessor; import com.datastax.oss.driver.internal.core.metadata.schema.events.TypeChangeEvent; import com.datastax.oss.driver.internal.core.session.BuiltInRequestProcessors; +import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.session.RequestProcessor; import com.datastax.oss.driver.internal.core.session.RequestProcessorRegistry; import com.datastax.oss.driver.shaded.guava.common.cache.RemovalListener; @@ -53,6 +57,7 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -117,6 +122,9 @@ private static class TestCqlPrepareAsyncProcessor extends CqlPrepareAsyncProcess private static final Logger LOG = LoggerFactory.getLogger(PreparedStatementCachingIT.TestCqlPrepareAsyncProcessor.class); + private final Set> retainedCacheValues = + ConcurrentHashMap.newKeySet(); + private static RemovalListener buildCacheRemoveCallback( @NonNull Optional context) { return (evt) -> { @@ -133,10 +141,25 @@ private static RemovalListener buildCacheRemoveCallback( } public TestCqlPrepareAsyncProcessor(@NonNull Optional context) { - // Default CqlPrepareAsyncProcessor uses weak values here as well. We avoid doing so - // to prevent cache entries from unexpectedly disappearing mid-test. + // Default CqlPrepareAsyncProcessor uses weak values. Retain the cached futures so this test + // validates type-change invalidation instead of racing cache value collection. super(context, builder -> builder.removalListener(buildCacheRemoveCallback(context))); } + + @Override + public CompletionStage process( + PrepareRequest request, + DefaultSession session, + InternalDriverContext context, + String sessionLogPrefix) { + CompletionStage stage = + super.process(request, session, context, sessionLogPrefix); + CompletableFuture cachedValue = cache.getIfPresent(request); + if (cachedValue != null) { + retainedCacheValues.add(cachedValue); + } + return stage; + } } private static class TestDefaultDriverContext extends DefaultDriverContext { @@ -222,11 +245,11 @@ private void invalidationTestInner( assertThat(getPreparedCacheSize(session)).isEqualTo(0); setupTestSchema.accept(session); - session.prepare(preparedStmtQueryType1); - ByteBuffer queryId2 = session.prepare(preparedStmtQueryType2).getId(); + PreparedStatement statement1 = session.prepare(preparedStmtQueryType1); + PreparedStatement statement2 = session.prepare(preparedStmtQueryType2); + ByteBuffer queryId2 = statement2.getId(); assertThat(getPreparedCacheSize(session)).isEqualTo(2); - CountDownLatch preparedStmtCacheRemoveLatch = new CountDownLatch(1); CountDownLatch typeChangeEventLatch = new CountDownLatch(expectedChangedTypes.size()); DefaultDriverContext ctx = (DefaultDriverContext) session.getContext(); @@ -260,7 +283,6 @@ private void invalidationTestInner( removedQueryEventError.set( Optional.of("Unable to set reference for PS removal event")); } - preparedStmtCacheRemoveLatch.countDown(); }); // alter test_type_2 to trigger cache invalidation and above events @@ -270,17 +292,20 @@ private void invalidationTestInner( assertThat(Uninterruptibles.awaitUninterruptibly(typeChangeEventLatch, 10, TimeUnit.SECONDS)) .withFailMessage("typeChangeEventLatch did not trigger before timeout") .isTrue(); - assertThat( - Uninterruptibles.awaitUninterruptibly( - preparedStmtCacheRemoveLatch, 10, TimeUnit.SECONDS)) - .withFailMessage("preparedStmtCacheRemoveLatch did not trigger before timeout") - .isTrue(); - /* Okay, the latch triggered so cache processing should now be done. Let's validate :allthethings: */ - assertThat(changedTypes.keySet()).isEqualTo(expectedChangedTypes); - assertThat(removedQueryIds.get()).isNotEmpty().get().isEqualTo(queryId2); + await() + .atMost(30, TimeUnit.SECONDS) + .untilAsserted(() -> assertThat(getPreparedCacheSize(session)).isEqualTo(1)); + + assertThat(session.prepare(preparedStmtQueryType1)).isSameAs(statement1); assertThat(getPreparedCacheSize(session)).isEqualTo(1); + assertThat(session.prepare(preparedStmtQueryType2)).isNotSameAs(statement2); + assertThat(getPreparedCacheSize(session)).isEqualTo(2); + + assertThat(changedTypes.keySet()).isEqualTo(expectedChangedTypes); + removedQueryIds.get().ifPresent(queryId -> assertThat(queryId).isEqualTo(queryId2)); + // check no errors were seen in callback (and report those as fail msgs) // if something is broken these may still succeed due to timing // but shouldn't intermittently fail if the code is working properly From 0c5f4fbf11c12dd06fd544f1707cb21eb21e3443 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Mon, 25 May 2026 07:05:43 -0400 Subject: [PATCH 19/32] test: provide Micrometer histogram bounds in no-SLA test Micrometer requires valid expected histogram bounds even when SLA buckets are disabled. Stub the lowest, highest, and precision options in the no-SLA session metric test so it exercises the intended branch instead of failing while constructing the distribution configuration. --- .../metrics/micrometer/MicrometerSessionMetricUpdaterTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/metrics/micrometer/src/test/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerSessionMetricUpdaterTest.java b/metrics/micrometer/src/test/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerSessionMetricUpdaterTest.java index 0deb377457a..79f7c3a8ff7 100644 --- a/metrics/micrometer/src/test/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerSessionMetricUpdaterTest.java +++ b/metrics/micrometer/src/test/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerSessionMetricUpdaterTest.java @@ -132,6 +132,9 @@ public void should_not_create_sla_percentiles( when(context.getMetricIdGenerator()).thenReturn(generator); when(profile.getDuration(DefaultDriverOption.METRICS_NODE_EXPIRE_AFTER)) .thenReturn(Duration.ofHours(1)); + when(profile.getDuration(lowest)).thenReturn(Duration.ofMillis(10)); + when(profile.getDuration(highest)).thenReturn(Duration.ofSeconds(1)); + when(profile.getInt(digits)).thenReturn(5); when(profile.isDefined(sla)).thenReturn(false); when(profile.getDurationList(sla)) .thenReturn(Arrays.asList(Duration.ofMillis(100), Duration.ofMillis(500))); From deaeb1c4429a6b8fcd81e40101298ef7be69093c Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sun, 24 May 2026 19:56:08 -0400 Subject: [PATCH 20/32] build: drop Java 8 support Raise the supported runtime and build floor to Java 11, and align docs, configuration, formatting, and dependency versions with that baseline. --- .github/workflows/docs-pages.yml | 4 +- .github/workflows/docs-pr.yml | 6 +- .github/workflows/release.yml | 4 +- .github/workflows/tests@v1.yml | 20 ++-- LICENSE_binary | 8 +- README-dev.md | 2 +- README.md | 2 +- core-shaded/pom.xml | 3 + .../core/data/time/DateRangePrecision.java | 6 +- .../oss/driver/api/core/CqlIdentifier.java | 3 +- .../api/core/context/DriverContext.java | 49 +++++---- .../oss/driver/api/core/metrics/Metrics.java | 1 + .../api/core/type/reflect/GenericType.java | 1 + .../SubnetAddressTranslator.java | 8 +- .../DefaultLoadBalancingPolicy.java | 4 - .../core/insights/InsightsClientTest.java | 2 +- .../internal/core/ContactPointsTest.java | 44 ++++++-- .../FixedHostNameAddressTranslatorTest.java | 3 +- .../core/type/util/VIntCodingTest.java | 2 +- distribution/pom.xml | 25 +++++ examples/pom.xml | 6 +- faq/README.md | 2 +- guava-shaded/pom.xml | 27 +---- guava-shaded/src/assembly/shaded-jar.xml | 1 + .../core/ssl/DefaultSslEngineFactoryIT.java | 2 +- manual/core/address_resolution/README.md | 2 +- manual/core/async/README.md | 4 +- manual/core/custom_codecs/README.md | 22 ++-- manual/core/integration/README.md | 8 +- manual/core/metadata/schema/README.md | 2 +- manual/core/paging/README.md | 2 +- manual/mapper/config/README.md | 6 +- manual/mapper/daos/delete/README.md | 4 +- manual/mapper/daos/getentity/README.md | 3 +- manual/mapper/daos/increment/README.md | 4 +- manual/mapper/daos/insert/README.md | 6 +- manual/mapper/daos/query/README.md | 8 +- manual/mapper/daos/select/README.md | 8 +- manual/mapper/daos/update/README.md | 6 +- .../mapper/processor/util/Classes.java | 2 +- performance/duration-test.yaml | 4 +- pom.xml | 104 ++++++++---------- .../driver/api/testinfra/ccm/CcmBridge.java | 3 +- 43 files changed, 223 insertions(+), 210 deletions(-) diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml index 973da8bfaa2..7ec207fad09 100644 --- a/.github/workflows/docs-pages.yml +++ b/.github/workflows/docs-pages.yml @@ -36,10 +36,10 @@ jobs: with: python-version: '3.13' - - name: Set up JDK 8.0 + - name: Set up JDK 11 uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: - java-version: '8' + java-version: '11' distribution: 'temurin' - name: Install uv diff --git a/.github/workflows/docs-pr.yml b/.github/workflows/docs-pr.yml index 58143e854f6..744838e1dc3 100644 --- a/.github/workflows/docs-pr.yml +++ b/.github/workflows/docs-pr.yml @@ -31,10 +31,10 @@ jobs: with: python-version: '3.13' - - name: Set up JDK 8.0 + - name: Set up JDK 11 uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: - java-version: '8' + java-version: '11' distribution: 'temurin' - name: Install uv @@ -44,4 +44,4 @@ jobs: run: make -C docs setupenv - name: Build docs - run: make -C docs test \ No newline at end of file + run: make -C docs test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3a55f680e7e..8c1aed7c783 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,10 +39,10 @@ jobs: RELEASE_TARGET_TAG: ${{ inputs.target-tag }} run: make checkout-one-commit-before - - name: Set up Java + - name: Set up JDK 11 uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: - java-version: '8' + java-version: '11' distribution: 'temurin' server-id: central gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} diff --git a/.github/workflows/tests@v1.yml b/.github/workflows/tests@v1.yml index 0f8d9dac934..063dfe2c6f7 100644 --- a/.github/workflows/tests@v1.yml +++ b/.github/workflows/tests@v1.yml @@ -35,7 +35,7 @@ jobs: strategy: matrix: - java-version: [8] + java-version: [11, 17] fail-fast: false steps: @@ -80,7 +80,7 @@ jobs: strategy: matrix: - java-version: [8] + java-version: [11, 17] fail-fast: false steps: @@ -109,14 +109,14 @@ jobs: strategy: matrix: - java-version: [8] + java-version: [11, 17] fail-fast: false steps: - name: Checkout source uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - - name: Set up JDK 8 + - name: Set up JDK ${{ matrix.java-version }} uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: java-version: ${{ matrix.java-version }} @@ -142,14 +142,14 @@ jobs: uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 if: always() with: - name: test-results + name: test-results-${{ matrix.java-version }} path: "*/**/target/*-reports/*.xml" - name: Parse test results uses: mikepenz/action-junit-report@3585e9575db828022551b4231f165eb59a0e74e3 # v5.6.2 if: always() with: - check_name: Unit tests report + check_name: Unit tests report (JDK ${{ matrix.java-version }}) require_tests: true report_paths: "*/**/target/*-reports/*.xml" follow_symlink: true @@ -180,7 +180,7 @@ jobs: strategy: matrix: cassandra-version: [3-LATEST, 4-LATEST] - java-version: [8] + java-version: [11, 17] test-group: [parallelizable, serial, isolated] fail-fast: false @@ -278,7 +278,7 @@ jobs: uses: mikepenz/action-junit-report@3585e9575db828022551b4231f165eb59a0e74e3 # v5.6.2 if: always() with: - check_name: Integration tests report for Cassandra ${{ steps.cassandra-version.outputs.value }} (${{ matrix.test-group }}) + check_name: Integration tests report for Cassandra ${{ steps.cassandra-version.outputs.value }} (${{ matrix.test-group }}, JDK ${{ matrix.java-version }}) require_tests: true report_paths: "*/**/target/*-reports/*.xml" follow_symlink: true @@ -295,7 +295,7 @@ jobs: strategy: matrix: scylla-version: [LTS-LATEST, LTS-PRIOR, LATEST] - java-version: [8] + java-version: [11, 17] test-group: [parallelizable, serial, isolated] fail-fast: false @@ -391,7 +391,7 @@ jobs: uses: mikepenz/action-junit-report@3585e9575db828022551b4231f165eb59a0e74e3 # v5.6.2 if: always() with: - check_name: Integration tests report for Scylla ${{ steps.scylla-version.outputs.value }} (${{ matrix.test-group }}) + check_name: Integration tests report for Scylla ${{ steps.scylla-version.outputs.value }} (${{ matrix.test-group }}, JDK ${{ matrix.java-version }}) require_tests: true report_paths: "*/**/target/*-reports/*.xml" follow_symlink: true diff --git a/LICENSE_binary b/LICENSE_binary index b59c6ec22bb..b4e9444159b 100644 --- a/LICENSE_binary +++ b/LICENSE_binary @@ -222,7 +222,7 @@ Copyright (C) 2018 Christian Stein This product includes software developed by Christian Stein see ci/install-jdk.sh -This product bundles Java Native Runtime - POSIX 3.1.15, +This product bundles Java Native Runtime - POSIX 3.1.22, which is available under the Eclipse Public License version 2.0. see licenses/jnr-posix.txt @@ -234,14 +234,14 @@ This product bundles ASM 9.2: a very small and fast Java bytecode manipulation f which is available under the 3-Clause BSD License. see licenses/asm.txt -This product bundles HdrHistogram 2.1.12: A High Dynamic Range (HDR) Histogram, +This product bundles HdrHistogram 2.2.2: A High Dynamic Range (HDR) Histogram, which is available under the 2-Clause BSD License. see licenses/HdrHistogram.txt -This product bundles The Simple Logging Facade for Java (SLF4J) API 1.7.26, +This product bundles The Simple Logging Facade for Java (SLF4J) API 2.0.18, which is available under the MIT License. see licenses/slf4j-api.txt -This product bundles Reactive Streams 1.0.3, +This product bundles Reactive Streams 1.0.4, which is available under the MIT License. see licenses/reactive-streams.txt diff --git a/README-dev.md b/README-dev.md index 5b8c85c6f92..07a4673f8a2 100644 --- a/README-dev.md +++ b/README-dev.md @@ -6,7 +6,7 @@ To build the documentation of this project, you need a UNIX-based operating syst You also need the following software installed to generate the reference documentation of the driver: -- Java JDK 8 or higher +- Java JDK 11 or higher - Maven Once you have installed the above software, you can build and preview the documentation by following the steps outlined in the `Quickstart guide `_. diff --git a/README.md b/README.md index 97447249ca0..cc0e3f8fae1 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ builder](manual/query_builder/), [mapper](manual/mapper)). The driver is compatible with Scylla and Apache Cassandra® 2.1 and higher. -It requires Java 8 or higher. +It requires Java 11 or higher. ## Migrating from previous versions diff --git a/core-shaded/pom.xml b/core-shaded/pom.xml index bf776b91e67..7775d4ad2a7 100644 --- a/core-shaded/pom.xml +++ b/core-shaded/pom.xml @@ -280,6 +280,9 @@ maven-javadoc-plugin + + true + attach-javadocs diff --git a/core/src/main/java/com/datastax/dse/driver/api/core/data/time/DateRangePrecision.java b/core/src/main/java/com/datastax/dse/driver/api/core/data/time/DateRangePrecision.java index ce811466c38..a0b5d0e5500 100644 --- a/core/src/main/java/com/datastax/dse/driver/api/core/data/time/DateRangePrecision.java +++ b/core/src/main/java/com/datastax/dse/driver/api/core/data/time/DateRangePrecision.java @@ -131,9 +131,9 @@ public enum DateRangePrecision { private final ChronoUnit roundingUnit; // The formatter is only used for formatting (parsing is done with DateRangeUtil.parseCalendar to // be exactly the same as DSE's). - // If that ever were to change, note that DateTimeFormatters with a time zone have a parsing bug - // in Java 8: the formatter's zone will always be used, even if the input string specifies one - // explicitly. + // If that ever were to change, note that DateTimeFormatters with a time zone had a parsing bug + // on older JDKs: the formatter's zone would always be used, even if the input string specified + // one explicitly. // See https://stackoverflow.com/questions/41999421 private final DateTimeFormatter formatter; diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java b/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java index 82e4c2b30a6..f11d7fa601a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/CqlIdentifier.java @@ -43,7 +43,8 @@ * * Examples: * - * + *
+ * * * * diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java index 6f0afd3df8a..0bb7098d445 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/context/DriverContext.java @@ -49,25 +49,26 @@ public interface DriverContext extends AttachmentPoint { @NonNull String getSessionName(); - /** @return The driver's configuration; never {@code null}. */ + /** Returns the driver's configuration; never {@code null}. */ @NonNull DriverConfig getConfig(); - /** @return The driver's configuration loader; never {@code null}. */ + /** Returns the driver's configuration loader; never {@code null}. */ @NonNull DriverConfigLoader getConfigLoader(); /** - * @return The driver's load balancing policies, keyed by profile name; the returned map is - * guaranteed to never be {@code null} and to always contain an entry for the {@value - * DriverExecutionProfile#DEFAULT_NAME} profile. + * Returns the driver's load balancing policies, keyed by profile name; the returned map is + * guaranteed to never be {@code null} and to always contain an entry for the {@value + * DriverExecutionProfile#DEFAULT_NAME} profile. */ @NonNull Map getLoadBalancingPolicies(); /** + * Returns the driver's load balancing policy for the given profile; never {@code null}. + * * @param profileName the profile name; never {@code null}. - * @return The driver's load balancing policy for the given profile; never {@code null}. */ @NonNull default LoadBalancingPolicy getLoadBalancingPolicy(@NonNull String profileName) { @@ -79,16 +80,17 @@ default LoadBalancingPolicy getLoadBalancingPolicy(@NonNull String profileName) } /** - * @return The driver's retry policies, keyed by profile name; the returned map is guaranteed to - * never be {@code null} and to always contain an entry for the {@value - * DriverExecutionProfile#DEFAULT_NAME} profile. + * Returns the driver's retry policies, keyed by profile name; the returned map is guaranteed to + * never be {@code null} and to always contain an entry for the {@value + * DriverExecutionProfile#DEFAULT_NAME} profile. */ @NonNull Map getRetryPolicies(); /** + * Returns the driver's retry policy for the given profile; never {@code null}. + * * @param profileName the profile name; never {@code null}. - * @return The driver's retry policy for the given profile; never {@code null}. */ @NonNull default RetryPolicy getRetryPolicy(@NonNull String profileName) { @@ -97,16 +99,17 @@ default RetryPolicy getRetryPolicy(@NonNull String profileName) { } /** - * @return The driver's speculative execution policies, keyed by profile name; the returned map is - * guaranteed to never be {@code null} and to always contain an entry for the {@value - * DriverExecutionProfile#DEFAULT_NAME} profile. + * Returns the driver's speculative execution policies, keyed by profile name; the returned map is + * guaranteed to never be {@code null} and to always contain an entry for the {@value + * DriverExecutionProfile#DEFAULT_NAME} profile. */ @NonNull Map getSpeculativeExecutionPolicies(); /** + * Returns the driver's speculative execution policy for the given profile; never {@code null}. + * * @param profileName the profile name; never {@code null}. - * @return The driver's speculative execution policy for the given profile; never {@code null}. */ @NonNull default SpeculativeExecutionPolicy getSpeculativeExecutionPolicy(@NonNull String profileName) { @@ -116,27 +119,27 @@ default SpeculativeExecutionPolicy getSpeculativeExecutionPolicy(@NonNull String : getSpeculativeExecutionPolicies().get(DriverExecutionProfile.DEFAULT_NAME); } - /** @return The driver's timestamp generator; never {@code null}. */ + /** Returns the driver's timestamp generator; never {@code null}. */ @NonNull TimestampGenerator getTimestampGenerator(); - /** @return The driver's reconnection policy; never {@code null}. */ + /** Returns the driver's reconnection policy; never {@code null}. */ @NonNull ReconnectionPolicy getReconnectionPolicy(); - /** @return The driver's address translator; never {@code null}. */ + /** Returns the driver's address translator; never {@code null}. */ @NonNull AddressTranslator getAddressTranslator(); - /** @return The authentication provider, if authentication was configured. */ + /** Returns the authentication provider, if authentication was configured. */ @NonNull Optional getAuthProvider(); - /** @return The SSL engine factory, if SSL was configured. */ + /** Returns the SSL engine factory, if SSL was configured. */ @NonNull Optional getSslEngineFactory(); - /** @return The driver's request tracker; never {@code null}. */ + /** Returns the driver's request tracker; never {@code null}. */ @NonNull RequestTracker getRequestTracker(); @@ -144,15 +147,15 @@ default SpeculativeExecutionPolicy getSpeculativeExecutionPolicy(@NonNull String @NonNull Optional getRequestIdGenerator(); - /** @return The driver's request throttler; never {@code null}. */ + /** Returns the driver's request throttler; never {@code null}. */ @NonNull RequestThrottler getRequestThrottler(); - /** @return The driver's node state listener; never {@code null}. */ + /** Returns the driver's node state listener; never {@code null}. */ @NonNull NodeStateListener getNodeStateListener(); - /** @return The driver's schema change listener; never {@code null}. */ + /** Returns the driver's schema change listener; never {@code null}. */ @NonNull SchemaChangeListener getSchemaChangeListener(); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metrics/Metrics.java b/core/src/main/java/com/datastax/oss/driver/api/core/metrics/Metrics.java index 58d531b3464..2cd294f6d3a 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metrics/Metrics.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metrics/Metrics.java @@ -42,6 +42,7 @@ public interface Metrics { * (Dropwizard Metrics manual) * @leaks-private-api */ + @SuppressWarnings("InvalidBlockTag") @NonNull MetricRegistry getRegistry(); diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java index d22b6f1bfaf..efe869660bb 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/reflect/GenericType.java @@ -370,6 +370,7 @@ public final Type getType() { * * @leaks-private-api */ + @SuppressWarnings("InvalidBlockTag") @NonNull public TypeToken __getToken() { return token; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTranslator.java b/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTranslator.java index 85f29e3fadd..59f614cad59 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTranslator.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTranslator.java @@ -61,8 +61,12 @@ public SubnetAddressTranslator(@NonNull DriverContext context) { .getDefaultProfile() .getBoolean(ADDRESS_TRANSLATOR_RESOLVE_ADDRESSES, false); this.subnetAddresses = - context.getConfig().getDefaultProfile().getStringMap(ADDRESS_TRANSLATOR_SUBNET_ADDRESSES) - .entrySet().stream() + context + .getConfig() + .getDefaultProfile() + .getStringMap(ADDRESS_TRANSLATOR_SUBNET_ADDRESSES) + .entrySet() + .stream() .map( e -> { // Quoted and/or containing forward slashes map keys in reference.conf are read to diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java index 09a4dff653f..ee563dbc770 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/loadbalancing/DefaultLoadBalancingPolicy.java @@ -331,10 +331,6 @@ private NodeResponseRateSample() { this.newest = OptionalLong.empty(); } - private NodeResponseRateSample(long oldestSample) { - this(oldestSample, nanoTime()); - } - private NodeResponseRateSample(long oldestSample, long newestSample) { this.oldest = oldestSample; this.newest = OptionalLong.of(newestSample); diff --git a/core/src/test/java/com/datastax/dse/driver/internal/core/insights/InsightsClientTest.java b/core/src/test/java/com/datastax/dse/driver/internal/core/insights/InsightsClientTest.java index 74869893b72..5085432dbec 100644 --- a/core/src/test/java/com/datastax/dse/driver/internal/core/insights/InsightsClientTest.java +++ b/core/src/test/java/com/datastax/dse/driver/internal/core/insights/InsightsClientTest.java @@ -109,7 +109,7 @@ public void should_construct_json_event_startup_message() throws IOException { OS os = new OS("linux", "1.2", "x64"); CPUS cpus = new CPUS(8, "intel i7"); Map javaDeps = - ImmutableMap.of("version", new RuntimeAndCompileTimeVersions("1.8.0", "1.8.0", false)); + ImmutableMap.of("version", new RuntimeAndCompileTimeVersions("11.0.0", "11.0.0", false)); Map> runtimeInfo = ImmutableMap.of("java", javaDeps); InsightsPlatformInfo insightsPlatformInfo = new InsightsPlatformInfo(os, cpus, runtimeInfo); diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/ContactPointsTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/ContactPointsTest.java index 72b875b8602..4428e36c53f 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/ContactPointsTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/ContactPointsTest.java @@ -73,8 +73,7 @@ public void should_parse_ipv4_address_and_port_in_configuration() { Set endPoints = ContactPoints.merge(Collections.emptySet(), ImmutableList.of("127.0.0.1:9042"), true); - assertThat(endPoints) - .containsExactly(new DefaultEndPoint(new InetSocketAddress("127.0.0.1", 9042))); + assertThat(endPoints).containsExactly(new DefaultEndPoint(socketAddress("127.0.0.1", 9042))); } @Test @@ -85,8 +84,8 @@ public void should_parse_ipv6_address_and_port_in_configuration() { assertThat(endPoints) .containsExactly( - new DefaultEndPoint(new InetSocketAddress("::1", 9042)), - new DefaultEndPoint(new InetSocketAddress("::2", 9042))); + new DefaultEndPoint(socketAddress("::1", 9042)), + new DefaultEndPoint(socketAddress("::2", 9042))); } @Test @@ -139,26 +138,25 @@ public void should_ignore_malformed_port_and_warn() { public void should_merge_programmatic_and_configuration() { Set endPoints = ContactPoints.merge( - ImmutableSet.of(new DefaultEndPoint(new InetSocketAddress("127.0.0.1", 9042))), + ImmutableSet.of(new DefaultEndPoint(socketAddress("127.0.0.1", 9042))), ImmutableList.of("127.0.0.2:9042"), true); assertThat(endPoints) .containsOnly( - new DefaultEndPoint(new InetSocketAddress("127.0.0.1", 9042)), - new DefaultEndPoint(new InetSocketAddress("127.0.0.2", 9042))); + new DefaultEndPoint(socketAddress("127.0.0.1", 9042)), + new DefaultEndPoint(socketAddress("127.0.0.2", 9042))); } @Test public void should_warn_if_duplicate_between_programmatic_and_configuration() { Set endPoints = ContactPoints.merge( - ImmutableSet.of(new DefaultEndPoint(new InetSocketAddress("127.0.0.1", 9042))), + ImmutableSet.of(new DefaultEndPoint(socketAddress("127.0.0.1", 9042))), ImmutableList.of("127.0.0.1:9042"), true); - assertThat(endPoints) - .containsOnly(new DefaultEndPoint(new InetSocketAddress("127.0.0.1", 9042))); + assertThat(endPoints).containsOnly(new DefaultEndPoint(socketAddress("127.0.0.1", 9042))); assertLog(Level.WARN, "Duplicate contact point /127.0.0.1:9042"); } @@ -168,11 +166,33 @@ public void should_warn_if_duplicate_in_configuration() { ContactPoints.merge( Collections.emptySet(), ImmutableList.of("127.0.0.1:9042", "127.0.0.1:9042"), true); - assertThat(endPoints) - .containsOnly(new DefaultEndPoint(new InetSocketAddress("127.0.0.1", 9042))); + assertThat(endPoints).containsOnly(new DefaultEndPoint(socketAddress("127.0.0.1", 9042))); assertLog(Level.WARN, "Duplicate contact point /127.0.0.1:9042"); } + private static InetSocketAddress socketAddress(String host, int port) { + try { + switch (host) { + case "127.0.0.1": + return new InetSocketAddress(InetAddress.getByAddress(new byte[] {127, 0, 0, 1}), port); + case "127.0.0.2": + return new InetSocketAddress(InetAddress.getByAddress(new byte[] {127, 0, 0, 2}), port); + case "::1": + return new InetSocketAddress( + InetAddress.getByAddress(new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}), + port); + case "::2": + return new InetSocketAddress( + InetAddress.getByAddress(new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}), + port); + default: + return new InetSocketAddress(InetAddress.getByName(host), port); + } + } catch (UnknownHostException e) { + throw new AssertionError(e); + } + } + private void assertLog(Level level, String message) { verify(appender, atLeast(1)).doAppend(loggingEventCaptor.capture()); Iterable logs = diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/addresstranslation/FixedHostNameAddressTranslatorTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/addresstranslation/FixedHostNameAddressTranslatorTest.java index 92800998056..da9c40f033c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/addresstranslation/FixedHostNameAddressTranslatorTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/addresstranslation/FixedHostNameAddressTranslatorTest.java @@ -42,6 +42,7 @@ public void should_translate_address() { new FixedHostNameAddressTranslator(defaultDriverContext); InetSocketAddress address = new InetSocketAddress("192.0.2.5", 6061); - assertThat(translator.translate(address)).isEqualTo(new InetSocketAddress("myaddress", 6061)); + assertThat(translator.translate(address)) + .isEqualTo(InetSocketAddress.createUnresolved("myaddress", 6061)); } } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/util/VIntCodingTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/util/VIntCodingTest.java index b85d6d66844..12a2d10c82d 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/util/VIntCodingTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/util/VIntCodingTest.java @@ -41,7 +41,7 @@ public static Object[] roundTripTestValues() { -1, 1 }; - }; + } private static final long[] LONGS = new long[] { diff --git a/distribution/pom.xml b/distribution/pom.xml index bf86d2e1fe8..b73b60e09cc 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -115,6 +115,26 @@ Java driver for Scylla and Apache Cassandra® ${project.version} API Java driver for Scylla and Apache Cassandra(R) ${project.version} API + + com.github.stephenc.jcip + jcip-annotations + 1.0-1 + + + com.github.spotbugs + spotbugs-annotations + 4.9.8 + + + com.google.code.findbugs + jsr305 + 3.0.2 + + + com.esri.geometry + esri-geometry-api + ${esri.version} + at.yawk.lz4 lz4-java @@ -135,6 +155,11 @@ tinkergraph-gremlin ${tinkerpop.version} + + org.javatuples + javatuples + 1.2 + diff --git a/examples/pom.xml b/examples/pom.xml index 0d9a5c64373..ac1835142fa 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -130,7 +130,7 @@ at.favre.lib bcrypt - 0.8.0 + 0.10.2 @@ -148,8 +148,8 @@ maven-compiler-plugin - 1.8 - 1.8 + 11 + 11 com.scylladb diff --git a/faq/README.md b/faq/README.md index 97cb4decd00..5bd02fab6f6 100644 --- a/faq/README.md +++ b/faq/README.md @@ -106,7 +106,7 @@ available. ### I want to set a date on a bound statement, where did `setTimestamp()` go? -The driver now uses Java 8's improved date and time API. CQL type `timestamp` is mapped to +The driver now uses the JDK's improved date and time API. CQL type `timestamp` is mapped to `java.time.Instant`, and the corresponding getter and setter are `getInstant` and `setInstant`. See [Temporal types](../manual/core/temporal_types/) for more details. diff --git a/guava-shaded/pom.xml b/guava-shaded/pom.xml index 27d5b69077d..8578787660b 100644 --- a/guava-shaded/pom.xml +++ b/guava-shaded/pom.xml @@ -57,32 +57,6 @@ - - - org.codehaus.mojo - build-helper-maven-plugin - 1.12 - - - regex-property - - regex-property - - - maven.main.skip - ${java.version} - ^(?!1.8).+ - true - false - - - - maven-shade-plugin @@ -159,6 +133,7 @@ java-driver-guava-shaded ${project.version} jar + **/module-info.class diff --git a/guava-shaded/src/assembly/shaded-jar.xml b/guava-shaded/src/assembly/shaded-jar.xml index 4a2eac46316..f660d62a196 100644 --- a/guava-shaded/src/assembly/shaded-jar.xml +++ b/guava-shaded/src/assembly/shaded-jar.xml @@ -38,6 +38,7 @@ ${project.build.outputDirectory} META-INF/maven/com.scylladb/java-driver-guava-shaded/pom.xml + **/module-info.class diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/core/ssl/DefaultSslEngineFactoryIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/core/ssl/DefaultSslEngineFactoryIT.java index 50c9561ff9a..1c7b8b06fa2 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/core/ssl/DefaultSslEngineFactoryIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/core/ssl/DefaultSslEngineFactoryIT.java @@ -117,7 +117,7 @@ protected String hostNoLookup(InetSocketAddress addr) { countNoLookups++; return super.hostNoLookup(addr); } - }; + } @Test public void should_respect_config_for_san_resolution() { diff --git a/manual/core/address_resolution/README.md b/manual/core/address_resolution/README.md index 0d80312d82d..ae44feea3ea 100644 --- a/manual/core/address_resolution/README.md +++ b/manual/core/address_resolution/README.md @@ -185,7 +185,7 @@ datastax-java-driver { DNS is resolved at connection time (not at route discovery time). The driver delegates to `InetAddress.getByName()`, which is a blocking call that uses the JVM's built-in DNS cache -(30 s default TTL in Java 8+). Because this runs on Netty I/O threads, slow or unresponsive +(30 s default TTL in the JDK). Because this runs on Netty I/O threads, slow or unresponsive DNS can block connection establishment and impact driver throughput. To mitigate this, configure the JVM DNS cache TTL via the `networkaddress.cache.ttl` security property (e.g. in `$JAVA_HOME/conf/security/java.security` or programmatically with diff --git a/manual/core/async/README.md b/manual/core/async/README.md index 5b4bac3dccf..24601020b32 100644 --- a/manual/core/async/README.md +++ b/manual/core/async/README.md @@ -21,7 +21,7 @@ under the License. ### Quick overview -Async driver methods return Java 8's [CompletionStage]. +Async driver methods return the JDK's [CompletionStage]. * don't call synchronous methods from asynchronous callbacks (the driver detects that and throws). * callbacks execute on I/O threads: consider providing your own executor for expensive computations. @@ -224,6 +224,6 @@ even when iterating through the results of a request. `session.executeAsync` ret This greatly simplifies asynchronous paging; see the [paging](../paging/#asynchronous-paging) documentation for more details and an example. -[CompletionStage]: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html +[CompletionStage]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/CompletionStage.html [AsyncResultSet]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/core/cql/AsyncResultSet.html diff --git a/manual/core/custom_codecs/README.md b/manual/core/custom_codecs/README.md index f3b7be1e3d9..038766a56f1 100644 --- a/manual/core/custom_codecs/README.md +++ b/manual/core/custom_codecs/README.md @@ -698,17 +698,17 @@ private static String formatRow(Row row) { [MappingCodec]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/core/type/codec/MappingCodec.html [SessionBuilder.addTypeCodecs]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/core/session/SessionBuilder.html#addTypeCodecs-com.datastax.oss.driver.api.core.type.codec.TypeCodec...- -[Enums]: https://docs.oracle.com/javase/8/docs/api/java/lang/Enum.html -[Enum.name()]: https://docs.oracle.com/javase/8/docs/api/java/lang/Enum.html#name-- -[Enum.ordinal()]: https://docs.oracle.com/javase/8/docs/api/java/lang/Enum.html#ordinal-- -[java.nio.ByteBuffer]: https://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html -[java.util.List]: https://docs.oracle.com/javase/8/docs/api/java/util/List.html -[java.util.Optional]: https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html -[Optional.empty()]: https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html#empty-- -[java.time.Instant]: https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html -[java.time.ZonedDateTime]: https://docs.oracle.com/javase/8/docs/api/java/time/ZonedDateTime.html -[java.time.LocalDateTime]: https://docs.oracle.com/javase/8/docs/api/java/time/LocalDateTime.html -[java.time.ZoneId]: https://docs.oracle.com/javase/8/docs/api/java/time/ZoneId.html +[Enums]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Enum.html +[Enum.name()]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Enum.html#name-- +[Enum.ordinal()]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Enum.html#ordinal-- +[java.nio.ByteBuffer]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/ByteBuffer.html +[java.util.List]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/List.html +[java.util.Optional]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Optional.html +[Optional.empty()]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Optional.html#empty-- +[java.time.Instant]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/Instant.html +[java.time.ZonedDateTime]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/ZonedDateTime.html +[java.time.LocalDateTime]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/LocalDateTime.html +[java.time.ZoneId]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/ZoneId.html [ExtraTypeCodecs]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/core/type/codec/ExtraTypeCodecs.html [ExtraTypeCodecs.BLOB_TO_ARRAY]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/core/type/codec/ExtraTypeCodecs.html#BLOB_TO_ARRAY diff --git a/manual/core/integration/README.md b/manual/core/integration/README.md index af52bad6792..4ec1dd59312 100644 --- a/manual/core/integration/README.md +++ b/manual/core/integration/README.md @@ -134,7 +134,7 @@ $ find . -type f ##### Project descriptor `pom.xml` is the [Project Object Model][maven_pom] that describes your application. We declare the -dependencies, and tell Maven that we're going to use Java 8: +dependencies, and tell Maven that we're going to use Java 11: ```xml @@ -165,8 +165,8 @@ dependencies, and tell Maven that we're going to use Java 8: maven-compiler-plugin - 1.8 - 1.8 + 11 + 11 @@ -281,7 +281,7 @@ version '1.0.0-SNAPSHOT' apply plugin: 'java' -sourceCompatibility = 1.8 +sourceCompatibility = 11 repositories { mavenCentral() diff --git a/manual/core/metadata/schema/README.md b/manual/core/metadata/schema/README.md index 673c87e57ed..ae119a20b57 100644 --- a/manual/core/metadata/schema/README.md +++ b/manual/core/metadata/schema/README.md @@ -352,4 +352,4 @@ take a look at the [Performance](../../performance/#schema-updates) page for a f [DseAggregateMetadata]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/dse/driver/api/core/metadata/schema/DseAggregateMetadata.html [JAVA-750]: https://datastax-oss.atlassian.net/browse/JAVA-750 -[java.util.regex.Pattern]: https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html +[java.util.regex.Pattern]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/regex/Pattern.html diff --git a/manual/core/paging/README.md b/manual/core/paging/README.md index 2df92bd69d1..99c7cecde9b 100644 --- a/manual/core/paging/README.md +++ b/manual/core/paging/README.md @@ -279,6 +279,6 @@ and offset paging. [OffsetPager]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/core/paging/OffsetPager.html [PagingState]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/core/cql/PagingState.html -[CompletionStage]: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html +[CompletionStage]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/CompletionStage.html [driver examples]: https://github.com/datastax/java-driver/tree/4.x/examples/src/main/java/com/datastax/oss/driver/examples/paging diff --git a/manual/mapper/config/README.md b/manual/mapper/config/README.md index 0154b03deaf..c4fe9ef0ce7 100644 --- a/manual/mapper/config/README.md +++ b/manual/mapper/config/README.md @@ -52,8 +52,8 @@ configuration (make sure you use version 3.5 or higher): maven-compiler-plugin 3.8.1 - 1.8 - 1.8 + 11 + 11 com.scylladb @@ -65,7 +65,7 @@ configuration (make sure you use version 3.5 or higher): org.slf4j slf4j-nop - 1.7.26 + 2.0.18 diff --git a/manual/mapper/daos/delete/README.md b/manual/mapper/daos/delete/README.md index e67ecdc8a6e..1d9f9c6cb9b 100644 --- a/manual/mapper/daos/delete/README.md +++ b/manual/mapper/daos/delete/README.md @@ -181,5 +181,5 @@ entity class and the [naming strategy](../../entities/#naming-strategy)). [ReactiveResultSet]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/dse/driver/api/core/cql/reactive/ReactiveResultSet.html -[CompletionStage]: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html -[CompletableFuture]: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html +[CompletionStage]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/CompletionStage.html +[CompletableFuture]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/CompletableFuture.html diff --git a/manual/mapper/daos/getentity/README.md b/manual/mapper/daos/getentity/README.md index de9a530b558..7b54aeddc38 100644 --- a/manual/mapper/daos/getentity/README.md +++ b/manual/mapper/daos/getentity/README.md @@ -159,7 +159,6 @@ If the return type doesn't match the parameter type (for example [PagingIterable [Row]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/core/cql/Row.html [UdtValue]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/core/data/UdtValue.html -[Stream]: https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html - +[Stream]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/Stream.html diff --git a/manual/mapper/daos/increment/README.md b/manual/mapper/daos/increment/README.md index 44b017be2e1..275c2272139 100644 --- a/manual/mapper/daos/increment/README.md +++ b/manual/mapper/daos/increment/README.md @@ -101,5 +101,5 @@ entity class and the naming convention). [@PartitionKey]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/mapper/annotations/PartitionKey.html [@CqlName]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/mapper/annotations/CqlName.html -[CompletionStage]: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html -[CompletableFuture]: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html +[CompletionStage]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/CompletionStage.html +[CompletableFuture]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/CompletableFuture.html diff --git a/manual/mapper/daos/insert/README.md b/manual/mapper/daos/insert/README.md index b90ffa33a32..bf9e5f223f8 100644 --- a/manual/mapper/daos/insert/README.md +++ b/manual/mapper/daos/insert/README.md @@ -135,6 +135,6 @@ entity class and the [naming strategy](../../entities/#naming-strategy)). [BoundStatement]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/core/cql/BoundStatement.html [ReactiveResultSet]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/dse/driver/api/core/cql/reactive/ReactiveResultSet.html -[CompletionStage]: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html -[CompletableFuture]: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html -[Optional]: https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html +[CompletionStage]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/CompletionStage.html +[CompletableFuture]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/CompletableFuture.html +[Optional]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Optional.html diff --git a/manual/mapper/daos/query/README.md b/manual/mapper/daos/query/README.md index a11753da880..e7b3c62e974 100644 --- a/manual/mapper/daos/query/README.md +++ b/manual/mapper/daos/query/README.md @@ -145,7 +145,7 @@ Then: [ReactiveResultSet]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/dse/driver/api/core/cql/reactive/ReactiveResultSet.html [MappedReactiveResultSet]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/dse/driver/api/mapper/reactive/MappedReactiveResultSet.html -[CompletionStage]: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html -[CompletableFuture]: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html -[Optional]: https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html -[Stream]: https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html +[CompletionStage]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/CompletionStage.html +[CompletableFuture]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/CompletableFuture.html +[Optional]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Optional.html +[Stream]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/Stream.html diff --git a/manual/mapper/daos/select/README.md b/manual/mapper/daos/select/README.md index fb6c4ca2077..d72715f61c4 100644 --- a/manual/mapper/daos/select/README.md +++ b/manual/mapper/daos/select/README.md @@ -194,7 +194,7 @@ entity class and the [naming strategy](../../entities/#naming-strategy)). [PagingIterable.spliterator]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/core/PagingIterable.html#spliterator-- [MappedReactiveResultSet]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/dse/driver/api/mapper/reactive/MappedReactiveResultSet.html -[CompletionStage]: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html -[CompletableFuture]: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html -[Optional]: https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html -[Stream]: https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html +[CompletionStage]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/CompletionStage.html +[CompletableFuture]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/CompletableFuture.html +[Optional]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Optional.html +[Stream]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/Stream.html diff --git a/manual/mapper/daos/update/README.md b/manual/mapper/daos/update/README.md index 87e9286c800..c87441a6e9d 100644 --- a/manual/mapper/daos/update/README.md +++ b/manual/mapper/daos/update/README.md @@ -166,9 +166,9 @@ entity class and the naming convention). [@Update]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/mapper/annotations/Update.html [AsyncResultSet]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/core/cql/AsyncResultSet.html -[Boolean]: https://docs.oracle.com/javase/8/docs/api/index.html?java/lang/Boolean.html -[CompletionStage]: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html -[CompletableFuture]: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html +[Boolean]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Boolean.html +[CompletionStage]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/CompletionStage.html +[CompletableFuture]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/CompletableFuture.html [ResultSet]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/core/cql/ResultSet.html [BoundStatement]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/core/cql/BoundStatement.html [ReactiveResultSet]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/dse/driver/api/core/cql/reactive/ReactiveResultSet.html diff --git a/mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/util/Classes.java b/mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/util/Classes.java index de45d36769d..ffd2651d033 100644 --- a/mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/util/Classes.java +++ b/mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/util/Classes.java @@ -106,7 +106,7 @@ public boolean isMap(DeclaredType declaredType) { } /** - * Whether a type mirror is a parameterized Java 8 future ({@code CompletionStage or + * Whether a type mirror is a parameterized future such as {@code CompletionStage} or {@code * CompletableFuture}. */ public boolean isFuture(DeclaredType declaredType) { diff --git a/performance/duration-test.yaml b/performance/duration-test.yaml index 6e718f2add8..de76b9e1e1f 100644 --- a/performance/duration-test.yaml +++ b/performance/duration-test.yaml @@ -47,7 +47,7 @@ ensemble: configuration_manager: - name: ctool properties: - java.version: openjdk8 + java.version: openjdk11 product.install.type: tarball product.type: {{server_type}} product.version: {{server_version}} @@ -69,7 +69,7 @@ ensemble: configuration_manager: - name: ctool properties: - java.version: openjdk8 + java.version: openjdk11 install.maven: true - name: java_driver properties: diff --git a/pom.xml b/pom.xml index e7e570a5e36..75f1d327a86 100644 --- a/pom.xml +++ b/pom.xml @@ -54,10 +54,10 @@ UTF-8 UTF-8 - 1.4.5 + 1.4.8 2.2.2 - 4.2.37 + 4.2.38 4.1.133.Final 1.2.1 3.5.6 - 1.7.36 + 2.0.18 1.0.4 20250517 - 2.21.1 - 2.20.0 + 2.21.3 + 2.21.3 1.9.12 1.1.10.8 - 1.10.1 + 1.11.0 3.27.7 - 1.5.0 + 1.6.0 4.13.2 - 1.2.13 + 1.5.32 6.0.0 7.0.5 4.13.4 2.6.17 - 0.13.0.0 + 0.14.0.0 1.1.4 2.47 2.6.1 @@ -94,7 +94,7 @@ 2.2.21 4.3.0 2.0.0-M19 - 3.5.4 + 3.5.5 22.0.0.2 false ${skipTests} @@ -182,7 +182,7 @@ com.google.guava guava - 33.3.1-jre + 33.6.0-jre com.typesafe @@ -213,7 +213,7 @@ com.github.jnr jnr-posix - 3.1.20 + 3.1.22 io.dropwizard.metrics @@ -281,7 +281,7 @@ com.github.spotbugs spotbugs-annotations - 4.9.6 + 4.9.8 com.squareup @@ -306,12 +306,12 @@ net.bytebuddy byte-buddy - 1.17.7 + 1.18.8-jdk5 org.mockito mockito-core - 2.28.2 + 5.23.0 io.reactivex.rxjava2 @@ -441,7 +441,7 @@ org.testng testng - 7.5.1 + 7.12.0 org.apache.directory.server @@ -553,7 +553,7 @@ maven-compiler-plugin - 3.8.1 + 3.15.0 org.apache.maven.plugins @@ -563,7 +563,7 @@ com.coveo fmt-maven-plugin - 2.9 + 2.13 au.com.acegi @@ -580,17 +580,17 @@ maven-shade-plugin - 3.6.1 + 3.6.2 maven-assembly-plugin - 3.7.1 + 3.8.0 net.alchim31.maven scala-maven-plugin - 4.9.6 + 4.9.10 2.11 @@ -601,7 +601,7 @@ maven-source-plugin - 3.3.1 + 3.4.0 maven-javadoc-plugin @@ -609,7 +609,7 @@ maven-jar-plugin - 3.4.2 + 3.5.0 maven-gpg-plugin @@ -617,7 +617,7 @@ maven-release-plugin - 2.5.3 + 3.3.1 maven-install-plugin @@ -641,12 +641,12 @@ maven-dependency-plugin - 3.9.0 + 3.10.0 org.jacoco jacoco-maven-plugin - 0.8.13 + 0.8.14 org.apache.felix @@ -687,7 +687,7 @@ org.codehaus.mojo versions-maven-plugin - 2.19.1 + 2.21.0 org.codehaus.mojo @@ -705,7 +705,7 @@ org.sonatype.central central-publishing-maven-plugin - 0.8.0 + 0.10.0 true central @@ -717,33 +717,24 @@ maven-compiler-plugin - javac-with-errorprone - true - 1.8 - 1.8 + 11 + 11 - -Xep:FutureReturnValueIgnored:OFF - -Xep:PreferJavaTimeOverload:OFF - -Xep:AnnotateFormatMethod:OFF - -Xep:WildcardImport:WARN - -XepExcludedPaths:.*/target/(?:generated-sources|generated-test-sources)/.* + -XDcompilePolicy=simple + --should-stop=ifError=FLOW + -Xplugin:ErrorProne -Xep:FutureReturnValueIgnored:OFF -Xep:PreferJavaTimeOverload:OFF -Xep:AnnotateFormatMethod:OFF -Xep:WildcardImport:WARN -Xep:MissingSummary:OFF -Xep:StringCaseLocaleUsage:OFF -Xep:InlineMeSuggester:OFF -Xep:AddressSelection:OFF -XepExcludedPaths:.*/target/(?:generated-sources|generated-test-sources)/.* + + + com.google.errorprone + error_prone_core + 2.31.0 + + true true false - - - org.codehaus.plexus - plexus-compiler-javac-errorprone - 2.8.6 - - - com.google.errorprone - error_prone_core - 2.3.4 - - com.coveo @@ -842,6 +833,7 @@ false true all,-missing + true com.datastax.*.driver.internal* @@ -849,12 +841,9 @@ a API note: - + - leaks + leaks-private-api X @@ -1086,13 +1075,6 @@ height="0" width="0" style="display:none;visibility:hidden"> ${testJavaHome} - - - test-jdk-8 - - [8,) - - test-jdk-11 diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java index e7b2b4e4a02..e35c8013e35 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java @@ -279,7 +279,8 @@ private static Version parseCcmVersion() { if (configDir != null) { execute(CommandLine.parse("ccm remove get_version --config-dir=" + configDir)); } - } catch (Exception ignored) { + } catch (Exception e) { + LOG.warn("Failed to remove temporary CCM cluster used to resolve {}", versionString, e); } } return result; From a6c2792a1d54fe40c5d3fcad536f0284c1cad771 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sun, 24 May 2026 19:56:08 -0400 Subject: [PATCH 21/32] fix: validate non-numeric subnet prefixes Reject invalid subnet prefix strings so address translation fails clearly instead of accepting bad configuration. --- .../internal/core/graph/GraphSON2SerdeTP.java | 2 +- .../core/insights/InsightsClient.java | 3 +-- .../internal/core/insights/PackageUtil.java | 17 +++--------- .../core/insights/PlatformInfoFinder.java | 14 +++++----- .../core/insights/schema/InsightMetadata.java | 2 +- .../schema/DefaultDseAggregateMetadata.java | 1 - .../oss/driver/api/core/data/CqlVector.java | 2 +- .../api/core/metadata/token/Partitioner.java | 2 +- .../driver/api/core/retry/RetryVerdict.java | 1 + .../type/codec/registry/CodecRegistry.java | 1 + .../Ec2MultiRegionAddressTranslator.java | 1 + .../internal/core/channel/ChannelFactory.java | 3 +-- .../internal/core/cql/DefaultTraceEvent.java | 1 + .../metadata/ClientRoutesTopologyMonitor.java | 7 +++-- .../core/metadata/DefaultTabletMap.java | 11 ++++---- .../core/metadata/PartitionerFactory.java | 2 +- .../core/metadata/schema/ScriptBuilder.java | 5 ++-- .../metadata/schema/parsing/RawColumn.java | 3 +-- .../queries/RuleBasedKeyspaceFilter.java | 4 +-- .../metadata/token/RandomTokenFactory.java | 6 ++--- .../core/metadata/token/TokenLong64.java | 3 +-- .../core/ssl/ReloadingKeyManagerFactory.java | 2 +- .../core/type/codec/extras/OptionalCodec.java | 4 +-- .../codec/extras/enums/EnumOrdinalCodec.java | 1 + .../core/util/collection/QueryPlan.java | 1 + .../core/graph/GraphSupportCheckerTest.java | 4 +-- .../internal/core/CompletionStageAssert.java | 2 ++ .../channel/MockChannelFactoryHelper.java | 4 +-- .../core/data/AccessibleByIdTestBase.java | 26 +++++++++---------- .../core/data/AccessibleByIndexTestBase.java | 14 +++++----- .../session/MockChannelPoolFactoryHelper.java | 4 +-- .../core/type/codec/TupleCodecTest.java | 12 ++++----- .../core/type/codec/UdtCodecTest.java | 18 ++++++------- .../registry/CachingCodecRegistryTest.java | 15 ++++++----- pom.xml | 2 +- 35 files changed, 98 insertions(+), 102 deletions(-) diff --git a/core/src/main/java/com/datastax/dse/driver/internal/core/graph/GraphSON2SerdeTP.java b/core/src/main/java/com/datastax/dse/driver/internal/core/graph/GraphSON2SerdeTP.java index d79afc71822..05e09b018ab 100644 --- a/core/src/main/java/com/datastax/dse/driver/internal/core/graph/GraphSON2SerdeTP.java +++ b/core/src/main/java/com/datastax/dse/driver/internal/core/graph/GraphSON2SerdeTP.java @@ -403,7 +403,7 @@ static final class ObjectGraphNodeGraphSON2Serializer extends StdSerializer new Date().getTime(), + System::currentTimeMillis, insightsConfiguration, new PlatformInfoFinder(), new ReconnectionPolicyInfoFinder(), diff --git a/core/src/main/java/com/datastax/dse/driver/internal/core/insights/PackageUtil.java b/core/src/main/java/com/datastax/dse/driver/internal/core/insights/PackageUtil.java index 3c61dec4f20..c7e15bbdfae 100644 --- a/core/src/main/java/com/datastax/dse/driver/internal/core/insights/PackageUtil.java +++ b/core/src/main/java/com/datastax/dse/driver/internal/core/insights/PackageUtil.java @@ -18,9 +18,6 @@ package com.datastax.dse.driver.internal.core.insights; import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting; -import com.datastax.oss.driver.shaded.guava.common.base.Joiner; -import java.util.Arrays; -import java.util.regex.Pattern; class PackageUtil { static final String DEFAULT_SPECULATIVE_EXECUTION_PACKAGE = @@ -28,8 +25,6 @@ class PackageUtil { static final String DEFAULT_LOAD_BALANCING_PACKAGE = "com.datastax.oss.driver.internal.core.loadbalancing"; static final String DEFAULT_AUTH_PROVIDER_PACKAGE = "com.datastax.oss.driver.internal.core.auth"; - private static final Pattern PACKAGE_SPLIT_REGEX = Pattern.compile("\\."); - private static final Joiner DOT_JOINER = Joiner.on("."); static String getNamespace(Class tClass) { String namespace = ""; @@ -61,18 +56,14 @@ private static ClassSettingDetails getClassSettingDetails( @VisibleForTesting static String getClassName(String classSetting) { - String[] split = PACKAGE_SPLIT_REGEX.split(classSetting); - if (split.length == 0) { - return ""; - } - return split[split.length - 1]; + int lastDot = classSetting.lastIndexOf('.'); + return lastDot < 0 ? classSetting : classSetting.substring(lastDot + 1); } @VisibleForTesting static String getFullPackageOrDefault(String classSetting, String defaultValue) { - String[] split = PACKAGE_SPLIT_REGEX.split(classSetting); - if (split.length <= 1) return defaultValue; - return DOT_JOINER.join(Arrays.copyOf(split, split.length - 1)); + int lastDot = classSetting.lastIndexOf('.'); + return lastDot < 0 ? defaultValue : classSetting.substring(0, lastDot); } static class ClassSettingDetails { diff --git a/core/src/main/java/com/datastax/dse/driver/internal/core/insights/PlatformInfoFinder.java b/core/src/main/java/com/datastax/dse/driver/internal/core/insights/PlatformInfoFinder.java index 30d41d40836..84b4d76e8b7 100644 --- a/core/src/main/java/com/datastax/dse/driver/internal/core/insights/PlatformInfoFinder.java +++ b/core/src/main/java/com/datastax/dse/driver/internal/core/insights/PlatformInfoFinder.java @@ -24,6 +24,7 @@ import com.datastax.dse.driver.internal.core.insights.schema.InsightsPlatformInfo.CPUS; import com.datastax.oss.driver.internal.core.os.Native; import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting; +import com.datastax.oss.driver.shaded.guava.common.base.Splitter; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -37,11 +38,10 @@ import java.util.Objects; import java.util.Properties; import java.util.function.Function; -import java.util.regex.Pattern; class PlatformInfoFinder { private static final String MAVEN_IGNORE_LINE = "The following files have been resolved:"; - private static final Pattern DEPENDENCY_SPLIT_REGEX = Pattern.compile(":"); + private static final Splitter DEPENDENCY_SPLITTER = Splitter.on(':'); static final String UNVERIFIED_RUNTIME_VERSION = "UNVERIFIED"; private final Function propertiesUrlProvider; @@ -197,11 +197,13 @@ private List extractMavenDependenciesFromFile(InputStream in } private DependencyFromFile extractDependencyFromLine(String line) { - String[] split = DEPENDENCY_SPLIT_REGEX.split(line); - if (split.length == 6) { // case for i.e.: com.github.jnr:jffi:jar:native:1.2.16:compile - return new DependencyFromFile(split[0], split[1], split[4], checkIsOptional(split[5])); + List split = DEPENDENCY_SPLITTER.splitToList(line); + if (split.size() == 6) { // case for i.e.: com.github.jnr:jffi:jar:native:1.2.16:compile + return new DependencyFromFile( + split.get(0), split.get(1), split.get(4), checkIsOptional(split.get(5))); } else { // case for normal: org.ow2.asm:asm:jar:5.0.3:compile - return new DependencyFromFile(split[0], split[1], split[3], checkIsOptional(split[4])); + return new DependencyFromFile( + split.get(0), split.get(1), split.get(3), checkIsOptional(split.get(4))); } } diff --git a/core/src/main/java/com/datastax/dse/driver/internal/core/insights/schema/InsightMetadata.java b/core/src/main/java/com/datastax/dse/driver/internal/core/insights/schema/InsightMetadata.java index cfa2644b0c7..d7397d813aa 100644 --- a/core/src/main/java/com/datastax/dse/driver/internal/core/insights/schema/InsightMetadata.java +++ b/core/src/main/java/com/datastax/dse/driver/internal/core/insights/schema/InsightMetadata.java @@ -68,7 +68,7 @@ public boolean equals(Object o) { } InsightMetadata that = (InsightMetadata) o; return Objects.equals(name, that.name) - && Objects.equals(timestamp, that.timestamp) + && timestamp == that.timestamp && Objects.equals(tags, that.tags) && insightType == that.insightType && Objects.equals(insightMappingId, that.insightMappingId); diff --git a/core/src/main/java/com/datastax/dse/driver/internal/core/metadata/schema/DefaultDseAggregateMetadata.java b/core/src/main/java/com/datastax/dse/driver/internal/core/metadata/schema/DefaultDseAggregateMetadata.java index 52a0b846076..1baa50e7fb8 100644 --- a/core/src/main/java/com/datastax/dse/driver/internal/core/metadata/schema/DefaultDseAggregateMetadata.java +++ b/core/src/main/java/com/datastax/dse/driver/internal/core/metadata/schema/DefaultDseAggregateMetadata.java @@ -64,7 +64,6 @@ public boolean isDeterministic() { } @Override - @Nullable public Optional getDeterministic() { return Optional.ofNullable(deterministic); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlVector.java b/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlVector.java index 8089d551750..a3bbe2c8020 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlVector.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/data/CqlVector.java @@ -80,7 +80,7 @@ public static CqlVector newInstance(List list) { * Create a new CqlVector instance from the specified string representation. * * @param str a String representation of a CqlVector - * @param subtypeCodec + * @param subtypeCodec the codec used to parse individual vector elements * @return a new CqlVector built from the String representation */ public static CqlVector from(@NonNull String str, @NonNull TypeCodec subtypeCodec) { diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/token/Partitioner.java b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/token/Partitioner.java index 033058a7084..530cca9d22d 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/metadata/token/Partitioner.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/metadata/token/Partitioner.java @@ -18,7 +18,7 @@ import java.nio.ByteBuffer; -/** Allows to hash partition key to a @code{Token}. */ +/** Allows to hash partition key to a {@code Token}. */ public interface Partitioner { Token hash(ByteBuffer partitionKey); } diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryVerdict.java b/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryVerdict.java index 9abb54156db..ee51059da07 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryVerdict.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/retry/RetryVerdict.java @@ -27,6 +27,7 @@ * where, and a method that allows the original request to be modified before the retry. */ @FunctionalInterface +@SuppressWarnings("ClassInitializationDeadlock") public interface RetryVerdict { /** A retry verdict that retries the same request on the same node. */ diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistry.java b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistry.java index 36472f34c79..42bfe6535c2 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistry.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/type/codec/registry/CodecRegistry.java @@ -57,6 +57,7 @@ public interface CodecRegistry { * but any attempt to {@linkplain MutableCodecRegistry#register(TypeCodec) register new codecs} * will throw {@link UnsupportedOperationException}. */ + @SuppressWarnings("ClassInitializationDeadlock") CodecRegistry DEFAULT = new DefaultCodecRegistry("default") { @Override diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/Ec2MultiRegionAddressTranslator.java b/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/Ec2MultiRegionAddressTranslator.java index 88e6cdb3bb2..bee22dc5335 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/Ec2MultiRegionAddressTranslator.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/Ec2MultiRegionAddressTranslator.java @@ -54,6 +54,7 @@ * the domain name of the target instance. Then it performs a forward DNS lookup of the domain name; * the EC2 DNS does the private/public switch automatically based on location. */ +@SuppressWarnings("BanJNDI") public class Ec2MultiRegionAddressTranslator implements AddressTranslator { private static final Logger LOG = LoggerFactory.getLogger(Ec2MultiRegionAddressTranslator.class); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java index 5ad8c111262..6bfc355f910 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/ChannelFactory.java @@ -410,8 +410,7 @@ protected void initChannel(Channel channel) { ChannelPipeline pipeline = channel.pipeline(); context .getSslHandlerFactory() - .map(f -> f.newSslHandler(channel, endPoint)) - .map(h -> pipeline.addLast(SSL_HANDLER_NAME, h)); + .ifPresent(f -> pipeline.addLast(SSL_HANDLER_NAME, f.newSslHandler(channel, endPoint))); // Only add meter handlers on the pipeline if metrics are enabled. SessionMetricUpdater sessionMetricUpdater = context.getMetricsFactory().getSessionUpdater(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultTraceEvent.java b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultTraceEvent.java index 9bf7ff7c8ee..e092f8302ec 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultTraceEvent.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/cql/DefaultTraceEvent.java @@ -78,6 +78,7 @@ public String getThreadName() { } @Override + @SuppressWarnings("JavaUtilDate") public String toString() { return String.format("%s on %s[%s] at %s", activity, source, threadName, new Date(timestamp)); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/ClientRoutesTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/ClientRoutesTopologyMonitor.java index 569637e0873..1ffc35fd9f4 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/ClientRoutesTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/ClientRoutesTopologyMonitor.java @@ -55,8 +55,8 @@ public class ClientRoutesTopologyMonitor extends DefaultTopologyMonitor { private static final Logger LOG = LoggerFactory.getLogger(ClientRoutesTopologyMonitor.class); - private static final String SELECT_ROUTES_COLUMNS = - "SELECT host_id, address, port, tls_port, connection_id FROM %s"; + private static final String SELECT_ROUTES_PREFIX = + "SELECT host_id, address, port, tls_port, connection_id FROM "; /** Disables result-set paging, matching the convention used by {@link DefaultTopologyMonitor}. */ private static final int NO_PAGING = -1; @@ -508,8 +508,7 @@ private static String buildQuery( boolean hasConnectionIds = !connectionIds.isEmpty(); boolean hasHostIds = eventHostIds != null && !eventHostIds.isEmpty(); - StringBuilder stmt = - new StringBuilder(String.format(SELECT_ROUTES_COLUMNS, config.getTableName())); + StringBuilder stmt = new StringBuilder(SELECT_ROUTES_PREFIX).append(config.getTableName()); if (hasConnectionIds) { // Prepared statements cannot be used here because AdminRequestHandler only supports diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTabletMap.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTabletMap.java index c6467a62ca8..a37ba434d0f 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTabletMap.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTabletMap.java @@ -33,11 +33,12 @@ * *

Thread-safety notes: This class uses ConcurrentMap and ConcurrentSkipListSet underneath. It is * safe to have multiple threads accessing it. However, multiple modifications i.e. multiple calls - * of {@link DefaultTabletMap#addTablet(CqlIdentifier, CqlIdentifier, Tablet) will race with each + * of {@link DefaultTabletMap#addTablet(CqlIdentifier, CqlIdentifier, Tablet)} will race with each * other. This may result in unexpected state of this structure when used in a vacuum. For example - * it may end up containing overlapping tablet ranges.

In actual use by the driver {@link - * MetadataManager} solves this by running modifications sequentially. It schedules them on {@link - * MetadataManager#adminExecutor}}'s thread. + * it may end up containing overlapping tablet ranges. + * + *

In actual use by the driver {@link MetadataManager} solves this by running modifications + * sequentially. It schedules them on the {@link MetadataManager#adminExecutor} thread. */ @Beta public class DefaultTabletMap implements TabletMap { @@ -209,7 +210,7 @@ public static DefaultTablet parseTabletPayloadV1(TupleValue tupleValue, Map 0) { - builder.append(Strings.repeat(" ", indent * INDENT_SIZE)); + builder.append(" ".repeat(indent * INDENT_SIZE)); } isAtLineStart = false; builder.append(s); @@ -65,7 +64,7 @@ public ScriptBuilder newLine() { } public ScriptBuilder forceNewLine(int count) { - builder.append(Strings.repeat("\n", count)); + builder.append("\n".repeat(count)); isAtLineStart = true; return this; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RawColumn.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RawColumn.java index 5dc0679deaf..db541df48cb 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RawColumn.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/parsing/RawColumn.java @@ -27,7 +27,6 @@ import com.datastax.oss.driver.api.core.metadata.schema.ColumnMetadata; import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; import com.datastax.oss.driver.shaded.guava.common.collect.Lists; -import com.datastax.oss.driver.shaded.guava.common.primitives.Ints; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Collection; import java.util.Collections; @@ -128,7 +127,7 @@ public int compareTo(@NonNull RawColumn that) { // First, order by kind. Then order partition key and clustering columns by position. For // other kinds, order by column name. if (!this.kind.equals(that.kind)) { - return Ints.compare(rank(this.kind), rank(that.kind)); + return Integer.compare(rank(this.kind), rank(that.kind)); } else if (kind.equals(KIND_PARTITION_KEY) || kind.equals(KIND_CLUSTERING_COLUMN)) { return Integer.compare(this.position, that.position); } else { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/RuleBasedKeyspaceFilter.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/RuleBasedKeyspaceFilter.java index 38a8c116c45..d78147f41c9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/RuleBasedKeyspaceFilter.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/RuleBasedKeyspaceFilter.java @@ -79,9 +79,9 @@ class RuleBasedKeyspaceFilter implements KeyspaceFilter { exactExcludes.add(name); } } else if ((matcher = REGEX_INCLUDE.matcher(spec)).matches()) { - compile(matcher.group(1)).map(regexIncludes::add); + compile(matcher.group(1)).ifPresent(regexIncludes::add); } else if ((matcher = REGEX_EXCLUDE.matcher(spec)).matches()) { - compile(matcher.group(1)).map(regexExcludes::add); + compile(matcher.group(1)).ifPresent(regexExcludes::add); } else { LOG.warn( "[{}] Error while parsing {}: invalid element '{}', skipping", diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenFactory.java index 59f1bcc865b..88881bffaa7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/RandomTokenFactory.java @@ -43,8 +43,7 @@ public RandomTokenFactory() { prototype = createMessageDigest(); boolean supportsClone; try { - prototype.clone(); - supportsClone = true; + supportsClone = prototype.clone() instanceof MessageDigest; } catch (CloneNotSupportedException e) { supportsClone = false; } @@ -104,7 +103,8 @@ private MessageDigest newMessageDigest() { if (supportsClone) { try { return (MessageDigest) prototype.clone(); - } catch (CloneNotSupportedException ignored) { + } catch (CloneNotSupportedException e) { + return createMessageDigest(); } } return createMessageDigest(); diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/TokenLong64.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/TokenLong64.java index 4d9d20c1470..32de62a31eb 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/TokenLong64.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/token/TokenLong64.java @@ -18,7 +18,6 @@ import com.datastax.oss.driver.api.core.metadata.token.Token; import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; -import com.datastax.oss.driver.shaded.guava.common.primitives.Longs; import edu.umd.cs.findbugs.annotations.NonNull; import net.jcip.annotations.Immutable; @@ -57,6 +56,6 @@ public int compareTo(@NonNull Token other) { Preconditions.checkArgument( other instanceof TokenLong64, "Cannot compare with non-64-bit-integer token"); TokenLong64 that = (TokenLong64) other; - return Longs.compare(this.value, that.value); + return Long.compare(this.value, that.value); } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/ReloadingKeyManagerFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/ReloadingKeyManagerFactory.java index 8a9e11bb2e9..7131652e182 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/ReloadingKeyManagerFactory.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/ssl/ReloadingKeyManagerFactory.java @@ -71,7 +71,7 @@ public class ReloadingKeyManagerFactory extends KeyManagerFactory implements Aut * @param keystorePassword the keystore password * @param reloadInterval the duration between reload attempts. Set to {@link Optional#empty()} to * disable scheduled reloading. - * @return + * @return a reloading key manager factory backed by the given keystore */ static ReloadingKeyManagerFactory create( Path keystorePath, String keystorePassword, Optional reloadInterval) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/extras/OptionalCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/extras/OptionalCodec.java index e62e244bf5e..b47771c9d0e 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/extras/OptionalCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/extras/OptionalCodec.java @@ -52,14 +52,14 @@ public boolean accepts(@NonNull Object value) { return false; } - @Nullable @Override protected Optional innerToOuter(@Nullable T value) { - return Optional.ofNullable(isAbsent(value) ? null : value); + return isAbsent(value) ? Optional.empty() : Optional.of(value); } @Nullable @Override + @SuppressWarnings("NullableOptional") protected T outerToInner(@Nullable Optional value) { return value != null && value.isPresent() ? value.get() : null; } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/extras/enums/EnumOrdinalCodec.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/extras/enums/EnumOrdinalCodec.java index 4d6ca26484e..091652569b7 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/extras/enums/EnumOrdinalCodec.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/extras/enums/EnumOrdinalCodec.java @@ -54,6 +54,7 @@ protected EnumT innerToOuter(@Nullable Integer value) { @Nullable @Override + @SuppressWarnings("EnumOrdinal") protected Integer outerToInner(@Nullable EnumT value) { return value == null ? null : value.ordinal(); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/util/collection/QueryPlan.java b/core/src/main/java/com/datastax/oss/driver/internal/core/util/collection/QueryPlan.java index 371e100a0e2..a5ca3443efc 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/util/collection/QueryPlan.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/util/collection/QueryPlan.java @@ -52,6 +52,7 @@ @ThreadSafe public interface QueryPlan extends Queue { + @SuppressWarnings("ClassInitializationDeadlock") QueryPlan EMPTY = new EmptyQueryPlan(); /** diff --git a/core/src/test/java/com/datastax/dse/driver/internal/core/graph/GraphSupportCheckerTest.java b/core/src/test/java/com/datastax/dse/driver/internal/core/graph/GraphSupportCheckerTest.java index ec31bd4b12d..eda3bbed446 100644 --- a/core/src/test/java/com/datastax/dse/driver/internal/core/graph/GraphSupportCheckerTest.java +++ b/core/src/test/java/com/datastax/dse/driver/internal/core/graph/GraphSupportCheckerTest.java @@ -25,7 +25,7 @@ import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import com.datastax.dse.driver.DseTestDataProviders; @@ -250,7 +250,7 @@ public void should_pickup_graph_protocol_from_statement(GraphProtocol graphProto graphStatement, executionProfile, mock(InternalDriverContext.class)); assertThat(inferredProtocol).isEqualTo(graphProtocol); - verifyZeroInteractions(executionProfile); + verifyNoInteractions(executionProfile); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/CompletionStageAssert.java b/core/src/test/java/com/datastax/oss/driver/internal/core/CompletionStageAssert.java index 6c0d78d62dd..f7996a95235 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/CompletionStageAssert.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/CompletionStageAssert.java @@ -69,6 +69,7 @@ public CompletionStageAssert isFailed() { return isFailed(f -> {}); } + @SuppressWarnings("EmptyCatch") public CompletionStageAssert isCancelled() { boolean cancelled = false; try { @@ -83,6 +84,7 @@ public CompletionStageAssert isCancelled() { return this; } + @SuppressWarnings("EmptyCatch") public CompletionStageAssert isNotCancelled() { boolean cancelled = false; try { diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java index 43768131108..6bc5f2b2025 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/channel/MockChannelFactoryHelper.java @@ -23,7 +23,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.metadata.Node; @@ -122,7 +122,7 @@ public static class Builder { public Builder(ChannelFactory channelFactory) { assertThat(MockUtil.isMock(channelFactory)).as("expected a mock").isTrue(); - verifyZeroInteractions(channelFactory); + verifyNoInteractions(channelFactory); this.channelFactory = channelFactory; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java index c27b55e3f25..fb03a7646da 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIdTestBase.java @@ -22,7 +22,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.CqlIdentifier; @@ -83,7 +83,7 @@ public void should_set_bytes_by_id() { t = t.setBytesUnsafe(FIELD0_ID, Bytes.fromHexString("0x00000001")); // Then - verifyZeroInteractions(codecRegistry); + verifyNoInteractions(codecRegistry); assertThat(t.getBytesUnsafe(FIELD0_ID)).isEqualTo(Bytes.fromHexString("0x00000001")); } @@ -97,7 +97,7 @@ public void should_set_to_null_by_id() { t = t.setToNull(FIELD0_ID); // Then - verifyZeroInteractions(codecRegistry); + verifyNoInteractions(codecRegistry); assertThat(t.getBytesUnsafe(FIELD0_ID)).isNull(); } @@ -144,7 +144,7 @@ public void should_set_with_explicit_codec_by_id() { t = t.set(FIELD0_ID, "1", intToStringCodec); // Then - verifyZeroInteractions(codecRegistry); + verifyNoInteractions(codecRegistry); verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); assertThat(t.getBytesUnsafe(FIELD0_ID)).isEqualTo(Bytes.fromHexString("0x00000001")); } @@ -189,7 +189,7 @@ public void should_get_bytes_by_id() { ByteBuffer bytes = t.getBytesUnsafe(FIELD0_ID); // Then - verifyZeroInteractions(codecRegistry); + verifyNoInteractions(codecRegistry); assertThat(bytes).isEqualTo(Bytes.fromHexString("0x00000001")); } @@ -203,7 +203,7 @@ public void should_test_if_null_by_id() { boolean isNull = t.isNull(FIELD0_ID); // Then - verifyZeroInteractions(codecRegistry); + verifyNoInteractions(codecRegistry); assertThat(isNull).isTrue(); } @@ -253,7 +253,7 @@ public void should_get_with_explicit_codec_by_id() { String s = t.get(FIELD0_ID, intToStringCodec); // Then - verifyZeroInteractions(codecRegistry); + verifyNoInteractions(codecRegistry); verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); assertThat(s).isEqualTo("1"); } @@ -295,7 +295,7 @@ public void should_set_bytes_by_name() { t = t.setBytesUnsafe(FIELD0_NAME, Bytes.fromHexString("0x00000001")); // Then - verifyZeroInteractions(codecRegistry); + verifyNoInteractions(codecRegistry); assertThat(t.getBytesUnsafe(FIELD0_NAME)).isEqualTo(Bytes.fromHexString("0x00000001")); } @@ -309,7 +309,7 @@ public void should_set_to_null_by_name() { t = t.setToNull(FIELD0_NAME); // Then - verifyZeroInteractions(codecRegistry); + verifyNoInteractions(codecRegistry); assertThat(t.getBytesUnsafe(FIELD0_NAME)).isNull(); } @@ -356,7 +356,7 @@ public void should_set_with_explicit_codec_by_name() { t = t.set(FIELD0_NAME, "1", intToStringCodec); // Then - verifyZeroInteractions(codecRegistry); + verifyNoInteractions(codecRegistry); verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); assertThat(t.getBytesUnsafe(FIELD0_NAME)).isEqualTo(Bytes.fromHexString("0x00000001")); } @@ -401,7 +401,7 @@ public void should_get_bytes_by_name() { ByteBuffer bytes = t.getBytesUnsafe(FIELD0_NAME); // Then - verifyZeroInteractions(codecRegistry); + verifyNoInteractions(codecRegistry); assertThat(bytes).isEqualTo(Bytes.fromHexString("0x00000001")); } @@ -415,7 +415,7 @@ public void should_test_if_null_by_name() { boolean isNull = t.isNull(FIELD0_NAME); // Then - verifyZeroInteractions(codecRegistry); + verifyNoInteractions(codecRegistry); assertThat(isNull).isTrue(); } @@ -465,7 +465,7 @@ public void should_get_with_explicit_codec_by_name() { String s = t.get(FIELD0_NAME, intToStringCodec); // Then - verifyZeroInteractions(codecRegistry); + verifyNoInteractions(codecRegistry); verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); assertThat(s).isEqualTo("1"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java index 94da926f2bc..39ddc3fe12c 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/data/AccessibleByIndexTestBase.java @@ -22,7 +22,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.DefaultProtocolVersion; @@ -121,7 +121,7 @@ public void should_set_bytes_by_index() { t = t.setBytesUnsafe(0, Bytes.fromHexString("0x00000001")); // Then - verifyZeroInteractions(codecRegistry); + verifyNoInteractions(codecRegistry); assertThat(t.getBytesUnsafe(0)).isEqualTo(Bytes.fromHexString("0x00000001")); } @@ -135,7 +135,7 @@ public void should_set_to_null_by_index() { t = t.setToNull(0); // Then - verifyZeroInteractions(codecRegistry); + verifyNoInteractions(codecRegistry); assertThat(t.getBytesUnsafe(0)).isNull(); } @@ -182,7 +182,7 @@ public void should_set_with_explicit_codec_by_index() { t = t.set(0, "1", intToStringCodec); // Then - verifyZeroInteractions(codecRegistry); + verifyNoInteractions(codecRegistry); verify(intToStringCodec).encode("1", ProtocolVersion.DEFAULT); assertThat(t.getBytesUnsafe(0)).isEqualTo(Bytes.fromHexString("0x00000001")); } @@ -273,7 +273,7 @@ public void should_get_bytes_by_index() { ByteBuffer bytes = t.getBytesUnsafe(0); // Then - verifyZeroInteractions(codecRegistry); + verifyNoInteractions(codecRegistry); assertThat(bytes).isEqualTo(Bytes.fromHexString("0x00000001")); } @@ -287,7 +287,7 @@ public void should_test_if_null_by_index() { boolean isNull = t.isNull(0); // Then - verifyZeroInteractions(codecRegistry); + verifyNoInteractions(codecRegistry); assertThat(isNull).isTrue(); } @@ -337,7 +337,7 @@ public void should_get_with_explicit_codec_by_index() { String s = t.get(0, intToStringCodec); // Then - verifyZeroInteractions(codecRegistry); + verifyNoInteractions(codecRegistry); verify(intToStringCodec).decode(any(ByteBuffer.class), eq(ProtocolVersion.DEFAULT)); assertThat(s).isEqualTo("1"); } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java b/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java index 6c3dc7f3689..e4b96a4427b 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/session/MockChannelPoolFactoryHelper.java @@ -23,7 +23,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.CqlIdentifier; @@ -124,7 +124,7 @@ public static class Builder { private Builder(ChannelPoolFactory channelPoolFactory) { assertThat(MockUtil.isMock(channelPoolFactory)).as("expected a mock").isTrue(); - verifyZeroInteractions(channelPoolFactory); + verifyNoInteractions(channelPoolFactory); this.channelPoolFactory = channelPoolFactory; } diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java index c51eea20c2e..16fa9d9f1c1 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/TupleCodecTest.java @@ -21,8 +21,8 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.ProtocolVersion; @@ -104,7 +104,7 @@ public void should_encode_tuple() { verify(intCodec).encodePrimitive(1, ProtocolVersion.DEFAULT); // null values are handled directly in the tuple codec, without calling the child codec: - verifyZeroInteractions(doubleCodec); + verifyNoInteractions(doubleCodec); verify(textCodec).encode("a", ProtocolVersion.DEFAULT); } @@ -122,7 +122,7 @@ public void should_decode_tuple() { assertThat(tuple.getString(2)).isEqualTo("a"); verify(intCodec).decodePrimitive(Bytes.fromHexString("0x00000001"), ProtocolVersion.DEFAULT); - verifyZeroInteractions(doubleCodec); + verifyNoInteractions(doubleCodec); verify(textCodec).decode(Bytes.fromHexString("0x61"), ProtocolVersion.DEFAULT); } @@ -141,9 +141,9 @@ public void should_decode_negative_element_length_as_null_field() { assertThat(tuple.isNull(1)).isTrue(); assertThat(tuple.isNull(2)).isTrue(); - verifyZeroInteractions(intCodec); - verifyZeroInteractions(doubleCodec); - verifyZeroInteractions(textCodec); + verifyNoInteractions(intCodec); + verifyNoInteractions(doubleCodec); + verifyNoInteractions(textCodec); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java index af94247f937..cb48ae1e58a 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/UdtCodecTest.java @@ -21,8 +21,8 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import com.datastax.oss.driver.api.core.CqlIdentifier; @@ -113,7 +113,7 @@ public void should_encode_udt() { verify(intCodec).encodePrimitive(1, ProtocolVersion.DEFAULT); // null values are handled directly in the udt codec, without calling the child codec: - verifyZeroInteractions(doubleCodec); + verifyNoInteractions(doubleCodec); verify(textCodec).encode("a", ProtocolVersion.DEFAULT); } @@ -131,7 +131,7 @@ public void should_decode_udt() { assertThat(udt.getString(2)).isEqualTo("a"); verify(intCodec).decodePrimitive(Bytes.fromHexString("0x00000001"), ProtocolVersion.DEFAULT); - verifyZeroInteractions(doubleCodec); + verifyNoInteractions(doubleCodec); verify(textCodec).decode(Bytes.fromHexString("0x61"), ProtocolVersion.DEFAULT); } @@ -165,9 +165,9 @@ public void should_decode_negative_element_length_as_null_field() { assertThat(udt.isNull(1)).isTrue(); assertThat(udt.isNull(2)).isTrue(); - verifyZeroInteractions(intCodec); - verifyZeroInteractions(doubleCodec); - verifyZeroInteractions(textCodec); + verifyNoInteractions(intCodec); + verifyNoInteractions(doubleCodec); + verifyNoInteractions(textCodec); } @Test @@ -178,9 +178,9 @@ public void should_decode_absent_element_as_null_field() { assertThat(udt.isNull(1)).isTrue(); assertThat(udt.isNull(2)).isTrue(); - verifyZeroInteractions(intCodec); - verifyZeroInteractions(doubleCodec); - verifyZeroInteractions(textCodec); + verifyNoInteractions(intCodec); + verifyNoInteractions(doubleCodec); + verifyNoInteractions(textCodec); } @Test diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java index 231f67a93e7..de5fefd88ae 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/type/codec/registry/CachingCodecRegistryTest.java @@ -22,7 +22,8 @@ import static org.assertj.core.api.Assertions.fail; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.data.TupleValue; @@ -80,7 +81,7 @@ public void should_find_primitive_codecs_for_types(TypeCodec codec) { Class javaClass = (Class) javaType.__getToken().getType(); assertThat(registry.codecFor(cqlType, javaClass)).isSameAs(codec); // Primitive mappings never hit the cache - verifyZeroInteractions(mockCache); + verifyNoInteractions(mockCache); } @Test @@ -90,7 +91,7 @@ public void should_find_primitive_codecs_for_types(TypeCodec codec) { public void should_find_primitive_codecs_for_value(Object value, TypeCodec codec) { TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); assertThat(registry.codecFor(value)).isEqualTo(codec); - verifyZeroInteractions(mockCache); + verifyNoInteractions(mockCache); } @Test @@ -101,7 +102,7 @@ public void should_find_primitive_codecs_for_cql_type_and_value( DataType cqlType, Object value, TypeCodec codec) { TestCachingCodecRegistry registry = new TestCachingCodecRegistry(mockCache); assertThat(registry.codecFor(cqlType, value)).isEqualTo(codec); - verifyZeroInteractions(mockCache); + verifyNoInteractions(mockCache); } @Test @@ -123,7 +124,7 @@ public void should_find_user_codec_for_built_in_java_type() { assertThat(registry.codecFor(DataTypes.INT)).isSameAs(TypeCodecs.INT); assertThat(registry.codecFor("123")).isSameAs(TypeCodecs.TEXT); - verifyZeroInteractions(mockCache); + verifyNoMoreInteractions(mockCache); } @Test @@ -144,7 +145,7 @@ public void should_find_user_codec_for_custom_java_type() { // The search by CQL type only still returns the built-in codec assertThat(registry.codecFor(DataTypes.TEXT)).isSameAs(TypeCodecs.TEXT); - verifyZeroInteractions(mockCache); + verifyNoMoreInteractions(mockCache); } @Test @@ -521,7 +522,7 @@ public void should_register_user_codec_at_runtime() { assertThat(registry.codecFor(DataTypes.INT)).isSameAs(TypeCodecs.INT); assertThat(registry.codecFor("123")).isSameAs(TypeCodecs.TEXT); - verifyZeroInteractions(mockCache); + verifyNoMoreInteractions(mockCache); } @Test diff --git a/pom.xml b/pom.xml index 75f1d327a86..a4ee47f62c0 100644 --- a/pom.xml +++ b/pom.xml @@ -722,7 +722,7 @@ -XDcompilePolicy=simple --should-stop=ifError=FLOW - -Xplugin:ErrorProne -Xep:FutureReturnValueIgnored:OFF -Xep:PreferJavaTimeOverload:OFF -Xep:AnnotateFormatMethod:OFF -Xep:WildcardImport:WARN -Xep:MissingSummary:OFF -Xep:StringCaseLocaleUsage:OFF -Xep:InlineMeSuggester:OFF -Xep:AddressSelection:OFF -XepExcludedPaths:.*/target/(?:generated-sources|generated-test-sources)/.* + -Xplugin:ErrorProne -Xep:FutureReturnValueIgnored:OFF -Xep:PreferJavaTimeOverload:OFF -Xep:AnnotateFormatMethod:OFF -Xep:WildcardImport:WARN -Xep:MissingSummary:OFF -Xep:StringCaseLocaleUsage:OFF -Xep:InlineMeSuggester:OFF -Xep:AddressSelection:OFF -XepExcludedPaths:.*/(?:src/test|target/(?:generated-sources|generated-test-sources))/.* From 82653258c4d39e4bd2e9143f99b7947894452197 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sun, 24 May 2026 19:56:08 -0400 Subject: [PATCH 22/32] ci: restore Java 11 and JDK 17 validation Update maintained CI paths for the Java 11 baseline, including JDK 17 compiler flags, javadoc skips, authenticated version lookup, examples compilation, and runtime-specific reports. --- .github/workflows/tests@v1.yml | 19 +++++++++++++++++++ .mvn/jvm.config | 11 +++++++++++ examples/pom.xml | 3 +++ .../mapper/processor/MapperProcessor.java | 3 +-- .../processor/dao/DaoReturnTypeKind.java | 2 +- osgi-tests/pom.xml | 3 +++ pom.xml | 15 +++++++++++++-- 7 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 .mvn/jvm.config diff --git a/.github/workflows/tests@v1.yml b/.github/workflows/tests@v1.yml index 063dfe2c6f7..0e3d9c411be 100644 --- a/.github/workflows/tests@v1.yml +++ b/.github/workflows/tests@v1.yml @@ -41,6 +41,8 @@ jobs: steps: - name: Checkout source uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + with: + persist-credentials: false - name: Set up JDK ${{ matrix.java-version }} uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 @@ -115,6 +117,8 @@ jobs: steps: - name: Checkout source uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + with: + persist-credentials: false - name: Set up JDK ${{ matrix.java-version }} uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 @@ -182,6 +186,11 @@ jobs: cassandra-version: [3-LATEST, 4-LATEST] java-version: [11, 17] test-group: [parallelizable, serial, isolated] + exclude: + - cassandra-version: 3-LATEST + java-version: 17 + - cassandra-version: 4-LATEST + java-version: 11 fail-fast: false steps: @@ -212,6 +221,7 @@ jobs: id: cassandra-version env: CASSANDRA_VERSION: ${{ matrix.cassandra-version }} + GET_VERSION_VERSION: 0.4.5 GH_TOKEN: ${{ github.token }} run: make resolve-cassandra-version @@ -226,6 +236,7 @@ jobs: if: steps.ccm-cache.outputs.cache-hit != 'true' env: CASSANDRA_VERSION_RESOLVED: ${{ steps.cassandra-version.outputs.value }} + GET_VERSION_VERSION: 0.4.5 run: make download-cassandra - name: Save CCM image into the cache @@ -249,6 +260,7 @@ jobs: env: CASSANDRA_VERSION: ${{ matrix.cassandra-version }} CASSANDRA_VERSION_RESOLVED: ${{ steps.cassandra-version.outputs.value }} + GET_VERSION_VERSION: 0.4.5 GH_TOKEN: ${{ github.token }} MAVEN_EXTRA_ARGS: ${{ steps.test-skip-args.outputs.value }} run: make test-integration-cassandra @@ -297,6 +309,13 @@ jobs: scylla-version: [LTS-LATEST, LTS-PRIOR, LATEST] java-version: [11, 17] test-group: [parallelizable, serial, isolated] + exclude: + - scylla-version: LTS-PRIOR + java-version: 17 + - scylla-version: LTS-LATEST + java-version: 17 + - scylla-version: LATEST + java-version: 11 fail-fast: false steps: diff --git a/.mvn/jvm.config b/.mvn/jvm.config new file mode 100644 index 00000000000..c3ad3948530 --- /dev/null +++ b/.mvn/jvm.config @@ -0,0 +1,11 @@ +--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED diff --git a/examples/pom.xml b/examples/pom.xml index ac1835142fa..8f257b703b0 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -33,6 +33,9 @@ java-driver-examples Java driver for Scylla and Apache Cassandra(R) - examples. A collection of examples to demonstrate Java Driver for Scylla and Apache Cassandra(R). + + true + diff --git a/mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/MapperProcessor.java b/mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/MapperProcessor.java index 619a07ea886..1b7c156f18c 100644 --- a/mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/MapperProcessor.java +++ b/mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/MapperProcessor.java @@ -20,7 +20,6 @@ import com.datastax.oss.driver.api.mapper.annotations.Dao; import com.datastax.oss.driver.api.mapper.annotations.Entity; import com.datastax.oss.driver.api.mapper.annotations.Mapper; -import com.datastax.oss.driver.shaded.guava.common.base.Strings; import com.datastax.oss.driver.shaded.guava.common.base.Throwables; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet; import java.lang.annotation.Annotation; @@ -167,7 +166,7 @@ private String computeIndent(Map options) { if (amountSpec != null) { try { int amount = Integer.parseInt(amountSpec); - return Strings.repeat(tabs ? "\t" : " ", amount); + return (tabs ? "\t" : " ").repeat(amount); } catch (NumberFormatException e) { messager.warn( "Could not parse %s: expected a number, got '%s'. Defaulting to %s.", diff --git a/mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/dao/DaoReturnTypeKind.java b/mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/dao/DaoReturnTypeKind.java index 72089fdccb3..233bf946406 100644 --- a/mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/dao/DaoReturnTypeKind.java +++ b/mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/dao/DaoReturnTypeKind.java @@ -40,7 +40,7 @@ public interface DaoReturnTypeKind { * @param helperFieldName the name of the helper for entity conversions (might not get used for * certain kinds, in that case it's ok to pass null). * @param methodElement the return type of the method (in case the result must be cast). - * @param typeParameters + * @param typeParameters the type parameters declared on the DAO method. */ void addExecuteStatement( CodeBlock.Builder methodBuilder, diff --git a/osgi-tests/pom.xml b/osgi-tests/pom.xml index dc9eae882d7..e776519ec6d 100644 --- a/osgi-tests/pom.xml +++ b/osgi-tests/pom.xml @@ -33,6 +33,9 @@ java-driver-osgi-tests jar Java driver for Scylla and Apache Cassandra(R) - OSGi tests + + true + com.scylladb diff --git a/pom.xml b/pom.xml index a4ee47f62c0..79f7be9e35d 100644 --- a/pom.xml +++ b/pom.xml @@ -101,6 +101,7 @@ false false + false @@ -717,8 +718,7 @@ maven-compiler-plugin - 11 - 11 + 11 -XDcompilePolicy=simple --should-stop=ifError=FLOW @@ -879,6 +879,7 @@ height="0" width="0" style="display:none;visibility:hidden"> process-classes + ${api.plumber.skip} com.datastax.oss.doclet.ApiPlumber com.datastax.oss @@ -1082,6 +1083,16 @@ height="0" width="0" style="display:none;visibility:hidden"> [11,) + + + skip-api-plumber-on-jdk-13 + + [13,) + + + true + + test-jdk-14 From 397df6faa0e128a134f7b6cdf1ba149e57134314 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Sun, 24 May 2026 19:56:08 -0400 Subject: [PATCH 23/32] test: fix Java 11 test compatibility and CI failures Enable required annotation processors, silence Java 11 compiler warnings, and improve CCM cleanup logging so integration failures are easier to diagnose. Update tests for newer Mockito behavior by avoiding shared MockitoJUnitRunner usage in parallel integration tests and by verifying cache interactions explicitly where the mock behavior changed. Fix CI failures exposed by the updated validation matrix: provide Micrometer histogram bounds in the no-SLA test, register config-change listeners before manual reloads, avoid assuming a transient one-connection node state before reconnection wins the race, and validate prepared-statement invalidation through observable cache behavior instead of a test-only removal-listener latch. --- .../driver/api/core/paging/OffsetPager.java | 6 +-- .../core/metadata/MetadataManagerTest.java | 6 +-- examples/pom.xml | 5 +++ .../examples/paging/ForwardPagingRestUi.java | 2 +- .../examples/paging/RandomPagingRestUi.java | 2 +- integration-tests/pom.xml | 36 ++++++++++++++++++ .../DriverExecutionProfileReloadIT.java | 10 ++++- .../oss/driver/core/metadata/NodeStateIT.java | 10 ++--- .../oss/driver/core/session/ListenersIT.java | 18 +++++++-- .../driver/core/tracker/RequestLoggerIT.java | 17 ++++++--- .../mapper/processor/MapperProcessor.java | 2 +- .../driver/api/mapper/annotations/Delete.java | 6 +-- .../api/mapper/annotations/GetEntity.java | 4 +- .../api/mapper/annotations/Increment.java | 6 +-- .../driver/api/mapper/annotations/Insert.java | 6 +-- .../driver/api/mapper/annotations/Query.java | 6 +-- .../driver/api/mapper/annotations/Select.java | 6 +-- .../api/mapper/annotations/SetEntity.java | 4 +- .../driver/api/mapper/annotations/Update.java | 6 +-- .../micrometer/MicrometerMetricUpdater.java | 6 +-- osgi-tests/pom.xml | 17 +++++++++ test-infra/pom.xml | 5 +++ .../gremlin/PatchedGremlinDslProcessor.java | 38 +++++++++++++++++++ 23 files changed, 174 insertions(+), 50 deletions(-) create mode 100644 test-infra/src/main/java/com/datastax/oss/driver/internal/gremlin/PatchedGremlinDslProcessor.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/paging/OffsetPager.java b/core/src/main/java/com/datastax/oss/driver/api/core/paging/OffsetPager.java index 3cb838f3171..03f48652907 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/paging/OffsetPager.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/paging/OffsetPager.java @@ -46,7 +46,7 @@ * a reasonable trade-off if the cardinality stays low. This class provides a way to emulate this * behavior on the client side. * - *

Performance considerations

+ *

Performance considerations

* * For each page that you want to retrieve: * @@ -71,7 +71,7 @@ * OffsetPager.Page<Row> page5 = pager.getPage(rs, 5); * * - *

Establishing application-level guardrails

+ *

Establishing application-level guardrails

* * Linear performance should be fine for the values typically encountered in real-world * applications: for example, if the page size is 25 and users never go past page 10, the worst case @@ -81,7 +81,7 @@ * maximum, so that an attacker can't inject a large value that could potentially fetch millions of * rows. * - *

Relation with protocol-level paging

+ *

Relation with protocol-level paging

* * Protocol-level paging refers to the ability to split large response into multiple network chunks: * see {@link Statement#setPageSize(int)} and {@code basic.request.page-size} in the configuration. diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java index 9c5cbdba8ee..6fb1384d889 100644 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/metadata/MetadataManagerTest.java @@ -389,9 +389,9 @@ public void refreshSchema_should_drain_queued_refresh_after_newInstance_failure( CompletionStage result2 = metadataManager.refreshSchema("bar", true, true); - // Now complete the agreement check - first refresh will fail at newInstance(), - // and onSchemaRefreshComplete should drain the queued second refresh - agreementFuture.complete(true); + // Now complete the agreement check on the admin event loop - first refresh will fail at + // newInstance(), and onSchemaRefreshComplete should drain the queued second refresh. + adminEventLoopGroup.next().execute(() -> agreementFuture.complete(true)); // Then both requests should complete (not hang forever) waitForPendingAdminTasks( diff --git a/examples/pom.xml b/examples/pom.xml index 8f257b703b0..5ec1487798c 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -154,6 +154,11 @@ 11 11 + + com.google.errorprone + error_prone_core + 2.31.0 + com.scylladb java-driver-mapper-processor diff --git a/examples/src/main/java/com/datastax/oss/driver/examples/paging/ForwardPagingRestUi.java b/examples/src/main/java/com/datastax/oss/driver/examples/paging/ForwardPagingRestUi.java index a512457d618..fe4d6cfa820 100644 --- a/examples/src/main/java/com/datastax/oss/driver/examples/paging/ForwardPagingRestUi.java +++ b/examples/src/main/java/com/datastax/oss/driver/examples/paging/ForwardPagingRestUi.java @@ -119,7 +119,7 @@ private static void populateSchema(CqlSession session) { int videoid = i * 100 + j; session.execute( prepare.bind( - i, "user " + i, Instant.ofEpochMilli(j * 100000), videoid, "video " + videoid)); + i, "user " + i, Instant.ofEpochMilli(j * 100000L), videoid, "video " + videoid)); } } } diff --git a/examples/src/main/java/com/datastax/oss/driver/examples/paging/RandomPagingRestUi.java b/examples/src/main/java/com/datastax/oss/driver/examples/paging/RandomPagingRestUi.java index 7eb3249b0ac..f88c93241ab 100644 --- a/examples/src/main/java/com/datastax/oss/driver/examples/paging/RandomPagingRestUi.java +++ b/examples/src/main/java/com/datastax/oss/driver/examples/paging/RandomPagingRestUi.java @@ -129,7 +129,7 @@ private static void populateSchema(CqlSession session) { int videoid = i * 100 + j; session.execute( prepare.bind( - i, "user " + i, Instant.ofEpochMilli(j * 100000), videoid, "video " + videoid)); + i, "user " + i, Instant.ofEpochMilli(j * 100000L), videoid, "video " + videoid)); } } } diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 05a3e0a315d..6f790752635 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -231,6 +231,42 @@ + + maven-compiler-plugin + + + -processor + com.datastax.oss.driver.internal.mapper.processor.MapperProcessor,com.datastax.oss.driver.internal.gremlin.PatchedGremlinDslProcessor + + + + com.google.errorprone + error_prone_core + 2.31.0 + + + com.scylladb + java-driver-test-infra + ${project.version} + + + com.scylladb + java-driver-mapper-processor + ${project.version} + + + com.scylladb + java-driver-mapper-runtime + ${project.version} + + + org.apache.tinkerpop + gremlin-core + ${tinkerpop.version} + + + + org.apache.maven.plugins maven-failsafe-plugin diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverExecutionProfileReloadIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverExecutionProfileReloadIT.java index 48ccc850060..b8c3b73ddaa 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverExecutionProfileReloadIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverExecutionProfileReloadIT.java @@ -125,8 +125,8 @@ public void should_reload_configuration_when_event_fired() { // Bump up request timeout to 10 seconds and trigger a manual reload. configSource.set("basic.request.timeout = 10s"); - session.getContext().getConfigLoader().reload(); - waitForConfigChange(session, 500, TimeUnit.MILLISECONDS); + waitForConfigChange( + session, () -> session.getContext().getConfigLoader().reload(), 3, TimeUnit.SECONDS); // Execute again, should not timeout. session.execute(query); @@ -217,11 +217,17 @@ public void should_reload_profile_config_when_reloading_config() { } private void waitForConfigChange(CqlSession session, long timeout, TimeUnit unit) { + waitForConfigChange(session, () -> {}, timeout, unit); + } + + private void waitForConfigChange( + CqlSession session, Runnable trigger, long timeout, TimeUnit unit) { CountDownLatch latch = new CountDownLatch(1); ((InternalDriverContext) session.getContext()) .getEventBus() .register(ConfigChangeEvent.class, (e) -> latch.countDown()); try { + trigger.run(); boolean success = latch.await(timeout, unit); assertThat(success).isTrue(); } catch (InterruptedException e) { diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/core/metadata/NodeStateIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/core/metadata/NodeStateIT.java index 41e7b73f2ed..6307b3cec6e 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/core/metadata/NodeStateIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/core/metadata/NodeStateIT.java @@ -81,12 +81,9 @@ import org.junit.experimental.categories.Category; import org.junit.rules.RuleChain; import org.junit.rules.TestRule; -import org.junit.runner.RunWith; import org.mockito.InOrder; -import org.mockito.junit.MockitoJUnitRunner; @Category(ParallelizableTests.class) -@RunWith(MockitoJUnitRunner.class) public class NodeStateIT { private SimulacronRule simulacron = new SimulacronRule(ClusterSpec.builder().withNodes(2)); @@ -213,10 +210,13 @@ public void should_keep_regular_node_up_when_still_one_connection() { simulacron.cluster().closeConnection(report.getConnections().get(0), CloseType.DISCONNECT); await() - .alias("Reconnection started") + .alias("Regular node stayed up after one connection closed") .pollInterval(500, TimeUnit.MILLISECONDS) .untilAsserted( - () -> assertThat(metadataRegularNode).isUp().hasOpenConnections(1).isReconnecting()); + () -> { + assertThat(metadataRegularNode).isUp(); + assertThat(metadataRegularNode.getOpenConnections()).isBetween(1, 2); + }); inOrder.verify(nodeStateListener, never()).onDown(metadataRegularNode); } diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/core/session/ListenersIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/core/session/ListenersIT.java index e23b7fba4e6..c00e4cf863a 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/core/session/ListenersIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/core/session/ListenersIT.java @@ -42,19 +42,19 @@ import java.util.Collections; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import org.junit.After; +import org.junit.Before; import org.junit.ClassRule; import org.junit.Ignore; import org.junit.Test; import org.junit.experimental.categories.Category; -import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InOrder; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.MockitoAnnotations; @Category(ParallelizableTests.class) -@RunWith(MockitoJUnitRunner.class) public class ListenersIT { @ClassRule @@ -71,6 +71,18 @@ public class ListenersIT { @Captor private ArgumentCaptor nodeCaptor1; @Captor private ArgumentCaptor nodeCaptor2; + private AutoCloseable mocks; + + @Before + public void setup() { + mocks = MockitoAnnotations.openMocks(this); + } + + @After + public void teardown() throws Exception { + mocks.close(); + } + @Test @Ignore( "@IntegrationTestDisabledFlaky") // sleep after build() fixes the flakiness, session readiness diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/core/tracker/RequestLoggerIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/core/tracker/RequestLoggerIT.java index 20ab395c5a6..45ef528ea36 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/core/tracker/RequestLoggerIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/core/tracker/RequestLoggerIT.java @@ -59,16 +59,14 @@ import org.junit.experimental.categories.Category; import org.junit.rules.RuleChain; import org.junit.rules.TestRule; -import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.mockito.internal.verification.VerificationModeFactory; -import org.mockito.junit.MockitoJUnitRunner; import org.mockito.verification.Timeout; import org.slf4j.LoggerFactory; -@RunWith(MockitoJUnitRunner.class) @Category(ParallelizableTests.class) public class RequestLoggerIT { private static final Pattern LOG_PREFIX_PER_REQUEST = Pattern.compile("\\[s\\d*\\|\\d*]"); @@ -192,9 +190,11 @@ public class RequestLoggerIT { @Mock private Appender appender; private Logger logger; private Level oldLevel; + private AutoCloseable mocks; @Before public void setup() { + mocks = MockitoAnnotations.openMocks(this); logger = (Logger) LoggerFactory.getLogger(RequestLogger.class); oldLevel = logger.getLevel(); logger.setLevel(Level.INFO); @@ -202,9 +202,14 @@ public void setup() { } @After - public void teardown() { - logger.detachAppender(appender); - logger.setLevel(oldLevel); + public void teardown() throws Exception { + if (logger != null) { + logger.detachAppender(appender); + logger.setLevel(oldLevel); + } + if (mocks != null) { + mocks.close(); + } } @Test diff --git a/mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/MapperProcessor.java b/mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/MapperProcessor.java index 1b7c156f18c..ffd1527893e 100644 --- a/mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/MapperProcessor.java +++ b/mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/MapperProcessor.java @@ -92,7 +92,7 @@ public boolean process( roundEnvironment, Dao.class, ElementKind.INTERFACE, generatorFactory::newDaoImplementation); processAnnotatedTypes( roundEnvironment, Mapper.class, ElementKind.INTERFACE, generatorFactory::newMapper); - return true; + return false; } protected ProcessorContext buildContext( diff --git a/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Delete.java b/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Delete.java index a9d2c03912f..e1f87e9d414 100644 --- a/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Delete.java +++ b/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Delete.java @@ -47,7 +47,7 @@ * } * * - *

Parameters

+ *

Parameters

* * The method can operate either on an entity instance, or on a primary key (partition key + * clustering columns). @@ -77,7 +77,7 @@ * parameter. It will be applied to the statement before execution. This allows you to customize * certain aspects of the request (page size, timeout, etc) at runtime. * - *

Return type

+ *

Return type

* * The method can return: * @@ -127,7 +127,7 @@ * practical purpose for that since those queries always return {@code wasApplied = true} and an * empty result set. * - *

Target keyspace and table

+ *

Target keyspace and table

* * If a keyspace was specified when creating the DAO (see {@link DaoFactory}), then the generated * query targets that keyspace. Otherwise, it doesn't specify a keyspace, and will only work if the diff --git a/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/GetEntity.java b/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/GetEntity.java index d86174bdc49..c80d928ee81 100644 --- a/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/GetEntity.java +++ b/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/GetEntity.java @@ -55,7 +55,7 @@ *

It does not perform a query. Instead, those methods are intended for cases where you already * have a query result, and just need the conversion logic. * - *

Parameters

+ *

Parameters

* * The method must have a single parameter. The following types are allowed: * @@ -69,7 +69,7 @@ * The data must match the target entity: the generated code will try to extract every mapped * property, and fail if one is missing. * - *

Return type

+ *

Return type

* * The method can return: * diff --git a/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Increment.java b/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Increment.java index bb86fa5b8ab..b6e0e6d7086 100644 --- a/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Increment.java +++ b/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Increment.java @@ -58,7 +58,7 @@ * } * * - *

Parameters

+ *

Parameters

* * The entity class must be specified with {@link #entityClass()}. * @@ -92,12 +92,12 @@ * parameter. It will be applied to the statement before execution. This allows you to customize * certain aspects of the request (page size, timeout, etc) at runtime. * - *

Return type

+ *

Return type

* *

The method can return {@code void}, a void {@link CompletionStage} or {@link * CompletableFuture}, or a {@link ReactiveResultSet}. * - *

Target keyspace and table

+ *

Target keyspace and table

* *

If a keyspace was specified when creating the DAO (see {@link DaoFactory}), then the generated * query targets that keyspace. Otherwise, it doesn't specify a keyspace, and will only work if the diff --git a/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Insert.java b/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Insert.java index a00c0e88c21..678c543a14c 100644 --- a/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Insert.java +++ b/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Insert.java @@ -48,7 +48,7 @@ * } * * - *

Parameters

+ *

Parameters

* * The first parameter must be the entity to insert. * @@ -66,7 +66,7 @@ * parameter. It will be applied to the statement before execution. This allows you to customize * certain aspects of the request (page size, timeout, etc) at runtime. * - *

Return type

+ *

Return type

* * The method can return: * @@ -122,7 +122,7 @@ *
  • a {@linkplain MapperResultProducer custom type}. * * - *

    Target keyspace and table

    + *

    Target keyspace and table

    * * If a keyspace was specified when creating the DAO (see {@link DaoFactory}), then the generated * query targets that keyspace. Otherwise, it doesn't specify a keyspace, and will only work if the diff --git a/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Query.java b/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Query.java index c362453bb3a..65543ab0a7d 100644 --- a/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Query.java +++ b/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Query.java @@ -55,7 +55,7 @@ * * This is the equivalent of what was called "accessor methods" in the driver 3 mapper. * - *

    Parameters

    + *

    Parameters

    * * The query string provided in {@link #value()} will typically contain CQL placeholders. The * method's parameters must match those placeholders: same name and a compatible Java type. @@ -70,7 +70,7 @@ * parameter. It will be applied to the statement before execution. This allows you to customize * certain aspects of the request (page size, timeout, etc) at runtime. * - *

    Return type

    + *

    Return type

    * * The method can return: * @@ -100,7 +100,7 @@ *
  • a {@linkplain MapperResultProducer custom type}. * * - *

    Target keyspace and table

    + *

    Target keyspace and table

    * * To avoid hard-coding the keyspace and table name, the query string supports 3 additional * placeholders: {@code ${keyspaceId}}, {@code ${tableId}} and {@code ${qualifiedTableId}}. They get diff --git a/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Select.java b/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Select.java index 53d9a58b6fc..58907049d73 100644 --- a/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Select.java +++ b/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Select.java @@ -48,7 +48,7 @@ * } * * - *

    Parameters

    + *

    Parameters

    * * If {@link #customWhereClause()} is empty, the mapper defaults to a selection by primary key * (partition key + clustering columns). The method's parameters must match the types of the primary @@ -87,7 +87,7 @@ * parameter. It will be applied to the statement before execution. This allows you to customize * certain aspects of the request (page size, timeout, etc) at runtime. * - *

    Return type

    + *

    Return type

    * *

    In all cases, the method can return: * @@ -132,7 +132,7 @@ *

  • a {@linkplain MapperResultProducer custom type}. * * - *

    Target keyspace and table

    + *

    Target keyspace and table

    * * If a keyspace was specified when creating the DAO (see {@link DaoFactory}), then the generated * query targets that keyspace. Otherwise, it doesn't specify a keyspace, and will only work if the diff --git a/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/SetEntity.java b/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/SetEntity.java index cc1cb9b7e88..82d988e4151 100644 --- a/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/SetEntity.java +++ b/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/SetEntity.java @@ -51,7 +51,7 @@ * It does not perform a query. Instead, those methods are intended for cases where you will execute * the query yourself, and just need the conversion logic. * - *

    Parameters

    + *

    Parameters

    * * The method must have two parameters: one is the entity instance, the other must be a subtype of * {@link SettableByName} (the most likely candidates are {@link BoundStatement}, {@link @@ -60,7 +60,7 @@ * *

    The order of the parameters does not matter. * - *

    Return type

    + *

    Return type

    * * The method can either be void, or return the exact same type as its settable parameter. * diff --git a/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Update.java b/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Update.java index 315ac1ecfe7..e8b2e77de04 100644 --- a/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Update.java +++ b/mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/Update.java @@ -49,7 +49,7 @@ * } * * - *

    Parameters

    + *

    Parameters

    * *

    The first parameter must be an entity instance. All of its non-PK properties will be * interpreted as values to update. @@ -88,7 +88,7 @@ * parameter. It will be applied to the statement before execution. This allows you to customize * certain aspects of the request (page size, timeout, etc) at runtime. * - *

    Return type

    + *

    Return type

    * *

    The method can return: * @@ -135,7 +135,7 @@ *

  • a {@linkplain MapperResultProducer custom type}. * * - *

    Target keyspace and table

    + *

    Target keyspace and table

    * *

    If a keyspace was specified when creating the DAO (see {@link DaoFactory}), then the generated * query targets that keyspace. Otherwise, it doesn't specify a keyspace, and will only work if the diff --git a/metrics/micrometer/src/main/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerMetricUpdater.java b/metrics/micrometer/src/main/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerMetricUpdater.java index b9507c8b7cf..808a963f096 100644 --- a/metrics/micrometer/src/main/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerMetricUpdater.java +++ b/metrics/micrometer/src/main/java/com/datastax/oss/driver/internal/metrics/micrometer/MicrometerMetricUpdater.java @@ -55,14 +55,14 @@ protected MicrometerMetricUpdater( @Override public void incrementCounter(MetricT metric, @Nullable String profileName, long amount) { if (isEnabled(metric, profileName)) { - getOrCreateCounterFor(metric).increment(amount); + getOrCreateCounterFor(metric).increment((double) amount); } } @Override public void updateHistogram(MetricT metric, @Nullable String profileName, long value) { if (isEnabled(metric, profileName)) { - getOrCreateDistributionSummaryFor(metric).record(value); + getOrCreateDistributionSummaryFor(metric).record((double) value); } } @@ -70,7 +70,7 @@ public void updateHistogram(MetricT metric, @Nullable String profileName, long v public void markMeter(MetricT metric, @Nullable String profileName, long amount) { if (isEnabled(metric, profileName)) { // There is no meter type in Micrometer, so use a counter - getOrCreateCounterFor(metric).increment(amount); + getOrCreateCounterFor(metric).increment((double) amount); } } diff --git a/osgi-tests/pom.xml b/osgi-tests/pom.xml index e776519ec6d..0067d5a009e 100644 --- a/osgi-tests/pom.xml +++ b/osgi-tests/pom.xml @@ -165,6 +165,23 @@ + + maven-compiler-plugin + + + + com.scylladb + java-driver-mapper-processor + ${project.version} + + + com.scylladb + java-driver-mapper-runtime + ${project.version} + + + + org.apache.servicemix.tooling depends-maven-plugin diff --git a/test-infra/pom.xml b/test-infra/pom.xml index b6821c0ccf0..ceb4f14ac3b 100644 --- a/test-infra/pom.xml +++ b/test-infra/pom.xml @@ -48,6 +48,11 @@ com.scylladb java-driver-core + + org.apache.tinkerpop + gremlin-core + provided + com.github.spotbugs spotbugs-annotations diff --git a/test-infra/src/main/java/com/datastax/oss/driver/internal/gremlin/PatchedGremlinDslProcessor.java b/test-infra/src/main/java/com/datastax/oss/driver/internal/gremlin/PatchedGremlinDslProcessor.java new file mode 100644 index 00000000000..e8639b2d396 --- /dev/null +++ b/test-infra/src/main/java/com/datastax/oss/driver/internal/gremlin/PatchedGremlinDslProcessor.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.gremlin; + +import java.util.Collections; +import java.util.Set; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.GremlinDslProcessor; + +@SupportedAnnotationTypes("org.apache.tinkerpop.gremlin.process.traversal.dsl.GremlinDsl") +public class PatchedGremlinDslProcessor extends GremlinDslProcessor { + + @Override + public Set getSupportedAnnotationTypes() { + return Collections.singleton("org.apache.tinkerpop.gremlin.process.traversal.dsl.GremlinDsl"); + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } +} From 5a61e12ecb24511e3a9ec532ff0e30ed1f6e7434 Mon Sep 17 00:00:00 2001 From: Dmitry Kropachev Date: Tue, 26 May 2026 00:04:50 -0400 Subject: [PATCH 24/32] refactor: hide mutable primitive codec array Keep PRIMITIVE_CODECS public for source and binary compatibility, deprecate direct array access, and add getPrimitiveCodecs() as the immutable-list accessor. Refs #908 --- .../codec/registry/CodecRegistryConstants.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CodecRegistryConstants.java b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CodecRegistryConstants.java index bbf77bdf5dc..01f7156a4c9 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CodecRegistryConstants.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/type/codec/registry/CodecRegistryConstants.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.codec.TypeCodecs; +import java.util.List; import java.util.function.BiConsumer; import java.util.function.BiFunction; @@ -31,7 +32,12 @@ public class CodecRegistryConstants { *

    This is exposed in case you want to call {@link * DefaultCodecRegistry#DefaultCodecRegistry(String, int, BiFunction, int, BiConsumer, * TypeCodec[])} but only customize the caching options. + * + * @deprecated use {@link #getPrimitiveCodecs()} instead. This array remains public for binary + * compatibility and will be made non-public in a future release. */ + @Deprecated + @SuppressWarnings("MutablePublicArray") public static final TypeCodec[] PRIMITIVE_CODECS = new TypeCodec[] { // Must be declared before AsciiCodec so it gets chosen when CQL type not available @@ -57,4 +63,12 @@ public class CodecRegistryConstants { TypeCodecs.COUNTER, TypeCodecs.ASCII }; + + /** + * Returns the driver's default primitive codecs (map all primitive CQL types to their "natural" + * Java equivalent). + */ + public static List> getPrimitiveCodecs() { + return List.of(PRIMITIVE_CODECS); + } } From e280db0ef5a7e2789934bfe7c9428523be89b4ae Mon Sep 17 00:00:00 2001 From: Mikita Hradovich Date: Fri, 29 May 2026 00:10:26 +0200 Subject: [PATCH 25/32] ci: update github-automation ref to fix unpinned actions/checkout The reusable workflow main_pr_events_jira_sync.yml in scylladb/github-automation used actions/checkout@v4 (a tag ref), which violates the scylladb org policy requiring all actions to be pinned to full commit SHAs. This caused every PR targeting scylla-4.x to fail with: Error: The action actions/checkout@v4 is not allowed in scylladb/java-driver because all actions must be pinned to a full-length commit SHA. Update the pinned SHA to ef3a6238c30003cbab9a339c32e5112f6889a197 which pins actions/checkout to 34e114876b0b11c390a56381ad16ebd13914f8d5 (v4.3.1). Fixes: scylladb/java-driver#911 Upstream fix: scylladb/github-automation#192 --- .github/workflows/call_jira_sync.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/call_jira_sync.yml b/.github/workflows/call_jira_sync.yml index 24753858e0a..1fa21660d5f 100644 --- a/.github/workflows/call_jira_sync.yml +++ b/.github/workflows/call_jira_sync.yml @@ -11,7 +11,7 @@ permissions: jobs: jira-sync: - uses: scylladb/github-automation/.github/workflows/main_pr_events_jira_sync.yml@7b9848eb304fd3af1e757fe3c3c1ed497515f0fc # main + uses: scylladb/github-automation/.github/workflows/main_pr_events_jira_sync.yml@ef3a6238c30003cbab9a339c32e5112f6889a197 # main with: caller_action: ${{ github.event.action }} secrets: From c830c20734164afcbf25ccc5fec86cfab9c80300 Mon Sep 17 00:00:00 2001 From: Mikita Hradovich Date: Fri, 29 May 2026 12:20:42 +0200 Subject: [PATCH 26/32] Revert "ci: update github-automation ref to fix unpinned actions/checkout" (#913) This reverts commit e280db0ef5a7e2789934bfe7c9428523be89b4ae. --- .github/workflows/call_jira_sync.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/call_jira_sync.yml b/.github/workflows/call_jira_sync.yml index 1fa21660d5f..24753858e0a 100644 --- a/.github/workflows/call_jira_sync.yml +++ b/.github/workflows/call_jira_sync.yml @@ -11,7 +11,7 @@ permissions: jobs: jira-sync: - uses: scylladb/github-automation/.github/workflows/main_pr_events_jira_sync.yml@ef3a6238c30003cbab9a339c32e5112f6889a197 # main + uses: scylladb/github-automation/.github/workflows/main_pr_events_jira_sync.yml@7b9848eb304fd3af1e757fe3c3c1ed497515f0fc # main with: caller_action: ${{ github.event.action }} secrets: From 1d65bceead70ce6551c2812e5d83e166f815b1a7 Mon Sep 17 00:00:00 2001 From: Sylwia Szunejko <52855732+sylwiaszunejko@users.noreply.github.com> Date: Mon, 8 Jun 2026 09:49:19 +0200 Subject: [PATCH 27/32] ci: update scylladb/github-automation to latest main (47138e9) (#914) --- .github/workflows/call_jira_sync.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/call_jira_sync.yml b/.github/workflows/call_jira_sync.yml index 24753858e0a..76c4de59cf2 100644 --- a/.github/workflows/call_jira_sync.yml +++ b/.github/workflows/call_jira_sync.yml @@ -11,7 +11,7 @@ permissions: jobs: jira-sync: - uses: scylladb/github-automation/.github/workflows/main_pr_events_jira_sync.yml@7b9848eb304fd3af1e757fe3c3c1ed497515f0fc # main + uses: scylladb/github-automation/.github/workflows/main_pr_events_jira_sync.yml@47138e9130250ee1a35166cff7dd0e94c8897196 # main with: caller_action: ${{ github.event.action }} secrets: From 2612f1ef42f1270e4164f57b4621f15d5ab63cfb Mon Sep 17 00:00:00 2001 From: Mikita Hradovich Date: Mon, 15 Jun 2026 14:56:15 +0200 Subject: [PATCH 28/32] fix: bump netty to 4.1.135.Final to remediate CVE-2026-44249 and CVE-2026-45416 netty-handler prior to 4.1.135.Final is affected by two HIGH severity vulnerabilities: - CVE-2026-44249 (CVSS 8.1): IpSubnetFilterRule.compareTo() performs an incorrect masking operation, allowing attackers to bypass IPv6 subnet ACL rules with valid public IP addresses. - CVE-2026-45416 (CVSS 7.5): SslClientHelloHandler.decode() eagerly allocates up to 16 MiB of unpooled memory when maxClientHelloLength=0 (the SniHandler default). A crafted TLS ClientHello can trigger memory exhaustion (DoS). One-line change in the root pom.xml. All consumers of java-driver-core will inherit the fix transitively once a new driver release is published. Tracked in: scylladb/kafka-connect-scylladb#184 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 79f7be9e35d..51ef91eeb6a 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ 2.2.2 4.2.38 - 4.1.133.Final + 4.1.135.Final 1.2.1

  • Examples
    Create statementCase-sensitive?CQL idInternal id
    CREATE TABLE t(foo int PRIMARY KEY)Nofoofoo
    CREATE TABLE t(Foo int PRIMARY KEY)Nofoofoo