# mmo.pdo — overview for AI code generation
`mmo.pdo` is Marc Mouret's SQL storage package for MMOFramework (PHP package `mmo.pdo`, PHP namespace
`MMO\pdo`). It is a drop-in alternative to `mmo.core`'s `JsonRepository` (and to a Mongo-backed repository, if
one exists in this application): `PDORepository` implements the same `iRepository` contract — collections,
`Query`, `\MMO\commit()` — but backed by SQLite or MySQL through PDO instead of a JSON file. Anything that
already knows how to work with entities, collections and queries in `mmo.core` needs nothing new to work
against a `PDORepository`; `MMO\core\controllers\RepositoryController` serves one over HTTP exactly like any
other.
Under the hood the package is its own small SQL layer: `SqliteHandler`/`MysqlHandler` (one connection plus a
dialect), a `helpers\Table` (a table's columns, and a generated `PDOEntity` subclass for its rows), a
`statements\*` query builder (SELECT/INSERT/UPDATE/DELETE/CREATE/DROP), and `PDOPrinter`/`PDOExporter` that turn
a statement and an entity's values into real SQL with bound parameters. Most application code only ever touches
the repository/collection/query layer; the SQL layer underneath is there when you need to drop down to it.
## The five things that matter most
1. **It is an `iRepository`, not a new API to learn.** Build a `Table` (or let `PDORepository::setTables()`
build one from a column array), register it, then use `getCollection()`/`query()`/`createEntity()`/
`\MMO\commit()` exactly as with any other repository. See [Repository](../Repository.md).
2. **`AUTO_INCREMENT` ids are negative placeholders until the row is inserted.** A newly created entity on such
a column gets `-1`, `-2`, … so it has *some* id from the moment it is created (the framework requires one) —
`commitEntity()` swaps it for the database's real id right after the `INSERT`. `PDORepository::IsPendingId()`
tells the two apart. See [Repository → Ids](../Repository.md#ids).
3. **A `Query`'s field list is schema-driven, and that cuts both ways.** Requesting a property no table column
has (`status`, `uid`, `metadata`, …) is silently dropped from the `SELECT`. Filtering on one, or on any other
column the table does not have, is an **error** (`eNotFound`) — never silently ignored, because dropping a
filter would return more rows than were asked for. Give the table the column (e.g. add a `status` column
before using `Query::listOnly()`), or filter on columns it has. See [Repository → Fields versus
filters](../Repository.md#fields-versus-filters) and [Troubleshooting](../Troubleshooting.md).
4. **A nested-path filter (`['author/name' => 'marc']`) is NOT turned into a join.** Nothing in the package
knows which columns relate two tables (that is what `helpers\Liaison` was meant to describe, and it was never
finished — it is dead code today). Build the join yourself with `SelectStatement::join()`/`on()` and filter
on the joined alias's column instead. See [Joins](../Joins.md).
5. **Never quote a `{placeholder}` in a raw statement template.** `name = "{name}"` renders the literal text
`"?"` while the value is still bound, so the bind count and the `?` count drift apart and PDO fails with
`column index out of range`. Write `name = {name}`, unquoted; the printer decides on its own whether the
value becomes a `?` or literal SQL. See [Statements](../Statements.md) and
[Troubleshooting](../Troubleshooting.md).
## Recommended reading order
The 7 files below are the detailed reference — this page is just the map. Read in this order the first time;
after that, jump straight to whichever one covers the task at hand.
1. **[Repository](../Repository.md)** — start here. Setting up a `PDORepository` (inline or from
configuration), reading (`query()`, pagination, `$total`), writing (`createEntity`, `\MMO\commit()`), and how
ids are handled for each of the three primary-key shapes (auto-increment, single, composite). This is the
layer almost all application code touches.
2. **[Tables](../Tables.md)** — declaring a `Table` (inline, as a subclass, or via configuration), the column
mini-language (`PRIMARY`, `NOT NULL`, `DEFAULT x`, …), and — importantly — how columns become entity
properties on a *generated* `PDOEntity` subclass, versus when to write your own entity class by hand (as soon
as it needs behaviour). Read this before declaring any real table.
3. **[Handlers](../Handlers.md)** — `SqliteHandler`/`MysqlHandler`: connecting, transactions (`do()` is the one
to use), and schema introspection (`table()` on an undeclared name reads the live database). Needed whenever
you are wiring up a connection or reasoning about transaction boundaries.
4. **[Filters](../Filters.md)** — reference for how a core `iFilter` (the same filter structure used everywhere
in the framework) becomes a SQL `WHERE`: the operator table, `null` handling (`IS`/`IS NOT`, never `=`), and
what is *not* supported (`$regex`, `$has`, `$text`, nested paths).
5. **[Statements](../Statements.md)** — reference for the query builder itself: the `{path:type}` placeholder
syntax, which types bind versus render as text, and the SELECT/INSERT/UPDATE/DELETE/CREATE statement classes.
Needed only when you drop below the repository layer to write a raw statement or a custom query.
6. **[Joins](../Joins.md)** — reference for joining tables explicitly (`join()`/`on()`), since the repository
layer does not do this for you (see point 4 above). Skim once, come back to it when a query actually needs a
join.
7. **[Troubleshooting](../Troubleshooting.md)** — reference, not reading-order material. A symptom → cause →
fix list (both drivers, then SQLite-specific, then MySQL-specific). Search it by the error message.
## Two drivers, uneven coverage
`SqliteHandler` and `MysqlHandler` render through the same `PDOPrinter`/`Table`/`Statement` pipeline and are
meant to behave identically from the caller's side, but the test suite only actually *executes* SQLite (a
temporary file needs no setup); MySQL is exercised by rendering tests only, so a MySQL-only bug is the kind most
likely to still be there. Prefer `SqliteHandler::MEMORY` for anything that does not specifically need MySQL —
tests, demos, quick scripts.