diff --git a/README.md b/README.md index 48244e2..1569480 100644 --- a/README.md +++ b/README.md @@ -11,18 +11,18 @@ 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.2 +mysql @ git+ssh://git@git.rethinkstudios.io/rethink-public/mysql.git@v0.1.3 ``` Direct: ```bash -pip install "mysql @ git+ssh://git@git.rethinkstudios.io/rethink-public/mysql.git@v0.1.2" +pip install "mysql @ git+ssh://git@git.rethinkstudios.io/rethink-public/mysql.git@v0.1.3" ``` Pulls `aiomysql` (which pulls `PyMySQL`). -Drop the `@v0.1.2` suffix from the line above to install the latest unpinned. +Drop the `@v0.1.3` suffix from the line above to install the latest unpinned. ## The two-layer API diff --git a/pyproject.toml b/pyproject.toml index 8212afc..22dd8d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "mysql" -version = "0.1.2" +version = "0.1.3" description = "async mysql/mariadb wrapper over aiomysql: two-layer API (friendly verbs + raw escape hatch), fail-loud, config-free" requires-python = ">=3.10" dependencies = [ diff --git a/src/mysql/mysql.py b/src/mysql/mysql.py index 410d6f5..d1a7f8a 100644 --- a/src/mysql/mysql.py +++ b/src/mysql/mysql.py @@ -92,9 +92,17 @@ class MysqlDB: host/port/db/user/password/minsize/maxsize are injected by the caller. extra pool_kwargs pass through to aiomysql.create_pool (unix_socket, ssl, charset, ...). - rows come back as dicts (DictCursor) and autocommit is on by default; both can be - overridden via pool_kwargs. + rows come back as dicts (DictCursor), overridable via pool_kwargs. autocommit is + always on: layer-1 verbs and raw execute() rely on it to commit on their own, so + `autocommit=False` in pool_kwargs is rejected (use transaction() for atomic + multi-statement blocks instead — it disables autocommit for just that block). """ + if pool_kwargs.get("autocommit") is False: + raise ValueError( + "mysql: autocommit=False is not supported via pool_kwargs — layer-1 verbs and " + "raw execute() need autocommit to persist their writes; use db.transaction() " + "for an atomic multi-statement block instead" + ) self._config = dict( host=host, port=port, @@ -409,9 +417,18 @@ class _Transaction: def _where(conditions: Optional[Dict[str, Any]]) -> tuple: """build a parameterized `WHERE col = %s AND ...` clause + the params list - returns ("", []) when there are no conditions. equality only. + returns ("", []) when there are no conditions. equality only. a None value renders as + `col IS NULL` (not `col = %s` bound to NULL, which sql never matches) and does not + consume a placeholder. """ if not conditions: return "", [] - clause = " AND ".join(f"{_quote_ident(c)} = %s" for c in conditions) - return f" WHERE {clause}", list(conditions.values()) + parts = [] + params = [] + for col, val in conditions.items(): + if val is None: + parts.append(f"{_quote_ident(col)} IS NULL") + else: + params.append(val) + parts.append(f"{_quote_ident(col)} = %s") + return f" WHERE {' AND '.join(parts)}", params