Repository navigation
Conversation
Add an asyncio dialect on top of databend-driver's AsyncDatabendClient, so
create_async_engine("databend+async://...") works, and a plain databend://
URL passed to create_async_engine selects it too.
- async_connector: a PEP 249-style async connection and cursor sharing the
blocking cursor's parameter rendering (pulled out into OperationRenderer).
All cursors of a connection share one Databend session, so temp tables
and SET persist across statements. Batch fetches drain the driver's row
iterator in a worker thread, since awaiting it once per row is about a
hundred times slower.
- async_dialect: built on SQLAlchemy's asyncio adapter, buffering results by
default and streaming with stream() or stream_results.
- The full SQLAlchemy compliance suite runs against the async dialect with
--db async, added as a CI step, plus tests/test_async.py for the asyncio
API itself. ServerSideCursorsTest overrides now assert real behaviour.
CI and packaging:
- Drop MINIO_ENABLED from CI: the nightly image's MinIO download now returns
HTTP 410, so Databend never starts; no test needs object storage.
- Skip DeprecatedCompoundSelectTest on SQLAlchemy 2.1, which removed it.
- Keep four pytest workers but put the sequence tests in one xdist group
(--dist loadgroup): Databend sequences belong to the tenant, not a
database, so workers raced on SequenceTest's tab_id_seq. conftest runs
SQLAlchemy's collection hook first so xdist sees the expanded classes.
- Move packaging metadata from setup.cfg to pyproject.toml, add an asyncio
extra, declare Python 3.10 to 3.15 and require SQLAlchemy >= 2.0.28, the
first release that reports the row counts the dialect reads from
Databend. SQLAlchemy 1.4 compatibility branches are removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EPoszSnPRjj34ySEsF4p68
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 37fbfed601
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Superset, up to its current 6.1 release, still requires SQLAlchemy < 2, so requiring SQLAlchemy 2.0.28 would stop Superset users installing this release. - Depend on sqlalchemy >= 1.4 again and restore the DOUBLE fallback. Only the asyncio extra needs SQLAlchemy 2.0.28, and the async dialect raises an ImportError naming that requirement on any older release. - Add tests/test_basic_usage.py, covering reflection, views, executemany, LIMIT/OFFSET, text() with % and type round trips on every supported SQLAlchemy release. The compliance suite needs SQLAlchemy 2.0.42's test requirements, so it isn't collected below that. - Below 2.0.28, don't collect the async tests, and skip only the COPY INTO row count assertions, as older releases report row counts as -1. - Run the tests on SQLAlchemy 1.4 in CI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@hantmac, please take a look if you get chance 👍 |
Summary
Adds an asyncio dialect, so Databend works with
create_async_engine,AsyncConnection.stream()andAsyncSession:create_async_enginegiven a plaindatabend://URL selects the async dialect too. Install withpip install "databend-sqlalchemy[asyncio]", which brings in greenlet. SQLAlchemy 2.1 no longer installs greenlet by default.This PR also fixes CI, which has been failing since mid-October for reasons unrelated to the open PRs (see below), and moves packaging to
pyproject.toml.How it works
databend_sqlalchemy/async_connector.py: the driver'sAsyncDatabendClienthas no async cursor, so this adds a PEP 249-style async connection and cursor.OperationRenderermixin pulled out ofconnector.py. Sync behaviour is unchanged: across about 2,000 parameter and statement shapes, the SQL handed to the driver is byte-identical to before.SETpersist across a connection's statements. The blocking connector opens a session per cursor.fetchmany/fetchall) drain the driver's row iterator in a worker thread, one hop per batch. Awaiting the iterator once per row costs a round trip through the driver's runtime each time, which made 200k rows take about 24 s; it now takes about 0.4 s.databend_sqlalchemy/async_dialect.py: builds on SQLAlchemy's asyncio adapter (sqlalchemy.connectors.asyncio).stream()orstream_results.executemanyleaves behind, becausepost_execreads the inserted row count back from them.Tests
pytest --db async, added as a second CI step.tests/test_async.pycovers the asyncio API itself:DBAPIError;async_creator;AsyncSessionround trip.ServerSideCursorsTestoverrides now assert real behaviour.test_ss_cursor_statusused to call a baresuper()and so never ran. It now checks buffered and streamed cursors under the async dialect.All runs had 0 failures. Before rebasing onto current
main, the same changes also passed on:CI fixes
The open PRs fail CI because the job dies before any tests run:
databend:nightlyimage's MinIO download now returns HTTP 410. MinIO never starts, so Databend fails withStorageUnavailableand the health check gets a connection reset. No test needs object storage (stage COPY INTO works on the default fs storage), soMINIO_ENABLEDand port 9000 are removed.pipenv --skip-lock) removedDeprecatedCompoundSelectTest, sotests/test_sqlalchemy.pyfailed to import. That import and override are now guarded to< 2.1.0.-n4: Databend sequences belong to the tenant, not a database, so parallel workers raced onSequenceTest'stab_id_seq(SequenceAlreadyExists/UnknownSequence).xdist_group, and pytest runs with--dist loadgroup, so they run on a single worker while everything else stays parallel.conftest.pyruns SQLAlchemy's collection hook first; otherwise xdist tags the items before SQLAlchemy replaces them with the per-backend classes.nvarchar_types) and isn't collected below that, nor are the async tests below 2.0.28. Newtests/test_basic_usage.pytherefore covers what Superset relies on, on every SQLAlchemy release: reflection, views, executemany, LIMIT/OFFSET,text()with%, and type round trips.Packaging
pyproject.toml: metadata moves there fromsetup.cfg(PEP 621, with a PEP 639 licence expression).setup.cfgkeeps only the flake8 and SQLAlchemy test-plugin sections, which have nopyproject.tomlsupport.asyncioextra added.requires-python >= 3.10, which the package already needed (types.NoneType, runtimeX | Yunions). Classifiers cover 3.10 to 3.15.>= 1.4for the sync dialect, because Superset (up to its current 6.1 release) requires SQLAlchemy < 2. Theasyncioextra requires>= 2.0.28, and the async dialect raises anImportErrorsaying so on anything older. 2.0.28 is the first release whereExecutionContext.rowcountreturns the_rowcountthe dialect sets inpost_exec. Before it,rowcountis -1 for INSERT/UPDATE/DELETE/COPY INTO.text/x-rst(it was declared as markdown), and one short heading underline is fixed so it renders.twine check --strictpasses.supersetextra now points atapache-superset.Known limitations
executemanymulti-row INSERT path doesn't undo the escaper's%doubling, so'50%'is stored as'50%%'. This affects sync and async alike; single-row inserts are fine.🤖 Generated with Claude Code