fix: close() nulls _pool + connect() lock (psql-7/8 twin), % in identifiers, doc caveats
close() now nulls self._pool so a closed instance reports not-connected instead of masquerading as live (twin of psql-7); connect() is guarded by an internal asyncio.Lock so concurrent connect() calls serialize instead of racing to create and orphan multiple live pools (twin of psql-8). _quote_ident now escapes a literal % in identifiers (mysql-7: PyMySQL's query % args substitution otherwise breaks any Layer-1 call against a %-bearing table/column name). fetchval/exists tolerate a caller-overridden tuple cursorclass via a new _first_value helper (mysql-8). Documents the %%-escaping rule for literal % in raw SQL text (mysql-6) and the upsert() VALUES() deprecation on MySQL 8.0.20+ (mysql-9). Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
@@ -11,13 +11,13 @@ wire-compatible and share the driver, so this covers both.
|
||||
`requirements.txt`:
|
||||
|
||||
```
|
||||
mysql @ git+ssh://git@git.rethinkstudios.io/rethink-public/mysql.git@v0.1.3
|
||||
mysql @ git+ssh://git@git.rethinkstudios.io/rethink-public/mysql.git@v0.1.4
|
||||
```
|
||||
|
||||
Direct:
|
||||
|
||||
```bash
|
||||
pip install "mysql @ git+ssh://git@git.rethinkstudios.io/rethink-public/mysql.git@v0.1.3"
|
||||
pip install "mysql @ git+ssh://git@git.rethinkstudios.io/rethink-public/mysql.git@v0.1.4"
|
||||
```
|
||||
|
||||
Pulls `aiomysql` (which pulls `PyMySQL`).
|
||||
@@ -99,7 +99,12 @@ For anything even Layer 2 doesn't model (`executemany`, server-side cursors), us
|
||||
- **Values are always parameterized.** Layer 1 builds `%s` internally; Layer 2 takes your
|
||||
`%s` placeholders + a params sequence. Never f-string/`%`-format a value into SQL (the
|
||||
`%s` is the DBAPI placeholder, not Python string formatting). Only identifiers
|
||||
(table/column names) are interpolated, and they're backtick-quoted.
|
||||
(table/column names) are interpolated, and they're backtick-quoted — Layer 1 also
|
||||
escapes a literal `%` inside an identifier for you.
|
||||
- **A literal `%` in Layer-2 SQL text must be written `%%`.** PyMySQL builds the query via
|
||||
`query % args` whenever you pass params, so `"... LIKE '%foo%'"` with params raises
|
||||
`TypeError`/`ValueError` — write `"... LIKE '%%foo%%'"` instead. Calls with no params are
|
||||
unaffected (no substitution happens), and Layer 1 handles this for you internally.
|
||||
|
||||
## Dialect vs psql
|
||||
|
||||
@@ -115,7 +120,8 @@ Layer 1 is portable — you never see these. A Layer-2 raw-SQL author does:
|
||||
|
||||
`upsert()`'s returned rowcount follows MySQL's `ON DUPLICATE KEY UPDATE` convention (1 for
|
||||
an insert, 2 for an update, 0 for a no-op) — a per-row count that differs from psql's; treat
|
||||
it as "affected", not "rows matched".
|
||||
it as "affected", not "rows matched". It emits the `col = VALUES(col)` update form, which
|
||||
MySQL 8.0.20+ deprecates (warning 1287, still functional; MariaDB is unaffected).
|
||||
|
||||
## Error contract — fail loud
|
||||
|
||||
@@ -134,7 +140,10 @@ result (no row, empty table) — never a swallowed failure.
|
||||
|
||||
`get`/`delete`/`exists`/`get_one` take **equality** conditions only (`col = val AND ...`);
|
||||
anything richer goes through Layer 2. The pool runs with `autocommit=True`; `transaction()`
|
||||
opens an explicit transaction for atomic multi-statement blocks.
|
||||
opens an explicit transaction for atomic multi-statement blocks. `cursorclass` is
|
||||
overridable via a constructor kwarg (default `DictCursor`); `fetchval`/`exists` work under
|
||||
either a dict or tuple cursorclass, everything else stays dict-shaped only under the
|
||||
default `DictCursor`.
|
||||
|
||||
## Versioning
|
||||
|
||||
|
||||
Reference in New Issue
Block a user