Files
superset2/tests/testcontainers/db_engine_specs/_pagination.py
T
Superset Dev 889d2a6a22 feat(ci): expand testcontainers coverage to postgres, mysql, clickhouse, starrocks
Stacked on feat/testcontainers-more-dialects. All four extras already
existed in pyproject.toml, so this only wires up tests -- no new
optional-dependency groups needed.

- postgres/mysql: straight copies of the timescaledb/mariadb pattern
  respectively, pointed at vanilla images instead of a fork/extension.
- clickhouse: connects over the container's HTTP port (8123), matching
  clickhouse-connect (Superset's driver), not the native TCP port (9000)
  the container's own docstring example uses. ClickHouse has no real
  primary-key concept and clickhouse-connect's DDL compiler rejects
  CREATE TABLE without an explicit engine, so _pagination.py gained an
  optional extra_table_args hook to pass MergeTree(order_by=...). Also
  works around a pre-existing quirk in db_engine_specs/clickhouse.py:
  its module-level type-formatting setup dereferences current_app.config,
  so importing it outside a Flask app context raises RuntimeError --
  tests/unit_tests/db_engine_specs/test_clickhouse.py already works
  around this with per-test local imports, but that suite also benefits
  from an autouse app_context fixture this suite doesn't have, so this
  test pushes one explicitly around the one-time import.
- starrocks: no dedicated testcontainers module, so a generic
  DockerContainer against the official allin1-ubuntu image (FE+BE in one
  container). Not verified locally (multiple-GB image, skipped to keep
  local Docker load low per session guidance); the fixture retries its
  first connection since the query port can accept TCP before StarRocks'
  query engine is fully initialized.
2026-08-28 10:47:31 -07:00

69 lines
2.9 KiB
Python

# 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.
"""
Shared body for the "paginated query returns correct rows in order" test
that db_engine_specs.{cockroachdb,crate,db2,mssql,oracle,trino}'s
testcontainers suites each run against their own real instance: a plain
SQLAlchemy Core LIMIT/OFFSET query, compiled and executed for real. Mocked
tests cannot catch a dialect compiling this incorrectly (see
apache/superset#42899, where Trino emitted OFFSET before LIMIT) -- only
real execution can.
Each call site keeps its own test function (and dialect-specific docstring)
so failures still report against the right module; this only factors out
the identical table setup/assert body, via an optional post-insert hook for
dialects (CrateDB) that need one, and an optional extra-table-args hook for
dialects (ClickHouse) whose CREATE TABLE requires a schema item a plain
Column/primary key can't express.
"""
from collections.abc import Callable
from typing import Any
from sqlalchemy import Column, insert, Integer, MetaData, select, Table as SATable
from sqlalchemy.engine import Connection, Engine
def assert_paginated_query_returns_correct_rows_in_order(
engine: Engine,
after_insert: Callable[[Connection], None] | None = None,
extra_table_args: tuple[Any, ...] = (),
) -> None:
metadata = MetaData()
t = SATable(
"pilot_pagination",
metadata,
# autoincrement=False: a single-column integer primary key otherwise
# implicitly becomes AUTO_INCREMENT on MySQL/MariaDB. That column
# type treats an explicit 0 as NULL by default (NO_AUTO_VALUE_ON_ZERO
# is off), so the id=0 row below would silently get auto-assigned 1,
# colliding with the explicit id=1 row in the same batch insert.
Column("id", Integer, primary_key=True, autoincrement=False),
*extra_table_args,
)
metadata.create_all(engine)
with engine.begin() as conn:
conn.execute(insert(t), [{"id": i} for i in range(10)])
if after_insert is not None:
after_insert(conn)
with engine.connect() as conn:
stmt = select(t.c.id).order_by(t.c.id).limit(3).offset(4)
rows = conn.execute(stmt).fetchall()
assert [row.id for row in rows] == [4, 5, 6]