seed-sql.sh ported to MySQL and MariaDB (2M-class seeder)
- Condition
- The last engine-tooling gap: the 2M-class direct-SQL seeder was Postgres-only
What was done
bin/seed-sql.sh is now engine-aware for postgres (unchanged,
generate_series) and mysql/mariadb (new, recursive CTE). Oracle is still
rejected with a loud pointer (see Remaining).
Three engine differences had to be handled, each of which would otherwise fail silently-late or corrupt the fixture:
- Identifier case. Keycloak creates UPPERCASE identifiers
(
REALM,USER_ENTITY,CREDENTIAL,USER_ATTRIBUTE,ID,REALM_ID,USERNAME, …). Postgres folds unquoted identifiers to lowercase; MySQL 8 / MariaDB on Linux (lower_case_table_names=0, the lab's setting) preserve them. The first MySQL attempt failed at once withTable 'keycloak.realm' doesn't exist. The shared lookups (realm id, template id, row count) and the INSERT column lists are now written per engine. - Recursion-depth knob. MySQL 8 caps a recursive CTE with
cte_max_recursion_depth; MariaDB calls the same thingmax_recursive_iterations. Both default to 1000, so a 250k batch fails without aSET SESSION— and the name is engine-specific. - Credential JSON. The argon2
secret_datacontains$argon2id$…, so it cannot be carried across the shell boundary (an unquoted heredoc would expand the$). On MySQL/MariaDB the JSON is copied in-database with a scalar subquery against the template row, so it never enters the shell at all.
Result (verified at 1000 users on both engines)
| Engine | users | credentials | attributes | credential hash copied from template |
|---|---|---|---|---|
| MySQL 8.4 | 1000 (+1 template) | 1000 | 2000 | ☑ (matched) |
| MariaDB 11.4 | 1000 (+1 template) | 1000 | 2000 | ☑ (matched) |
Also exercised on MariaDB: --start extension (append users), --batch
(multi-batch loop), and --no-analyze. The Postgres path was re-verified
unchanged (100 users).
Observation worth its own note
--no-analyze on MariaDB reports the planner's row estimate from
information_schema.tables.table_rows, and that value is inherently fuzzier
than Postgres's pg_class.reltuples: after seeding 1101 rows InnoDB reported
1303. Postgres's reltuples is exact after ANALYZE; InnoDB's table_rows
is a statistical estimate even when freshly analyzed. So the "stale planner"
model the lab uses to reproduce the index-skip threshold (Run A) will not map
one-to-one onto MySQL/MariaDB — the pre-condition that Keycloak reads there is a
number that is never exact. Worth pinning before claiming the threshold finding
generalises.
Remaining
- Oracle — still not supported. Two blockers: the credential columns are
VARCHAR2/CLOB(the changelog notes theSECRET_DATA/CREDENTIAL_DATAfill is "used on all databases beside Oracle"), and row generation needsCONNECT BY LEVELinstead of a CTE. Separate port.
Verification
| Claim | Evidence |
|---|---|
| MySQL seeds 1000 with correct shape | 1000 users / 1000 creds / 2000 attrs, hash copied ☑ |
| MariaDB seeds 1000 with correct shape | same counts, hash copied ☑ |
| Extension + multi-batch + no-analyze work | --start/--batch/--no-analyze runs ☑ |
| Postgres path unchanged | 100 users seeded, 101 rows ☑ |