Fixing has_table() alone wasn't enough: get_columns() -- the actual
column-introspection call OceanBaseEngineSpec.get_columns() needs, and
what this test's second half exercises -- has the identical
connection.execute(raw string) bug, confirmed on real CI as the same
ObjectNotExecutableError. Patched the same way, reusing has_table()
correctly since self.has_table already resolves to the earlier patch.
Confirmed on real CI: even with exec_driver_sql fixing the SQLAlchemy 2.0
incompatibility, has_table() still failed -- DESCRIBE on a nonexistent
table raises 1146 ("table doesn't exist") rather than returning an empty
result set, and the original method never catches that at all. It can
only ever return True; checking for a table that doesn't exist (exactly
what create_all()'s checkfirst does first) raised instead of returning
False. Catches the error and returns False, same as any other dialect's
has_table() would.
checkfirst=False only sidestepped create_all()'s own call to has_table();
Inspector.get_columns() (used by OceanBaseEngineSpec.get_columns(), which
the second test needs to actually exercise) calls the same broken
has_table() internally and hit the identical ObjectNotExecutableError
(confirmed on real CI). Every other raw-SQL method in the same dialect
module correctly uses connection.exec_driver_sql(...) -- this looks like
an isolated oversight in just has_table(), not a deliberate design
choice, so this monkeypatches it to do the same thing the rest of the
dialect already does, fixing the root cause for both call sites instead
of routing around one of them.
oceanbase_py's has_table() -- called by create_all()'s default
checkfirst=True before creating each table -- passes a raw string
straight to Connection.execute(), which SQLAlchemy 2.0 rejects outright
(ObjectNotExecutableError, confirmed on real CI). Added an optional
checkfirst override to the shared _pagination.py helper and used it here;
safe to skip the existence check since each test gets a genuinely fresh
container.
Stacked on feat/testcontainers-nightly-only-gating. All six extras already
existed in pyproject.toml. oceanbase and vertica run nightly_only: true
(heavy first-boot and a ~12GB RAM floor, respectively), so they don't run
per-PR; databend/risingwave/firebird/ydb run on every PR like the rest of
this suite.
- oceanbase_py pins sqlalchemy-utils<0.39, which conflicts outright with
Superset's own sqlalchemy-utils==0.42.1 pin -- kept out of the baseline
dev install (same reason as db2's ibm-db-sa) and installed on demand,
--no-deps, only for its own CI leg (it never actually imports
sqlalchemy_utils itself, so the version mismatch is inert at runtime).
- databend: connects to the local standalone image's builtin `root` user
(no password) with sslmode=disable, since Superset's default
encryption_parameters assume TLS the local image doesn't have.
- risingwave: RisingWave's storage engine checkpoints asynchronously --
a SELECT immediately after INSERT can see zero rows without an explicit
FLUSH (confirmed on a real instance). Uses the shared _pagination.py
helper's after_insert hook (originally added for CrateDB) to do that.
- firebird: sqlalchemy-firebird's driver is a pure-Python ctypes wrapper
(py3-none-any wheel, confirmed by downloading it directly) that
dynamically loads the native libfbclient from the host rather than
bundling it -- CI installs that system package on demand. Also confirms
in the test docstring that FirebirdEngineSpec's `limit_method =
LimitMethod.FETCH_MANY` (comment: "uses FIRST to limit") is stale
against the modern driver, which compiles real ROWS-based pagination.
- ydb: needed three real fixes to make a generic DockerContainer usable
at all. (1) YDB's gRPC client does endpoint discovery and reconnects to
whatever the server reports, which by default is the container's own
internal Docker hostname -- fixed by binding the same port on the host
as inside the container and advertising "localhost" as the container's
own hostname, so the discovered endpoint is actually reachable. (2) The
gRPC port opens before storage pools are fully initialized, so an early
CREATE TABLE fails; the fixture retries a real metadata.create_all()
probe rather than trusting the open port. (3) YDB rejects DDL inside an
explicit transaction ("Scheme operations cannot be executed inside
transaction") -- confirmed this only affects a raw text("CREATE
TABLE..."), not metadata.create_all()'s own DDL execution path, which
already does the right thing.