Skip to content

Instantly share code, notes, and snippets.

@lefred
Created July 16, 2026 08:19
Show Gist options
  • Select an option

  • Save lefred/aa4d7bc99707baf61f4b21116f361a37 to your computer and use it in GitHub Desktop.

Select an option

Save lefred/aa4d7bc99707baf61f4b21116f361a37 to your computer and use it in GitHub Desktop.
Compare the MySQL and MariaDB ADBC drivers against a MariaDB server
#!/usr/bin/env python3
"""Compare the MySQL and MariaDB ADBC drivers against a MariaDB server.
The demo only creates a TEMPORARY table, once per connection. It prints the
Arrow type, field metadata, and value (or error) returned by each driver.
See the blog post: https://lefred.be/content/when-the-sea-lion-teaches-the-duck-how-to-bark/
This script requires adbc-driver-manager and pyarrow
"""
from __future__ import annotations
import argparse
import os
from dataclasses import dataclass, field
from typing import Any
from adbc_driver_manager import dbapi
CREATE_TABLE = """
CREATE TEMPORARY TABLE adbc_driver_comparison (
id UUID NOT NULL,
embedding VECTOR(3),
ipv4 INET4,
location POINT,
precise_value DECIMAL(65,30),
elapsed TIME(6),
event_year YEAR,
document JSON
)
"""
INSERT_ROW = """
INSERT INTO adbc_driver_comparison VALUES (
UUID_v7(),
VEC_FromText('[1.25,-2.5,3.75]'),
'192.168.1.10',
ST_PointFromText('POINT(4.35 50.85)'),
12345678901234567890123456789012345.123456789012345678901234567890,
'-838:59:59.123456',
2026,
'{"name":"MariaDB","valid":true}'
)
"""
CASES = {
"UUID (protocol reports CHAR)": "id",
"VECTOR (protocol reports VARBINARY)": "embedding",
"INET4 (protocol reports CHAR)": "ipv4",
"MariaDB geometry": "location",
"65-digit DECIMAL": "precise_value",
"negative TIME duration": "elapsed",
"YEAR": "event_year",
"JSON query result (protocol reports LONGTEXT)": "document",
}
METADATA_TABLE = f"adbc_metadata_comparison_{os.getpid()}"
CREATE_METADATA_TABLE = """
CREATE TABLE {table} (
base INT PRIMARY KEY,
hidden_note VARCHAR(20) INVISIBLE COMMENT 'secret',
doubled INT AS (base * 2) VIRTUAL,
document JSON,
native_uuid UUID,
embedding VECTOR(3),
address INET4,
status ENUM('new', 'active', 'disabled'),
flags SET('read', 'write', 'admin')
)
"""
@dataclass
class Result:
arrow_type: str = ""
metadata: dict[str, str] = field(default_factory=dict)
value: str = ""
error: str = ""
def uri(driver: str, args: argparse.Namespace) -> str:
# Each driver deliberately accepts its own URI scheme.
return (
f"{driver}://{args.user}:{args.password}@"
f"{args.host}:{args.port}/{args.database}"
)
def display_value(value: Any) -> str:
if isinstance(value, bytes):
return "0x" + value.hex()
return repr(value)
def run_driver(driver: str, args: argparse.Namespace) -> dict[str, Result]:
results: dict[str, Result] = {}
try:
connection = dbapi.connect(
driver=driver, uri=uri(driver, args), autocommit=True
)
except Exception as exc: # Keep the other driver useful if one is absent.
return {name: Result(error=f"connection failed: {exc}") for name in CASES}
with connection, connection.cursor() as cursor:
cursor.execute(CREATE_TABLE)
cursor.execute(INSERT_ROW)
for name, column in CASES.items():
try:
cursor.execute(f"SELECT `{column}` FROM adbc_driver_comparison")
table = cursor.fetch_arrow_table()
field = table.schema.field(0)
metadata = {
key.decode(): value.decode()
for key, value in (field.metadata or {}).items()
}
results[name] = Result(
arrow_type=str(field.type),
metadata=metadata,
value=display_value(table.column(0)[0].as_py()),
)
except Exception as exc:
results[name] = Result(error=str(exc).replace("\n", " "))
return results
def print_results(all_results: dict[str, dict[str, Result]]) -> None:
for case in CASES:
print(f"\n=== {case} ===")
for driver in ("mysql", "mariadb"):
result = all_results[driver][case]
print(f"{driver:7}:")
if result.error:
print(f" ERROR: {result.error}")
else:
print(f" Arrow type: {result.arrow_type}")
print(f" value: {result.value}")
print(" metadata:")
print_metadata(result.metadata, indent=" ")
def print_metadata(metadata: dict[str, Any], indent: str) -> None:
if not metadata:
print(f"{indent}(none)")
return
for key, value in metadata.items():
print(f"{indent}{key}")
print(f"{indent} = {value}")
def find_columns(objects: Any) -> dict[str, dict[str, Any]]:
"""Flatten the standard nested ADBC GetObjects response."""
columns: dict[str, dict[str, Any]] = {}
for catalog in objects:
for schema in catalog.get("catalog_db_schemas") or []:
for table in schema.get("db_schema_tables") or []:
for column in table.get("table_columns") or []:
columns[column["column_name"]] = column
return columns
def find_constraints(objects: Any) -> list[dict[str, Any]]:
for catalog in objects:
for schema in catalog.get("catalog_db_schemas") or []:
for table in schema.get("db_schema_tables") or []:
if table.get("table_name") == METADATA_TABLE:
return table.get("table_constraints") or []
return []
def decoded_metadata(field: Any) -> dict[str, str]:
return {
key.decode(): value.decode()
for key, value in (field.metadata or {}).items()
}
def compact_type(data_type: Any) -> str:
value = str(data_type)
if value.startswith("extension<arrow.opaque["):
marker = "type_name="
start = value.find(marker) + len(marker)
end = value.find(",", start)
return f"opaque({value[start:end]})"
if value == "fixed_size_list<item: float>[3]":
return "fixed_list<float32>[3]"
return value
def metadata_for_driver(driver: str, args: argparse.Namespace) -> dict[str, Any]:
with dbapi.connect(driver=driver, uri=uri(driver, args), autocommit=True) as connection:
objects = connection.adbc_get_objects(
depth="columns",
catalog_filter=args.database,
table_name_filter=METADATA_TABLE,
).read_all().to_pylist()
columns = find_columns(objects)
constraints = find_constraints(objects)
schema = connection.adbc_get_table_schema(
METADATA_TABLE, catalog_filter=args.database
)
json_field = schema.field("document")
return {
"invisible remarks": columns["hidden_note"].get("remarks"),
"generated flag": columns["doubled"].get("xdbc_is_generatedcolumn"),
"JSON Arrow type": str(json_field.type),
"JSON metadata": decoded_metadata(json_field),
"native schema types": {
name: compact_type(schema.field(name).type)
for name in ("native_uuid", "embedding", "address")
},
"ENUM metadata": decoded_metadata(schema.field("status")),
"SET metadata": decoded_metadata(schema.field("flags")),
"constraint types": [item["constraint_type"] for item in constraints],
}
def run_metadata_comparison(args: argparse.Namespace) -> None:
"""Compare discovery APIs using a short-lived regular table.
INFORMATION_SCHEMA does not list temporary tables, so this part must use a
regular table. Its process-ID-suffixed name is removed in a finally block.
"""
setup_uri = uri("mariadb", args)
try:
with dbapi.connect(driver="mariadb", uri=setup_uri, autocommit=True) as connection:
with connection.cursor() as cursor:
cursor.execute(f"DROP TABLE IF EXISTS `{METADATA_TABLE}`")
cursor.execute(CREATE_METADATA_TABLE.format(table=f"`{METADATA_TABLE}`"))
print("\n=== ADBC metadata discovery ===")
for driver in ("mysql", "mariadb"):
print(f"{driver:7}:")
try:
metadata = metadata_for_driver(driver, args)
print(f" invisible remarks: {metadata['invisible remarks']!r}")
print(f" generated flag: {metadata['generated flag']!r}")
print(f" JSON Arrow type: {metadata['JSON Arrow type']}")
print(" JSON metadata:")
print_metadata(metadata["JSON metadata"], indent=" ")
print(" Native schema types:")
print_metadata(metadata["native schema types"], indent=" ")
print(" ENUM/SET allowed values:")
enum_values = metadata["ENUM metadata"].get("mariadb.enum_values")
set_values = metadata["SET metadata"].get("mariadb.set_values")
print(f" ENUM = {enum_values or '(not reported)'}")
print(f" SET = {set_values or '(not reported)'}")
constraints = metadata["constraint types"]
print(f" Constraints: {', '.join(constraints) if constraints else '(not reported)'}")
except Exception as exc:
print(f" ERROR: {str(exc).replace(chr(10), ' ')}")
finally:
with dbapi.connect(driver="mariadb", uri=setup_uri, autocommit=True) as connection:
with connection.cursor() as cursor:
cursor.execute(f"DROP TABLE IF EXISTS `{METADATA_TABLE}`")
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--host", default=os.getenv("MARIADB_HOST", "127.0.0.1"))
parser.add_argument("--port", type=int, default=int(os.getenv("MARIADB_PORT", "13100")))
parser.add_argument("--user", default=os.getenv("MARIADB_USER", "msandbox"))
parser.add_argument("--password", default=os.getenv("MARIADB_PASSWORD", "msandbox"))
parser.add_argument("--database", default=os.getenv("MARIADB_DATABASE", "test"))
return parser.parse_args()
def main() -> None:
args = parse_args()
print(f"MariaDB server: {args.host}:{args.port}/{args.database}")
print("The password is intentionally not printed.")
results = {driver: run_driver(driver, args) for driver in ("mysql", "mariadb")}
print_results(results)
run_metadata_comparison(args)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment