Skip to content

PRD: sqlalchemy-cubrid — CUBRID Dialect for SQLAlchemy 2.0–2.1

Historical snapshot (v1.8.0) — this is an archived requirements and status document, not live support, release, API, or CI guidance. For the current supported versions and test matrix, see the Support Matrix and CI Policy. Historical planning claims below are preserved as a record, not promises about the current package.

1. Overview

Project: sqlalchemy-cubrid Version at snapshot: 1.8.0 Status: Production/Stable (actively maintained; async + JSON shipped) Repository: github.com/cubrid-lab/sqlalchemy-cubrid License: MIT

1.1 Problem Statement (Original)

The original sqlalchemy-cubrid project was abandoned and broken: - Targeted SQLAlchemy < 1.4 (current is 2.0+) - Used deprecated APIs (basestring, inspect.getargspec, super(ClassName, self)) - Had critical bugs in compiler, type system, and reflection methods - No CI pipeline, no tests, could not install on modern environments

1.2 What Was Built

A complete ground-up rewrite delivering a modern CUBRID dialect for SQLAlchemy 2.0–2.1:

  • Full SQLAlchemy 2.0–2.1 dialect with statement caching
  • Complete SQL feature coverage — everything CUBRID supports is enabled
  • Comprehensive type system — 20+ types including CUBRID-specific collections and JSON
  • Schema reflection — tables, views, columns, PKs, FKs, indexes, unique constraints, comments
  • DML extensions — ON DUPLICATE KEY UPDATE, MERGE, GROUP_CONCAT, TRUNCATE
  • DDL support — COMMENT, IF NOT EXISTS / IF EXISTS, AUTO_INCREMENT
  • Alembic migration support via CubridImpl, registered when the dialect loads
  • Extensive offline and integration test suites — CI enforces a minimum 95% line-coverage gate on the offline suite (--cov-fail-under=95); see CI job output for current test counts
  • CI/CD — Python 3.11–3.15 × CUBRID 10.2–11.4 matrix
  • 17 English documentation files in docs/ covering the dialect

1.3 Success Criteria — Status

Criterion Target Achieved
Installable on Python 3.11+ ✅ ✅ pip install sqlalchemy-cubrid
SQLAlchemy 2.0 – 2.1 compatible ✅ ✅ Full API compliance
Offline tests (no live DB) ✅ ✅ Extensive suite, coverage ≥ 95% CI-enforced
All dialect methods implemented ✅ ✅ Reflection, compilation, types
CI/CD with version matrix ✅ ✅ Py 3.11–3.15 × CUBRID 10.2–11.4
Publishable to PyPI ✅ ✅ Release workflow on tag
Alembic support ✅ ✅ CubridImpl auto-registered + autogenerate
≥ 95% code coverage ✅ ✅ CI-enforced via --cov-fail-under=95
Comprehensive documentation ✅ ✅ 17 English docs files + README

2. Technical Architecture

2.1 Module Structure

graph TD
    root["sqlalchemy_cubrid/ (Python package modules + py.typed marker)"]
    init["__init__.py - Public API exports types, insert(), merge(), replace(), trace_query(), __version__"]
    compat["_compat.py - SQLAlchemy private API compatibility helpers"]
    base["base.py - CubridExecutionContext, CubridIdentifierPreparer"]
    compiler["compiler.py - CubridSQLCompiler, CubridDDLCompiler, CubridTypeCompiler"]
    dialect["dialect.py - CubridDialect reflection, connection, isolation levels"]
    pycubrid["pycubrid_dialect.py - PyCubridDialect pure Python driver variant"]
    aio["aio_pycubrid_dialect.py - PyCubridAsyncDialect async driver variant"]
    dml["dml.py - ON DUPLICATE KEY UPDATE (Insert), MERGE statement"]
    trace["trace.py - Query tracing helper"]
    types["types.py - CUBRID type system numeric, string, LOB, collection"]
    req["requirements.py - SA 2.0 test requirement flags"]
    alembic["alembic_impl.py - CubridImpl for Alembic migrations"]
    typed["py.typed - PEP 561 marker"]

    root --> init
    root --> compat
    root --> base
    root --> compiler
    root --> dialect
    root --> pycubrid
    root --> aio
    root --> dml
    root --> trace
    root --> types
    root --> req
    root --> alembic
    root --> typed

2.2 Dependency Matrix

Package Version Purpose
SQLAlchemy ≥ 2.0, < 2.2 Core ORM/engine framework
Python ≥ 3.11 Runtime
CUBRID-Python any DBAPI driver (optional extra)
Alembic ≥ 1.7.2 Migration support (optional extra)
pytest ≥ 7.0 Testing (dev)
ruff ≥ 0.4 Lint + format (dev)

2.3 Entry Points

[project.entry-points."sqlalchemy.dialects"]
cubrid = "sqlalchemy_cubrid.dialect:CubridDialect"
"cubrid.cubrid" = "sqlalchemy_cubrid.dialect:CubridDialect"
"cubrid.pycubrid" = "sqlalchemy_cubrid.pycubrid_dialect:PyCubridDialect"
"cubrid.aiopycubrid" = "sqlalchemy_cubrid.aio_pycubrid_dialect:PyCubridAsyncDialect"

3. Implemented Features

3.1 Type System (types.py — 349 lines)

Standard SQL Types

SQLAlchemy Type CUBRID SQL Type Notes
Integer INTEGER 32-bit signed
SmallInteger SMALLINT 16-bit signed
BigInteger BIGINT 64-bit signed
Float FLOAT 7-digit precision
Double, REAL DOUBLE 15-digit precision
Numeric(p, s) NUMERIC(p, s) Exact numeric, up to 38 digits
String(n) VARCHAR(n) Variable-length
Text STRING VARCHAR(1,073,741,823)
Unicode(n) VARCHAR(n) Database charset
UnicodeText STRING Same as Text (CUBRID has no TEXT)
LargeBinary BLOB Binary Large Object
Boolean SMALLINT Emulated 0/1
Date / Time / DateTime / TIMESTAMP Native Direct mapping

CUBRID-Specific Types

Type Description
STRING VARCHAR(1,073,741,823) — max-length variable string
BIT(n) / BIT VARYING(n) Fixed/variable-length bit strings
CLOB Character Large Object
MONETARY Fixed-point currency type
OBJECT OID reference type
SET(type) Unordered collection of unique elements
MULTISET(type) Unordered collection allowing duplicates
SEQUENCE(type) Ordered collection allowing duplicates

3.2 SQL Compiler (compiler.py — 507 lines)

SQLCompiler

Method Output
visit_cast CAST(expr AS type)
visit_sysdate_func SYSDATE
visit_utc_timestamp_func UTC_TIME()
visit_group_concat_func GROUP_CONCAT(...)
render_literal_value Backslash escaping
get_select_precolumns DISTINCT handling
visit_join INNER JOIN / LEFT OUTER JOIN
limit_clause LIMIT offset, count (MySQL-style)
for_update_clause FOR UPDATE [OF col, ...]
update_limit_clause LIMIT n on UPDATE

DDLCompiler

Method Output
get_column_specification Column DDL with AUTO_INCREMENT, COMMENT
visit_set_table_comment ALTER TABLE t COMMENT = 'text'
visit_drop_table_comment ALTER TABLE t COMMENT = ''
visit_set_column_comment ALTER TABLE t MODIFY col ... COMMENT 'text'

TypeCompiler

All CUBRID types compiled to DDL strings: SMALLINT, INTEGER, BIGINT, NUMERIC(p,s), DECIMAL(p,s), FLOAT(p), REAL(p), DOUBLE, DOUBLE PRECISION, CHAR(n), VARCHAR(n), NCHAR(n), NVARCHAR(n), STRING, BIT(n), BIT VARYING(n), BLOB, CLOB, SET(type), MULTISET(type), SEQUENCE(type), MONETARY, OBJECT, BOOLEAN → SMALLINT.

3.3 Dialect (dialect.py — 605 lines)

Dialect Configuration

class CubridDialect(default.DefaultDialect):
    name = "cubrid"
    supports_statement_cache = True
    supports_native_boolean = False       # Emulated via SMALLINT
    supports_native_enum = True            # Native ENUM('a','b') DDL
    supports_native_decimal = True
    supports_sequences = False            # Uses AUTO_INCREMENT
    supports_default_values = True        # INSERT ... DEFAULT VALUES
    supports_empty_insert = True
    supports_multivalues_insert = True
    supports_comments = True              # Table + column comments
    supports_is_distinct_from = True      # Emulated via null-safe <=>
    insert_returning = False              # No RETURNING clause
    update_returning = False
    delete_returning = False
    postfetch_lastrowid = True
    requires_name_normalize = False       # CUBRID already folds to lowercase
    max_identifier_length = 254
    default_paramstyle = "qmark"

Async dialect: PyCubridAsyncDialect subclasses the pycubrid dialect with is_async = True and wraps pycubrid.aio via AsyncAdapt_pycubrid_dbapi. Registered under the cubrid.aiopycubrid entry point for use with create_async_engine("cubrid+aiopycubrid://...").

Reflection Methods (All Implemented)

Method Source
get_table_names() db_class (class_type = 'CLASS', non-system tables)
get_view_names() db_class (class_type = 'VCLASS')
get_view_definition() SHOW CREATE VIEW
get_columns() SHOW COLUMNS IN + db_attribute.comment
get_pk_constraint() db_index + db_index_key (falls back to SHOW COLUMNS IN when no PK row is found)
get_foreign_keys() SHOW CREATE TABLE parsing
get_indexes() SHOW INDEXES IN + db_index flags
get_unique_constraints() db_index + SHOW INDEXES IN (falls back to SHOW CREATE TABLE parsing only when db_index lists no index of the table or, on 11.2+, the name resolves to another owner's class with no unique index; with one, SHOW INDEXES IN raises NoSuchTableError first)
get_table_comment() db_class.comment
get_check_constraints() Returns [] (CUBRID ignores CHECK)
get_schema_names() Returns [] (no schema objects)
has_table() Parameterized query on db_class
has_index() Query on db_index
has_sequence() Returns False always

Connection & Isolation

  • URL translation: cubrid://user:pass@host:port/db → CUBRID:host:port:db:::
  • 3 MVCC isolation levels — READ COMMITTED (default), REPEATABLE READ, SERIALIZABLE
  • No statement-text autocommit: DML and DDL are committed only through the SQLAlchemy 2.x Connection API (commit(), begin(), Session)
  • Savepoints: Supported; RELEASE SAVEPOINT is a no-op

3.4 DML Extensions (dml.py — 267 lines)

ON DUPLICATE KEY UPDATE

from sqlalchemy_cubrid import insert

stmt = insert(users).values(id=1, name="alice")
stmt = stmt.on_duplicate_key_update(name="updated_alice")
# Or reference inserted values:
stmt = stmt.on_duplicate_key_update(name=stmt.inserted.name)

MERGE Statement

from sqlalchemy_cubrid.dml import merge

stmt = (
    merge(target)
    .using(source)
    .on(target.c.id == source.c.id)
    .when_matched_then_update({"name": source.c.name})
    .when_not_matched_then_insert({"id": source.c.id, "name": source.c.name})
)

3.5 Alembic Support (alembic_impl.py — 141 lines)

  • CubridImpl(DefaultImpl) with transactional_ddl = True (CUBRID DDL is transactional); emit_begin() is a no-op because CUBRID has no BEGIN statement, so offline scripts only emit COMMIT;
  • Registered with Alembic when the dialect module loads
  • Autogenerate: render_type() for SET/MULTISET/SEQUENCE rendering in migration scripts
  • Autogenerate: compare_type() for semantic comparison of collection types
  • Native alter_column: type change (MODIFY), rename (RENAME COLUMN), combined (CHANGE); batch_alter_table only for lossy conversions (alter_table_change_type_strict)

4. Test Coverage

4.1 Test Matrix

The offline suite spans test/test_compiler.py, test_types.py, test_requirements.py, test_dialect_offline.py, test_base.py, test_alembic*.py, test_dialects.py, test_trace.py, and many more modules covering reflection, collections, Alembic plugin registration, driver contracts and repository tooling. Per-file and total test counts shift with nearly every PR; run pytest test/ -m "not integration and not repo" --collect-only or check the offline-tests CI job output for current numbers instead of a pinned snapshot here. CI enforces a minimum 95% line-coverage gate (--cov-fail-under=95).

4.2 Unreachable Lines

A few lines in compiler.py and dml.py are defensive fallbacks (an empty for_update_clause/limit_clause return, a DDL type-compilation fallback, an else branch in type normalization) that cannot trigger through SQLAlchemy's public API. Their exact line numbers shift as the modules change; run pytest test/ -m "not integration and not repo" --cov=sqlalchemy_cubrid --cov-report=term-missing (or make test) and check the Missing column for the current set.

4.3 CI Matrix

Workflow Python versions CUBRID versions Source
PR/push offline tests PR: 3.12; push, weekly and dispatch: 3.11 and 3.15 (full suite, 95% coverage) N/A (offline) .github/workflows/ci.yml
PR/push integration tests Reduced matrix: 3.14 (with 11.4), 3.11 (with 10.2) 11.4, 10.2 — 2 combinations only, not a cross product .github/workflows/ci.yml
Release-gate / manual full integration matrix 3.11, 3.12, 3.13, 3.14, 3.15 10.2, 11.0, 11.2, 11.4 .github/workflows/integration-full.yml

5. Known Limitations

Limitations imposed by CUBRID itself (not the dialect):

Feature Status Reason
RETURNING clause ❌ CUBRID has no INSERT/UPDATE/DELETE ... RETURNING
Native BOOLEAN ⚠️ Emulated via SMALLINT (0/1)
JSON type ✅ Native JSON type/functions are supported in CUBRID 10.2+ and mapped by the dialect
ARRAY type ❌ Use SET / MULTISET / SEQUENCE collections
Sequences ❌ CUBRID uses AUTO_INCREMENT
Multi-schema ❌ Single-schema model
Temporary tables ❌ CUBRID has no CREATE TEMPORARY TABLE
IS DISTINCT FROM ❌ Not a CUBRID SQL operator
RELEASE SAVEPOINT ❌ Dialect implements as no-op
Check constraint reflection ❌ CUBRID parses but ignores CHECK
Async DBAPI support ✅ Via pycubrid.aio async driver (cubrid+aiopycubrid://)
Two-phase commit (XA) ❌ CUBRID has no distributed transaction support
Server-side cursors ❌ CUBRID Python driver limitation
Alembic ALTER COLUMN TYPE ✅ Native MODIFY; batch_alter_table for lossy conversions
Alembic RENAME COLUMN ✅ Native RENAME COLUMN (CHANGE when combined with a type change)
FOR UPDATE NOWAIT / SKIP LOCKED ❌ Not supported by CUBRID

6. Documentation

Document Content
README.md Concise landing page
docs/CONNECTION.md Connection strings, URL format, driver setup
docs/TYPES.md Full type mapping, CUBRID-specific types
docs/ISOLATION_LEVELS.md The three CUBRID MVCC isolation levels
docs/DML_EXTENSIONS.md ON DUPLICATE KEY UPDATE, MERGE, GROUP_CONCAT
docs/ALEMBIC.md Alembic migration guide, limitations
docs/FEATURE_SUPPORT.md Feature comparison with MySQL/PG/SQLite
docs/DEVELOPMENT.md Dev setup, testing, Docker, CI/CD
CHANGELOG.md Release history (Keep a Changelog)
CONTRIBUTING.md Contribution guidelines

7. Release History

| v0.1.0 | 2026-03-12 | Complete rewrite — SA 2.0, all reflection, compiler, types, CI/CD | | v0.2.0 | 2026-03-12 | SQL feature expansion — FOR UPDATE, window functions, MERGE, ODKU, COMMENT, DDL extensions | | v0.3.0 | 2026-03-12 | Alembic support, edge-case tests, 99% coverage | | v0.3.1 | 2026-03-12 | Cleanup — legacy file removal, sample modernization, community files | | v0.3.2 | 2026-03-12 | Documentation restructuring — 6 new guide files, README rewrite | | v0.4.0 | 2026-03-12 | Driver & compatibility hardening — error codes, do_ping, connection pool tuning | | v0.5.0 | 2026-03-12 | Query feature expansion — REPLACE INTO, recursive CTEs, ODKU subqueries, trace | | v0.6.0 | 2026-03-12 | Type system expansion, Alembic autogenerate, SA 2.1 readiness, ORM cookbook |


8. Roadmap

v0.4.0 — Driver & Compatibility Hardening

Goal: Improve real-world usability and CUBRID driver compatibility.

| Item | Description | Priority | | Item | Description | Priority | Status | |---|---|---|---| | CUBRID-Python driver audit | Test against latest CUBRID-Python releases, document version compatibility matrix | High | ✅ Done | | Connection pool tuning | Test and document SA connection pool behavior with CUBRID (pool_size, pool_recycle, pool_pre_ping) | High | ✅ Done | | postfetch_lastrowid validation | Verify get_last_insert_id() behavior across CUBRID versions, fix if inconsistent | High | ✅ Done | | Error code mapping | Map CUBRID error codes to SA exceptions (IntegrityError, OperationalError, etc.) for proper exception handling | Medium | ✅ Done | | CUBRID 12.x support | Test against CUBRID 12 when released, add to CI matrix | Medium | ⏳ Blocked (not released) | | Python 3.14 support | Add Python 3.14 to CI matrix when available | Low | ✅ Done | | Python 3.15 support | Add Python 3.15 to the classifiers and the full matrix after final-runtime validation (#666) | Low | ✅ Done |

v0.5.0 — Query Feature Expansion

Goal: Maximize SQL feature coverage within CUBRID's capabilities.

Item Description Priority Status
REPLACE statement CUBRID supports REPLACE INTO — add as custom DML construct Medium ✅ Done
INSERT ... ON DUPLICATE KEY UPDATE with subqueries Support subquery values in ODKU clause Medium ✅ Done
Lateral joins Investigate CUBRID's lateral join support, enable if available Low ❌ Not supported
Recursive CTEs Test recursive WITH RECURSIVE support in CUBRID 11.x+ Low ✅ Done
Full-text search CUBRID has full-text indexes — expose via custom construct if feasible Low ❌ Not supported
EXPLAIN output Add EXPLAIN prefix support for query plan inspection Low ✅ Done (trace_query)

v0.6.0 — SQLAlchemy 2.1+ & Async

Goal: Full SQLAlchemy 2.1 alignment and modern async support.

Item Description Priority Status
SQLAlchemy 2.1 compatibility Track SA 2.1 breaking changes, update dialect accordingly High ⏳ SA 2.1 not released
SQLAlchemy 2.2+ forward compat Test and adjust for future SA releases High ⏳ Not released
Async DBAPI support If CUBRID Python driver adds async support, implement create_async_engine compatibility Medium ✅ Done
Type annotation improvements Add full overload signatures for insert(), merge() return types Medium ✅ Done
RETURNING emulation Investigate TRIGGER-based or LAST_INSERT_ID workaround for single-row returning Low ⏳ Not started

Long-Term — Community & Ecosystem

Goal: Sustainable open-source project with active community.

Item Description Priority Status
PyPI publication Publish to PyPI for pip install from registry High ✅ Done
Documentation site Deploy docs via GitHub Pages or Read the Docs Medium ⏳ Not started
Alembic autogenerate tuning Improve autogenerate for CUBRID-specific types (SET, MULTISET, SEQUENCE) Medium ✅ Done
SQLAlchemy test suite pass rate Track and increase pass rate against SA's standard dialect test suite Medium ⏳ Not started
CUBRID ORM cookbook Practical examples: relationships, eager loading, hybrid properties with CUBRID Low ✅ Done
Performance benchmarks Benchmark dialect overhead vs raw CUBRID-Python for common operations Low ⏳ Not started
Community contributors Issue templates, good-first-issue labels, contributor docs Low ✅ Done (issue templates)

Roadmap Priorities

graph LR
    v130["v0.4.0"] --> v130a["Driver compatibility hardening"]
    v130 --> v130b["Error code mapping + connection pooling"]

    v140["v0.5.0"] --> v140a["REPLACE statement"]
    v140 --> v140b["Recursive CTEs, full-text search"]

    v200["v0.6.0"] --> v200a["SQLAlchemy 2.1+ full compatibility"]
    v200 --> v200b["Async support (when driver supports it)"]

    longterm["Long-term"] --> longa["PyPI, docs site, community growth"]
    longterm --> longb["SA test suite pass rate, benchmarks"]

9. Architecture Decisions

9.1 Why Single Package (not separate repos)

Alembic support, types, and DML extensions are all in sqlalchemy_cubrid/ — one package. This follows the pattern of all mature SA dialects (MySQL, PostgreSQL, SQLite). Separate repos create versioning complexity with no benefit for a dialect this size.

9.2 Why Offline Tests by Default

CUBRID requires a running server instance. Most dialect logic (SQL compilation, type mapping, reflection parsing) is deterministic and testable without a database. The offline suite (the default pytest test/ -m "not integration and not repo" selection) runs without a database, enabling fast CI and contributor onboarding.

9.3 Why 95% Coverage Threshold

SA dialect bugs are subtle — wrong SQL generation, missing escaping, broken reflection. High coverage catches regressions before they reach users. Any uncovered lines are expected to be unreachable defensive fallbacks; see the coverage report for current figures.

9.4 Native ALTER Column Support in Alembic

CUBRID natively supports ALTER TABLE ... MODIFY, CHANGE, and RENAME COLUMN (MySQL-compatible syntax), so the dialect emits native DDL for column type changes and renames rather than raising. Type conversions are governed by the alter_table_change_type_strict system parameter. For lossy or incompatible conversions, users can still fall back to Alembic's batch_alter_table table-recreate strategy when they need full control over data migration.


Last updated: September 2026 · sqlalchemy-cubrid v1.8.0


10. Example-first Design Philosophy

Why Example-first

CUBRID's ecosystem is small compared to PostgreSQL or MySQL. For a small-ecosystem project, the entry barrier must be minimized — users should be able to copy-paste working code within 30 seconds of reading the documentation.

Because the ecosystem is still small, the project provides extensive examples and cookbook-style documentation to lower the adoption barrier.

Hello World

pip install sqlalchemy-cubrid
from sqlalchemy import create_engine, text

engine = create_engine("cubrid://dba:password@localhost:33000/demodb")

with engine.connect() as conn:
    result = conn.execute(text("SELECT 1"))
    print(result.scalar())  # 1

This code connects to a CUBRID database via SQLAlchemy, runs a query, and prints the result. No ORM setup required — just install and connect.

Hello World — ORM

from sqlalchemy import create_engine, String
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column


class Base(DeclarativeBase):
    pass


class User(Base):
    __tablename__ = "users"

    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    name: Mapped[str] = mapped_column(String(100))
    email: Mapped[str] = mapped_column(String(200), unique=True)


engine = create_engine("cubrid://dba:password@localhost:33000/demodb")
Base.metadata.create_all(engine)

with Session(engine) as session:
    user = User(name="Alice", email="alice@example.com")
    session.add(user)
    session.commit()

AI Agent Discoverability

Modern developers increasingly rely on AI coding assistants — Claude Code, OpenCode, Cursor, GitHub Copilot, Devin, and others. These agents read README files, PRDs, documentation, and example code to decide which libraries to recommend.

More examples in documentation = higher probability of AI recommendation.

By embedding working code directly in the PRD, README, and docs, we ensure that AI agents can discover, learn, and recommend sqlalchemy-cubrid accurately.

Cookbook Integration

The cubrid-cookbook repository provides runnable examples for sqlalchemy-cubrid:

Example Description
python/sqlalchemy/01_connect.py Core engine connection
python/sqlalchemy/02_orm.py ORM models and CRUD
python/sqlalchemy/03_dml.py DML extensions (ODKU, MERGE, REPLACE)
python/sqlalchemy/04_relationships.py ORM relationships
python/sqlalchemy/05_reflection.py Schema reflection
python/sqlalchemy/06_alembic.py Alembic migrations

Framework integration examples:

Example Framework Description
python/fastapi/ FastAPI REST API with automatic docs
python/django/ Django Django project via SQLAlchemy bridge
python/flask/ Flask Flask + Flask-SQLAlchemy
python/pandas/ Pandas Data analysis pipeline
python/streamlit/ Streamlit Interactive data dashboard
python/celery/ Celery Async task queue

Inspiration from Successful Projects

Projects that succeeded partly through example-heavy documentation:

Project What They Did
FastAPI Every endpoint documented with runnable examples; became the fastest-growing Python web framework
LangChain Cookbook-first approach drove explosive adoption in the AI space
SQLAlchemy Extensive ORM cookbook and tutorial; de facto Python ORM for 15+ years
Pandas "10 Minutes to pandas" and cookbook lowered entry barrier for data science

sqlalchemy-cubrid follows the same philosophy: examples are not supplementary — they are the primary documentation.