rowquery 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. {rowquery-0.1.0 → rowquery-0.2.0}/.github/ISSUE_TEMPLATE/config.yml +1 -1
  2. rowquery-0.2.0/.github/workflows/test.yml +115 -0
  3. rowquery-0.2.0/CHANGELOG.md +85 -0
  4. {rowquery-0.1.0 → rowquery-0.2.0}/CLAUDE.md +14 -3
  5. {rowquery-0.1.0 → rowquery-0.2.0}/CONTRIBUTING.md +1 -1
  6. {rowquery-0.1.0 → rowquery-0.2.0}/PKG-INFO +13 -9
  7. {rowquery-0.1.0 → rowquery-0.2.0}/README.md +6 -2
  8. rowquery-0.2.0/llms-full.txt +940 -0
  9. rowquery-0.2.0/llms.txt +21 -0
  10. {rowquery-0.1.0 → rowquery-0.2.0}/pyproject.toml +6 -6
  11. {rowquery-0.1.0 → rowquery-0.2.0}/row_query/__init__.py +6 -0
  12. rowquery-0.2.0/row_query/adapters/mysql.py +109 -0
  13. rowquery-0.2.0/row_query/adapters/oracle.py +123 -0
  14. rowquery-0.2.0/row_query/adapters/postgresql.py +91 -0
  15. rowquery-0.2.0/row_query/adapters/protocol.py +106 -0
  16. rowquery-0.2.0/row_query/adapters/sqlite.py +85 -0
  17. rowquery-0.2.0/row_query/core/connection.py +220 -0
  18. rowquery-0.2.0/row_query/core/engine.py +128 -0
  19. {rowquery-0.1.0 → rowquery-0.2.0}/row_query/core/exceptions.py +33 -3
  20. rowquery-0.2.0/row_query/core/execution.py +340 -0
  21. rowquery-0.2.0/row_query/core/lexer.py +183 -0
  22. rowquery-0.2.0/row_query/core/migration.py +198 -0
  23. rowquery-0.2.0/row_query/core/params.py +234 -0
  24. rowquery-0.2.0/row_query/core/pool.py +271 -0
  25. rowquery-0.2.0/row_query/core/sanitizer.py +206 -0
  26. rowquery-0.2.0/row_query/core/transaction.py +192 -0
  27. {rowquery-0.1.0 → rowquery-0.2.0}/row_query/mapping/aggregate.py +90 -28
  28. {rowquery-0.1.0 → rowquery-0.2.0}/row_query/mapping/builder.py +65 -25
  29. {rowquery-0.1.0 → rowquery-0.2.0}/row_query/mapping/plan.py +5 -0
  30. {rowquery-0.1.0 → rowquery-0.2.0}/tests/contract/test_adapter_protocol.py +6 -20
  31. rowquery-0.2.0/tests/integration/test_server_backends.py +236 -0
  32. {rowquery-0.1.0 → rowquery-0.2.0}/tests/unit/test_builder.py +0 -15
  33. rowquery-0.2.0/tests/unit/test_engine_regressions.py +164 -0
  34. rowquery-0.2.0/tests/unit/test_nested_collections.py +314 -0
  35. rowquery-0.2.0/tests/unit/test_pool.py +184 -0
  36. rowquery-0.2.0/tests/unit/test_review_regressions.py +390 -0
  37. {rowquery-0.1.0 → rowquery-0.2.0}/tests/unit/test_sanitizer.py +3 -5
  38. rowquery-0.1.0/.github/workflows/test.yml +0 -48
  39. rowquery-0.1.0/CHANGELOG.md +0 -44
  40. rowquery-0.1.0/row_query/adapters/mysql.py +0 -111
  41. rowquery-0.1.0/row_query/adapters/oracle.py +0 -120
  42. rowquery-0.1.0/row_query/adapters/postgresql.py +0 -104
  43. rowquery-0.1.0/row_query/adapters/protocol.py +0 -81
  44. rowquery-0.1.0/row_query/adapters/sqlite.py +0 -98
  45. rowquery-0.1.0/row_query/core/connection.py +0 -132
  46. rowquery-0.1.0/row_query/core/engine.py +0 -419
  47. rowquery-0.1.0/row_query/core/migration.py +0 -148
  48. rowquery-0.1.0/row_query/core/params.py +0 -122
  49. rowquery-0.1.0/row_query/core/sanitizer.py +0 -233
  50. rowquery-0.1.0/row_query/core/transaction.py +0 -334
  51. {rowquery-0.1.0 → rowquery-0.2.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  52. {rowquery-0.1.0 → rowquery-0.2.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  53. {rowquery-0.1.0 → rowquery-0.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  54. {rowquery-0.1.0 → rowquery-0.2.0}/.github/dependabot.yml +0 -0
  55. {rowquery-0.1.0 → rowquery-0.2.0}/.github/workflows/quality.yml +0 -0
  56. {rowquery-0.1.0 → rowquery-0.2.0}/.github/workflows/release.yml +0 -0
  57. {rowquery-0.1.0 → rowquery-0.2.0}/.gitignore +0 -0
  58. {rowquery-0.1.0 → rowquery-0.2.0}/.pre-commit-config.yaml +0 -0
  59. {rowquery-0.1.0 → rowquery-0.2.0}/CODE_OF_CONDUCT.md +0 -0
  60. {rowquery-0.1.0 → rowquery-0.2.0}/LICENSE +0 -0
  61. {rowquery-0.1.0 → rowquery-0.2.0}/examples/01_basic_query.py +0 -0
  62. {rowquery-0.1.0 → rowquery-0.2.0}/examples/02_model_mapping.py +0 -0
  63. {rowquery-0.1.0 → rowquery-0.2.0}/examples/03_aggregate.py +0 -0
  64. {rowquery-0.1.0 → rowquery-0.2.0}/examples/04_transactions.py +0 -0
  65. {rowquery-0.1.0 → rowquery-0.2.0}/examples/05_async.py +0 -0
  66. {rowquery-0.1.0 → rowquery-0.2.0}/examples/06_migrations.py +0 -0
  67. {rowquery-0.1.0 → rowquery-0.2.0}/examples/07_repository.py +0 -0
  68. {rowquery-0.1.0 → rowquery-0.2.0}/examples/README.md +0 -0
  69. {rowquery-0.1.0 → rowquery-0.2.0}/row_query/adapters/__init__.py +0 -0
  70. {rowquery-0.1.0 → rowquery-0.2.0}/row_query/core/__init__.py +0 -0
  71. {rowquery-0.1.0 → rowquery-0.2.0}/row_query/core/enums.py +0 -0
  72. {rowquery-0.1.0 → rowquery-0.2.0}/row_query/core/registry.py +0 -0
  73. {rowquery-0.1.0 → rowquery-0.2.0}/row_query/mapping/__init__.py +0 -0
  74. {rowquery-0.1.0 → rowquery-0.2.0}/row_query/mapping/model.py +0 -0
  75. {rowquery-0.1.0 → rowquery-0.2.0}/row_query/mapping/protocol.py +0 -0
  76. {rowquery-0.1.0 → rowquery-0.2.0}/row_query/repository/__init__.py +0 -0
  77. {rowquery-0.1.0 → rowquery-0.2.0}/row_query/repository/base.py +0 -0
  78. {rowquery-0.1.0 → rowquery-0.2.0}/tests/__init__.py +0 -0
  79. {rowquery-0.1.0 → rowquery-0.2.0}/tests/conftest.py +0 -0
  80. {rowquery-0.1.0 → rowquery-0.2.0}/tests/contract/__init__.py +0 -0
  81. {rowquery-0.1.0 → rowquery-0.2.0}/tests/fixtures/sql/billing/invoice/list.sql +0 -0
  82. {rowquery-0.1.0 → rowquery-0.2.0}/tests/fixtures/sql/user/count.sql +0 -0
  83. {rowquery-0.1.0 → rowquery-0.2.0}/tests/fixtures/sql/user/get_by_id.sql +0 -0
  84. {rowquery-0.1.0 → rowquery-0.2.0}/tests/fixtures/sql/user/insert.sql +0 -0
  85. {rowquery-0.1.0 → rowquery-0.2.0}/tests/fixtures/sql/user/list.sql +0 -0
  86. {rowquery-0.1.0 → rowquery-0.2.0}/tests/integration/__init__.py +0 -0
  87. {rowquery-0.1.0 → rowquery-0.2.0}/tests/integration/test_sqlite.py +0 -0
  88. {rowquery-0.1.0 → rowquery-0.2.0}/tests/unit/__init__.py +0 -0
  89. {rowquery-0.1.0 → rowquery-0.2.0}/tests/unit/test_aggregate_mapper.py +0 -0
  90. {rowquery-0.1.0 → rowquery-0.2.0}/tests/unit/test_engine.py +0 -0
  91. {rowquery-0.1.0 → rowquery-0.2.0}/tests/unit/test_migration.py +0 -0
  92. {rowquery-0.1.0 → rowquery-0.2.0}/tests/unit/test_model_mapper.py +0 -0
  93. {rowquery-0.1.0 → rowquery-0.2.0}/tests/unit/test_params.py +0 -0
  94. {rowquery-0.1.0 → rowquery-0.2.0}/tests/unit/test_registry.py +0 -0
  95. {rowquery-0.1.0 → rowquery-0.2.0}/tests/unit/test_repository.py +0 -0
  96. {rowquery-0.1.0 → rowquery-0.2.0}/tests/unit/test_transaction.py +0 -0
@@ -1,5 +1,5 @@
1
1
  blank_issues_enabled: false
2
2
  contact_links:
3
3
  - name: Question or Discussion
4
- url: https://github.com/maksim-shevtsov/RowQuery/discussions
4
+ url: https://github.com/MaksimShevtsov/RowQuery/discussions
5
5
  about: Ask questions or discuss ideas
@@ -0,0 +1,115 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main, "001-*"]
6
+ pull_request:
7
+ branches: [main]
8
+ workflow_dispatch:
9
+
10
+ jobs:
11
+ test:
12
+ name: Test (Python ${{ matrix.python-version }})
13
+ runs-on: ubuntu-latest
14
+ strategy:
15
+ fail-fast: false
16
+ matrix:
17
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
18
+
19
+ steps:
20
+ - uses: actions/checkout@v6
21
+
22
+ - name: Install uv
23
+ uses: astral-sh/setup-uv@v5
24
+ with:
25
+ enable-cache: true
26
+
27
+ - name: Set up Python ${{ matrix.python-version }}
28
+ uses: actions/setup-python@v6
29
+ with:
30
+ python-version: ${{ matrix.python-version }}
31
+
32
+ - name: Install dependencies
33
+ run: uv sync --extra sqlite --extra dev
34
+
35
+ - name: Run unit and contract tests
36
+ run: uv run pytest tests/unit/ tests/contract/ -v --cov=row_query --cov-report=xml --cov-report=term
37
+
38
+ - name: Run integration tests
39
+ run: uv run pytest tests/integration/ -m integration -v
40
+
41
+ - name: Upload coverage to Codecov
42
+ if: matrix.python-version == '3.12'
43
+ uses: codecov/codecov-action@v4
44
+ with:
45
+ file: ./coverage.xml
46
+ flags: unittests
47
+ name: codecov-umbrella
48
+ fail_ci_if_error: false
49
+
50
+ integration-servers:
51
+ name: Integration (PostgreSQL + MySQL + Oracle)
52
+ runs-on: ubuntu-latest
53
+
54
+ services:
55
+ postgres:
56
+ image: postgres:16-alpine
57
+ env:
58
+ POSTGRES_PASSWORD: rowquery
59
+ POSTGRES_DB: rowquery
60
+ ports:
61
+ - 5432:5432
62
+ options: >-
63
+ --health-cmd "pg_isready -U postgres"
64
+ --health-interval 5s
65
+ --health-timeout 5s
66
+ --health-retries 10
67
+ mysql:
68
+ image: mysql:8.4
69
+ env:
70
+ MYSQL_ROOT_PASSWORD: rowquery
71
+ MYSQL_DATABASE: rowquery
72
+ ports:
73
+ - 3306:3306
74
+ options: >-
75
+ --health-cmd "mysqladmin ping -prowquery --silent"
76
+ --health-interval 5s
77
+ --health-timeout 5s
78
+ --health-retries 20
79
+ oracle:
80
+ image: gvenzl/oracle-free:23-slim-faststart
81
+ env:
82
+ ORACLE_PASSWORD: rowquery
83
+ APP_USER: rowquery
84
+ APP_USER_PASSWORD: rowquery
85
+ ports:
86
+ - 1521:1521
87
+ options: >-
88
+ --health-cmd healthcheck.sh
89
+ --health-interval 10s
90
+ --health-timeout 5s
91
+ --health-retries 30
92
+
93
+ env:
94
+ ROWQUERY_TEST_POSTGRESQL: postgresql://postgres:rowquery@127.0.0.1:5432/rowquery
95
+ ROWQUERY_TEST_MYSQL: mysql://root:rowquery@127.0.0.1:3306/rowquery
96
+ ROWQUERY_TEST_ORACLE: oracle://rowquery:rowquery@127.0.0.1:1521/FREEPDB1
97
+
98
+ steps:
99
+ - uses: actions/checkout@v6
100
+
101
+ - name: Install uv
102
+ uses: astral-sh/setup-uv@v5
103
+ with:
104
+ enable-cache: true
105
+
106
+ - name: Set up Python
107
+ uses: actions/setup-python@v6
108
+ with:
109
+ python-version: "3.12"
110
+
111
+ - name: Install dependencies
112
+ run: uv sync --extra all --extra dev
113
+
114
+ - name: Run server integration tests
115
+ run: uv run pytest tests/integration/test_server_backends.py -m integration -v -rs
@@ -0,0 +1,85 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.2.0] - 2026-10-21
11
+
12
+ ### Fixed
13
+ - **Transaction state no longer leaks between calls.** Connections are rolled back when they return to the pool, so reads no longer leave PostgreSQL sessions `idle in transaction`, MySQL `REPEATABLE READ` readers no longer see a stale snapshot, and a failed statement no longer leaves a PostgreSQL connection in the `current transaction is aborted` state.
14
+ - **Real connection pooling.** The pool now waits for a free connection (up to `pool_timeout`) instead of raising `RuntimeError` when exhausted, opens connections lazily, honours `pool_recycle`, and discards connections that fail the reset or are reported broken. `Engine` can be used from several threads (SQLite connections are opened with `check_same_thread=False`), and `asyncio.gather` with more tasks than `pool_size` works.
15
+ - **`%` in SQL for PostgreSQL/MySQL.** Literal `%` (e.g. `LIKE 'a%'`) is escaped when `:name` placeholders are converted to `%(name)s`.
16
+ - **Migrations**: files may contain several statements; versions are ordered numerically (`9_x` before `10_y`); the tracking table and inserts work on PostgreSQL, MySQL and Oracle; duplicate version numbers are rejected.
17
+ - **SQLSanitizer bypasses**: constructs whose extent differs between MySQL and PostgreSQL are rejected (backslashes inside any quoted literal or identifier, dollar-quoted strings, `#` outside literals, `--` not followed by whitespace, `
18
+ ` inside `--` comments); the `allowed_verbs` check skips leading comments and rejects SQL whose verb cannot be determined (fail-closed); MySQL `/*! ... */` executable comments are rejected when comments are not stripped; unterminated block comments are rejected.
19
+ - `Engine.transaction()` acquires its connection on `__enter__` instead of at call time.
20
+ - Transactions now have the same query API as engines: `fetch_one` raises `MultipleRowsError`, plus `mapper=` and `fetch_scalar`.
21
+ - `fetch_one` reads at most two rows instead of the whole result.
22
+ - MySQL cursors are buffered and always closed, avoiding `Unread result found` after `fetch_scalar`/`fetch_one`.
23
+ - PostgreSQL connection strings are built with `psycopg.conninfo.make_conninfo` (passwords with spaces or quotes work); `ConnectionConfig.extra` is passed to every driver.
24
+ - Oracle: DSN defaults to port 1521 when `port` is unset; only ALL-CAPS column names are lowercased, so quoted mixed-case aliases are preserved.
25
+ - Single-word inline SQL (`VACUUM`, `COMMIT`) is no longer mistaken for a registry key.
26
+ - SQLite transactions and migrations now cover DDL: an explicit `BEGIN` is issued, so a failing transaction or migration rolls back `CREATE`/`ALTER`/`DROP` too (the `sqlite3` module only opens transactions implicitly before DML).
27
+ - A connection released after `close_pool()` is closed instead of being handed to the new pool; pool initialisation is thread-safe.
28
+ - MySQL connection liveness is checked locally instead of pinging the server on every acquire; pool health checks run outside the pool lock.
29
+ - Applied migrations are matched by version number, so renaming `9_x.sql` to `009_x.sql` does not re-apply it.
30
+ - Parameter conversion honours the backend's string-literal rules (`'C:\'` is a complete literal on PostgreSQL).
31
+ - Strict aggregate mapping also validates reference and value-object columns.
32
+ - Project URLs in `pyproject.toml` point to the correct GitHub repository.
33
+
34
+ ### Added
35
+ - **Nested collections** in aggregate mappings: `.collection("lines.discounts", Discount, prefix="disc__", key="id")` nests a collection inside a previously declared one, at any depth. Children are deduplicated per parent instance; strict mode validates nested columns, prefixes and identity key columns (root, collections at every level, references) and reports collections by their full path. Dotted paths for `reference()` / `value_object()` are rejected at `build()`. `CollectionPlan.children` is now populated and used by `AggregateMapper`.
36
+ - Portable positional placeholders: `?` is converted to `%s` (PostgreSQL, MySQL) or `:1, :2, ...` (Oracle); `??` is a literal `?` (e.g. the PostgreSQL JSONB operator). SQL already using native `%s` / `%(name)s` placeholders is passed through unchanged.
37
+ - `QueryExecutionError` (database rejected the query) and `IntegrityError` (constraint violation), both carrying the driver exception as `__cause__`; `PoolTimeoutError`.
38
+ - `Engine.close()` / `AsyncEngine.close()`; `ConnectionManager.acquire()` / `release()`.
39
+ - CI runs the integration suite against PostgreSQL, MySQL and Oracle (23ai Free) service containers.
40
+
41
+ ### Changed
42
+ - **Breaking:** errors that are not binding problems now raise `QueryExecutionError` instead of `ParameterBindingError` (which is now a subclass of `QueryExecutionError`). Catch `ExecutionError` or `QueryExecutionError` to handle all database errors.
43
+ - **Breaking (adapter protocol):** adapters now implement `connect`/`close_connection`/`is_connection_alive`/`execute` (async: `connect_async`/`close_connection_async`/`execute_async`) plus `begin`/`begin_async`, `positional_paramstyle` and `backslash_escapes`; pooling moved into `row_query.core.pool`. `TransactionManager`/`AsyncTransactionManager` take a connection manager instead of a raw connection.
44
+ - `ConnectionConfig.pool_timeout` and `pool_recycle` are floats and are now enforced.
45
+
46
+ ## [0.1.2] - 2026-02-22
47
+
48
+ ### Added
49
+ - **Inline SQL support**: All engine and transaction methods (`fetch_one`, `fetch_all`, `fetch_scalar`, `execute`) now accept raw SQL strings in addition to registry keys. A string containing whitespace is treated as inline SQL; a dot-separated identifier like `user.get_by_id` is resolved from the registry.
50
+ - **Flexible parameter binding**: `params` argument now accepts `dict` (named), `tuple`/`list` (positional), or a single scalar value (automatically wrapped in a tuple). Previously only `dict | None` was accepted.
51
+ - **`SQLSanitizer`** — configurable sanitizer applied to inline SQL before execution:
52
+ - `strip_comments` (default `True`): removes `--` line comments and `/* */` block comments while preserving string literals.
53
+ - `block_multiple_statements` (default `True`): rejects SQL containing a statement-terminating `;` followed by additional content (prevents query stacking attacks).
54
+ - `allowed_verbs` (default `None`): restricts the leading SQL keyword to a caller-supplied `frozenset` (e.g. `frozenset({"SELECT"})`). Registry queries are never sanitized.
55
+ - **`SQLSanitizationError`** exception (subclass of `ExecutionError`) raised when a sanitization check fails.
56
+ - **`is_raw_sql()`** and **`coerce_params()`** helpers exported from `row_query.core.params`.
57
+ - 65 new unit tests covering all sanitizer behaviour (`tests/unit/test_sanitizer.py`).
58
+
59
+ ## [0.1.0] - 2025-02-16
60
+
61
+ ### Added
62
+ - Initial alpha release
63
+ - Core query execution engine (`Engine`, `AsyncEngine`)
64
+ - Connection management with pooling support (`ConnectionManager`, `AsyncConnectionManager`)
65
+ - Multi-database adapter pattern supporting SQLite, PostgreSQL, MySQL, and Oracle
66
+ - SQL registry for loading `.sql` files into dot-separated namespaces (`SQLRegistry`)
67
+ - Parameter normalization supporting `:name` syntax across all database backends
68
+ - Model mapping for dataclasses, Pydantic models, and plain classes (`ModelMapper`)
69
+ - Aggregate mapping for complex object graph reconstruction with single-pass O(n) algorithm
70
+ - Collection mapping for one-to-many relationships in aggregate patterns
71
+ - Value object mapping for embedded entities in aggregates
72
+ - Reference mapping for foreign key relationships
73
+ - Transaction management with context manager support (`TransactionManager`, `AsyncTransactionManager`)
74
+ - Migration management system with version tracking (`MigrationManager`, `MigrationInfo`)
75
+ - Repository pattern base classes for DDD-style code organization (`Repository`, `AsyncRepository`)
76
+ - Comprehensive exception hierarchy with 18 custom exception classes
77
+ - Full type safety with mypy strict mode compliance
78
+ - Support for Python 3.10, 3.11, 3.12, and 3.13
79
+ - 125 unit, contract, and integration tests
80
+
81
+ [Unreleased]: https://github.com/MaksimShevtsov/RowQuery/compare/v0.2.0...HEAD
82
+ [0.2.0]: https://github.com/MaksimShevtsov/RowQuery/releases/tag/v0.2.0
83
+ [0.1.2]: https://github.com/MaksimShevtsov/RowQuery/releases/tag/v0.1.2
84
+ [0.1.1]: https://github.com/MaksimShevtsov/RowQuery/releases/tag/v0.1.1
85
+ [0.1.0]: https://github.com/MaksimShevtsov/RowQuery/releases/tag/v0.1.0
@@ -19,7 +19,9 @@ uv sync --extra all # Install all database drivers
19
19
  ## Commands
20
20
 
21
21
  ```bash
22
- uv run pytest # Run all tests (125 tests)
22
+ uv run pytest # Run all tests
23
+ # Server integration tests run when ROWQUERY_TEST_POSTGRESQL / ROWQUERY_TEST_MYSQL
24
+ # are set to postgresql://user:pw@host:port/db and mysql://user:pw@host:port/db URLs
23
25
  uv run pytest tests/unit/ # Unit tests only
24
26
  uv run pytest tests/contract/ # Protocol contract tests
25
27
  uv run pytest tests/integration/ # SQLite integration tests
@@ -32,13 +34,17 @@ uv run mypy row_query/ # Type check (strict mode)
32
34
 
33
35
  - **`row_query/core/`** — Core engine
34
36
  - `connection.py` — `ConnectionConfig` (Pydantic), `ConnectionManager`, `AsyncConnectionManager`
37
+ - `pool.py` — `SyncPool`/`AsyncPool`: bounded, lazy, timeout + recycle, rollback on release
38
+ - `execution.py` — `SyncExecutor`/`AsyncExecutor` (query API shared by engines and transactions), driver error translation
35
39
  - `engine.py` — `Engine` and `AsyncEngine` (fetch_one, fetch_all, fetch_scalar, execute, transaction)
36
40
  - `registry.py` — `SQLRegistry` (loads .sql files into dot-separated namespaces)
37
41
  - `transaction.py` — `TransactionManager`, `AsyncTransactionManager`
38
42
  - `migration.py` — `MigrationManager`, `MigrationInfo`
39
- - `params.py` — `:name` → `%(name)s` parameter normalizer with cache
43
+ - `params.py` — `:name`/`?` → driver placeholder conversion (escapes `%` for pyformat)
44
+ - `lexer.py` — SQL tokenizer (strings, `$$`, `E''`, identifiers, comments) and statement splitter
45
+ - `sanitizer.py` — fail-closed `SQLSanitizer` for inline SQL
40
46
  - `enums.py` — `DatabaseBackend` enum
41
- - `exceptions.py` — Full exception hierarchy (18 classes)
47
+ - `exceptions.py` — Full exception hierarchy
42
48
 
43
49
  - **`row_query/adapters/`** — Database drivers
44
50
  - `protocol.py` — `SyncAdapter`, `AsyncAdapter` runtime-checkable protocols
@@ -66,3 +72,8 @@ uv run mypy row_query/ # Type check (strict mode)
66
72
  ## Active Technologies
67
73
  - Python >=3.10 + pydantic >=2.0.0 (core); psycopg >=3.1.0, aiosqlite, aiomysql, oracledb (optional per-backend)
68
74
  - SQLite, PostgreSQL, MySQL, Oracle via adapter pattern
75
+ - Python >=3.10 (documented library) + N/A (documentation-only feature; no new code dependencies) (001-llm-docs)
76
+ - N/A (static markdown/text files in repository) (001-llm-docs)
77
+
78
+ ## Recent Changes
79
+ - 001-llm-docs: Added Python >=3.10 (documented library) + N/A (documentation-only feature; no new code dependencies)
@@ -13,7 +13,7 @@ Thank you for your interest in contributing! This document provides guidelines f
13
13
 
14
14
  ```bash
15
15
  # Clone repository
16
- git clone https://github.com/maksim-shevtsov/RowQuery.git
16
+ git clone https://github.com/MaksimShevtsov/RowQuery.git
17
17
  cd RowQuery
18
18
 
19
19
  # Install all dependencies (including all database drivers)
@@ -1,12 +1,12 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: rowquery
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: A SQL-first Python library for querying and mapping data across multiple database backends.
5
- Project-URL: Homepage, https://github.com/maksim-shevtsov/RowQuery
6
- Project-URL: Documentation, https://github.com/maksim-shevtsov/RowQuery#readme
7
- Project-URL: Repository, https://github.com/maksim-shevtsov/RowQuery.git
8
- Project-URL: Issues, https://github.com/maksim-shevtsov/RowQuery/issues
9
- Project-URL: Changelog, https://github.com/maksim-shevtsov/RowQuery/blob/main/CHANGELOG.md
5
+ Project-URL: Homepage, https://github.com/MaksimShevtsov/RowQuery
6
+ Project-URL: Documentation, https://github.com/MaksimShevtsov/RowQuery#readme
7
+ Project-URL: Repository, https://github.com/MaksimShevtsov/RowQuery.git
8
+ Project-URL: Issues, https://github.com/MaksimShevtsov/RowQuery/issues
9
+ Project-URL: Changelog, https://github.com/MaksimShevtsov/RowQuery/blob/main/CHANGELOG.md
10
10
  Author-email: Maksim Shevtsov <maksim.shautsou.dev@gmail.com>
11
11
  License: MIT
12
12
  License-File: LICENSE
@@ -52,8 +52,8 @@ Description-Content-Type: text/markdown
52
52
  [![PyPI version](https://badge.fury.io/py/rowquery.svg)](https://pypi.org/project/rowquery/)
53
53
  [![Python Versions](https://img.shields.io/pypi/pyversions/rowquery.svg)](https://pypi.org/project/rowquery/)
54
54
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
55
- [![Tests](https://github.com/maksim-shevtsov/RowQuery/workflows/Tests/badge.svg)](https://github.com/maksim-shevtsov/RowQuery/actions)
56
- [![codecov](https://codecov.io/gh/maksim-shevtsov/RowQuery/branch/main/graph/badge.svg)](https://codecov.io/gh/maksim-shevtsov/RowQuery)
55
+ [![Tests](https://github.com/MaksimShevtsov/RowQuery/actions/workflows/test.yml/badge.svg)](https://github.com/MaksimShevtsov/RowQuery/actions/workflows/test.yml)
56
+ [![codecov](https://codecov.io/gh/MaksimShevtsov/RowQuery/branch/main/graph/badge.svg)](https://codecov.io/gh/MaksimShevtsov/RowQuery)
57
57
  [![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)
58
58
 
59
59
  A SQL-first Python library for querying and mapping data across multiple database backends.
@@ -119,6 +119,10 @@ count = engine.fetch_scalar("SELECT COUNT(*) FROM users")
119
119
  rows = engine.fetch_all("SELECT * FROM users WHERE id IN (?, ?)", [1, 2])
120
120
  ```
121
121
 
122
+ Placeholders are portable: use `:name` with a dict or `?` with positional values on any
123
+ backend. RowQuery converts them to the driver's native style (`%(name)s` / `%s` for
124
+ PostgreSQL and MySQL, `:1` for Oracle) and escapes literal `%` such as `LIKE 'a%'`.
125
+
122
126
  ### 3. Map to Models
123
127
  ```python
124
128
  from dataclasses import dataclass
@@ -3,8 +3,8 @@
3
3
  [![PyPI version](https://badge.fury.io/py/rowquery.svg)](https://pypi.org/project/rowquery/)
4
4
  [![Python Versions](https://img.shields.io/pypi/pyversions/rowquery.svg)](https://pypi.org/project/rowquery/)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
- [![Tests](https://github.com/maksim-shevtsov/RowQuery/workflows/Tests/badge.svg)](https://github.com/maksim-shevtsov/RowQuery/actions)
7
- [![codecov](https://codecov.io/gh/maksim-shevtsov/RowQuery/branch/main/graph/badge.svg)](https://codecov.io/gh/maksim-shevtsov/RowQuery)
6
+ [![Tests](https://github.com/MaksimShevtsov/RowQuery/actions/workflows/test.yml/badge.svg)](https://github.com/MaksimShevtsov/RowQuery/actions/workflows/test.yml)
7
+ [![codecov](https://codecov.io/gh/MaksimShevtsov/RowQuery/branch/main/graph/badge.svg)](https://codecov.io/gh/MaksimShevtsov/RowQuery)
8
8
  [![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)
9
9
 
10
10
  A SQL-first Python library for querying and mapping data across multiple database backends.
@@ -70,6 +70,10 @@ count = engine.fetch_scalar("SELECT COUNT(*) FROM users")
70
70
  rows = engine.fetch_all("SELECT * FROM users WHERE id IN (?, ?)", [1, 2])
71
71
  ```
72
72
 
73
+ Placeholders are portable: use `:name` with a dict or `?` with positional values on any
74
+ backend. RowQuery converts them to the driver's native style (`%(name)s` / `%s` for
75
+ PostgreSQL and MySQL, `:1` for Oracle) and escapes literal `%` such as `LIKE 'a%'`.
76
+
73
77
  ### 3. Map to Models
74
78
  ```python
75
79
  from dataclasses import dataclass