Skip to content

Story #307: MariaDB DDL for entity_tags + direct driver deps - #359

Open
joestump wants to merge 1 commit into
mainfrom
feature/307-mariadb-ddl-entity-tags-direct-deps
Open

Story #307: MariaDB DDL for entity_tags + direct driver deps#359
joestump wants to merge 1 commit into
mainfrom
feature/307-mariadb-ddl-entity-tags-direct-deps

Conversation

@joestump

Copy link
Copy Markdown
Owner

What / Why

The entity_tags migration (internal/database/entity_tags.go) only had Postgres and SQLite DDL — when SPOTTER_DATABASE_DRIVER=mysql, it fell through to SQLite syntax (INTEGER PRIMARY KEY AUTOINCREMENT), which fails on MariaDB. This was flagged [CRITICAL] by the 2026-05-10 /sdd:audit.

  • MariaDB/MySQL dialect DDL: BIGINT AUTO_INCREMENT PRIMARY KEY, explicit ENGINE=InnoDB (FK + ON DELETE CASCADE semantics require it), DATETIME DEFAULT CURRENT_TIMESTAMP, and secondary indexes declared inline in CREATE TABLE — MySQL (unlike MariaDB 10.1+) has no CREATE INDEX IF NOT EXISTS, and inline KEY clauses stay idempotent on both.
  • Per-statement execution: DDL now runs one statement at a time instead of a multi-statement Exec batch, because go-sql-driver/mysql rejects multi-statement queries unless the DSN opts into multiStatements=true (the SPEC-0014 default MySQL DSN does not).
  • Direct go.mod deps: lib/pq and go-sql-driver/mysql promoted from // indirect to direct requires via go mod tidy (run after make generate, since Ent generated code drives the dependency graph). Tidy also promoted golang-jwt/jwt/v5 and golang.org/x/time, which were mislabeled indirect — both are directly imported by application code.

Test Evidence

New internal/database/entity_tags_test.go (no prior driver-matrix pattern existed in the repo; this establishes an env-var-gated one):

  • TestEntityTagsDDLDialects — CI-friendly unit test asserting per-dialect DDL tokens (no server needed).
  • TestCreateEntityTagsTable_Matrix — real DDL execution: SQLite in-memory always runs; Postgres/MariaDB run when SPOTTER_TEST_POSTGRES_DSN / SPOTTER_TEST_MYSQL_DSN are set, otherwise skip gracefully (also skips on unreachable server). Each dialect verifies: idempotent double-run, column types via information_schema / PRAGMA table_info, insert/select round-trip, and the (tag_id, entity_type, entity_id) unique constraint. Docker one-liners for local runs are documented in the test file header.

Verified locally against throwaway postgres:16 and mariadb:11 containers:

--- PASS: TestCreateEntityTagsTable_Matrix (0.60s)
    --- PASS: TestCreateEntityTagsTable_Matrix/sqlite3 (0.00s)
    --- PASS: TestCreateEntityTagsTable_Matrix/postgres (0.20s)
    --- PASS: TestCreateEntityTagsTable_Matrix/mysql (0.39s)

make test green (all packages); gofmt -l . clean; go vet ./... clean.

Scope Note

The issue AC "tag upsert end-to-end on a MariaDB instance" depends on internal/tags/upsert.go, which uses INSERT ... ON CONFLICT DO NOTHING (Postgres/SQLite syntax). Making the upsert dialect-aware is explicitly #346's scope; this PR delivers the DDL layer it will build on.

Closes #307
Part of #302 (EPIC: Multi-Database MariaDB Compatibility)
Governing: SPEC multi-database-support REQ "Driver Registration", REQ "Denormalized Entity Tags Table", ADR-0023

🤖 Posted on behalf of @joestump by Claude.

Implements #307.

- internal/database/entity_tags.go: dedicated MySQL/MariaDB dialect DDL
  (BIGINT AUTO_INCREMENT PRIMARY KEY, inline KEY indexes, ENGINE=InnoDB)
  instead of falling through to SQLite syntax that breaks on MariaDB.
  DDL statements now execute one at a time so the default MySQL DSN
  (no multiStatements=true) works.
- internal/database/entity_tags_test.go: per-dialect DDL unit test plus a
  driver-matrix integration test — SQLite in-memory always; Postgres and
  MariaDB gated behind SPOTTER_TEST_POSTGRES_DSN / SPOTTER_TEST_MYSQL_DSN
  with graceful skips (verified locally against postgres:16 + mariadb:11).
- go.mod: lib/pq and go-sql-driver/mysql promoted to direct requires via
  go mod tidy (per SPEC-0014 REQ "go.mod Dependencies").

Governing: SPEC-0014 REQ "Denormalized Entity Tags Table", REQ "Driver
Registration", REQ "go.mod Dependencies"; ADR-0023.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
joestump-agent added a commit to joestump-agent/spotter that referenced this pull request Jul 10, 2026
…iver matrix tests (#33)

* Port entity_tags dialect DDL test coverage and driver-matrix tests

Ports joestump#359, implements joestump#307.

The fork already had the core of upstream PR 359: dedicated MySQL/MariaDB
dialect DDL (BIGINT AUTO_INCREMENT, table-level FKs, single-statement
Exec with an information_schema index guard) and lib/pq +
go-sql-driver/mysql as direct go.mod requires. This ports the remaining
gaps, adapted to the fork's structure:

- internal/database/entity_tags_test.go: per-dialect DDL token unit test
  (TestEntityTagsDDLDialects) now covering Postgres and SQLite in
  addition to the fork's existing MySQL invariants; unknown-driver
  fallback test; driver-matrix integration test
  (TestCreateEntityTagsTable_Matrix) — SQLite in-memory always, Postgres
  and MariaDB gated behind SPOTTER_TEST_POSTGRES_DSN /
  SPOTTER_TEST_MYSQL_DSN with graceful skips (also skips when the DSN is
  set but the server is unreachable). The matrix proves idempotency,
  insert/select round-trip, the (tag_id, entity_type, entity_id) unique
  constraint, column types via PRAGMA/information_schema, and (fork
  extension) that the MySQL information_schema index guard sees both
  indexes after migration.
- internal/database/entity_tags.go: MySQL created_at is now DATETIME
  instead of TIMESTAMP (avoids the 2038 range limit and MySQL's implicit
  TIMESTAMP auto-init/auto-update behavior) and ENGINE=InnoDB is pinned
  explicitly (FK + ON DELETE CASCADE require InnoDB), matching
  upstream's dialect parity. Existing tables are unaffected (CREATE
  TABLE IF NOT EXISTS).

Defect fixed while porting: upstream's reject-token check for the MySQL
dialect used a substring that would false-positive on
"CURRENT_TIMESTAMP,"; the ported test rejects "created_at TIMESTAMP"
instead. Upstream's destructive DROP TABLE cleanup in the live matrix
helpers is retained but now called out with an explicit warning to use
throwaway databases.

Governing: SPEC-0016 REQ "Denormalized Entity Tags Table", ADR-0023.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Xyerp7w1SH5vG48z93DxN

* Document that DATETIME created_at applies to fresh MySQL databases only

CREATE TABLE IF NOT EXISTS never migrates, so existing MySQL deployments
keep a TIMESTAMP created_at (UTC-normalized, 2038-limited) while new
installs get DATETIME (session-timezone literal). Benign while
created_at is never read; the note prevents surprise if a future
feature reads it. Addresses PR #33 review feedback.

Governing: SPEC-0016 REQ "Denormalized Entity Tags Table", ADR-0023.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Xyerp7w1SH5vG48z93DxN

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant