# Setup

1. [Install Docker](https://docs.docker.com/desktop/install/mac-install/)
2. Create and start mysql container: `docker run -p 127.0.0.1:3306:3306  --name mdb -e MARIADB_ALLOW_EMPTY_ROOT_PASSWORD=TRUE -d mariadb:latest`
3. (optional) Connect to mysql cli: `docker exec -it mdb mariadb --user root`

# What the driver's log lines carry

The driver never prints the value of a parameter bound to a statement. Its failure line (`Failed when executing sql`, at error) carries the SQL text (placeholders only), as `params` a description of each parameter (its position — or, for a bindings dictionary bound to `:name` placeholders, its name — the kind of its value and, for strings, arrays and bytes, its length), and as `cause` a summary of the failure: the error's name, the vendor's codes (`ER_DUP_ENTRY`, errno, SQLSTATE) and, as `message`, a sentence of the driver's own (`a row with that key already exists`) — never the server's message, which quotes the value it refused (`Duplicate entry '…' for key …`). The vendor error itself is no longer on the line: the query layer rewrites a failed query's `message` to the SQL with every binding interpolated, and the client library's error carries the same text as `sql`.

To debug against a local dev database with the real values, set `DB_LOG_PARAM_VALUES=1` in a process that also has `DEVELOPMENT` set: the line then adds `paramValues` (the parameters as bound), adds the server's message to `cause` as `sqlMessage`, and carries the vendor error itself as `error`. With either variable unset the line never carries them. The switch changes log lines only.

Writing the line never changes what the driver throws. The description is built for whatever bindings the statement carries (a value that cannot be read is described as `unreadable`), and the line is written through one guarded door: if building or writing it fails — a log writer that throws, a value the writer cannot serialize — the driver writes the fixed line `Failed to write a statement log line` instead, which carries nothing of the statement, and throws the vendor's error exactly as it was going to.

What the driver THROWS is the vendor's error itself, untouched — and a caller's log line about it is covered too. Every failure that leaves the driver (a statement's, a refused commit's) is marked with the logger (`LogLineErrors.mark` in `@proteinjs/logger`; `KnexFailureLine` owns the wording), which never touches the error: any `Logger` line that carries it — as the line's `error`, inside its `obj`, as another error's `cause` — prints a stand-in made of the vendor's codes, the driver's sentence (`Failed when executing sql (ER_DUP_ENTRY, errno 1062): a row with that key already exists`) and the original's stack frames, in place of the interpolated SQL and the server's message. A statement past its operation deadline fails with the query layer's own timeout error, which carries the statement's bindings too: it is marked the same way and reads `Failed when executing sql: the statement or its wait for a connection timed out`. Behind the values switch a marked error prints as it is. What is covered is an error OBJECT that reaches a `Logger`: text copied out of one first (`error.message` in a template, `String(error)`) prints as it is — log the error itself. A schema operation of this driver rejects with a STRING that embeds the vendor's reason; a string cannot be marked, so that text is not covered.

What this does NOT cover: text a caller copies out of the error before logging it (`` `failed: ${error.message}` ``, `String(error)`) is a string by the time the logger sees it — pass the error itself. And the schema operations reject with a string that embeds the vendor's reason; a caller that logs that string prints it.
